You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Retire the three i18n exemptions once the platform ships their keys — and re-take the two English-island screenshots #106
Restart-when:objectstack-ai/objectstack#14381 merged and released in a published @objectstack/* version this app can install. Both, not just the merge — nothing here can be verified against an unpublished platform.
Why this card exists separately
#18 recorded three authored display surfaces as declared, reasoned exemptions in src/translations/authored-text.ts, because no key in TranslationDataSchema could reach them:
Chinese tile titles with Untouched > 14 days directly beneath
custom validation rule messages
11
platform refusals in Chinese, ours in English, in one envelope
The platform PR gives all three a key. It does not make our screens Chinese — our authored strings are unchanged, so the new keys resolve to nothing and every one of those 72 strings still falls back to its English literal.
What "done" is
Retire the three exemptions in src/translations/authored-text.ts and let the coverage walk emit the new paths. The gate should go red first on missing zh-CN keys — that is the signal the exemptions were load-bearing and not decorative.
Author the zh-CN entries for all 72 strings, following the terminology decisions already recorded in src/translations/zh-CN.ts's header (职责 vs 任务 above all — a bulk action's label sits next to a task list and must not blur them).
Re-take the two screenshots that are embedded in objectstack#14253 and show them Chinese. Those images are the issue's evidence; replacing them is what closes the loop rather than asserting it closed.
Check the third surface has a screen too. The validation-message island had no screenshot in the issue — trip one of duly_duty's own rules (standing_no_frequency is the easy one) in a zh-CN session and confirm the refusal comes back Chinese.
Two things not to get wrong
The platform-derived pin in test/i18n-coverage.test.ts will already be red the moment we upgrade, before any of the above — TRANSLATABLE_METADATA_TYPES gains dataset, and the gate pins that set deliberately so a platform change cannot pass silently. Re-derive the untranslatable verdicts; do not widen the pin to make it green.
i18n: no coverage gate for the locales a deployment adds after zh-CN #102 is still open and touches the same file: the bundle half of the gate compares against 'zh-CN' by name, so a third locale would pass the locale-set assertion with its key coverage unchecked. Worth folding in here if it is cheap — this card is already in that code — but say so rather than silently absorbing another card.
Latest published release is still 17.2.0 (2026-08-23) — it does not carry the three keys. Every @objectstack/* in this app is pinned ^17.2.0, and node_modules/@objectstack/spec is 17.2.0, so TRANSLATABLE_METADATA_TYPES here still lacks dataset and authoredRuleMessage / the _validations and bulkActions groups are not installed.
Still pm:on-hold until a release newer than 17.2.0 is published. A daily check watches the release list; on the first release that carries b2eab9594, the deps get bumped in a worktree, the platform-derived pin in test/i18n-coverage.test.ts is re-derived (not widened), and this card is dispatched.
Restart condition met — @objectstack/* 17.3.0 published 2026-09-04 10:52Z. Verified from the npm tarballs: @objectstack/spec@17.3.0 has translateDataset in METADATA_DOCUMENT_TRANSLATORS (so TRANSLATABLE_METADATA_TYPES gains dataset), the translation schema carries _validations and bulkActions keys, and @objectstack/objectql@17.3.0 ships authoredRuleMessage. All 14 packages this app depends on are at 17.3.0.
Scope addendum (this card now also carries the upgrade, because nothing in it can be green without it)
Preflight measured on a throwaway worktree (^17.2.0 → ^17.3.0 across package.json, pnpm install): validate 0 · typecheck 0 · build 0 · test 870/872. The two reds are both this card's:
test/i18n-coverage.test.ts › pins the metadata types the PLATFORM says are translatable — the expected pin failure from the issue body. Re-derive, do not widen.
test/task-hook.test.ts › a predicate write clearing done is NOT refused (the completed_at block; the sibling case in the completed_late block at ~L1165 is skipped only because the first one aborts the file). 17.3.0 now throws MultiUpdateHookKeyDivergenceError on a multi-row update when beforeUpdate writes completed_at/completed_late for some matched rows and not others: the clearing branch in src/hooks/task.hook.ts writes the nulls only on rows whose pre-image was done, so a batch containing a never-done row diverges and the whole update is refused. Fix in the hook, not the test: when a row leaves done, write completed_at = null and completed_late = null for every matched row regardless of its pre-image — null is the correct value for all of them (the test's own reasoning), so the key set is row-invariant and the write goes through. Keep the completing direction exactly as is (that divergence is the one the platform is right to refuse; the test above it pins it).
So "done" for this card is now: bump to ^17.3.0 · hook fix above · retire the three exemptions in src/translations/authored-text.ts · author the zh-CN entries (re-count after #117: the bulk-action labels, dataset labels, and validation messages the walk now emits) · re-derive the pin · re-take the two screenshots in objectstack#14253 plus one of a validation refusal in a zh-CN session (standing_no_frequency or returned_needs_note). Prove it on a real pnpm demo:zh boot: dashboard tile sub-labels, the pie legend, the bulk bar's two buttons, and a refusal toast all Chinese.
Also present in the preflight, not this card: validate now prints three warnings that duly_assignment_fanout's loop body (fanout_find_existing, fanout_find_unit, fanout_create_task) runs without a try_catch — filed separately.
worktree: /home/user/duly-issue-106 off origin/main
Working the scope addendum's list: ^17.2.0 → ^17.3.0 (14 packages) · task.hook.ts clearing-branch fix · retire the three exemptions in src/translations/authored-text.ts · author the zh-CN entries · re-derive (not widen) the TRANSLATABLE_METADATA_TYPES pin · three browser screenshots from a real pnpm demo:zh boot.
{
"issue": 106,
"status": "done",
"branch": "claude/issue-106-i18n-exemptions",
"pr_url": "https://github.com/objectstack-ai/duly/pull/125",
"pr_number": 125,
"head_sha": "b8bbdb0",
"gates": {
"validate": "pass — '✓ Validation passed (488ms)'; only the expected hierarchy-security warning. The three duly_assignment_fanout try_catch warnings the addendum listed are gone (grep -c try_catch = 0) because #124 fixed them on main and this branch merged it.",
"typecheck": "pass — tsc --noEmit, exit 0",
"test": "pass — 'Test Files 35 passed (35) / Tests 885 passed (885)' (873 this branch + 12 from #124)",
"build": "pass — '✓ Build complete (812ms)'"
},
"upgrade": {
"packages_bumped": "14 of 14 (@objectstack/spec, runtime, plugin-hono-server, connector-rest, connector-openapi, connector-mcp, cli, platform-objects, plugin-email, plugin-sharing, service-automation, service-job, service-messaging, trigger-schedule) ^17.2.0 -> ^17.3.0; lockfile committed. Baseline before any fix reproduced the preflight exactly: 870/872 with the two named reds.",
"hook_fix_summary": "src/hooks/task.hook.ts: the clearing branch was `wasDone && !isDone` — gated on THIS row's pre-image — so a predicate write reopening a mixed batch wrote completed_at/completed_late for the done rows only, and 17.3.0 refuses that divergent key set with MultiUpdateHookKeyDivergenceError. Now `'status' in input && !isDone`: keyed on the ONE shared payload, so the key set is row-invariant. `'status' in input` is load-bearing — `!isDone` alone would reopen the same divergence for a status-less administrative bulk write. Completing direction untouched. test/task-hook.test.ts 64/64, both clearing tests now running (the second was previously skipped only because the first aborted the file)."
},
"i18n": {
"exemptions_retired": "All 3. dataset label/description/dimension/measure -> `datasets.<d>.…` via translateDataset; view bulkActionDefs -> `objects.<o>._views.<v>.bulkActions.<def>.…` via translateView; validation messages -> `objects.<o>._validations.<r>.message`, resolved by objectql AT THROW TIME (translateObject does NOT rewrite it — measured). Exemption list is now designer/operator-facing only; no end-user screen remains on it.",
"zh_keys_added": "84 (re-count after #117 as the addendum asked; the issue body's 72 was pre-#107/#52/p20). 36 dataset + 35 bulkActions + 13 validations. en/zh key parity 363/363 in both directions, 0 missing, 0 orphan.",
"pin_rederived": "Yes, re-derived not widened: ['action','app','dashboard','dataset','object','page','view']. The pin went red on the bump before any other file was touched. The three untranslatable counts are pinned at ZERO rather than deleted (the file's own idiom for the gap #69 closed) so they cannot silently reopen. Reverse-verified: with the exemptions retired and main's zh-CN bundle read out of git (no tracked file mutated), the gate goes RED with exactly 84 missing keys, split 36/35/13 — the exemptions were load-bearing, not decorative.",
"issue_102_folded": "YES — folded in and closed by this PR. It was cheap and in the exact assertion this card already rewrites. The two directional gates named 'zh-CN' as a literal; they now iterate the locales dulyTranslations ships. Proven on a synthetic three-locale barrel: a ja-JP carrying 2 of 363 keys reports 361 missing => RED, where it was previously unchecked. Two non-vacuity guards ship with it (zh-CN must be in the derived list, since it.each over an empty array passes with zero cases; and the barrel's zh-CN entry must BE the authored dulyChinese bundle, closing the gap the file->barrel refactor opened)."
},
"browser_evidence": [
"docs/i18n/01-dashboard-zh.png — /_console/apps/ai.objectstack.duly/dashboard/duly_duty_health at 1440x2300. Tile sub-labels Chinese: 超过 14 天无动静 / 超过 30 天无动静 / 最久未动 / 按期率 / 逾期未完成 / 审定率 — the dataset measure labels that were the 'Untouched > 14 days' island. CAVEAT: the work-mix pie legend is still English (One-off/Recurring/Standing) — NOT a missing key and not fixable in app metadata; filed as objectui#7696.",
"docs/i18n/02-bulk-bar-zh.png — my_week with one row ticked. Bulk bar reads 已选择 1 项 · 完成 · 跳过 · 清除 (was 已选择 1 项 · Complete · Skip · 清除). 'Duly' is the only Latin run left anywhere on the page.",
"docs/i18n/03-validation-refusal-zh.png — a duty from 待我审定, Edit, 审定状态 -> 已打回 with no reason, Save. Toast reads 请写明打回的原因——负责人需要据此知道该改什么。 PATCH returns 400 with that same zh-CN message in the VALIDATION_FAILED envelope."
],
"files_changed": [
"package.json (14 deps ^17.2.0 -> ^17.3.0)",
"pnpm-lock.yaml",
"src/hooks/task.hook.ts (clearing-branch gate + module header)",
"src/translations/authored-text.ts (3 exemptions -> translate verdicts, bulk-action key builders, viewNode on view surface ids, header re-derived)",
"src/translations/zh-CN.ts (84 new keys: datasets group, shared TASK_BULK_ACTIONS across 5 views, _validations on duly_duty/duly_task/duly_catalog_item)",
"test/i18n-coverage.test.ts (pin re-derived, exemption set, 3 counts -> 0, view-label filter tightened, self-test exemption swapped, #102 per-locale generalisation)",
"docs/i18n/01-dashboard-zh.png, 02-bulk-bar-zh.png, 03-validation-refusal-zh.png"
],
"decisions": [
"Bulk-action toolbar copy is ONE shared TASK_BULK_ACTIONS object spread into 5 views rather than 5 hand-copies — the platform keys bulkActions per view, so the same action in 5 views is 5 key sets, and 5 copies is 4 that eventually drift.",
"Two assertions beyond the addendum's list had to move, both because retiring the exemptions changed what they see: the view-registry-name check filtered '.label' + '._views.' as a substring and started sweeping in 15 nested bulkActions label slots (now an exact 5-segment regex); and the synthetic self-test named object.validations[].message as its example stale exemption (now job.label, which has no translator at all).",
"Merged origin/main (#124) into the branch rather than leaving the overlap for the PM — #124 touched src/translations/authored-text.ts too. It auto-merged (its change is in REENTER, mine in the verdict table) and the full gate union was re-run on the merge commit b8bbdb0.",
"Screenshots were taken with accept-language 'zh-CN,zh;q=0.9' (what a Chinese Chrome sends). Playwright's chromium sends bare 'zh' for locale:'zh-CN', which exposed a separate platform defect — see concerns — rather than anything wrong with this work."
],
"concerns": [
"objectui#7696 (FILED, new): a LOCAL select dimension on a CHART widget still renders English option labels. The server bakes them into rows[] — identical across zh-CN/zh/en — while fields[].label in the same response follows the locale. This is the uncovered cell of a 2x2: objectui#4324 fixed charts with client-resolved (dotted) dimensions, objectui#4330/#4388 fixed local select dimensions on table/pivot; a local select dimension on a chart was in neither scope. This is why the work-mix pie legend in screenshot 1 is still English. Not worked around.",
"objectstack#15757 (FILED, new): the validation-message bridge matches the locale EXACTLY while every document translator does variant fallback via resolveBundleLocale/pickData. accept-language 'zh' therefore refuses in English on the same response whose dataset labels are Chinese. 'zh-CN' and 'zh-CN,zh;q=0.9' are both correct. Invisible to an app: the bundle key is right and the coverage gate is green.",
"Neither of the above is a regression from this PR and neither is worked around here. Both were found only because the card demanded browser proof rather than a green gate."
]
}
Activity
os-warren commentedon Sep 2, 2026
Restart condition: half met.
mainatb2eab9594(2026-09-02 05:28Z); objectstack#14253 closed by it.@objectstack/*in this app is pinned^17.2.0, andnode_modules/@objectstack/specis 17.2.0, soTRANSLATABLE_METADATA_TYPEShere still lacksdatasetandauthoredRuleMessage/ the_validationsandbulkActionsgroups are not installed.Still
pm:on-holduntil a release newer than 17.2.0 is published. A daily check watches the release list; on the first release that carriesb2eab9594, the deps get bumped in a worktree, the platform-derived pin intest/i18n-coverage.test.tsis re-derived (not widened), and this card is dispatched.Generated by Claude Code
os-warren commentedon Sep 5, 2026
Restart condition met —
@objectstack/*17.3.0 published 2026-09-04 10:52Z. Verified from the npm tarballs:@objectstack/spec@17.3.0hastranslateDatasetinMETADATA_DOCUMENT_TRANSLATORS(soTRANSLATABLE_METADATA_TYPESgainsdataset), the translation schema carries_validationsandbulkActionskeys, and@objectstack/objectql@17.3.0shipsauthoredRuleMessage. All 14 packages this app depends on are at 17.3.0.Scope addendum (this card now also carries the upgrade, because nothing in it can be green without it)
Preflight measured on a throwaway worktree (
^17.2.0→^17.3.0acrosspackage.json,pnpm install): validate 0 · typecheck 0 · build 0 · test 870/872. The two reds are both this card's:test/i18n-coverage.test.ts › pins the metadata types the PLATFORM says are translatable— the expected pin failure from the issue body. Re-derive, do not widen.test/task-hook.test.ts › a predicate write clearing done is NOT refused(thecompleted_atblock; the sibling case in thecompleted_lateblock at ~L1165 is skipped only because the first one aborts the file). 17.3.0 now throwsMultiUpdateHookKeyDivergenceErroron a multi-row update whenbeforeUpdatewritescompleted_at/completed_latefor some matched rows and not others: the clearing branch insrc/hooks/task.hook.tswrites the nulls only on rows whose pre-image was done, so a batch containing a never-done row diverges and the whole update is refused. Fix in the hook, not the test: when a row leavesdone, writecompleted_at = nullandcompleted_late = nullfor every matched row regardless of its pre-image — null is the correct value for all of them (the test's own reasoning), so the key set is row-invariant and the write goes through. Keep the completing direction exactly as is (that divergence is the one the platform is right to refuse; the test above it pins it).So "done" for this card is now: bump to
^17.3.0· hook fix above · retire the three exemptions insrc/translations/authored-text.ts· author the zh-CN entries (re-count after #117: the bulk-action labels, dataset labels, and validation messages the walk now emits) · re-derive the pin · re-take the two screenshots in objectstack#14253 plus one of a validation refusal in a zh-CN session (standing_no_frequencyorreturned_needs_note). Prove it on a realpnpm demo:zhboot: dashboard tile sub-labels, the pie legend, the bulk bar's two buttons, and a refusal toast all Chinese.Also present in the preflight, not this card:
validatenow prints three warnings thatduly_assignment_fanout's loop body (fanout_find_existing,fanout_find_unit,fanout_create_task) runs without atry_catch— filed separately.Generated by Claude Code
os-warren commentedon Sep 5, 2026
Claimed.
claude/issue-106-i18n-exemptions/home/user/duly-issue-106offorigin/mainWorking the scope addendum's list:
^17.2.0→^17.3.0(14 packages) ·task.hook.tsclearing-branch fix · retire the three exemptions insrc/translations/authored-text.ts· author the zh-CN entries · re-derive (not widen) theTRANSLATABLE_METADATA_TYPESpin · three browser screenshots from a realpnpm demo:zhboot.Generated by Claude Code
Retire the three i18n exemptions on 17.3.0, and fix the hook the bump…
os-warren commentedon Sep 5, 2026
os-dev-report
{ "issue": 106, "status": "done", "branch": "claude/issue-106-i18n-exemptions", "pr_url": "https://github.com/objectstack-ai/duly/pull/125", "pr_number": 125, "head_sha": "b8bbdb0", "gates": { "validate": "pass — '✓ Validation passed (488ms)'; only the expected hierarchy-security warning. The three duly_assignment_fanout try_catch warnings the addendum listed are gone (grep -c try_catch = 0) because #124 fixed them on main and this branch merged it.", "typecheck": "pass — tsc --noEmit, exit 0", "test": "pass — 'Test Files 35 passed (35) / Tests 885 passed (885)' (873 this branch + 12 from #124)", "build": "pass — '✓ Build complete (812ms)'" }, "upgrade": { "packages_bumped": "14 of 14 (@objectstack/spec, runtime, plugin-hono-server, connector-rest, connector-openapi, connector-mcp, cli, platform-objects, plugin-email, plugin-sharing, service-automation, service-job, service-messaging, trigger-schedule) ^17.2.0 -> ^17.3.0; lockfile committed. Baseline before any fix reproduced the preflight exactly: 870/872 with the two named reds.", "hook_fix_summary": "src/hooks/task.hook.ts: the clearing branch was `wasDone && !isDone` — gated on THIS row's pre-image — so a predicate write reopening a mixed batch wrote completed_at/completed_late for the done rows only, and 17.3.0 refuses that divergent key set with MultiUpdateHookKeyDivergenceError. Now `'status' in input && !isDone`: keyed on the ONE shared payload, so the key set is row-invariant. `'status' in input` is load-bearing — `!isDone` alone would reopen the same divergence for a status-less administrative bulk write. Completing direction untouched. test/task-hook.test.ts 64/64, both clearing tests now running (the second was previously skipped only because the first aborted the file)." }, "i18n": { "exemptions_retired": "All 3. dataset label/description/dimension/measure -> `datasets.<d>.…` via translateDataset; view bulkActionDefs -> `objects.<o>._views.<v>.bulkActions.<def>.…` via translateView; validation messages -> `objects.<o>._validations.<r>.message`, resolved by objectql AT THROW TIME (translateObject does NOT rewrite it — measured). Exemption list is now designer/operator-facing only; no end-user screen remains on it.", "zh_keys_added": "84 (re-count after #117 as the addendum asked; the issue body's 72 was pre-#107/#52/p20). 36 dataset + 35 bulkActions + 13 validations. en/zh key parity 363/363 in both directions, 0 missing, 0 orphan.", "pin_rederived": "Yes, re-derived not widened: ['action','app','dashboard','dataset','object','page','view']. The pin went red on the bump before any other file was touched. The three untranslatable counts are pinned at ZERO rather than deleted (the file's own idiom for the gap #69 closed) so they cannot silently reopen. Reverse-verified: with the exemptions retired and main's zh-CN bundle read out of git (no tracked file mutated), the gate goes RED with exactly 84 missing keys, split 36/35/13 — the exemptions were load-bearing, not decorative.", "issue_102_folded": "YES — folded in and closed by this PR. It was cheap and in the exact assertion this card already rewrites. The two directional gates named 'zh-CN' as a literal; they now iterate the locales dulyTranslations ships. Proven on a synthetic three-locale barrel: a ja-JP carrying 2 of 363 keys reports 361 missing => RED, where it was previously unchecked. Two non-vacuity guards ship with it (zh-CN must be in the derived list, since it.each over an empty array passes with zero cases; and the barrel's zh-CN entry must BE the authored dulyChinese bundle, closing the gap the file->barrel refactor opened)." }, "browser_evidence": [ "docs/i18n/01-dashboard-zh.png — /_console/apps/ai.objectstack.duly/dashboard/duly_duty_health at 1440x2300. Tile sub-labels Chinese: 超过 14 天无动静 / 超过 30 天无动静 / 最久未动 / 按期率 / 逾期未完成 / 审定率 — the dataset measure labels that were the 'Untouched > 14 days' island. CAVEAT: the work-mix pie legend is still English (One-off/Recurring/Standing) — NOT a missing key and not fixable in app metadata; filed as objectui#7696.", "docs/i18n/02-bulk-bar-zh.png — my_week with one row ticked. Bulk bar reads 已选择 1 项 · 完成 · 跳过 · 清除 (was 已选择 1 项 · Complete · Skip · 清除). 'Duly' is the only Latin run left anywhere on the page.", "docs/i18n/03-validation-refusal-zh.png — a duty from 待我审定, Edit, 审定状态 -> 已打回 with no reason, Save. Toast reads 请写明打回的原因——负责人需要据此知道该改什么。 PATCH returns 400 with that same zh-CN message in the VALIDATION_FAILED envelope." ], "files_changed": [ "package.json (14 deps ^17.2.0 -> ^17.3.0)", "pnpm-lock.yaml", "src/hooks/task.hook.ts (clearing-branch gate + module header)", "src/translations/authored-text.ts (3 exemptions -> translate verdicts, bulk-action key builders, viewNode on view surface ids, header re-derived)", "src/translations/zh-CN.ts (84 new keys: datasets group, shared TASK_BULK_ACTIONS across 5 views, _validations on duly_duty/duly_task/duly_catalog_item)", "test/i18n-coverage.test.ts (pin re-derived, exemption set, 3 counts -> 0, view-label filter tightened, self-test exemption swapped, #102 per-locale generalisation)", "docs/i18n/01-dashboard-zh.png, 02-bulk-bar-zh.png, 03-validation-refusal-zh.png" ], "decisions": [ "Bulk-action toolbar copy is ONE shared TASK_BULK_ACTIONS object spread into 5 views rather than 5 hand-copies — the platform keys bulkActions per view, so the same action in 5 views is 5 key sets, and 5 copies is 4 that eventually drift.", "Two assertions beyond the addendum's list had to move, both because retiring the exemptions changed what they see: the view-registry-name check filtered '.label' + '._views.' as a substring and started sweeping in 15 nested bulkActions label slots (now an exact 5-segment regex); and the synthetic self-test named object.validations[].message as its example stale exemption (now job.label, which has no translator at all).", "Merged origin/main (#124) into the branch rather than leaving the overlap for the PM — #124 touched src/translations/authored-text.ts too. It auto-merged (its change is in REENTER, mine in the verdict table) and the full gate union was re-run on the merge commit b8bbdb0.", "Screenshots were taken with accept-language 'zh-CN,zh;q=0.9' (what a Chinese Chrome sends). Playwright's chromium sends bare 'zh' for locale:'zh-CN', which exposed a separate platform defect — see concerns — rather than anything wrong with this work." ], "concerns": [ "objectui#7696 (FILED, new): a LOCAL select dimension on a CHART widget still renders English option labels. The server bakes them into rows[] — identical across zh-CN/zh/en — while fields[].label in the same response follows the locale. This is the uncovered cell of a 2x2: objectui#4324 fixed charts with client-resolved (dotted) dimensions, objectui#4330/#4388 fixed local select dimensions on table/pivot; a local select dimension on a chart was in neither scope. This is why the work-mix pie legend in screenshot 1 is still English. Not worked around.", "objectstack#15757 (FILED, new): the validation-message bridge matches the locale EXACTLY while every document translator does variant fallback via resolveBundleLocale/pickData. accept-language 'zh' therefore refuses in English on the same response whose dataset labels are Chinese. 'zh-CN' and 'zh-CN,zh;q=0.9' are both correct. Invisible to an app: the bundle key is right and the coverage gate is green.", "Neither of the above is a regression from this PR and neither is worked around here. Both were found only because the card demanded browser proof rather than a green gate." ] }Generated by Claude Code
Upgrade to @objectstack 17.3.0 and retire the three i18n exemptions (#…