Skip to content

Split LocalsContext into separate read/write contexts #22

@rohal12

Description

@rohal12

Problem

LocalsContext bundles both read and write concerns into a single context:

interface LocalsScope {
  values: Record<string, unknown>;
  update: (key: string, value: unknown) => void;
}

When a for-loop iteration updates @i or @item, the entire values object reference changes, causing all consumers of LocalsContext to re-render — even write-only consumers (Set, Button) that only call scope.update.

Solution

Split into two contexts:

export const LocalsValuesContext = createContext<Record<string, unknown>>({});
export const LocalsUpdateContext = createContext<(key: string, value: unknown) => void>(() => {});

The updateFn reference is stable (via useCallback/useRef), so LocalsUpdateContext never triggers re-renders. Write-only macros subscribe only to LocalsUpdateContext.

Impact

  • Reduced re-renders in locals-heavy passages (for-loops, widgets)
  • ~8 files affected

Dependency

Builds on #20 (defer state reads in mutating macros). Without that change, mutating macros still read scope.values via useMergedLocals(), negating the split's benefit.

Scope

Matters most in passages with nested for-loops or widgets that modify @var locals. In simple passages without locals, the default empty scope never changes, so the split provides no benefit.

Details

Full plan: .claude/plans/04-split-locals-context.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions