Jump to content

Very happy but I still want more


ctwise

Recommended Posts

I'm very happy with Alfred but I still want more. :-)

 

This is a recap of feature requests I've made in the past. Some of my requests have made it into Alfred (so I'm not repeating them), some are still unfulfilled. Hopefully resurfacing these will help to bump them up.

 
1. Ability to flag workflows as working with file paths instead of just strings. This would be done to support file path completion in the Alfred bar.
 
2. Set environment variables to pass various information to Alfred when scripts are run:
 
a) State of the option key, command key, etc.
B) Original query value (_not_ the value passed from the previous workflow stage)
 
3. Support JSON or XML returned from script. JSON and XML would mirror each other
 
4. Returned items should optionally include a quicklook URL
 
5. Provide explicit support for multi-level menus, including calling another script to get the next level down.
 
Link to comment
Share on other sites

Big thumbs-up for no. 5. It would make workflows so much more powerful (and much easier to code) if there were explicit support for passing control from one script (filter) to another, and the ability to back up again (ESC?)

 

IMO, the keyword used to call the workflow should also be in the env vars.

Link to comment
Share on other sites

There are definitely improvements coming to workflows in the future, so stay tuned.

 

As for passing keyword as an environment variable, do you have a specific use case for needing this? The reason I've abstracted the keyword out of workflow scripts is that it's not fixed, i.e. somebody installing the workflow would change the keyword which means you can't use it as an identifier in the script.

Link to comment
Share on other sites

There are definitely improvements coming to workflows in the future, so stay tuned.

 

As for passing keyword as an environment variable, do you have a specific use case for needing this? The reason I've abstracted the keyword out of workflow scripts is that it's not fixed, i.e. somebody installing the workflow would change the keyword which means you can't use it as an identifier in the script.

That's exactly why it would be useful. Because users can change keywords, workflows can't reliably call themselves.

In any workflow that calls itself, e.g. Sebastian's Recent Items, everything breaks if the user changes the keyword(s) because the workflow can't know this without doing some hardcore info.plist parsing.

I also use this technique a lot to reopen the settings screen after a user toggles a setting (it gets very tedious very quickly if the user has to reopen Alfred and re-enter the keyword each time).

I know External Triggers are intended for that kind of thing, but I avoid them whenever possible because they put Alfred in a special mode with no way back to Alfred's standard mode. Instead of hitting ⌥+⌫ to clear the query and start using Alfred, I have to close and reopen it. It's by no means a huge chore, but it is clunky.

That is to say, the reliable way of calling your own workflow is clunky to use (you have to close and reopen Alfred when you're done), and the more useable way is unreliable because you can't tell when users have changed keywords.

 

If the keyword were available in an env var, it would be trivial to call Alfred via AppleScript using that value instead of a hard-coded one (which may no longer be the right keyword), and you could avoid the weird External-Trigger mode.

 

Link to comment
Share on other sites

I know External Triggers are intended for that kind of thing, but I avoid them whenever possible because they put Alfred in a special mode with no way back to Alfred's standard mode. Instead of hitting ⌥+⌫ to clear the query and start using Alfred, I have to close and reopen it. It's by no means a huge chore, but it is clunky.

Wouldn't it be better if delete were simply able to clear the External Trigger? Then you have the benefit of External Triggers (special UI, specifically designed for this use case, scoped specifically to workflow, prevents keyword collision with other workflows, etc.) and the ability to easily clear/cancel.

I'm giving a -1 to keyword in the environment variable, unless there's a better example of how this could be generally useful

Otherwise, good stuff. Would love to see more batteries-included workflow features

Edited by Tyler Eich
Link to comment
Share on other sites

Wouldn't it be better if delete were simply able to clear the External Trigger? Then you have the benefit of External Triggers (special UI, specifically designed for this use case, scoped specifically to workflow, prevents keyword collision with other workflows, etc.) and the ability to easily clear/cancel.

 

In a word, no. As long as interaction takes place within the standard Alfred window, a user always knows exactly where he/she is.

 

External Triggers lead to a non-obvious, special mode. Even if a user could backspace out of them, where do they end up then? Back at an empty Alfred window or will Alfred repopulate the keyword that lead to the External Trigger being called (if it was a keyword)?

 

The former leaves you having to start over entering the keyword, which doesn't solve the problem; the latter could easily lead to a main window-External Trigger loop or cause other complications due to altered workflow state.

 

Before putting your -1 on the table, could you show an example of how the issue of multi-level workflow or settings toggles has been (or could be) solved in a better way that doesn't make development more complicated (for Andrew and for workflow authors)?

Edited by deanishe
Link to comment
Share on other sites

Before putting your -1 on the table, could you show an example of how the issue of multi-level workflow or settings toggles has been (or could be) solved in a better way that doesn't make development more complicated (for Andrew and for workflow authors)?

 

What about a new value for the type attribute? Something like type="category". When actioned, the icon of the category is placed into Alfred's UI similarly to the way External Triggers do.

 

Example of how it would work:

 

User is activating a workflow that uses a script filter to drill into a folder structure to find a specific file

 

User activates script filter with "drill alpha-folder"

User selects item with:

type: "category"

arg: "/alpha-folder"

icon: "alpha-icon.png"

 

Alfred places "drill-icon.png" and "alpha-icon.png" into the left part of the UI (as occurs for External Triggers)

Alfred sends "/alpha-folder" as query to script filter

​Script filter responds with results from /alpha-folder

 

User selects item with:

type: "category"

arg: "/bravo-folder"

icon: "bravo-icon.png"

 

Alfred places "bravo-icon.png" above/next to "alpha-icon.png" to indicate hierarchy (at some reasonable point, icons collapse instead of stacking at full width)

Alfred sends "/alpha-folder [tab] /bravo-folder" as query to script filter (tab separated, similar File Actions)

Script filter responds with results from /foo-folder/bar-folder

 

User presses delete key

 

Alfred removes "bravo-icon.png" from UI stack

Alfred sends "/alpha-folder" as query to script filter

​Script filter response with items from /alpha-folder

 

User selects item with:

type: "file"

arg: "/alpha-folder/charlie-name.txt"

icon: "charlie-icon.png"

 

Alfred sends "/alpha-folder/charlie-name.txt" to the next action

Action does whatever it does, just like always…

 

Let me know what's good, bad, and/or ugly about this solution. I'm very interested to hear how other people would approach this issue, especially those with more experience developing hierarchical workflows.

Edited by Tyler Eich
Link to comment
Share on other sites

We're talking about two different things here, I think. Adding the keyword to env vars make Alfred's current workflow execution model easier to work with.

 

You're talking about some fairly significant changes to the model (notably that Alfred would need to maintain a workflow call stack).

 

I like your suggestion broadly, but I think if we're talking about a stateful, multi-level execution model, I think it'd be worth going the whole hog.

 

In your proposed model, Alfred is keeping track of where the workflow is in its hierarchy, but the workflow still has to figure that out for itself by parsing the query into its component levels and acting accordingly. This rapidly gets very complicated as the depth of the hierarchy increases. (I've been toying with the idea of a Flask-like URL {query} routing utility class to make this easier, but that's a fairly complex undertaking in itself.)

 

I would prefer a model where the workflow can tell Alfred to call a specific Script Filter via its results. Being able to arbitrarily pass control to other Script Filters within a workflow would simplify development considerably. In any case, if there's a call stack, I'd like Alfred to take care of that for you in some fashion (e.g. mapping each level to a different Script Filter), so it doesn't have to be replicated in every single workflow. Not only is it tricky (two levels is okay, but three gets complicated), but there'd also be the possibility of the two stacks (Alfred's and the workflow's) getting out of sync due to bugs.

 

I very much like the idea of Alfred maintaining a navigable call stack, but I think text would be a better fit than icons: if a workflow is showing dynamic data, it's not always possible to provide a suitable icon, and if you're 3 levels down in, say, a filesystem, 3 folder icons aren't particularly expressive. A specific keyboard shortcut (perhaps ⌘+↑ or ⌘+⌫) to back out would be more convenient than having to delete any query and then hit delete.

Link to comment
Share on other sites

In your proposed model, Alfred is keeping track of where the workflow is in its hierarchy, but the workflow still has to figure that out for itself by parsing the query into its component levels and acting accordingly. This rapidly gets very complicated as the depth of the hierarchy increases. (I've been toying with the idea of a Flask-like URL {query} routing utility class to make this easier, but that's a fairly complex undertaking in itself.)

 

Yes, the method I proposed would require Alfred to remember and send the stack to the script filter. Currently, this task is handled manually via query chaining by workflow authors.

 

I've seen quite a few workflows (like Carlos Recent Items workflow) using the ➣ character to separate the components of the hierarchy. The method I proposed eliminates the need for a special delimiter, as Alfred would handle the stack automatically. Based on how popular character-delimited hierarchy navigation is, I'd call this a net win for developers.

 

Even if parsing is just as tedious (what with splitting the query and parsing components as is already required), at least Alfred could allow developers to keep odd Unicode characters out of the query string. I'd argue that a native UI is more intuitive to the user than a Unicode delimiter, and no more difficult for the workflow developer.

 

I would prefer a model where the workflow can tell Alfred to call a specific Script Filter via its results. Being able to arbitrarily pass control to other Script Filters within a workflow would simplify development considerably. In any case, if there's a call stack, I'd like Alfred to take care of that for you in some fashion (e.g. mapping each level to a different Script Filter), so it doesn't have to be replicated in every single workflow. Not only is it tricky (two levels is okay, but three gets complicated), but there'd also be the possibility of the two stacks (Alfred's and the workflow's) getting out of sync due to bugs.

 

I value Alfred's support of different programming styles. Allowing multiple script filters to handle a task is therefore a necessary consideration.

 

You made a note about "passing control to other script filters within a workflow". That could mean two things in my mind:

  1. A script filter object (represented by a white block in Alfred's UI) calls another script filter object (another white block)
  2. The underlying script used by a script filter passes control to another script/function in the same directory

Both options are currently possible in Alfred. For option 1, use an External Trigger. For option 2, use a switch construct with cases for passing data to each function. If I've misunderstood you, please do let me know.

 

As for "mapping each level to a different script filter", how would Alfred accomplish this? I don't know of any simple way to show Alfred that when option A happens, use this filter, but if B or C happens, use this filter, etc. A programming language is far better suited to handle this logic. Simply point a single script filter to a script that acts as a control dispatcher. Neither the script filter nor Alfred has to know where you're heading; it is left totally to the underlying script to select the correct function and send the results, which Alfred is only too happy to display.

 

Not sure how "getting out of sync" would be an issue…would love more info

 

I very much like the idea of Alfred maintaining a navigable call stack, but I think text would be a better fit than icons: if a workflow is showing dynamic data, it's not always possible to provide a suitable icon, and if you're 3 levels down in, say, a filesystem, 3 folder icons aren't particularly expressive. A specific keyboard shortcut (perhaps ⌘+↑ or ⌘+⌫) to back out would be more convenient than having to delete any query and then hit delete.

 

I agree. Icons are not always the most expressive, nor are they readily available.

 

Would a token system be better? Similar UI to how Mail displays tokens around email addresses and only allows them to be deleted (not edited) once they're generated.

 

Keyboard shortcut all the things B)

Edited by Tyler Eich
Link to comment
Share on other sites

Yes, the method I proposed would require Alfred to remember and send the stack to the script filter. Currently, this task is handled manually via query chaining by workflow authors.

 

I've seen quite a few workflows (like Carlos Recent Items workflow) using the ➣ character to separate the components of the hierarchy. The method I proposed eliminates the need for a special delimiter, as Alfred would handle the stack automatically. Based on how popular character-delimited hierarchy navigation is, I'd call this a net win for developers.

 

Even if parsing is just as tedious (what with splitting the query and parsing components as is already required), at least Alfred could allow developers to keep odd Unicode characters out of the query string. I'd argue that a native UI is more intuitive to the user than a Unicode delimiter, and no more difficult for the workflow developer.

Getting rid of Unicode delimiters from the query string is hardly a big win. Parsing isn't so much tedious as tricky and error prone.

 

I value Alfred's support of different programming styles. Allowing multiple script filters to handle a task is therefore a necessary consideration.

 

You made a note about "passing control to other script filters within a workflow". That could mean two things in my mind:

  • A script filter object (represented by a white block in Alfred's UI) calls another script filter object (another white block)
  • The underlying script used by a script filter passes control to another script/function in the same directory
Both options are currently possible in Alfred. For option 1, use an External Trigger. For option 2, use a switch construct with cases for passing data to each function. If I've misunderstood you, please do let me know.

 

As explained previously, External Triggers suck. They're a UI dead-end, a one-way street. The only way to back out of an External Trigger is to call your workflow via AppleScript and its keyword, which raises the question of why you'd use External Triggers in the first place. What's more, they're an implementation detail leaking into the user interface.

User: Why do I have to hit ENTER to access the settings in your workflow, but not in others?

Developer: Because it's implemented using External Triggers!

User: …?

If I thought they were a satisfactory solution, I wouldn't be asking for an alternative…

With regard to number two, it really isn't a simple as you think it is. I mean, do you think it never occurred to developers to try a switch statement?

What if you have 4+ levels because you're navigating deeply-nested data from an API? How is a switch statement going to help if you don't know what options will be available? Even if the structure is static, how do you reasonably manage switch statements nested 4 levels deep?

 

 Having Alfred pass you a tab-delimited stack would be marginally helpful. From the developer's point of view, it's hardly much of an advance on a ➣-delimited query…

 

As for "mapping each level to a different script filter", how would Alfred accomplish this? I don't know of any simple way to show Alfred that when option A happens, use this filter, but if B or C happens, use this filter, etc. A programming language is far better suited to handle this logic. Simply point a single script filter to a script that acts as a control dispatcher. Neither the script filter nor Alfred has to know where you're heading; it is left totally to the underlying script to select the correct function and send the results, which Alfred is only too happy to display.

You'd tell Alfred via the results XML. In addition to keywords, Script Filters, Actions etc. could be assigned IDs. In your results, you could specify next=settings.timezones (or whatever) and Alfred would call the Script Filter/Action with that ID, passing the value of arg.

The assignment of results to Script Filters/Actions would have to be dynamic, as a lot of workflows don't use a hard-coded structure.

 

Not sure how "getting out of sync" would be an issue…would love more info

 

Because you have two programs (Alfred and the Script Filter) both independently trying to stay in sync. Apart from that being unnecessary duplication of effort, as I've said, keeping track of the call stack and appropriately managing the control flow is tricky and error prone. Alfred separating the stack from the query for you (as you're suggesting) takes care of one minor detail, but it doesn't help very much if you still have to parse the stack to figure out what your script is supposed to do now.

 

Can I do it? Yes. Could you do it? Sure. Not everyone codes as well as we do, however, and I've seen plenty of workflow authors tie themselves in knots trying much simpler things.

 

And shouldn't Alfred, where reasonable, do what it can to make writing workflows easier for developers?

 

Being able to say in a result item "if actioned, pass the arg ABC to Script Filter/Action with ID XYZ" (note: I mean as an argument, not a query to display in Alfred) gives you a more imperative execution model that's much easier to reason about (do this with argument X) than the current one (figure out where you are and what to do now with query XYZ).

 

It's by no means impossible to write that layer yourself, so you can structure the rest of your code in a do-this-with-this way, but neophite coders typically don't think to structure their code in different layers.

 

With this model, you could build a hierarchically-structured workflow from a few simple scripts instead of something distinctly more application-like.

 

I agree. Icons are not always the most expressive, nor are they readily available.

 

Would a token system be better? Similar UI to how Mail displays tokens around email addresses and only allows them to be deleted (not edited) once they're generated.

 

Keyboard shortcut all the things B)

I don't envisage the stack/breadcrumb nav being editable. Or even part of the query box. I just think that icons alone wouldn't be a workable solution for many cases, and it's generally a challenge for us less graphically-abled folks to think of (let alone create) suitable icons for things.

Link to comment
Share on other sites

In reply to Dean:

 

Your last explanation clicked for me

 

Let me try to explain what I see between our two approaches:

 

Tyler's:

  • UI sugar on top of the already-available workflow capabilities (delimiters, submenus)
  • Developer is in the same position as previously
  • Script filters act statefully, using tab-delimited inputs to rebuild state for each execution

Pros:

  • Prettier UI
  • Simpler to back out of an action (just remove the last entry from the stack)

Cons:

  • Stateful programming sucks
  • Developers receive no real help with control flow

 

Dean's:

  • Send output directly to an (a.) action that does a thing (b.) script filter that return more options
  • Each item is responsible for declaring where it should be sent if actioned
  • No action or script filter needs to know how the last step got its result, it only needs the result
  • Actions and Script Filters are labelled, basically eliminating the need for external triggers to pass data within a workflow

Pros:

  • Stateless components
  • Much simpler control flow

Cons:

  • Could require significant rethinking of the Trigger/Input/Action/Output paradigm (may also be a Pro)
  • To allow undo/back out, Alfred needs to maintain a stack of previous actions and the outputs sent to them

 

I have an intuitive sense that each solution has a different optimal use case, but I haven't thought too hard about it. Maybe my idea would be better suited to simple drill-down navigation, while yours is better suited to control flow between functional components.

 

Either way, I like what I'm understanding about your solution. It's as big of a leap in capability as Alfred 2 was to Alfred 1.

 

To me, it sounds like script filters have outgrown their implementation. From what I'm seeing, a script filter does not always need to have a keyword, nor does it always make sense to do so.

 

It sounds like people want a first-class way of telling Alfred which UI to display next. The current solution (using Applescript to call an External Trigger or to populate Alfred with a keyword) is sub-optimal. I don't know about you, but I feel dirty every time I type osascript -e 'tell application "Alfred 2" to search "keyword"'.

 

I can see benefit to having the keyword in the environment if you want a quick fix that's as dirty as Applescript, but not if the system itself were built to handle this interaction better. If workflow objects had developer-specified labels, the keyword chosen by the user would be irrelevant.

Edited by Tyler Eich
Link to comment
Share on other sites

Yup, that's exactly it!
 

It sounds like people want a first-class way of telling Alfred which UI to display next. The current solution (using Applescript to call an External Trigger or to populate Alfred with a keyword) is sub-optimal. I don't know about you, but I feel dirty every time I type osascript -e 'tell application "Alfred 2" to search "keyword"'.
 
I can see benefit to having the keyword in the environment if you want a quick fix that's as dirty as Applescript, but not if the system itself were built to handle this interaction better. If workflow objects had developer-specified labels, the keyword chosen by the user would be irrelevant.

 
Don't get me wrong, I also feel icky running an Alfred search via AppleScript, but I feel that, on balance, it's preferable to External Triggers due to their funky UX. Adding the keyword to the env vars would be a simple way (in terms of effort for Andrew and the current execution model) to eliminate the fairly substantial risk of everything breaking because a user changed the keyword. It's a stopgap while we wait for that first-class way of telling Alfred what to display next.
 
I also like your idea of Alfred maintaining a stack. It's just that I think if Alfred is going to start tracking workflow state (which maintaining a stack would involve at some level, unless it's just doing some parsing of {query} for you), it may as well take it a bit further, so the workflow itself doesn't also have to worry about state (in the common case). As you say, stateful programming sucks, so clearly it's better to leave that to Andrew. That's what we pay him for :)
 
In terms of the execution model, your suggestion isn't a huge leap. It would make the UI a lot cleaner for users, but not change much for developers. What I'm suggesting would change the execution model significantly (although it shouldn't break existing workflows), but also provide significant benefits for developers.
 
In terms of effort for Andrew, neither is insignificant. I suspect the most significant component would be getting the UI for the breadcrumb navigation/stack right.
 

I have an intuitive sense that each solution has a different optimal use case, but I haven't thought too hard about it. Maybe my idea would be better suited to simple drill-down navigation, while yours is better suited to control flow between functional components.

 
Both should work equally well in situations where the stack = the argument, such as directory trees. If you're working with API or database data, where every node has an ID, you'd want to pass the ID and wouldn't care about the stack (if Alfred were taking care of it for you).

Edited by deanishe
Link to comment
Share on other sites

It sounds almost like we're talking about creating a full local server for each workflow (which is a cool idea) where Alfred's search query is basically an address bar and one that has an internal history.

 

Currently, we seem to use "autocomplete" and string matching to define the state, but this would add in something closer to tokens.

 

I've toyed around with creating a class for Alphred that would dynamically generate multi-level menus, but the problem I ran into each time via conceptualization was the routing in that it always created more complexity than it was worth. Revisiting it with this thinking might be productive.

Link to comment
Share on other sites

What do you mean by server? A long-running workflow process? Also what do you mean by "tokens"?
 
I've been thinking over a hierarchy manager for Alfred-Workflow for a long time. The clearest model (from a user's perspective) is probably something akin to the URL-routing in Flask or similar web application frameworks:

@workflow.route('/settings/<query>')
def show_settings(query=None):
    ...

@workflow.route('/settings/languages/<query>')
def choose_language(query=None):
    ...

@workflow.route('/settings/client/<query>')
def choose_client(query=None):
    ....

I don't know if you can decorate functions like that in PHP. I'm sure Ruby will have some similar mechanism.

 

In any case, it'd be quite a complicated beast to write. All that faffing around trying to work out precedence and the like.

 

@shawn: You've written a lot of Ruby code recently. Are you not seriously thinking about ditching PHP?

Link to comment
Share on other sites

For server, I'm not thinking a long running process, but something that acts more like a web-server but without the actual server. So that might not actually affect the way that Alfred is run. By tokens, I mean more like chunked text in that the text isn't really a set of characters but a unit as a whole (this is a bad analogy and explanation).

 

I'm pretty sure that you can fake decorators in PHP. Ruby has decorators.

 

The URL routing mechanism that I'm now thinking of this in is Rails' router.

 

The last time I tried to write this class in PHP, I basically tried to make the entire multi-level menu be represented as a large associative array and that each endpoint ended up being a function that would be called. But that wasn't quite the right approach.

 

 

@shawn: You've written a lot of Ruby code recently. Are you not seriously thinking about ditching PHP?

 

I do heart Ruby, but PHP still has a lovely place in my heart. Also, since a lot of people seem to write workflows in PHP, having a solid PHP library for them to use and abuse seems productive. I have it somewhere on a todo list to, basically, port Alphred into Ruby as well as Bash.

Link to comment
Share on other sites

this is a bad analogy and explanation.

Yes. Yes it it  :P 

 

I do heart Ruby, but PHP still has a lovely place in my heart. Also, since a lot of people seem to write workflows in PHP, having a solid PHP library for them to use and abuse seems productive. I have it somewhere on a todo list to, basically, port Alphred into Ruby…

After writing enough Ruby, you'll come around and see PHP for the manifest dementia that it is.

 

…as well as Bash.

Or maybe not… *shudders*

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...