FAST automation rules [hack]

Automation rules are great, but at times I have wished to have the automation rules trigger as fast as formulas. The most tantalizing use case for this is two-way editable lookups. (I also list four more potential use cases in the above link.)

Now I have figured out way to do exactly that, albeit in a hacky way with some caveats (listed below). What it does is allow you to set up automation rules just as flexible (if not moreso) than native automation rules, except the rules react within a fraction of a second to any row changes rather than waiting for tens of seconds before the user sees any update.

Check out the demo below for some examples:

How does it work?

Essentially, it’s a button push-back loop that runs several times a second. Every iteration also pushes a list of other buttons (the rules) which in turn push buttons on rows that actually contain the code to run.

The pushback button action is _Delay(loopButton, 0). Without the Delay wrapper, the tab seems to accumulate calculations that block user interaction once you click “stop”. I don’t know why the delay helps but it does.

I also put a kill switch in there, and a Start and Stop button to manage the kill switch for safety.

In general, a rule consists of:

  • a “shadow copy” of the column you are watching for changes
  • an action to perform on row change
    • in the case of multi-select lookups, probably two actions to account for items added and items removed from the list.
  • a “disable if” rule similar to thisrow.column = thisrow.shadow_column
    • :point_up_2: that’s important to avoid infinite loops

Take a look at the logic in the button columns for a more detailed look into how these rules work. It does get complicated quite fast, but once you write one or two rules, it’s mostly copy-paste to write more rules.

Caveats

  • The biggest caveat is that the tab crashes after about an hour (in my limited testing) of running. After that, you just reload the tab and press the button again.
  • The second caveat is that the “rules” are quite complicated to write (compared to native automation rules.
  • Requires at least two extra columns per automation rule
  • This may have an impact on doc performance and/or tab CPU usage, but I haven’t noticed anything yet. (Maybe someone knows how to measure that.)
7 Likes

Hm. I wonder if there are two more things you can do to help create a more “set and forget” type situation.
The first would be to set a delay of perhaps 250ms between button pushes. It would involve experimentation of how fast you want the automations to occur - but something between 2 and 10 times a second feels like it would keep a very usable document.

And if crashes keep occurring, you could introduce a new automation that stops and starts your “auto automation” once every hour or so (or just in an amount of time that is LESS than say the 1 percentile amount for a crash to occur)

I have managed to find work arounds for automations using button based UI in our current project management doc, but while rebuilding I keep finding really interesting use cases for automation for us that are only useful if they occur immediately. In the next few weeks I’ll get to testing this using this modified version of your methods and if I remember I’ll report back :slight_smile:

1 Like

Interesting

Having worked near full time in Airtable for the last 6 months I do really wish Codas automations could happen in a range that is even marginally close to Airtables

Will definitely take a look and let you know what I think!

3 Likes

@Ryan_Martens2,

we have been using this hack for a while for situations similar to your demo.

but the way we trigger the rule is much simpler and easier to set up than what you have shown.

the table has a hidden button column called ‘WhenChanged’ and that button will be pressed when ANY item on the row is changed!

the table also has a date-time column called ‘Changed’ but it does NOT have a formula (instead it is set to Now() at the end of the ‘WhenChanged’ action.

the ‘WhenChanged’ button has a Disable If clause as follows

Modified(thisRow)-thisRow.Changed<=Seconds(2)

this means that the button is only enabled when the user changes any item on the row, making the Modified() function return a recent timestamp (ie: greater than the old saved timestamp). the 2 seconds lee-way is needed due to a delay of zero to two seconds in the Modified() function.

like you, we use a checkbox (called Active) that is set ON to run the automations, and can be set OFF if you need to stop the loop. this is an important fail-safe in case you have errors in your actions!

we have two global buttons to run the loop called ‘Start’ and ‘Repeat’.
the ‘Repeat’ button is disabled when Active is OFF - and that stops the loop (important).

the action for the ‘Start’ button is

RunActions(MyTable.WhenChanged, Repeat)

you can add further tables to the list if needed, before the ‘Repeat’.

and the action for ‘Repeat’ is

RunActions( _Delay(Start, 0 ))

remember, the ‘Repeat’ button is disabled if the Active checkbox is false!

the RunActions() are needed - i think they prevent the browser crashes that you experienced
and, as you discovered, the _Delay() is needed to allow the coda user interface to run at the same time as the loop - otherwise the UX freezes up.

finally, the ‘WhenChanged’ button action looks like this…

thisRow.ModifyRows(
    thisRow.someColum,someComputedValue,
    <any other changes to the row>
    thisRow.Changed, Now()
)

that last part is important. the last thing the action does is set the ‘Changed’ timestamp to Now() to record when the row was last changed by the action.

of course, you can add other actions inside the ‘WhenChanged’ button to modify other tables or control-values etc.

so basically, instead of having to shadow several columns to detect changes, we ‘shadow’ the Modified() time-stamp to detect when any column has changed.

we have not experienced any browser locking or crashes.

i hope i have explained it clearly. if not, let me know and i will rustle-up a sample doc

respect
max

8 Likes

I really like this approach max. It makes sense to me. Super well thought through.

1 Like

///the table has a hidden button column called ‘WhenChanged’ and that button will be pressed when ANY item on the row is changed!

So, how is that button pressed. By an automation? I am asking because I thought the problem was that automations can be delayed by an indetermined amount of time. If there is another way to trigger the button I would be interested to know.

so the two buttons ‘Start’ and ‘Repeat’ run as a flip-flop pair all the time (as long as Active=True).

and the ‘Start’ button pushes the ‘WhenChanged’ column of buttons in your table.

So the ‘WhenChanged’ buttons are pushed each time the ‘Start’ button fires (several times per second). But 99% of the time all the ‘WhenChanged’ buttons are disabled - except when a change is made to a row; then the Modified() timestamp is updated and no longer matches the Changed timestamp and the ‘WhenChanged’ button is enabled - so it gets pressed almost immediately.

so there are no coda automations involved at all

max

2 Likes

Very smart - I had not thought of using modified() as a trigger. I would prefer for Coda to add an OnModified() action to a button (in a table or on the canvas).

I would also like an OnOpenDocument() or an OnOpenPage() trigger to handle some stuff for our users and for logging purposes.

3 Likes

Joost

I agree that those triggers would be most useful. While we are at it, some other useful triggers might be OnCreated() and OnDelete() on table rows as well.

Max

2 Likes

Smart :slight_smile:

The _Delay() works because it puts the execution of the rest of the action on the next frame (i.e. into the queue), effectively unblocking the doc to process whatever else is pressed or changed. It’s basically JavaScript’s setTimeout()

2 Likes

a further refinement if required

if you do want to detect changes in just a FEW of the columns (say Col3 & Col4) then you change the disabled-if clause to be

(Modified(thisRow.Col3)-thisRow.Changed<=Seconds(2))
  OR 
(Modified(thisRow.Col4)-thisRow.Changed<=Seconds(2))

which will only ‘trigger’ for a row where either Col3 or Col4 has been changed.

this is because Modified() can take a ROW or a COLUMN as its parameter.

max

2 Likes