Skip to content

Define contextual variables exposed by VariablesAndExpressionParser using a static map in all cases, not just for the ThisLocation-related use case#4225

Open
jswalden wants to merge 1 commit into
bitfocus:mainfrom
jswalden:contextual-variables
Open

Define contextual variables exposed by VariablesAndExpressionParser using a static map in all cases, not just for the ThisLocation-related use case#4225
jswalden wants to merge 1 commit into
bitfocus:mainfrom
jswalden:contextual-variables

Conversation

@jswalden

@jswalden jswalden commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Only the final rev is new here -- the rest are just #4216 -- but Github doesn't seem to have a way to encode that and only show the final rev, that I know of.

This changes parser handling up enough that all statically injected variables are now enumerated and computed from a static map object, similar to what ThisLocationVariables currently does. VariablesAndExpressionParser loses its #thisValues map and instead gains 1) a #contextData containing stuff like control location, surface ID, etc. as needed for all defined statically injected variables, and 2) a #contextVariables map of all statically injected variables' names (for the particular variables flavor being implemented) to functions computing their values from that #contextData value.

Presently override variable values are used for exactly two things: 1) to inject this:surface_id into internal actions' variable parsing, and 2) per-graphical-element property overrides in createParseElementsContext. This patch changes the first case into a statically injected variable and removes override variable values from the parser interface entirely -- except through createChildParser. Given that overriding is the exceptional case, I think it is a good thing to impose that interface complexity on only the single caller that wants it -- and to make it easier to find the complexity by routing variable overriding through one call signature easily searched for and downstream uses readily tracked.

A bunch of the new function names are more or less just placeholder names so I could keep moving forward, with very little thought given to them. Feel free to suggest better names.

createStandaloneParser is called in once place with undefined surfaceId and nonnull localValues, in one place with non-undefined surfaceId and null localValues, and all other places with undefined/null for them. The first two could perhaps be split into separately named functions, and the rest left in a no-argument function, with correspondingly reduced statically-injected contextual variables, if that made things clearer.

Summary by CodeRabbit

  • Refactor

    • Variable/expression parsing redesigned to be context-aware (control, surface, or standalone) for more consistent and correct handling of contextual "this:*" and surface-scoped values.
  • Tests

    • Parser tests updated to validate new contextual behaviors, inheritance rules, and precedence between local, contextual, and override values.
  • Chores

    • Added a dev dependency for type-testing.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9bc9edab-05da-45bc-9af3-89f1ce0de634

📥 Commits

Reviewing files that changed from the base of the PR and between fbadae9 and caa54b9.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (22)
  • companion/lib/Controls/ControlStore.ts
  • companion/lib/Controls/ControlTypes/Button/Base.ts
  • companion/lib/Controls/ControlTypes/Button/Layered.ts
  • companion/lib/Controls/ControlTypes/Button/Preset.ts
  • companion/lib/Controls/ControlTypes/PageButton.ts
  • companion/lib/Controls/Controller.ts
  • companion/lib/Controls/Entities/EntityListPoolBase.ts
  • companion/lib/Controls/IControlStore.ts
  • companion/lib/ImportExport/Backups.ts
  • companion/lib/ImportExport/Controller.ts
  • companion/lib/ImportExport/Export.ts
  • companion/lib/Instance/Connection/ChildHandlerLegacy.ts
  • companion/lib/Instance/Connection/ChildHandlerNew.ts
  • companion/lib/Instance/Connection/EntityManager.ts
  • companion/lib/Internal/Controller.ts
  • companion/lib/Surface/Controller.ts
  • companion/lib/Variables/Values.ts
  • companion/lib/Variables/VariablesAndExpressionParser.ts
  • companion/test/Instance/EntityManager.test.ts
  • companion/test/Variables/Values.test.ts
  • companion/test/Variables/VariablesAndExpressionParser.test.ts
  • package.json
✅ Files skipped from review due to trivial changes (1)
  • companion/test/Instance/EntityManager.test.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • companion/lib/Controls/Controller.ts
  • companion/test/Variables/Values.test.ts
  • companion/lib/Controls/ControlTypes/PageButton.ts
  • companion/lib/Instance/Connection/ChildHandlerNew.ts
  • companion/lib/Instance/Connection/ChildHandlerLegacy.ts
  • companion/lib/Controls/ControlTypes/Button/Base.ts
  • companion/lib/ImportExport/Backups.ts
  • package.json
  • companion/lib/ImportExport/Controller.ts
  • companion/lib/ImportExport/Export.ts
  • companion/lib/Controls/ControlTypes/Button/Layered.ts
  • companion/lib/Controls/ControlTypes/Button/Preset.ts
  • companion/lib/Instance/Connection/EntityManager.ts
  • companion/lib/Controls/IControlStore.ts
  • companion/lib/Controls/Entities/EntityListPoolBase.ts
  • companion/lib/Surface/Controller.ts
  • companion/lib/Variables/Values.ts
  • companion/lib/Controls/ControlStore.ts
  • companion/test/Variables/VariablesAndExpressionParser.test.ts

📝 Walkthrough

Walkthrough

The PR replaces override-based parser creation with explicit factories (standalone, control, surface), refactors contextual this:* resolution into typed context maps, updates all caller sites to the new APIs/signatures, and updates tests to assert the new contextual behavior.

Changes

Variable parsing refactor across runtime flows

Layer / File(s) Summary
Parser context engine and construction refactor
companion/lib/Variables/VariablesAndExpressionParser.ts
VariablesAndExpressionParser now resolves contextual and local variables via typed context-variable maps and a value accessor; construction uses forControl/forSurface and child cloning preserves local entries.
VariablesValues parser factory API
companion/lib/Variables/Values.ts
VariablesValues removes createVariablesAndExpressionParser and adds createStandaloneParser, createParserForControl, and createParserForSurface, delegating to the parser factories.
Control store and interface signatures
companion/lib/Controls/IControlStore.ts, companion/lib/Controls/ControlStore.ts, companion/lib/Controls/Entities/EntityListPoolBase.ts
Control store and interfaces replace overrideVariableValues with surfaceId and call the new parser factories accordingly.
Control entity pools and button implementations
companion/lib/Controls/ControlTypes/*, companion/lib/Controls/Controller.ts
Entity pools and button controls create parsers via createParserForControl or createStandaloneParser, removing injected-variable construction from button rendering paths.
Surface, internal, instance, and import/export callsites
companion/lib/Surface/Controller.ts, companion/lib/Internal/Controller.ts, companion/lib/Instance/Connection/*.ts, companion/lib/ImportExport/*
Runtime callsites now use the new parser factories and pass surfaceId or undefined as appropriate for surface evaluation, action parsing, entity option parsing, and import/export preview/filename parsing.
Test suite updates for context variables and factory methods
companion/test/Variables/*, companion/test/Instance/EntityManager.test.ts, package.json
Tests construct parsers via forControl/forSurface, assert contextual this:* exposure and precedence, remove obsolete injected-location tests, and package.json adds type-testing as a devDependency.

Parsers shed their overrides and find new roots,
this:* wakes in contexts, local keys take their turns—
surface IDs whisper where scope must be known,
callers now ask the right questions, tests nod true. 🌿

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main refactoring: replacing override variable values with a static map approach for defining contextual variables in VariablesAndExpressionParser.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jswalden jswalden changed the title Contextual variables Define contextual variables exposed by VariablesAndExpressionParser using a static map in all cases, not just for the ThisLocation-related use case Jun 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
companion/test/Variables/Values.test.ts (1)

5-5: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix the stale import/export contract for location variable helper

Thanks for expanding the raw this:* coverage — nice test direction. Right now, Line 5 imports InjectedVariablesForLocation, but CI shows ../../lib/Variables/Values.js no longer exports it, so type-check and tests fail before execution. Please align this test with the current exported API (or re-export the helper from Values.ts) so the suite compiles again.

Source: Pipeline failures


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4ebb3599-6863-4645-a50f-c00d2263fd14

📥 Commits

Reviewing files that changed from the base of the PR and between 28ba9a5 and 4b96a68.

📒 Files selected for processing (31)
  • companion/lib/Controls/ControlStore.ts
  • companion/lib/Controls/ControlTypes/Button/Base.ts
  • companion/lib/Controls/ControlTypes/Button/Layered.ts
  • companion/lib/Controls/ControlTypes/Button/Preset.ts
  • companion/lib/Controls/ControlTypes/PageButton.ts
  • companion/lib/Controls/Controller.ts
  • companion/lib/Controls/Entities/EntityListPoolBase.ts
  • companion/lib/Controls/Entities/EntityListPoolButton.ts
  • companion/lib/Controls/IControlStore.ts
  • companion/lib/Graphics/ConvertGraphicsElements.ts
  • companion/lib/ImportExport/Backups.ts
  • companion/lib/ImportExport/Controller.ts
  • companion/lib/ImportExport/Export.ts
  • companion/lib/Instance/Connection/ChildHandlerLegacy.ts
  • companion/lib/Instance/Connection/ChildHandlerNew.ts
  • companion/lib/Instance/Connection/EntityManager.ts
  • companion/lib/Internal/Controller.ts
  • companion/lib/Preview/ElementStream.ts
  • companion/lib/Preview/ExpressionStream.ts
  • companion/lib/Preview/Graphics.ts
  • companion/lib/Surface/Controller.ts
  • companion/lib/Surface/IP/Satellite.ts
  • companion/lib/Surface/PluginPanel.ts
  • companion/lib/Surface/Types.ts
  • companion/lib/Variables/Util.ts
  • companion/lib/Variables/Values.ts
  • companion/lib/Variables/VariablesAndExpressionParser.ts
  • companion/test/Variables/Values.test.ts
  • companion/test/Variables/VariablesAndExpressionParser.test.ts
  • companion/test/Variables/executeExpression.test.ts
  • companion/test/Variables/parse.test.ts

Comment thread companion/lib/Instance/Connection/ChildHandlerLegacy.ts
Comment thread companion/lib/Instance/Connection/ChildHandlerNew.ts
Comment thread companion/test/Variables/VariablesAndExpressionParser.test.ts Outdated
@jswalden jswalden force-pushed the contextual-variables branch from 4b96a68 to 17c2ad4 Compare June 6, 2026 00:08
@Julusian Julusian changed the base branch from main to stable-4.3 June 6, 2026 13:36
@Julusian Julusian changed the base branch from stable-4.3 to main June 6, 2026 13:36
@jswalden jswalden force-pushed the contextual-variables branch from 17c2ad4 to f3ddc2a Compare June 7, 2026 19:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
companion/test/Variables/VariablesAndExpressionParser.test.ts (1)

82-104: ⚡ Quick win

Consider adding one direct test for forControl(..., surfaceId) branch.

You already cover forControl (without surface id) and forSurface, which is great. A small additional case for forControl with a defined surfaceId would lock in the dedicated branch that uses ThisLocationThroughSurfaceVariables.

[Suggestion only — this can be a follow-up if you prefer.]

Also applies to: 161-202


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cf77883d-630e-49c5-baa0-072986c218b7

📥 Commits

Reviewing files that changed from the base of the PR and between 17c2ad4 and f3ddc2a.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (22)
  • companion/lib/Controls/ControlStore.ts
  • companion/lib/Controls/ControlTypes/Button/Base.ts
  • companion/lib/Controls/ControlTypes/Button/Layered.ts
  • companion/lib/Controls/ControlTypes/Button/Preset.ts
  • companion/lib/Controls/ControlTypes/PageButton.ts
  • companion/lib/Controls/Controller.ts
  • companion/lib/Controls/Entities/EntityListPoolBase.ts
  • companion/lib/Controls/IControlStore.ts
  • companion/lib/ImportExport/Backups.ts
  • companion/lib/ImportExport/Controller.ts
  • companion/lib/ImportExport/Export.ts
  • companion/lib/Instance/Connection/ChildHandlerLegacy.ts
  • companion/lib/Instance/Connection/ChildHandlerNew.ts
  • companion/lib/Instance/Connection/EntityManager.ts
  • companion/lib/Internal/Controller.ts
  • companion/lib/Surface/Controller.ts
  • companion/lib/Variables/Values.ts
  • companion/lib/Variables/VariablesAndExpressionParser.ts
  • companion/test/Instance/EntityManager.test.ts
  • companion/test/Variables/Values.test.ts
  • companion/test/Variables/VariablesAndExpressionParser.test.ts
  • package.json
✅ Files skipped from review due to trivial changes (2)
  • package.json
  • companion/test/Instance/EntityManager.test.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • companion/lib/Instance/Connection/EntityManager.ts
  • companion/lib/ImportExport/Backups.ts
  • companion/lib/Controls/ControlTypes/Button/Preset.ts
  • companion/lib/Instance/Connection/ChildHandlerNew.ts
  • companion/lib/Controls/ControlTypes/Button/Layered.ts
  • companion/lib/Instance/Connection/ChildHandlerLegacy.ts
  • companion/test/Variables/Values.test.ts
  • companion/lib/Internal/Controller.ts
  • companion/lib/Controls/Controller.ts
  • companion/lib/Variables/Values.ts
  • companion/lib/Controls/ControlStore.ts
  • companion/lib/Variables/VariablesAndExpressionParser.ts

Comment thread companion/test/Variables/VariablesAndExpressionParser.test.ts
@jswalden jswalden force-pushed the contextual-variables branch from f3ddc2a to fbadae9 Compare June 7, 2026 19:35
@jswalden jswalden force-pushed the contextual-variables branch from fbadae9 to caa54b9 Compare June 7, 2026 19:57
@jswalden

jswalden commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Heh, I see #4234 is afoot. Guess that'll be another case of overriding to do some minor work shunting through this, once it's completed.

@Julusian

Julusian commented Jun 9, 2026

Copy link
Copy Markdown
Member

yeah sorry about that. It might be one minor merge conflict though? (unless you also want to move some of the new code I have added?)

@jswalden

jswalden commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Yeah, the new code I think ought be transitioned over. Shouldn't be hard to do. No worries about mild merge conflicts, it comes with the terrain working on shared code.

@Julusian

Julusian commented Jun 9, 2026

Copy link
Copy Markdown
Member

I am wondering if the right approach is to be pushing all the 'overrides' so that VariablesAndExpressionParser owns the definitions of these variations, or whether it is better for the places which use these parsers to bring this knowledge.
I appreciate that the current state is that it isn't exactly clear on any particular direction for this, and you have largely continued with the shape that was forming.

But I'm wondering that when I come to add more types of controls (with differing supported this variables), should it be the VariablesAndExpressionParser that has to know about this, or it feels like it would be cleaner (from a modularity view, less clean in some other ways) to have that knowledge be held by the type of the control (which wasnt in place to handle at all).

I am aware that this is very much something that doesnt have a right and a wrong answer, so this is really just a gentle question and giving myself some time to see if I form a stronger feeling either way.

@jswalden

jswalden commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Allowing users to bring their own static variables set seems pretty reasonable. Something like (with the necessary typing added to force vars/context type compatibility)

class BringYourOwnVars {
  vars
  context

  constructor(vars, context) {
    this.vars = vars
    this.context = context
  }
  
  has(id) { return id in this.vars }
  get(id) { return id in this.vars ? this.vars[id](this.context) : undefined }
}

would let a user pass in their statically defined record along the lines of the couple static sets in this patch, without the parser having to know all the details.

I like the idea of keeping some clear delineated separation between statically enumerable variables, and the arbitrary variables provided by $(target:*), or similar kinds of forwarding of unspecified sets of variable IDs. But in principle you could use a BYOV-like thing for this as well, at expense of a little clarity of purpose.

@Julusian Julusian added this to the v5.0 milestone Jun 10, 2026
@github-project-automation github-project-automation Bot moved this to In Progress in Companion Plan Jun 10, 2026
@jswalden

Copy link
Copy Markdown
Contributor Author

Okay, I'm trying to merge all this...and also trying to figure out how to conceptualize the new stuff and the existing stuff. You're right, they're kind of skew on the purpose front.

My aim here was to make at least somewhat clear, semi-statically, what variables are incorporated into each created parser -- and to eliminate per-contextual-variable up-front cost.

This is somewhat motivated by the $(this:surface_id) variable: mostly by accident, it is currently (by accident) "supported" in store-result UI fields (and will be autosuggested by them in UI) but will consistently be undefined. (If all the variable parsing were done upon execution of the action, then you could easily incorporate in the real surface ID as happens for action options. But the store-result stuff is precomputed, so we don't know the surface ID to include!) I wanted to make clearer at creation sites exactly which contextual variables are supported (and then once this cleanup is done, shift the store-result stuff to use a this:surface_id-less version).

It would make sense to build this:value and this:current into statically-defined paths. But the target:* variables are obviously not statically knowable. (I've only semi-skimmed the #4234 PR, so it's possible I've missed other additions.)

Perhaps the lowest-level parser construction path should take 1) a staticVariables record along the lines proposed already here, 2) a staticContext value along the lines laid out here, and 3) a dynamicVariables that's a VariableValues akin to overrides here. And perhaps export two functions to create the static-only parsing form and the with-dynamic-additions form. We would probably want to have separate files to create the separate kinds of parser, but they could mostly self-containedly know the exact variables they exposed and requisite data needed to compute their values.

@Julusian

Copy link
Copy Markdown
Member

It would make sense to build this:value and this:current into statically-defined paths. But the target:* variables are obviously not statically knowable

this:value is not a name to consider, it was early naming that got changed to this:current.
But yes, the target variables arent knowable, similar to local.


Honestly I am not sure what the approach should be here. I understand what you are saying, but am struggling to get into the space to figure out what all the routes are and how to tidy things up. So I dont have any helpful input on the approach to take at this point.

This isnt the first time that the ui and backend have drifted on which special variables they support, so I do agree it seems like a weak point that would be good to address somehow.

A thought that could help on the ui side when you get to that; if the list of variables does belong to the control, the control could report these same list to the ui in its data blob. This could include a bunch of flags (eg 'excludeFromStoreResult') and could be thrown into a react context, or maybe just passed as a prop to bubble it through the hierarchy and lets the usages pick it up.
While it wouldnt solve the drift, it at least keeps things together in one file and avoids the fixed lists hardcoded in the ui. Or maybe something like this done some other way.

I am not set on it necessarily being driven by the control though, thats just feels like the natural ownership to me currently.
And yeah if that wants to be a class fed to the parser constructor instead of a loose bag of values that is fine with me. Those classes should be easy enough to give test coverage to try to catch mismatches.

the arbitrary variables provided by $(target:*), or similar kinds of forwarding of unspecified sets of variable IDs

I think this target forwarding is a bit of a special case, I cant think of where else we might want something like this at the moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants