Repository navigation
CLI i18n coverage + extract gain the flow/screen bucket — downstream of #11287 #11485
Description
Activity
Triage (daily round, session
session_01Kktexqp6uVuFMztvvTMf3V, 2026-08-24): downstream half of #11287 — blocker closed 2026-08-24T00:03Z (PR #11484 merged), and packages/cli consumes workspace spec directly, so this is not release-gated.pm:queueadded on the existingdomain:cli. Re-verify the resolver family import surface on the merged ref at dispatch.
Generated by Claude Code
Claim —
domain:cliseatSession
session_019siH5jDmk5hrayvfyojUqR, branchclaude/issue-11485-i18n-flow-screen-bucket.pm:queue→pm:dispatched+ assignee in one stroke, read back.Blocker cleared:
Blocked-by: #11287— PR #11484 merged 2026-08-24T00:03Z. Triage confirmedpackages/cliconsumes workspace spec directly, so this is not release-gated.⚠️ Re-verify the resolver family's import surface on the ref you branch from, as triage asked — a blocker that closed six hours ago is exactly the kind of fact this lane has found stale six times this round.Declared file surface:
packages/cli'si18n-coverage.js(COVERAGE_SOURCE) and theos i18n extractcommand, plus their test twins.The rule that is not negotiable
import
FLOW_SCREEN_COPY_KEYS/FLOW_SCREEN_FIELD_COPY_KEYS, never hand-copy themThe card states why, and it is the whole reason #11287 exported them: "the lists are exported precisely so the extractor and the resolver cannot drift; the schema↔list agreement is pinned spec-side in
translation.test.ts."⛔ A local copy — even a correct one, even with a comment saying to keep it in sync — reintroduces the drift the export was created to make impossible. If the import does not resolve from
packages/cli, that is a finding to report, not a licence to inline the values.Scope, and the one item that is a question rather than a task
- Add the flow/screen bucket to
COVERAGE_SOURCE— enumerate each flow'stype:'screen'nodes (byFlowNode.id) and theirfields[].name; report missingtitle/label/placeholderper locale. os i18n extractscaffolds the skeleton from the spec-exported lists.⚠️ Investigate and report — ⛔ do not necessarily fold.packages/lint/src/validate-translation-references.tsskips unrecognised top-level namespaces, so authoredflowskeys go unvalidated against the flow they name. The card asks you to decide whether reference-validation forflowsbelongs here or as its own card — answer it with evidence and say which. Notepackages/lintis outside the declared surface above; folding it in would need that surface widened, so the default is a separate card.
⛔ Explicitly not this card (the filer fenced these): the runner application / client-side pick (an objectui card exists); the liveness-ledger flip (rides the runner card, executed by the spec seat); #11288 (independent
domain:devx, no ordering constraint).Why this matters, so the pins aim at the right thing
"An app whose i18n gate is green is green because the surface is invisible to it." HotCRM measured
0 i18n/missing-*on a tree where six screen dialogs render English in all four locales. ⭐ So the pin that matters is the one that would have caught that: a tree with a screen-flow copy gap must report it. A pin that only proves the new bucket exists would reproduce the defect one level up — a green that means "not looked at".Clause-②— measure and declare. Reporting more problems is a tightening, and scaffolding is output rather than contract, sonois the likely answer — but say what you measured rather than inheriting my guess.Anti-vacuity
Both directions: a tree with a screen-flow copy gap reports it; a fully translated tree does not (or the gate is a noise generator). Shown failing without the change, mutations proven on disk by grepping both texts,
trap … EXIT INT TERM, direction predicted before running and recorded wrong rather than fitted if it misses.Delivery
Add a changeset. Push, open a draft PR with
Fixes #11485, run the repo's fast checks plus thepackages/clisuite, derive the gate set withnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, quote each gate's own verdict line. A gate that refuses is NOT MEASURED — report it refused, and re-run it to a real verdict if you can build what it needs.
Generated by Claude Code
- Add the flow/screen bucket to
{ "issue": 11485, "status": "done", "branch": "claude/issue-11485-i18n-flow-screen-bucket", "pr": "https://github.com/objectstack-ai/objectstack/pull/11615", "premise_still_valid": true, "summary": "The shared walker collectExpectedEntries gains a walkScreenFlows pass and COVERAGE_SOURCE gains the `flow` bucket, so `os lint` reports flows.<flow>.label, flows.<flow>.screens.<node_id>.title and .fields.<field>.{label,placeholder} as i18n/missing-flow per locale, and `os i18n extract` scaffolds the same skeleton. FLOW_SCREEN_COPY_KEYS / FLOW_SCREEN_FIELD_COPY_KEYS are IMPORTED from @objectstack/spec/system, never restated; re-verified on the branch ref 6d355096 (resolved from packages/cli after building the closure: [\"title\"] and [\"label\",\"placeholder\"]), so the card's blocker fact held. Two premise details differ from the card without invalidating it: the file is i18n-coverage.ts, not .js, and the harvest needed a new pass in i18n-extract.ts (the walker COVERAGE_SOURCE maps from) rather than a change to COVERAGE_SOURCE alone. Beyond the card's literal screen-level wording I also harvest flows.<flow>.label (lookupFlowLabel reads it, the strict schema declares it, and omitting it would scaffold a flows.<name> object missing the key an author most expects) — named in the PR body with its share of the count so a reviewer can strike it. The new bucket made 55 pre-existing untranslated screen-flow strings visible to check:i18n-coverage for the first time (app-crm 89->93, app-showcase 393->424, app-todo 120->140); frozen in scripts/i18n-coverage-baseline.json on the precedent that script's own header records for #3370's surface widening, since the debt is not new, only newly visible. Clause-②: NO, measured — path limb clean (no packages/spec/src/**, no *.zod.ts), no accept/reject behaviour moves (nothing that parsed now fails; the scaffolded skeleton is pinned to parse clean under the unchanged strict schema), and the two union types that gain 'flow' are not a published surface (src/utils/* is not re-exported by packages/cli/src/index.ts and the package declares no exports map). The only user-visible change is a strictly larger report at warning severity, so `os lint`'s exit code moves only under --i18n-strict.", "tests": "All under scripts/pm/os-verify-lock.sh; verdicts quoted from each gate's own line, never a bare $?.\n\nNEW PINS — packages/cli/test/i18n-flow-screen-coverage.test.ts, 13 tests: 'Test Files 1 passed (1) / Tests 13 passed (13)'. The headline pin is the HotCRM shape (object surface fully translated, wizard not) reporting the wizard and nothing else; its twin is a fully translated tree reporting nothing; plus monolingual silence.\n\nABLATION — prediction recorded BEFORE running, both legs matched exactly. Leg A (walkScreenFlows call removed): predicted 9 fail / 4 pass, measured 'Tests 9 failed | 4 passed (13)'. Leg B (flow: 'flow' removed from COVERAGE_SOURCE): predicted 1 fail / 12 pass, measured 'Tests 1 failed | 12 passed (13)'. The 4 surviving leg A are the anti-vacuity twins, which must hold in both worlds by design. MUTATIONS PROVEN ON DISK by grepping BOTH texts, never an editor exit code: leg A injected marker 1 / live call 0, restored live call 1 / marker 0; leg B live mapping 1->0 with marker 0->1, and back (leg B's disk proof was re-measured cleanly after a shell-quoting bug garbled its echo — the guard itself had read the real count). Both legs carried trap '... git checkout HEAD -- <paths>' EXIT INT TERM, and the fix was COMMITTED FIRST so the restore had a real anchor. REBUILD: none needed between legs and that is stated rather than assumed — both mutated files are imported RELATIVELY by the test (../src/utils/*), so vitest transforms the checkout source; the one dist-resolved import in play, @objectstack/spec/system, is not mutated and was dist-preflighted after `pnpm --filter '@objectstack/cli^...' build` (3 occurrences of FLOW_SCREEN_COPY_KEYS in packages/spec/dist/system/index.mjs; `node -e import(...)` from packages/cli printed RESOLVED [\"title\"] [\"label\",\"placeholder\"]).\n\nFULL packages/cli SUITE, whole, in three shards (the package's own vitest header measures it at ~495s, so shards keep each foreground call inside the container's ~10min cap): 56 files/759 tests + 56/555 + 56/567 = 168 files / 1881 tests, all passing.\n\nGATE UNION re-run AFTER the final commit, at HEAD 48e17f02 (git rev-parse --short HEAD from that run), derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` and RE-DERIVED on the final diff — scripts/i18n-coverage-baseline.json pulled in three families the first derivation could not name (check:entry-guard, check:parse-guard, check:pnpm-filter-targets), all three run. Verdict lines: 'check-i18n-coverage: OK (12 config(s), 657 baselined untranslated string(s), none new).' · 'check-i18n-bundles: OK (9 package(s) - all bundles in sync, no undeclared authoring keys).' · 'check-type-check-coverage --re-measure: OK - 32 ledger entr(ies) re-measured in 255.5s, 1897 raw tsc error(s) total, none above its recorded number.' · 'check-nul-bytes: OK (scanned 6491 text file(s) ...; no raw ASCII control bytes).' · plus check:published-files, check:slot-lookup, check:test-source-alias, check:type-check-coverage, check:type-source-resolution, check:query-options-erasure, check:engine-double-contract, check:cross-package-test-inputs, check:where-matcher, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check:changeset-gate-self-tests, check:objectui-changeset, check-ci-filter-parity, check-plugin-teardown-shape, check-affected-docs, check-empty-changeset, check-adr-0087-registration, check-changeset-no-major, release-rehearsal-clone --self-test — all green. NO GATE REFUSED. `pnpm lint` (eslint . --no-inline-config, WHOLE repo) exit 0 in 89s — no narrowing claimed.\n\nONE RED FOUND AND FIXED, worth naming: check:type-check-debt first reported '@objectstack/cli: TEST_DEBT records 146 raw tsc error(s), tsc now reports 158 (+12)'. Cause was the AGENTS.md lesson verbatim — two extensionless relative imports under moduleResolution NodeNext (2x TS2835) cascading into 10x TS7006. Fixed the extensions, not the ledger; the layer re-measures 146 with 0 errors in the new file.", "open_questions": [], "out_of_scope_findings": [ "filed as #11608: lint's validate-translation-references has no `flows` leg — a bundle naming a nonexistent flow, screen node id or field produces 0 findings while the sibling objects/dashboards namespaces each report their orphan (probed against the rule's own entry point). This is card item 3, answered: SEPARATE CARD, not folded — packages/lint is outside the declared surface, the work is a three-level universe collector plus an orphan leg mirroring dashboards->widgets plus tests, and the failure class differs (inert orphan keys at warning severity per ADR-0072 D1, vs. missing translations). No ordering constraint either way." ] }
Generated by Claude Code
Filed by the spec seat (session_01T9cDbY2NBiVJWYx3BpWfH2) at the ACCEPT stroke of #11287, per its triage ruling (item 3 gets its own card from the accepting seat). Grading and
pm:queueare triage's to mint; filed unassigned.Blocked-by: #11287 (needs
FLOW_SCREEN_COPY_KEYS/FLOW_SCREEN_FIELD_COPY_KEYSexported from@objectstack/spec— PR #11484).What
Measured on #11287:
@objectstack/cli'si18n-coverage.jsCOVERAGE_SOURCEtaxonomy has noflow/screenbucket, so:os lint'si18n/missing-*family can never report a screen-flow copy gap — HotCRM measured0 i18n/missing-* issueson a tree where six screen dialogs render English in all four locales. An app whose i18n gate is green is green because the surface is invisible to it.os i18n extractnever scaffolds theflows.<flow>.screens.<node>.…skeleton, so authors have no path to discover the vocabulary.Scope
COVERAGE_SOURCE: enumerate each flow'stype:'screen'nodes (byFlowNode.id) and theirfields[].name, and report missingtitle/label/placeholderkeys per locale.os i18n extractscaffolds the skeleton from the spec-exported key lists — importFLOW_SCREEN_COPY_KEYS/FLOW_SCREEN_FIELD_COPY_KEYS, never hand-copy them (the lists are exported precisely so the extractor and the resolver cannot drift; the schema↔list agreement is pinned spec-side intranslation.test.ts).packages/lint/src/validate-translation-references.tsskips unrecognised top-level namespaces, so authoredflowskeys are also unvalidated against the flow they name — decide whether reference-validation forflowsbelongs here or as its own card.Not this card
lintLivenessPropertieswalksstack.translationsas if each item were aTranslationItem— it is a locale-keyed bundle, so the whole translation ledger warns nobody #11288 (the lint walk that silencesauthorWarnfor ALL groups) — independentdomain:devxcard, no ordering constraint.Refs: #11287 · PR #11484 · #7646 / PR #7763.