Uh oh!
There was an error while loading. Please reload this page.
fix(devx): add a property-chain leg to check-i18n-dead-keys - #6995
Merged
zhuangjianguo merged 1 commit intoAug 31, 2026
Conversation
A consumer that imports a locale PACK OBJECT and reads it by property access is invisible to both of the sweep's legs at once: there is no t()/tt() call for the AST pass to classify, and the namespace segment is bound to a local variable so the full dotted key is never spelled for the text safety net to grep. Such a key lands in CONFIRMED — the tier documented as the safest thing to delete — while a shipping screen renders it. textFootprint() now probes each candidate's property chain (the key minus its leading namespace segment, leading dot kept) alongside the full dotted key, bounded three ways: three segments or more, never two; the match must end at a property boundary so a leaf does not demote on a longer sibling; and a chain-only hit is reported with a "(via property chain)" suffix so the report cannot send a reader grepping for a dotted key the file does not contain. Both control groups are pinned as self-tests, on synthetic fixtures and against this repository, because detection alone does not prove discrimination. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GgDDqh6YnkXqsnVTCa7wHk
zhuangjianguo
marked this pull request as ready for review
August 31, 2026 08:20
This was referenced Aug 31, 2026
Uh oh!
There was an error while loading. Please reload this page.
zhuangjianguo
deleted the
claude/issue-6666-i18n-dead-keys-property-chain
branch
August 31, 2026 12:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#6666
A consumer that imports a locale pack object and reads it by property access is invisible to both of the sweep's legs at once. The AST pass classifies
t()/tt()call sites and there is no call; the text safety net greps the full dotted key and the namespace segment is bound to a local variable, so the dotted key is never spelled. The key lands in CONFIRMED — the tier documented as the safest thing to delete — while a shipping screen renders it.textFootprint()now probes each candidate's property chain (the key minus its leading namespace segment, leading dot kept) alongside the full dotted key, in the same singlegrep -Fpass.The card's tier claim is stale — measured, and stated rather than repeated
The card measured the five keys at CONFIRMED on
813bf8323. Onorigin/mainat4357ec754they are NEEDS-REVIEW, demoted by side effect: PR #6662's retirement pin andar-participle-orthography-6610.test.tsboth spell the dotted keys, which gives them a textual footprint. Per the triage ruling that is a mitigation, not a fix — the defect is unchanged, and the before/after evidence below is therefore built on what the hits are rather than on a tier flip.4357ec754)fb4ccd348)LoadingScreen.tsxNothing was demoted; no pre-existing hit was lost. What changed is that seven keys now name the file that actually renders them, marked
(via property chain):console.actions.retry/console.actions.retrying— two more the card did not list, read by the same file the same way (LoadingScreen.tsx:118,123).Both negative controls stay CONFIRMED with an empty hit list.
Three boundaries, each pinned by an ablation
.ok,.no,.empty).propertyChainProbe()returnsnullbelow three segments. Two-segment keys stay on the enumerated importer list, read by hand — which is what refactor(i18n): retire 25 confirmed-dead locale keys across all ten packs #6662 did.(via property chain), so the report never sends a reader grepping for a dotted key that file does not contain.The importer list is written into the file
Per the ruling, the pack-object importer class is enumerated in the script header with the two-stage grep that re-derives it. The
outboundAgentText.tscaveat is stated explicitly: it indexes its subtree dynamically (ai?.[key]), so neither leg sees the read — its four keys land in NEEDS-REVIEW only because those property names happen to appear as string literals in theOutboundAgentTextKeyunion above them. By luck, not by design. Replace that union with anything generated and all four drop to CONFIRMED with a live consumer still reading them.Self-pollution, and why the tests spell keys in segments
textFootprint()grepsscripts/too, so a dotted key written into the test file would make that file a hit for it — the negative controls would stop being reader-less because the test asserting they are reader-less mentioned them. The real-repo controls are therefore joined from segments, and this was verified empirically: with the new test file on disk,console.error.serverUnreachableandconsole.error.timeoutare still CONFIRMED and CONFIRMED is still 115.Verification — all on
fb4ccd348Whole
scripts/__tests__tree (not a subject-matter subset):Test Files 89 passed (89)·Tests 2512 passed (2512).Ablations. No build step is involved — vitest loads
scripts/*.mjsfrom source, there is nodist/for a stale artifact to hide in. Each leg mutated withperl -0pi, the mutation proved on disk by counting the injected and removed text, restored withgit checkout HEAD --against an absolute path, and the restore proved byte-identical (git hash-objectequal to the HEAD blob851663da3andgit diff HEADempty) before the next reading:null(leg removed)Tests 4 failed,23 passed— all four detection assertions red, every negative control still greenTests 2 failed,25 passedincludesTests 1 failed,26 passedThe first row is the discrimination proof: removing the leg loses detection and touches nothing on the negative side.
Gates, each exit code captured before any pipe:
pnpm type-check:scripts— exit 0pnpm check:control-bytes—check-control-bytes: OK (scanned 5804 tracked text file(s); skipped 85 binary)pnpm check:shell-escape-residue—OK (4/4 root(s) resolved ...)pnpm check:vi-mock-specifiers—OK (4032 tracked source file(s) ...)pnpm check:esm-specifiers— exit 0node scripts/check-changeset-presence.mjs—No source of a released package changed in this range, so no changeset is owed.No changeset and no label are owed: the guarded surface is derived from published packages andscripts/is not one.node scripts/check-lint-coverage.mjs—lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total)eslint --no-inline-config --format json .resolved 4030 files from its own config; both changed files report 0 errors, 0 warnings. The run's own non-zero exit reflects 90 pre-existing errors in files outside the per-package lint scope, unchanged by this diff and confirmed green by the coverage gate above.Generated by Claude Code
Generated by Claude Code