Skip to content

feat: transient variables — reactive but excluded from persistence (#137)#138

Merged
rohal12 merged 12 commits into
mainfrom
worktree-feat-transient-variables
Mar 27, 2026
Merged

feat: transient variables — reactive but excluded from persistence (#137)#138
rohal12 merged 12 commits into
mainfrom
worktree-feat-transient-variables

Conversation

@rohal12
Copy link
Copy Markdown
Owner

@rohal12 rohal12 commented Mar 27, 2026

Summary

  • Add new % variable scope (transient): reactive Zustand-backed variables excluded from all persistence (history snapshots, save payloads, session storage). Declared in an optional StoryTransients passage with %name = value syntax.
  • Full twee-side support: {%var} display, {set %var = expr}, {unset %var}, conditionals, loops, expressions, and Story.set('%var', val) / Story.get('%var') API with variableChanged events using %-prefixed keys.
  • Cross-scope name collision validation, storeVar rejection for input macros, and transient propagation through all evaluate() callsites (ExprDisplay, Computed, triggers, WidgetInvocation).

Closes #137

Changes (27 files, +557 −59)

Layer Files What
Expression engine expression.ts TRANS_RE regex with (?<!\w) lookbehind for modulo disambiguation, transient parameter on evaluate()/execute()
Store store.ts transient/transientDefaults state, setTransient/deleteTransient actions, init/restart/load integration
Parsing story-variables.ts, index.tsx Parameterized parseStoryVariables with sigil, StoryTransients passage parsing, cross-scope collision check
Tokenizer/AST tokenizer.ts, ast.ts % sigil in bare and CSS-selector-prefixed variable paths
Rendering VarDisplay.tsx, render.tsx, interpolation.ts transient scope in display, markdown integration, string interpolation
Hooks use-merged-locals.ts, use-interpolate.ts 3-tuple → 4-tuple with transient
Macros define-macro.ts, execute-mutation.ts, Unset.tsx merged flag, mutation diffing, {unset %var} routing
Macro guards define-macro.ts storeVar rejects % sigil for input macros
Story API story-api.ts % sigil routing in get()/set(), variableChanged event with % prefix
Callsite fixes ExprDisplay.tsx, Computed.tsx, triggers.ts, WidgetInvocation.tsx Pass transient to all evaluate() callsites
Types types/index.d.ts JSDoc for % prefix on get()/set()
Docs variables.md, special-passages.md, story-api.md, markup.md, CHANGELOG.md Full transient variables documentation
Tests expression.test.ts, transient-variables.test.tsx 10 unit + 9 integration tests

Test plan

  • npx tsc --noEmit — clean
  • npx vitest run — 1068 tests pass (1 pre-existing build integration skip)
  • Unit tests: % transform, modulo disambiguation, string literal preservation, mixed sigils
  • Integration tests: {%var} display, {set}, {unset}, {if}, {for}, navigation persistence, goBack stays current, save exclusion, restart reset

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown

Release preview: merging this PR will publish v0.43.0 (minor bump from v0.42.0)

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 27, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 77.31% 2818 / 3645
🔵 Statements 76.3% 3078 / 4034
🔵 Functions 68.83% 508 / 738
🔵 Branches 73.28% 1539 / 2100
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/define-macro.ts 96.72% 91.17% 100% 96.42% 124, 190-194
src/execute-mutation.ts 93.33% 68.75% 100% 93.33% 52, 57
src/expression.ts 86.55% 76.28% 86.95% 87.35% 81-82, 104-105, 116-117, 121, 123-124, 132-133, 151-152, 160, 228-229, 267-268, 271-274, 342
src/index.tsx 0% 0% 0% 0% 22-241
src/interpolation.ts 89.65% 86.84% 100% 90.74% 52, 96-97, 105, 111-112
src/store.ts 81.08% 73% 72.04% 83.06% 92, 126, 318-319, 385, 394-397, 431-436, 554, 610, 636-639, 653, 657-660, 672-744
src/story-api.ts 30.43% 14.06% 25% 31.78% 76, 102-104, 198-232, 240-324, 337-415, 431-448, 475-480, 507-542
src/story-variables.ts 96.15% 84.61% 100% 97.22% 37-39, 81-83, 120
src/triggers.ts 75% 60.52% 65.21% 82.89% 56, 80, 83-88, 95-96, 99, 102-103, 112, 119, 130, 140, 155-156, 170, 172, 186
src/components/macros/Computed.tsx 75.47% 76.31% 100% 78.26% 13, 14, 17-18, 20, 26-28, 35-37, 48-51, 71-75, 97-101
src/components/macros/ExprDisplay.tsx 5.55% 0% 0% 6.66% 14-45
src/components/macros/Unset.tsx 93.75% 90% 100% 93.75% 23-25
src/components/macros/VarDisplay.tsx 91.3% 95% 100% 90.9% 40-41
src/components/macros/WidgetInvocation.tsx 96% 95.28% 66.66% 97.19% 45, 199, 201, 252
src/hooks/use-interpolate.ts 80% 75% 100% 75% 13
src/hooks/use-merged-locals.ts 100% 100% 100% 100%
src/markup/ast.ts 88.78% 85.18% 100% 89.13% 147-154, 172-174, 183-188, 304-305
src/markup/render.tsx 88.79% 79.1% 100% 92.07% 77, 87, 125, 190-196, 211-212, 223, 244, 245, 246, 250-251
src/markup/tokenizer.ts 77.77% 77.49% 100% 78.62% 169-170, 213, 233-235, 253, 283-287, 313-314, 318, 401, 407-409, 435-454, 474, 490, 497-499, 504-544, 555, 561-563, 586-588, 626-628, 666-668, 706-708, 732-748, 771-773, 816, 864-865
Generated in workflow #211 for commit c8f3db9 by the Vitest Coverage Report Action

clem and others added 12 commits March 27, 2026 13:29
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- render.tsx: getVariableTextValue resolves transient scope for markdown
- interpolation.ts: INTERP_TEST, resolveSimple, interpolate, and
  interpolateExpression all handle % sigil and transient namespace
- VarDisplay.tsx already handled by Task 4 (no changes needed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rohal12 rohal12 force-pushed the worktree-feat-transient-variables branch from 5de3656 to c8f3db9 Compare March 27, 2026 05:30
@rohal12 rohal12 merged commit 9900a49 into main Mar 27, 2026
5 checks passed
@rohal12 rohal12 deleted the worktree-feat-transient-variables branch March 27, 2026 05:32
rohal12 added a commit that referenced this pull request Mar 27, 2026
…143) (#144)

## Summary

- **Performance fix for issue #143**: Extend the `renderNodes` fast path
to skip the micromark + innerHTML pipeline when all text nodes contain
only whitespace (indentation, newlines between HTML tags)
- Eliminates ~97 redundant pipeline calls per render in `{for}` loops
over HTML + macro content, addressing 89% of wall time in Chrome CPU
profiles
- Preserves blank-line (`\n\n`) detection to maintain paragraph
separation semantics

## Also includes (prior work on this branch)

- Published types sync (#135)
- Mutation buffer (#136)
- Transient variables (#137, #138)
- Computed @-target in for-loops (#140)

## Test plan

- [x] 3 new correctness baseline tests for the fast path
- [x] Full test suite passes (1090 tests)
- [x] Type check clean (`tsc --noEmit`)
- [ ] Manual verification: re-profile the 23-item for-loop passage in
Chrome to confirm wall time reduction

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: transient variables — reactive but excluded from history snapshots

1 participant