Skip to content

fix(spec): localise the column heads of the view Columns, Sort and Tabs tables - #19968

Merged
hotlong merged 4 commits into
mainfrom
claude/issue-19955-view-repeater-row-i18n
Sep 24, 2026
Merged

hotlong merged 4 commits into
mainfrom
claude/issue-19955-view-repeater-row-i18n

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #19955
Clause-②: no

This is the localisation follow-up the maintainer asked for, in the shape #17508 ruling A settled. PR #19917 gave the row items of the view columns, sort and tabs repeaters English titles. view.form.ts enumerated no row children for them, so Studio showed those titles untranslated in zh-CN / ja-JP / es-ES.

  • packages/spec/src/ui/view.form.ts: the three repeaters enumerate their row children. Each label equals the item schema's own .meta({ title }); the dev compared them on the built dist.
  • The four metadata-forms catalogs: the generator emitted the en leaves; zh-CN, ja-JP and es-ES are AI-authored, reusing the catalog's existing renderings where one exists. The dev report 5813748776 lists every reused and invented rendering.
  • repeater-row-properties.test.ts: view moves inside the derived population, so its no-en-echo case covers the new leaves. The dev ablated it red.
  • object-lifecycle-panel-echo-decisions.test.ts: the translated-label positive control moves by exactly the rows that landed, as its comment prescribes. The seat extended the claim to this file (5813775189).
  • Changeset: @objectstack/spec and @objectstack/platform-objects at patch.

The view.columns union renderer question is carried as objectstack-ai/objectui#10239.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1

… row children

Each child's label is the item schema's own `.meta({ title })`, so the
i18n extractor emits a catalog key per column.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…erties in all four catalogs

The extractor emits the new keys for en; the zh-CN, ja-JP and es-ES
leaves are hand-authored, reusing each catalog's existing rendering where
one exists. The repeater-row-properties pin now counts `view` inside its
population, so its en-echo case covers these leaves.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 24, 2026
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec, touching 26 documentable anchor(s).

9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/permissions/authorization.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/permissions/permission-metadata.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/permissions/permission-sets.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/permissions/positions.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/permissions/profiles.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/protocol/objectui/layout-dsl.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/ui/apps.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/ui/pages.mdx (via isDefault (literal, a string literal in sections))

⛔ 4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/releases/v13.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/releases/v15.mdx (via isDefault (literal, a string literal in sections))
  • content/docs/releases/v17/17-0.mdx (via isDefault (literal, a string literal in sections))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see

Coarse fallback — 136 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 3b5607019f6b1f84b14716c9c5e3359a986de08e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from dfcbd1dbb8b674bb82689f2f8600f1e2e26f1d91 — the merge of head b05a88136d1e1d0e0d34b7da5c0c45be220cc910 into base 3b5607019f6b1f84b14716c9c5e3359a986de08e, 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 dfcbd1dbb8b674bb82689f2f8600f1e2e26f1d91 && git checkout dfcbd1dbb8b674bb82689f2f8600f1e2e26f1d91
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3b5607019f6b1f84b14716c9c5e3359a986de08e b05a88136d1e1d0e0d34b7da5c0c45be220cc910 && git checkout -B drift-repro 3b5607019f6b1f84b14716c9c5e3359a986de08e && git merge --no-ff b05a88136d1e1d0e0d34b7da5c0c45be220cc910

node scripts/docs-audit/affected-docs.mjs --json 3b5607019f6b1f84b14716c9c5e3359a986de08e

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 3b5607019f6b1f84b14716c9c5e3359a986de08e → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…the 25 view repeater row labels

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b05a88136d1e1d0e0d34b7da5c0c45be220cc910

① Derived judgments

  • packages/spec/src/ui/view.form.ts (shipped via @objectstack/spec/ui, packages/spec/src/ui/index.ts:37) — right. The three repeaters gain fields arrays only; no type/widget on any child, so widgets stay schema-derived (objectui SchemaForm.tsx:1795-1800 uses fieldSpec.fields for the row list and face.itemSchema for sub-schemas). The view schema (view.zod.ts) is untouched: nothing is accepted or refused differently. Every child label equals the item schema's .meta({ title }): columns 14/14 (view.zod.ts:1018-1046), sort 2/2 (view.zod.ts:2717-2718), tabs 9/9 (view.zod.ts:1673-1681); property order also matches the schemas. defineForm parses at import (view.zod.ts:6301), and the derived-population test imports viewForm green, so the child shape is accepted by FormViewSchema.
  • Generated catalogs (4 locales) — right. Key set per locale is byte-identical (md5 of the added key lines equal across en/zh-CN/ja-JP/es-ES); 25 keys each = 14+2+9. Placement (children immediately after their repeater's own entry, quoted dotted keys, label only, no helpText) matches the extractor's walk (packages/cli/src/utils/i18n-extract.ts:1837-1847) and the existing header.actions.* precedent (en.metadata-forms.generated.ts:1173-1179); the en leaves equal the form labels verbatim, so the en file is consistent with generator output, not a structural hand-edit. Translated catalogs: zero leaves echo the en source (comm against en additions: 0/0/0), and every concept the catalog already names reuses its existing rendering — Label→显示名称/表示名/Etiqueta, Direction→排序方向/並び方向/Dirección, Pinned→固定/固定/Fijada, Hidden→隐藏/非表示/Oculta, Icon→图标/アイコン/Icono, Name→名称/名前/Nombre, Filter→筛选/フィルター/Filtro, Sortable→可排序/並び替え可能/Ordenable, Field→字段/フィールド/Campo, Visible→可见/表示/Visibilidad (all pre-existing in the same files at the same line offsets). The invented ones (Renderer Type, Click Action, Primary Link, Default Tab, List View, Ajustar texto) read natively. Ruling A's "four locales together" is met.
  • Counting pin 584→609 (object-lifecycle-panel-echo-decisions.test.ts:1116) — right. The pin counts en .label keys present in the locale and different from en (:1103); per-locale label: delta measured against the base is exactly +25 in en, zh-CN, ja-JP and es-ES, and no new leaf echoes en, so 584+25=609 in every locale.
  • repeater-row-properties.test.ts — right. view moves into the carrying set (:139), the three carriers are asserted lit (:145-147), the stale "view outside" dark pin is removed, and the population is still derived from the forms (:98-120), so the no-en-echo case now covers the 25 leaves.
  • Changeset prose — true: schema unchanged, no type on children, catalogs carry the keys, reused renderings named correctly.

② Semver level

@objectstack/spec: patch, @objectstack/platform-objects: patch (.changeset/19955-view-repeater-row-i18n.md:1-4) — correct for a bug card. The spec diff adds presentation metadata to an exported form value (no new exported symbol, no accept-set change), and platform-objects adds catalog leaves; neither is minor/major. Clause-②: no is right for the same reason.

③ Boundary flags

  1. Extend claim surface to object-lifecycle-panel-echo-decisions.test.ts, 584→609 (A vs B) — accept A. The pin's own comment (:1109-1113) prescribes that it moves by exactly the rows a landing adds; the move is +25 in every locale (verified above), the added comment names view.columns / view.sort / view.tabs repeater row properties show English column heads in every locale — the #17508 ruling-A landing for the three view repeaters #19955, and B would leave the ruled one-coherent-landing unlandable. Not a design fork.
  2. view.columns union with string-array arm first (A/B/C) — accept A with C, already done (objectui#10239 filed per PR body). Verified on the pinned objectui 62597c58807: repeaterItemSchema (SchemaForm.tsx:316-331) resolves by value, so a legacy string-array value picks the string arm and the declared children now render with {} sub-schemas; a strict ListColumnSchema refuses a string spread into an object at save (loud). The designer's ViewVariantInspector excludes columns (ViewVariantInspector.tsx:94,349), so only the generic fallback is affected, and it is a renderer concern outside packages/spec. B would depart from ruling A's landing shape; no maintainer escalation needed.
  3. Out-of-scope note (repeater-item-titles.test.ts header drift vs unconditional setSchemaTitleAtPath) — accept as noted-not-filed; comment drift, affects no verdict here.

Implemented-by: claude/issue-19955-view-repeater-row-i18n
Reviewed-by: session_01EcrTi7s5oDYPHS4Pi7h31d

VERDICT: PASS

Isolated at-tier reviewer adopted by the director seat, summon #29, on the maintainer's instruction 「执行契约复审」 · fed only the card(s), the governing rulings, the PR body, the dev's flags and the code


Generated by Claude Code

@hotlong
hotlong marked this pull request as ready for review September 24, 2026 17:01
@hotlong
hotlong added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 4ac9319 Sep 24, 2026
51 checks passed
@hotlong
hotlong deleted the claude/issue-19955-view-repeater-row-i18n branch September 24, 2026 17:27
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…ing its at-tier review (objectstack-ai#19993)

Fixes objectstack-ai#19973
Clause-②: no

## 维护者速读(草稿)

**改了什么**:把 `needs:contract-review` 恢复为「PR 在等达档契约复核」的可见标记。规则落在
`contract-review.md`
新增的一节(何时挂、何时摘、谁读,并写明没有任何检查读取它);`landing-operations.md`
里「子代理起不来」那一行原地改写,点名这个标记(行数不变);`ensure-pm-labels.sh`
加回这个标签的定义(新颜色,说明写明「只是标记,没有检查读它」)。

**为什么改**:您的原话「恢复 needs:contract-review,把这句原话写进一张 skills
车道的卡」,回答的是「只作等复核标记,不作闸门」那一问。上一班达档复核子代理连续被限流,12 个 CI 全绿的 PR
在等达档复核记录,只能靠翻座位贴才看得到;现在一个过滤 `is:pr is:open label:needs:contract-review`
就能列出来,交接也不必先读座位贴。

**风险与代价(含回滚)**:不新增任何门禁:没有 check、workflow、队列守卫或巡查脚本读它,落地仍只认 `## Contract
review` 记录,标记丢了或多挂了都不会放行或拦下任何 PR。代价是派发席每个条款② PR 多两次标签写(ACCEPT 时挂,PASS
时摘)。已逐个核对本仓会写 PR 标签的 workflow:今天没有任何自动机制会摘掉手挂的 PR 标签。回滚 = revert 本
PR;GitHub 上的标签对象不受影响。

**席位意见**:(留空,待席位填写)

**你要做的**:合并后请持有 `gh` 的人跑一次 `bash scripts/pm/ensure-pm-labels.sh
--reconcile`,把现存标签对象的颜色与说明对齐(它现在是 GitHub 自动建的灰色、说明为空)。本 PR 的受管路径全在
`.claude/**`(Tier S),由席位在达档复核 PASS 后落地,不等您点合并。

## Summary

The maintainer's instruction recorded on objectstack-ai#19973 — 「恢复
needs:contract-review,把这句原话写进一张 skills 车道的卡」, answering a question that
proposed 「只作等复核标记,不作闸门」 — brings `needs:contract-review` back as a
**visibility marker, never a gate**. Every layer that ruling record
5770886272 (letter B) retired stays retired: no queue-guard refusal, no
`--pair`, no double carrier, no independence pair. The enqueue gate
still decides on the `## Contract review` record alone, and nothing in
this diff reads the label.

## What changed — 3 files, +28 / -2

| path | change |
|---|---|
| `.claude/skills/pm-dispatch/references/contract-review.md` | a new
section, heading plus 5 rule lines (:30-:36); the :3 pointer now lists
it. 28 → 36 lines, ceiling 60 |
| `.claude/skills/pm-dispatch/references/landing-operations.md` | :13
rewritten in place to name the marker. 101 / 101 lines; that line goes
113 → 119 bytes |
| `scripts/pm/ensure-pm-labels.sh` | one main-repo row after
`needs:pack-smoke`: colour `bfdadc`, a 95-character `-d`, and a comment
block naming the label's readers |

The rule as landed (contract-review.md :32-:36):

- it is only a marker, not a gate. The PR is the single carrier; a copy
on the card is outside the rule and not required.
- **hang**: at ACCEPT, if either clause-② limb hits and no same-form
PASS is on file for the current head, the dispatching seat hangs it on
the PR in the same stroke.
- **clear**: when a same-form PASS is on file for the current head, the
seat that posts it clears the marker in the same stroke. When the PR
merges or closes, the dispatching seat clears it. **A FAIL does not
clear it.**
- **readers**: the maintainer's filter `is:pr is:open
label:needs:contract-review`, and each seat's patrol and handover.
- ⛔ no check, workflow, queue guard or patrol script reads it. Enqueue
recognises only the same-form record, and the marker being present or
absent changes no verdict.

landing-operations.md :13, before and after:

```text
- 子代理起不来 ⇒ 复核缺席,PR 留 draft 队列外等档;唯一旁路是维护者亲审,逐次为准。
- 子代理起不来 ⇒ 复核缺席,PR 带 `needs:contract-review` 留 draft 队列外;旁路仅维护者逐次亲审
```

The line keeps three facts: the review is absent; the PR stays draft,
outside the queue; and the maintainer's own review is the only bypass,
per instance (唯一 … 逐次为准 → 仅 … 逐次). 「等档」 is carried by the marker itself,
which already says the PR awaits its at-tier review. Keeping 「等档」 as
well measured 125 bytes, over the 120-byte cap.

## Durability — what removes a PR label on this repo today (read at base
`ba77509eee`)

- `pr-automation.yml` job `pr-size` → `scripts/pr-labels.mjs --size`. It
POSTs the computed `size/*` label, then sends a targeted DELETE only for
stale `size/*` labels (`planSizeWrites` loops over the size family and
nothing else). The job is skipped on `labeled` / `unlabeled` / `edited`.
- `pr-automation.yml` job `auto-label` → `scripts/pr-labels.mjs
--paths`. It only POSTs: 「Path labels are ADD-ONLY … So this half issues
POST and has no DELETE at all」 (:225-:227). The keys in
`.github/labeler.yml` are documentation, `protocol:*`, ci/cd,
dependencies, tests and tooling; none is a `needs:*` label.
- `lint.yml` runs `node scripts/pr-labels.mjs --self-test`, which pins
that no write plan emits a PUT. It also runs `node
scripts/check-whole-set-label-write.mjs`, which reds on a whole-set `PUT
/issues/{n}/labels` in any spelling anywhere in the repo. That verb
(third-party labelers, and a `labels` field written through MCP) is what
removed this label in the gate era.
- `stale.yml` (`actions/stale`) removes only its own `stale` label. It
closes a PR after 37 idle days, and a close is already a clear trigger
in the rule.
- `half-state-patrol.yml` runs `sweep-closed-cards.mjs --write`, which
strips `PM_RESIDUE_LABELS` (the `pm:*` state labels) from **closed
cards** only.
- `merge-queue-triage.yml` adds labels to its anchor issues only.
`fleet-write.yml` runs only the ops a seat names.
- objectui's labeler runs with `sync-labels: true`, but that is
objectui's. This label is created in this repo only.

⇒ **Today no mechanism on this repo removes a PR label that a seat hung
by hand.** The live carriers' event history agrees. Every labeled or
unlabeled event for `needs:contract-review` on PR objectstack-ai#19962, PR objectstack-ai#19968,
objectstack-ai#19955 and objectstack-ai#19953 is by `objectstack-fleet[bot]`, that is, by a seat.
The only removal pair (PR objectstack-ai#19962 at 11:27:07Z, objectstack-ai#19953 at 11:27:41Z) was
the spec seat's own stroke after an at-tier FAIL (comment 5813182458,
「Carriers stripped on the PR and on this card」), and both were hung
again at 12:14Z. Losing a marker is also the safe failure: a waiting PR
drops out of the filter, but nothing is released, because the queue
guard reads the record.

## Live carriers at dispatch (read 2026-09-24T14:46Z) — ⛔ this PR
changes no label on any of them

| carrier | kind | what the rule says |
|---|---|---|
| objectstack-ai#19962 | PR, draft, head `22c9473c86` | path limb hits
(`packages/spec/src/security/rls.zod.ts`). The marker stays until a
same-form PASS is on file for its current head; whoever posts that PASS
clears it. Under the rule, the 11:27Z clear after the FAIL would not
happen: a FAIL leaves the marker on. |
| objectstack-ai#19968 | PR, draft, head `b05a88136d` | path limb hits
(`packages/spec/src/ui/view.form.ts`). Same as above. |
| objectstack-ai#19955 | card | a card copy is outside the rule and not required. What
happens to it is for the spec seat that hung it. |
| objectstack-ai#19953 | card | same as objectstack-ai#19955. |

Aligning these four carriers is the dispatching seat's closeout step
once the rule is on `main`, as the claim amendment on the card says. It
is not part of this PR.

## Four scripts that still name the label as retired — unchanged, on
purpose

`scripts/pm/check-half-states.mjs` :11927 and :18308,
`scripts/pm/check-skill-line-ratchet.mjs` :448 and :830,
`scripts/pm/check-widening-tells.mjs` :673, and
`scripts/pm/clause2-line.mjs` :11 and :306. Each one describes the
**gate role** (a half-state row that patrolled it, a raise provenance, a
dated census line, the ruling's summary, a measured incident). That role
is still retired, so every sentence stays true. None of them reads the
label, and this PR does not make any of them a reader. `AGENTS.md`,
`SKILL.md`, `state-machine.md` and `.claude/agents/os-dev.md` are
untouched too; the claim excluded them.

## Acceptance notes

- The filer's reading on the card calls objectstack-ai#19955 and objectstack-ai#19953 PRs. The REST
objects carry no `pull_request` key, so they are cards; the claim
amendment already reads them that way.
- 40 cards and PRs that are no longer open still carry the label from
the gate era (for example PR objectstack-ai#19666 and PR objectstack-ai#19618; 44 items in all, 4 of
them open). The filter reads `is:open` and no script reads the label, so
they are inert. Nothing in this PR touches them.
- Governed PRs on either landing tier, this one included, also wait on
an at-tier `## Contract review` record. They are outside the restored
marker's population, which is only the two clause-② limbs, the
population the retired label had. Whether to widen it is left to the
seat as an open question in the report.
- `SKILL.md`'s state-model table does not list the marker. It is a PR
label, not a card state; its rule lives in `contract-review.md`; and
`SKILL.md` is outside this PR's surface.

## Pending after merge — the seat's, not this PR's

- Someone holding `gh` runs `bash scripts/pm/ensure-pm-labels.sh
--reconcile` once. The live object is `ededed` with an empty description
(read 2026-09-24T14:46Z), and create-if-missing never changes an object
that already exists.

## Tests — on `84f4580e`

- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths; three-dot vs merge base
`ba77509ee`) derived 32 commands. The dispatch named four more: `node
scripts/check-skills-token-ratchet.mjs`, `node
scripts/pm/check-governed-queue-guard.mjs --self-test`, `pnpm
check:pm-expected-skips` and `pnpm check:pm-governed-prose`. **All 36
exit 0**, each exit code captured before any pipe. `--ran`
reconciliation: 「32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN」.
- `pnpm check:pm-label-desc-cap`: 「39 label descriptions … all ≤100
characters (longest: 100, tooling)」 (38 on base).
- `pnpm check:pm-skill-ratchet`: 「contract-review.md is 36 lines
(ceiling 60; headroom 24)」 and 「landing-operations.md is 101 lines
(ceiling 101; headroom 0)」. All lines are ≤120 bytes; :3 is at exactly
120.
- `pnpm check:pm-skill-id-lint`: 「34 file(s) clean」. `pnpm
check:skill-frame-sync`, `pnpm check:doc-authoring`, `pnpm
check:pm-governed-prose` and `pnpm check:nul-bytes` are green.
- `pnpm --filter @objectstack/lint run check:doc-formula-expressions`
first exited **3** (PREREQUISITE NOT MET: `@objectstack/formula` /
`@objectstack/lint` not built). That run measured nothing. After `pnpm
exec turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint` under `scripts/pm/os-verify-lock.sh`
(VERDICT command-exit 0), the rerun exited 0.
- `bash -n scripts/pm/ensure-pm-labels.sh` exits 0. A fake `gh` on PATH
ran `ensure-pm-labels.sh --reconcile`, exit 0: the new row issued `label
create needs:contract-review -R objectstack-ai/objectstack -c bfdadc -d
…` and the matching `label edit … --color bfdadc --description …` with
the same string.
- `node scripts/pm/check-governed-merges.mjs --test` on the three paths
returns GOVERNED, **Tier S** (`.claude/** ×2`);
`scripts/pm/ensure-pm-labels.sh` is not on the register.
- A self-scan for control bytes on the three files finds none.
- Not run locally: no package is touched, so there is no build closure
and no package test or typecheck. `pnpm lint` and the CI-only families
(the shard attestation, the test-completeness reader and the type-check
lanes) are left to CI.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
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:ui size/m tests tooling

Projects

None yet

2 participants