Skip to content

i18n: translateFlow walks flow.nodes flat — a screen node inside an ADR-0031 region is never overlaid, in the fourth pass of a class three earlier cards each fixed once #11745

Description

@os-steve

Filed unassigned by the domain:devx seat while implementing #11608 (the packages/lint flows leg). packages/spec is outside that card's declared file surface, so this is its own card, not a rider.

Measured

packages/spec/src/system/i18n-resolver.ts, translateFlow:

const nodes = Array.isArray(flow.nodes)
  ? flow.nodes.map((node) => {  translateScreenNode(node, name, bundle, opts)  })
  : undefined;

It reads the flat flow.nodes array only. But FlowNode.config carries ADR-0031 regions — loop.config.body, parallel.config.branches[].nodes, try_catch.config.try / .catch, each holding a full nodes: z.array(FlowNodeSchema), nesting arbitrarily. A type: 'screen' node in any of them is a real screen: the executor pauses on it and the client receives its ScreenSpec.nodeId.

So for a nested screen, flows.<name>.screens.<node_id>.{title,fields.*} is authored, parses (the schema is keyed by node id and knows nothing about depth), and translateFlow walks straight past it. The wizard step renders its source-locale heading and field labels while the sibling screens one level up are translated — the same invisible hole the flows group was added to close (#7646).

Why this is worth a card rather than a note

This is the fourth pass in this repo to be written against the flat one-liner, and the first three were each filed and fixed separately:

card pass outcome
#4347 applyConversionsToFlow conditions inside a loop never converted to CEL
#4380 flow lint rules two gating errors silently inert, a third downgraded
#5383 flow-inert-node-condition 8 real inert conditions shipped

packages/spec/src/automation/region-slots.ts exists precisely so this fact has one declaration (FLOW_REGION_SLOTS), and its header names the four passes that need it. translateFlow is a fifth consumer that needs it and does not read it.

Note the asymmetry that makes this easy to miss: the client-side resolver path is already correct. resolveFlowScreenTitle / lookupFlowScreenCopy take a bare flowName + nodeId, so a paused nested screen resolves fine. Only the document-overlay path (translateFlow) is depth-blind — so whether this ships as a user-visible bug depends on which of the two the runner wiring picks, which #11287's docblock explicitly defers to a downstream card.

Shape of the fix

Walk regions via FLOW_REGION_SLOTS_BY_TYPE (or reuse the region-aware walk) inside translateFlow, preserving its reference-identity discipline: a node that resolved nothing must come back as the same reference, or nodesChanged starts reporting untouched documents as changed.

Not blocking #11608

The lint leg landing there collects its universe with walkFlowNodes, so it already resolves nested screens and does not report them as orphans — deliberately the safe direction for a warning. That means the lint rule and translateFlow currently disagree about which keys are live, and this card is what closes the gap.

Refs: #11608 · #11287 · #7646 · #4380 · #5383 · #4347

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions