Skip to content

i18n-extract: walkScreenFlows walks flow.nodes flat — a screen inside an ADR-0031 region gets no skeleton entry and no coverage row #17511

Description

@os-bill

Filed by the domain:spec dev implementing #11745 (translateFlow's region blindness). packages/cli is outside that card's declared file surface, so this is its own card, not a rider.

Measured

On origin/main, packages/cli/src/utils/i18n-extract.ts, walkScreenFlows:

function walkScreenFlows(config: any, out: ExpectedEntry[]): void {
  const flows: any[] = Array.isArray(config?.flows) ? config.flows : [];
  for (const flow of flows) {
    
    const nodes: any[] = Array.isArray(flow.nodes) ? flow.nodes : [];
    for (const node of nodes) {
      if (!node || typeof node !== 'object' || node.type !== SCREEN_NODE_TYPE) continue;

Region-awareness symbols in that file:

FLOW_REGION_SLOTS       0
walkFlowNodes           0
config.body             0
try_catch               0
branches                4   ← all four are prose/unrelated (lines 690, 1237, 1435, 1831)
LIT CONTROL 'walkScreenFlows'  2   ← so the zeros are absences, not a bad path

FlowNode.config carries ADR-0031 regions — loop.config.body, parallel.config.branches[].nodes, try_catch.config.try / .catch — each holding a full nodes array, nesting arbitrarily. A type: 'screen' node inside one is a real screen: the executor pauses on it and the client receives its ScreenSpec.nodeId. So flows.NAME.screens.NODE_ID.{title,fields.*} is a live key for it, and the extractor never emits one.

Consequence: os i18n extract writes a skeleton with a hole in it, and the coverage gate has no row to demand. A translator is never shown the key, so the nested wizard step keeps rendering source-locale text — the invisible hole the flows bucket was added to close.

This is the file's own stated principle, applied to itself

walkScreenFlows's docblock already argues the case, one step short of the nesting:

A screen node whose waitForInput is false is deliberately NOT skipped: translateFlow overlays every screen node, and a walker that skipped one would re-open the extractable-but-ungated gap in miniature.

translateFlow does now overlay every screen node at every depth (#11745), so the premise that sentence rests on is true and this walker is the one skipping.

Why it is a separate card from #11745

#11745's fix moves the runtime overlay onto the shared per-flow region walk (mapFlowNodeList, packages/spec/src/conversions/walk.ts). That closes the runtime half only. The extractor is a different package, a different unit (it collects expected KEYS with a diagnostic scope, not a copy-on-write rewrite), and reaching the region table from packages/cli is its own choice — @objectstack/spec/automation exports FLOW_REGION_SLOTS_BY_TYPE, and packages/lint's walkFlowNodes is a third shape again.

Shape of the fix

Collect the node universe region-aware — either through the spec-side slot table or by whichever walker packages/cli already depends on — so the skeleton offers exactly the keys the resolver reads. The key face itself (FLOW_SCREEN_COPY_KEYS / FLOW_SCREEN_FIELD_COPY_KEYS) is already imported rather than restated and needs no change.

Note the ordering hazard the seed rules record: the title seed falls back to node.label, and the field label seed falls back to field.name via pushDerived. A nested screen must get the same treatment, not a narrower one, or the coverage gate starts demanding a translation of a string nobody authored.

Refs: #11745 · #11485 · #11287 · #7646

Activity

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

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions