Skip to content

feat(spec): settings info_banner translation slot — banners.ID.{title,text} and its two resolvers - #23886

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23855-settings-banner-translation-slot
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23855-settings-banner-translation-slot

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23855
Clause-②: yes

What changed

The platform translation contract gains a slot for a settings info_banner row, and the resolveSettings* family gains its two readers.

  • packages/spec/src/system/translation.zod.ts — platformSettingsShape declares settings.NAMESPACE.banners.BANNER_ID.{title,text}, keyed by the info_banner specifier's id, beside groups / keys / actions. It is built with the same strictObject({ surface, history, aliases }) helper as its siblings. The convention list in the docblock names the two new paths.
    • Leaf aliases: label, name, heading → title; bannerText, body, description, message → text.
    • Namespace aliases: banner, notices, alerts → banners. A wrong key now gets a named remedy, not a bare unrecognized_keys.
  • packages/spec/src/system/i18n-resolver.ts — resolveSettingsBannerTitle and resolveSettingsBannerText. They walk the requested locale and then the declared fallback chain, and finally return the manifest literal (label / bannerText).
  • packages/spec/src/system/settings-manifest.zod.ts — SpecifierSchema.id TSDoc names info_banner among the specifiers whose text is looked up by id. TSDoc only: no superRefine, and the specifier's accept set is unchanged.
  • Generated: api-surface/system.json and export-origins/system.json (+2 exports each), content/docs/references/system/translation.mdx, strictness-ledger counts (system/ 373 → 374).
  • .changeset/23855-settings-banner-translation-slot.md — minor, Clause-②: yes (widening).

Spelling: why title / text

The specifier carries label and bannerText. The nearest house precedent is groups: a group specifier's label translates as groups.ID.title. So the banner's heading reuses that leaf name, title. For the body there is no sibling that holds the same field. description on a group is the specifier's description, a different field, so reusing it would invite confusion. text is the card's proposal and names the bannerText field it translates. The specifier's own spellings and the group's description are aliases, so the likely borrowings are refused with the right leaf.

Who reads it

Measured at origin/main 7a6ba03109: git grep "resolveSettings[A-Z]" outside i18n-resolver.ts finds only the generated baselines and docblocks. The family has no caller in objectstack outside its own exports. The objectui console resolves by convention in its own hook. That hook was not measured here, because no objectui checkout exists in this container. The resolvers are the family's executable statement of the convention. Rendering the slot in the console is a follow-up card in objectui, filed at landing. This PR makes no claim that the console renders it yet.

Verification (head 1f081ab7c1 unless noted)

  • New pins: packages/spec/src/system/settings-banner-translation.test.ts, 8 tests.
    • The slot parses on the platform face.
    • bannerText / label / description on a banner and the singular banner group are each refused at the right path, naming text / title / banners.
    • Each resolver resolves a hit, walks the fallback chain, and returns the manifest literal on a miss.
  • Ablations (fix committed first, mutation through scripts/ablation-replace.mjs, restore proven by blob == HEAD and an empty git diff HEAD):
    • Rename the banners key out of the shape: 4 schema pins go red (accept, plus the three leaf-alias refusals). The singular-banner pin stays green, because its alias lives one level up.
    • Point resolveSettingsBannerTitle at groups.ID.description: 2 resolver pins go red (hit, fallback chain).
    • The same mutation on resolveSettingsBannerText: the same 2 go red.
  • Control, the four shipped bundles: a one-off probe parsed settingsBuiltinTranslations (en, zh-CN, ja-JP, es-ES) with PlatformTranslationDataSchema. All four parse.
  • @objectstack/spec: typecheck exit 0. The full vitest run at 63894195e5 (pre-merge) passed 748 files, 21985 tests. Rerun on the merged head: see the report.
  • pnpm --filter @objectstack/spec check:generated after a real build: all 14 artifacts up to date, before and after merging origin/main.
  • Derived gates (dispatch-gates --commands): 109 of 109 exit 0, reconciled with --ran (a derived zero, all exit codes recorded). Nine first exited 3 (prerequisite: unbuilt workspace or a shallow clone). After a workspace build and an unshallow fetch, all nine exit 0.

Acceptance notes


Generated by Claude Code

Add settings.<namespace>.banners.<banner_id>.{title,text} to the platform
translation contract, keyed by the info_banner specifier id, with
strictObject aliases for the borrowed specifier spellings, plus
resolveSettingsBannerTitle / resolveSettingsBannerText in the
resolveSettings* family.

Claude-Session: https://claude.ai/code/session_01MbQTsgWtBx7fxYsCN5UKgC
Co-authored-by: Claude <noreply@anthropic.com>
…nd strictness ledger

Claude-Session: https://claude.ai/code/session_01MbQTsgWtBx7fxYsCN5UKgC
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 2 changed file(s) yielded no anchor (packages/spec/api-surface/system.json, packages/spec/export-origins/system.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/api-surface/system.json, packages/spec/export-origins/system.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 142 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 94114c1f269f6b3d885f24b6f4689dce29504574 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from b7c1381b5f3ab7cd22e273ea2ed504c1aeee911b — the merge of head 1f081ab7c11fbe7fb7dd13a9ab24bd62a6aacbd4 into base 94114c1f269f6b3d885f24b6f4689dce29504574, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b7c1381b5f3ab7cd22e273ea2ed504c1aeee911b && git checkout b7c1381b5f3ab7cd22e273ea2ed504c1aeee911b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 94114c1f269f6b3d885f24b6f4689dce29504574 1f081ab7c11fbe7fb7dd13a9ab24bd62a6aacbd4 && git checkout -B drift-repro 94114c1f269f6b3d885f24b6f4689dce29504574 && git merge --no-ff 1f081ab7c11fbe7fb7dd13a9ab24bd62a6aacbd4

node scripts/docs-audit/affected-docs.mjs --json 94114c1f269f6b3d885f24b6f4689dce29504574

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1f081ab7c11fbe7fb7dd13a9ab24bd62a6aacbd4
Local-runs: none
Scope: packages/spec/src/system/translation.zod.ts, packages/spec/src/system/i18n-resolver.ts, packages/spec/src/system/settings-manifest.zod.ts + packages/spec/src/shared/strict-object.ts, packages/spec/src/shared/alias-integrity.test.ts, packages/spec/src/system/settings-banner-translation.test.ts, packages/spec/api-surface/system.json, packages/spec/export-origins/system.json, content/docs/references/system/translation.mdx, docs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md, .changeset/23855-settings-banner-translation-slot.md, packages/services/service-settings/src/translations/en.ts, zh-CN.ts, ja-JP.ts, es-ES.ts, index.ts, packages/services/service-settings/src/settings-service-plugin.ts, and in objectui at the pinned daeb77fc84: apps/console/src/pages/settings/useSettingsLabel.ts and SettingsField.tsx

Read in this act, clock 2026-10-16T12:21Z: card #23855 body and all three comments (first grade 6212923943, claim 6213624416, dev report 6214981300); PR #23886 body, its nine-file list and the net diff 94114c1f (the merge-base, equal to the PR base) to 1f081ab7; the 41 check-runs on the head. Nothing built, run or re-run.

① Derived judgments

  • The widening, right. platformSettingsShape gains one optional key, banners, a z.record of strictObject({ surface, history: TRANSLATION_HISTORY, aliases }, { title?, text? }) beside groups / keys / actions, built with the same helper and the same history sentence as its three siblings and keyed by the specifier id as groups and actions are. platformSettingsShape() is spread in exactly one place (translation.zod.ts:1762, PlatformTranslationDataSchema), so the accept set that grows is the platform face alone. Nothing is removed, renamed or narrowed.
  • Leaf aliases, right. label / name / heading to title and bannerText / body / description / message to text: every target is a key the banner shape declares, and none of the seven alias keys is itself declared there, so no entry is dead and none collides. description to text is judged correct rather than confusing: an info_banner specifier's own description is never drawn by the console's banner branch (SettingsField.tsx:273-281 renders the label and the raw bannerText only), so the only string a translator can mean by description on a banner is the body. heading to title mirrors the groups table.
  • Namespace aliases, right. banner / notices / alerts to banners name a declared key and collide with neither the declared keys (title, description, groups, keys, actions, banners) nor the five pre-existing aliases (label, name, sections, fields, settings). alias-integrity.test.ts judges both halves of every table at runtime (alias key rejected, target accepted) and Test Core is green on this head, so the audit has passed over these tables.
  • The per-app face still refuses settings. TranslationDataSchema is built from appTranslationDataShape() with APP_TRANSLATION_KEY_GUIDANCE (settings and setting both answered by PER_APP_SETTINGS_PLATFORM_ONLY); the diff touches neither, and TranslationItemSchema is untouched. Unchanged, as required.
  • The resolvers behave as their siblings do. resolveSettingsBannerTitle (fallback string) and resolveSettingsBannerText (fallback string | undefined) are the exact shape of resolveSettingsGroupTitle / resolveSettingsGroupDescription: resolveOptionalString walks localeChain(opts) (the requested locale, then the declared fallbackChain, which is skipped for the deployment default locale) and returns the first non-empty string, then ?? fallback returns the manifest literal. The new test pins a hit, the chain walk, the literal fallback, an undefined bundle and an unknown id. Both are exported through the export * barrel at system/index.ts:125.
  • Docblocks and changeset claim only what the code does. The convention list gains the two paths and its "auto-resolved by resolveSettings* helpers" sentence stays true because the resolvers ship. "A banner without an id has no address here and renders its manifest literals" is true at the pinned console, which renders literals for every banner today. The SpecifierSchema.id edit is comment lines only (diff lines 393-402); the info_banner superRefine at settings-manifest.zod.ts:596 is untouched, so the specifier's accept set is unchanged as both texts say. The changeset promises the slot, the named-remedy refusals (pinned), the two resolvers and an unchanged specifier, and says nothing about the console rendering the slot; the PR body states the opposite explicitly, and that is what objectui holds at daeb77fc84: useSettingsLabel.ts declares no banner helper and SettingsField.tsx:273-281 draws literalLabel and spec.bannerText.
  • Generated outputs match the source. api-surface/system.json and export-origins/system.json each gain exactly the two functions and nothing else. The mdx row lands in the PlatformTranslationData.settings[string] table carrying the schema's own describe text. The strictness ledger moves by one, the one new strictObject( call in system/. No committed JSON schema carries this shape (the sibling describe text occurs only in the zod source and the mdx), so nothing else is stale; check:generated runs under Lint & Repo Gates, green.
  • The four shipped bundles still parse. Read at the head: every settings.NAMESPACE entry in en, zh-CN, ja-JP and es-ES carries only title, description, groups, keys and actions, and no banner, banners, notices, alerts, heading or body identifier occurs anywhere in the four files (the only message hits are prose inside help strings). The widening adds an optional key and aliases for keys they never write, so the parse result cannot change; Type Check also holds them as object literals typed PlatformTranslationBundle. One gap, not blocking: no committed test parses these bundles with PlatformTranslationDataSchema (the coverage test never calls safeParse and the plugin's loadTranslations does not validate), so the dev's one-off probe is the only runtime parse on record. That pin belongs to service-settings: the storage, knowledge and AI manifests carry their ⚠ warnings inside description and help strings; move each into an info_banner row (bannerSeverity: warning), item 2 of objectui#12610 #23839's coverage-test surface.
  • File surface held. The diff touches none of the claim's barred paths: no service-settings manifest or bundle, not the coverage test, not the info_banner accept set, not objectui.

② Semver level

minor with Clause-②: yes (widening) matches the diff: one optional key added to a published accept set, two functions added to the public surface, nothing removed, renamed or narrowed. The new aliases change only the refusal text of keys the shape already refused. The PR body's Clause-②: yes is the same declaration (clause2-line.mjs reads yes and yes (widening) as one reading). The api-surface and export-origin baselines carry exactly the two new exports each. Check Changeset, Lint & Repo Gates (the ADR-0087 registration and no-major gates) and Spec property liveness are green on the head.

③ Boundary flags

Implemented-by: claude/issue-23855-settings-banner-translation-slot
Reviewed-by: session_01HjR8RUwTPQ3TgbfKihmxp9

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · PR #23886 @ 1f081ab7c1 (#23855) · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T12:23Z

Reviewer of record. Every item below was checked against GitHub and the code, not against the report (6214981300 on #23855).

  • Shape: Draft, base main. The first line is Fixes #23855, the body's only closing keyword, then Clause-②: yes, matching claim 6213624416. 9 files, +162/−4, none governed. Assignee os-elon-musk.
  • The slot: platformSettingsShape gains banners, a z.record of strictObject({ surface, history, aliases }) with title and text. It is built the way its groups / keys / actions siblings are, and it is spread into PlatformTranslationDataSchema only, so the per-app TranslationDataSchema still refuses settings by name.
  • The leaf names: title follows groups.ID.title for the same label field. text names the bannerText it translates; description would have pointed at the specifier's own description. The aliases (label / name / heading → title; bannerText / body / description / message → text; banner / notices / alerts → banners) each name a declared key.
  • The resolvers: resolveSettingsBannerTitle and resolveSettingsBannerText have exactly the shape of resolveSettingsGroupTitle / GroupDescription: resolveOptionalString over the locale chain, then the manifest literal.
  • TSDoc and changeset: SpecifierSchema.id's TSDoc names info_banner, with no accept-set change. The minor changeset with Clause-②: yes (widening) claims nothing about the console.
  • Contract review: PASS 6215400067 on this head. The review also confirmed the console at objectui @daeb77fc84 has no banner reader.
  • The report's three open questions, the seat's calls: A, A, A: text, ship the resolvers, keep notices / alerts.
  • The dev's .objectui-sha note: right, and the seat's dispatch was wrong. The pin is daeb77fc84, as the card said; the seat corrected it in the dev session.
  • CI on this head: 36 success, 5 skipped by their filters, 0 failed. main has moved none of the PR's files since its base.

At landing, this seat: files the objectui settings-reader card (useSettingsLabel and SettingsField.tsx read no banner slot today), and tells #23839's lane that its blocker has closed.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 12:24
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit dd11563 Oct 16, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23855-settings-banner-translation-slot branch October 16, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system size/m tests tooling

Projects

None yet

2 participants