Skip to content

Conversation

@chhoumann
Copy link
Owner

@chhoumann chhoumann commented Nov 22, 2025

Summary

  • replace params.variables object with a Map-backed proxy so macros/user scripts share a single source of truth (no pull/push sync needed)
  • keep conditional commands and other engines using the same shared map; merge existing executor variables when a custom map is provided to avoid state loss
  • add hasOwnProperty shim for backward compatibility; expand proxy edge-case coverage
  • fix AI helper assignments to write assistant outputs into the variables Map instead of Object.assign on Map

Testing

  • bun run lint
  • bun run build-with-lint
  • bun run test

Summary by CodeRabbit

  • New Features

    • Introduced a proxy-backed variables mechanism for consistent variable access across macros.
  • Improvements

    • Refactored variable handling to use the new proxy, improving reliability of variable reads/writes during macro execution.
    • Updated how assistant outputs populate variables to use the Map-backed storage.
  • Tests

    • Added extensive tests validating variable proxy behavior and propagation across sequential user scripts.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Nov 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
quickadd Ready Ready Preview Nov 23, 2025 11:26am

@coderabbitai
Copy link

coderabbitai bot commented Nov 22, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

A Map-backed variables proxy was introduced and integrated into MacroChoiceEngine and SingleMacroEngine, replacing prior param/executor synchronization. createVariablesProxy exposes a Map as an object-like proxy. Tests were added for the proxy and for macro variable propagation; one test suite was duplicated.

Changes

Cohort / File(s) Summary
Variable proxy utility
src/utils/variablesProxy.ts, src/utils/variablesProxy.test.ts
New createVariablesProxy(store) exported function; Proxy maps object-like operations (get/set/delete/ownKeys/has/getOwnPropertyDescriptor) to a backing Map. Extensive test suite added validating reads/writes, enumeration, external Map mutations, Object APIs, symbol handling, and multi-proxy synchronization.
MacroChoiceEngine refactor
src/engine/MacroChoiceEngine.ts, src/engine/MacroChoiceEngine.entry.test.ts
Integrated proxy via new buildParams and initSharedVariables helpers; engine now uses a proxy-wrapped shared Map and assigns it to choiceExecutor.variables. Removed pullExecutorVariablesIntoParams and related synchronization. Test suite asserting sequential user-script variable propagation added (duplicated in file).
SingleMacroEngine adjustment
src/engine/SingleMacroEngine.ts
Removed pre-execution copying of executor Map variables into engine params inside executeResolvedMember; engine relies on shared proxy-backed variables instead.
QuickAdd API variable assignment
src/quickAddApi.ts
Replaced Object.assign-based population with Map-aware updates: iterate result entries and call choiceExecutor.variables.set(key, value) in three guarded locations.
Tests setup tweak
vitest setup import locations
Added/adjusted afterEach import usage in Vitest setup referenced by tests (test scaffolding change).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MacroChoiceEngine
    participant VariablesProxy as Proxy(Map)
    participant Executor as choiceExecutor.variables

    User->>MacroChoiceEngine: construct(engine, app, plugin, providedVars?)
    MacroChoiceEngine->>MacroChoiceEngine: initSharedVariables(providedVars)
    MacroChoiceEngine->>VariablesProxy: createVariablesProxy(sharedMap)
    VariablesProxy-->>MacroChoiceEngine: proxy (object-like)
    MacroChoiceEngine->>Executor: assign proxy reference

    Note over MacroChoiceEngine,Executor: During execution, reads/writes use proxy
    MacroChoiceEngine->>VariablesProxy: read/write key
    VariablesProxy->>Executor: Map.get/Map.set/Map.delete
    Executor-->>VariablesProxy: external Map mutations visible immediately
    VariablesProxy-->>MacroChoiceEngine: reflected value
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas to focus:
    • src/utils/variablesProxy.ts: correctness of traps (get/set/deleteProperty/ownKeys/getOwnPropertyDescriptor) and hasOwnProperty shim behavior.
    • src/engine/MacroChoiceEngine.ts: initSharedVariables merge semantics and buildParams construction; removal of synchronization calls.
    • src/engine/SingleMacroEngine.ts: ensuring engines rely correctly on proxy rather than copied params.
    • src/quickAddApi.ts: verify all three replacement sites correctly call Map.set with intended keys/values.
    • src/engine/MacroChoiceEngine.entry.test.ts: duplicated test block — confirm intentional duplication or remove one.

Poem

🐰 I hopped into Maps and knitted a sleeve,
Wrapped keys in a proxy for all to believe.
No copying dances, no sync left to do,
Variables whisper, and changes shine through. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preserving macro user script variable updates through a Map-backed proxy instead of manual synchronization.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/bug995-macro-vars

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43ed335 and 6a34036.

📒 Files selected for processing (1)
  • src/utils/variablesProxy.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/variablesProxy.test.ts

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/engine/MacroChoiceEngine.ts (2)

159-159: Push completes the bidirectional sync pattern.

The push after each command ensures mutations to params.variables by user scripts are persisted back into the executor's Map, making them available to subsequent macro commands and the broader execution context.

Optional consideration: If a user script deletes a key from params.variables (e.g., delete params.variables.foo), the deletion won't propagate to the executor's Map. This is likely intentional since variables typically accumulate, but worth noting if you expect deletion semantics.

Also applies to: 496-500


462-463: Redundant variable pull after line 131 addition.

The pullExecutorVariablesIntoParams() call here is now redundant because line 131 already pulls executor variables before each command, including this conditional command. The extra call is harmless but unnecessary.

Consider removing this line:

 	private async executeConditional(command: IConditionalCommand) {
-		this.pullExecutorVariablesIntoParams();
 		const shouldRunThenBranch = await evaluateCondition(command.condition, {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b4f177 and 9f7adf9.

📒 Files selected for processing (2)
  • src/engine/MacroChoiceEngine.entry.test.ts (2 hunks)
  • src/engine/MacroChoiceEngine.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/engine/MacroChoiceEngine.entry.test.ts (3)
src/IChoiceExecutor.ts (1)
  • IChoiceExecutor (4-18)
src/quickAddApi.ts (1)
  • QuickAddApi (32-614)
src/engine/MacroChoiceEngine.ts (1)
  • MacroChoiceEngine (57-612)
🔇 Additional comments (4)
src/engine/MacroChoiceEngine.ts (1)

129-131: Excellent fix for variable propagation.

The per-command pull ensures each command starts with the freshest shared state, making user script mutations immediately visible to subsequent scripts. The inline comment clearly explains the intent.

src/engine/MacroChoiceEngine.entry.test.ts (3)

1-1: Good test hygiene with spy cleanup.

The afterEach properly restores the GetApi spy, preventing test pollution.

Also applies to: 286-288


214-284: Well-structured regression test for variable propagation.

The test setup correctly simulates a sequence of four user scripts where each script reads the current value of params.variables.target, logs it, and writes an updated value. This directly exercises the per-command synchronization logic introduced in the fix.


290-303: Comprehensive assertions validate the fix.

The test properly validates both directions of synchronization:

  • The logs array [undefined, 1, 2, 3] proves each script sees the previous script's mutation (pull is working).
  • The final assertion on choiceExecutor.variables.get("target") proves mutations are persisted back to the executor (push is working).

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/utils/variablesProxy.ts (1)

31-37: Consider adding value to the property descriptor.

The current getOwnPropertyDescriptor trap returns only configurable and enumerable, but omits the value property. While this works for basic enumeration (which your tests verify), operations that rely on complete descriptors—such as Object.getOwnPropertyDescriptor() for introspection or certain framework reflection patterns—may receive incomplete information.

Apply this diff to include the value:

 getOwnPropertyDescriptor(_t, prop: string | symbol) {
   if (typeof prop !== "string" || !store.has(prop)) return undefined;
   return {
+    value: store.get(prop),
+    writable: true,
     configurable: true,
     enumerable: true,
   };
 },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e9b9f0 and 242c003.

📒 Files selected for processing (4)
  • src/engine/MacroChoiceEngine.ts (2 hunks)
  • src/engine/SingleMacroEngine.ts (0 hunks)
  • src/utils/variablesProxy.test.ts (1 hunks)
  • src/utils/variablesProxy.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/engine/SingleMacroEngine.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/utils/variablesProxy.test.ts (1)
src/utils/variablesProxy.ts (1)
  • createVariablesProxy (5-39)
src/engine/MacroChoiceEngine.ts (1)
src/utils/variablesProxy.ts (1)
  • createVariablesProxy (5-39)
🔇 Additional comments (3)
src/utils/variablesProxy.test.ts (1)

1-27: LGTM! Comprehensive test coverage for the proxy.

The tests effectively verify the core functionality: read/write operations sync with the backing Map, deletion propagates correctly, and enumeration/JSON serialization work as expected for plain-object usage patterns.

src/engine/MacroChoiceEngine.ts (1)

97-105: Excellent fix! The proxy approach resolves the variable synchronization issue.

By creating a shared Map and exposing it through a proxy, all mutations—whether through params.variables in user scripts or direct choiceExecutor.variables.set() calls (e.g., line 454 in executeAIAssistant)—operate on the same backing store. This eliminates the copy-then-overwrite pattern that previously discarded updates, directly addressing the concern raised in the past review comment.

src/utils/variablesProxy.ts (1)

11-14: No action needed—the implementation is correct and intentional.

The verification confirms that rejecting non-string properties in the get trap is the correct design. A comprehensive search of the codebase found zero instances of numeric key access (e.g., variables[0]), and all variable access uses string keys derived from Object.keys() or direct string identifiers. The underlying store is Map<string, unknown>, and the proxy is explicitly designed for plain-object-style access with string keys, as documented in the code comment. The test suite validates this behavior.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
src/engine/MacroChoiceEngine.ts (1)

465-467: Fix Object.assign calls to work with Map interface in src/quickAddApi.ts.

The verification confirms the original concern is valid. Two instances of Object.assign(choiceExecutor.variables, assistantRes) remain in src/quickAddApi.ts (lines 277 and 376), which cannot add entries to a Map object. These must be updated to use Map.set() or equivalent logic to ensure consistency with the MacroChoiceEngine.ts implementation that correctly iterates and sets variables into the Map.

🧹 Nitpick comments (1)
src/engine/MacroChoiceEngine.ts (1)

97-113: Simplify the merge logic for clarity.

The merge logic correctly preserves existing executor variables when a fresh map is provided, but the flow is confusing because sharedVariables is assigned to variables on line 99, then variables is mutated on line 109, and finally sharedVariables is assigned to the executor on line 113.

Consider this clearer flow:

-		const existingVariables = this.choiceExecutor.variables;
-		const sharedVariables =
-			variables ?? existingVariables ?? new Map<string, unknown>();
-
-		// If a fresh map was provided, merge any existing executor variables
-		// so we don't accidentally drop state the caller wanted to keep.
-		if (
-			variables &&
-			existingVariables &&
-			variables !== existingVariables
-		) {
-			existingVariables.forEach((value, key) => {
-				if (!variables.has(key)) variables.set(key, value);
-			});
-		}
-
-		this.choiceExecutor.variables = sharedVariables;
+		const existingVariables = this.choiceExecutor.variables;
+		let sharedVariables: Map<string, unknown>;
+
+		if (variables) {
+			sharedVariables = variables;
+			// Merge any existing executor variables so we don't drop state
+			if (existingVariables && variables !== existingVariables) {
+				existingVariables.forEach((value, key) => {
+					if (!sharedVariables.has(key)) {
+						sharedVariables.set(key, value);
+					}
+				});
+			}
+		} else {
+			sharedVariables = existingVariables ?? new Map<string, unknown>();
+		}
+
+		this.choiceExecutor.variables = sharedVariables;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 242c003 and 836ef3a.

📒 Files selected for processing (4)
  • src/engine/MacroChoiceEngine.entry.test.ts (2 hunks)
  • src/engine/MacroChoiceEngine.ts (2 hunks)
  • src/utils/variablesProxy.test.ts (1 hunks)
  • src/utils/variablesProxy.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils/variablesProxy.test.ts
  • src/engine/MacroChoiceEngine.entry.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/engine/MacroChoiceEngine.ts (2)
src/quickAddApi.ts (1)
  • QuickAddApi (32-614)
src/utils/variablesProxy.ts (1)
  • createVariablesProxy (5-41)
🔇 Additional comments (3)
src/utils/variablesProxy.ts (1)

1-41: LGTM! Solid proxy implementation.

The proxy implementation correctly wraps a Map with a plain-object interface. All necessary traps are implemented:

  • Property access operations (get/set/delete/has) are properly delegated to the Map
  • Reflection operations (ownKeys/getOwnPropertyDescriptor) support enumeration and introspection
  • Symbol properties are consistently filtered out
  • The null-prototype target prevents prototype pollution

This enables scripts to use familiar object syntax (params.variables.foo = "bar") while maintaining the Map as the canonical source of truth.

src/engine/MacroChoiceEngine.ts (2)

50-50: LGTM! Correct import.

The import brings in the new proxy utility for shared variable management.


117-117: LGTM! Proxy-based shared variables solve the synchronization issue.

Replacing the plain object with createVariablesProxy(this.choiceExecutor.variables) ensures that all reads/writes to params.variables are immediately reflected in the shared executor Map, eliminating the manual push/pull synchronization that caused the overwriting bug mentioned in past reviews.

Copy link

@coderabbitai coderabbitai bot left a comment

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)
src/engine/MacroChoiceEngine.ts (1)

50-51: Shared variables Map + proxy cleanly fixes stale-variable sync issues

The constructor’s new logic to derive a sharedVariables Map (preferring the provided variables, merging in any missing keys from an existing executor map, and then assigning it back to this.choiceExecutor.variables) ensures a single source of truth for macro variables without discarding existing executor state. Wiring params.variables through createVariablesProxy(this.choiceExecutor.variables) lets user scripts keep their plain-object ergonomics while reading/writing directly to the shared Map, eliminating the previous pull/push synchronization that could overwrite newer executor updates.

One minor clarity tweak you could consider: since the code branches on if (variables), you might either mark the constructor parameter as optional (variables?: Map<string, unknown>) or drop the if and assume it’s always defined, depending on the intended contract. Not required, but it would better align types with runtime behavior.

Also applies to: 97-115, 118-123

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 836ef3a and decad2a.

📒 Files selected for processing (4)
  • src/engine/MacroChoiceEngine.ts (2 hunks)
  • src/quickAddApi.ts (2 hunks)
  • src/utils/variablesProxy.test.ts (1 hunks)
  • src/utils/variablesProxy.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/variablesProxy.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/engine/MacroChoiceEngine.ts (2)
src/quickAddApi.ts (1)
  • QuickAddApi (32-618)
src/utils/variablesProxy.ts (1)
  • createVariablesProxy (7-54)
src/utils/variablesProxy.test.ts (1)
src/utils/variablesProxy.ts (1)
  • createVariablesProxy (7-54)
🪛 GitHub Actions: Build With Lint
src/utils/variablesProxy.test.ts

[error] 77-77: bun run build-with-lint failed. TS2578: Unused '@ts-expect-error' directive in src/utils/variablesProxy.test.ts:77.

🪛 GitHub Check: Build With Lint
src/utils/variablesProxy.test.ts

[failure] 77-77:
Unused '@ts-expect-error' directive.

🔇 Additional comments (4)
src/quickAddApi.ts (1)

275-279: Map-based assignment of AI assistant results is correct and consistent

Using Object.entries(assistantRes).forEach(([key, value]) => choiceExecutor.variables.set(key, value)) aligns with the new Map-backed variable store and preserves prior semantics of copying all assistant result keys (e.g., output, output-quoted) into the executor variables when shouldAssignVariables is set. No issues found here.

Also applies to: 376-380

src/utils/variablesProxy.test.ts (1)

1-80: Proxy behavior tests are thorough and aligned with the implementation

The test suite exercises all key proxy behaviors (read/write/delete, key enumeration, external Map mutations, symbol handling, for...in/Object.assign, and the hasOwnProperty shim), matching createVariablesProxy’s traps and guarding against regressions. No functional gaps spotted.

src/engine/MacroChoiceEngine.ts (2)

466-468: AI assistant outputs now correctly populate the shared variables Map

Storing aiOutputVariables via this.choiceExecutor.variables.set(key, aiOutputVariables[key]) ensures AI-generated values land in the shared executor Map, making them immediately visible to both macro commands and user scripts through params.variables. This removes the earlier risk of results being lost or overwritten by params–executor sync.


472-476: Conditional scripts correctly consume the same proxy-backed variables

Passing this.params.variables into evaluateCondition and into conditional scripts loaded via loadConditionalScript means all conditional logic now operates on the same proxy-backed variables object that user scripts see. This keeps condition evaluation in sync with live macro state without any manual copy-back, and is consistent with the new Map/proxy design.

Also applies to: 548-552

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/utils/variablesProxy.test.ts (1)

4-78: Consider optional edge case coverage.

The test suite is comprehensive and well-structured. For completeness, you might optionally consider adding test coverage for:

  • Object.entries() and Object.values() enumeration
  • Empty string keys ("")
  • Numeric string keys ("0", "123")
  • Distinguishing proxy.key = undefined vs delete proxy.key
  • Multiple proxy instances sharing the same backing Map

These are nice-to-have refinements rather than critical gaps.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between decad2a and 6a291b0.

📒 Files selected for processing (2)
  • docs/dev/macro-variables-proxy.md (1 hunks)
  • src/utils/variablesProxy.test.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/dev/macro-variables-proxy.md
🧰 Additional context used
🧬 Code graph analysis (1)
src/utils/variablesProxy.test.ts (1)
src/utils/variablesProxy.ts (1)
  • createVariablesProxy (7-54)
🔇 Additional comments (7)
src/utils/variablesProxy.test.ts (7)

1-4: LGTM!

The imports and test suite structure are correct and well-organized.


5-15: LGTM!

This test correctly validates that the proxy forwards read, write, and delete operations to the backing Map.


17-26: LGTM!

This test correctly validates enumeration behavior through Object.keys() and JSON.stringify(), ensuring the proxy's ownKeys trap works as expected.


28-38: LGTM!

This test validates the core requirement of the PR: the proxy provides a live view of the backing Map, ensuring all engines and scripts see mutations immediately without manual synchronization.


40-50: LGTM!

This test correctly validates that symbol properties are gracefully ignored without throwing errors or modifying the backing Map. The @ts-expect-error directives here are appropriate and intentional.


52-67: LGTM!

This test validates that the proxy works correctly with for...in iteration and Object.assign(), ensuring compatibility with common object manipulation patterns used in user scripts.


69-78: LGTM!

This test correctly validates the hasOwnProperty shim while ensuring prototype methods remain unexposed. The cast to Record<string, unknown> on line 77 is the proper fix for the previous build failure.

@chhoumann chhoumann merged commit 26a7cf5 into master Nov 23, 2025
4 checks passed
@chhoumann chhoumann deleted the fix/bug995-macro-vars branch November 23, 2025 12:14
github-actions bot pushed a commit that referenced this pull request Dec 9, 2025
# [2.9.0](2.8.0...2.9.0) (2025-12-09)

### Bug Fixes

* honor dateFormat in requestInputs ([903df23](903df23))
* preserve macro user script variable updates ([#999](#999)) ([26a7cf5](26a7cf5))
* reuse current tab when open file new-tab is off ([#1007](#1007)) ([1b67d77](1b67d77))

### Features

* improve macro open file command ([#994](#994)) ([9b4f177](9b4f177))
@github-actions
Copy link

github-actions bot commented Dec 9, 2025

🎉 This PR is included in version 2.9.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] User Script Variable Mutation Persists Incorrectly Across Scripts

2 participants