Repository navigation
[finding] Untitled repeater row properties: view.columns (14), view.tabs (9), view.sort (2) — BLOCKED on #17360 #17507
Description
Activity
Blocker cleared.
Blocked-by: #17360— that card is now closed / completed: PR #17498 landed and was verified by content onorigin/mainbea76c9280at 2026-09-10T19:16Z.A GROUPING_FIELD_NON_PADDED_PATTERN in ui/view.zod.ts : 2 (lit: export const GroupingFieldSchema = 1) B the os-regen-routed doc line in content/docs/references/ui/view.mdx : 2 (lit: 'Group sort order' = 2) C id: 'ui-list-view-grouping-field-padded-refused' in registry.ts : 1⭐ Probe B is the one worth noting:
content/docs/references/**is routedmerge=os-regen, a driver measured exiting 0 while silently dropping one side, and #17298 is a co-tenant on that same path. It read 2, so both sides survived the merge. Had it read 0 or 1 that would have been an incident, not a pass.⇒
packages/spec/src/ui/view.zod.tsis no longer held. All three fences named on this card's siblings have now dropped, so #17505, #17506 and this card are each free to dispatch — ⛔ once triage grades them; they are ungradedfindings and this seat does not grade its own lane.
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't workingand removed
on Sep 10, 2026 Triage:
domain:specconfirmed; typeBug,priority:p3,pm:queue— ⛔ NOTpm:blocked. The fence has dropped.⚠️ The card says BLOCKED; it is not, as of 35 minutes before I read itThe body declares
Blocked-by: #17360and 「PR #17498 is open and still draft against that file (measured 2026-09-10T18:02Z)」. Re-read 2026-09-10T19:30:55Z:reading value #17360 closed/completedPR #17498 merged 2026-09-10T18:55:32Z, no longer draft ⇒ Dispatchable. The
Blocked-by:line is struck in the body rather than deleted, because it is machine-read by H4/H19 and leaving it would park this card in the patrol's UNJUDGED bucket.⭐ Third card this fire whose declared blocker had already cleared (with #17495 and #17493 at R+170, and #17483 at R+169). ⇒ The discipline is now earned four fires running: ⛔ never grade a
Blocked-by:from the card's text — read the blocker.Carriers:
view.columns(14),view.tabs(9),view.sort(2) inpackages/spec/src/ui/view.zod.ts.⚠️ Two measured traps, specific to this file — do not re-discover themViewMetadataSchemais a 4-arm union in whichcolumnsis an object array (14 row props) on the list arm and an INTEGER (form body columns) on the form arm. ⇒ A derivation that MERGES arm properties keeps whichever zod emitted last and answers the integer — a first pass did exactly that and dropped the carrier asNOT_AN_ARRAY. Keep every union arm as a separate candidate.view.sort's array arm is an INLINE{field, order}, ⛔ not the sharedSortItemSchema. Titling the shared schema (done in PR feat(spec): a repeater's property-panel table has column names, and an untitled item schema is now loud #17500, merged 19:06:56Z) did not reach it — measured, ⛔ not assumed.
⇒ Both traps are the reason this is the largest slice, and both are the kind that produce a plausible wrong answer rather than an error.
The family, graded as one shape
All three are
Part of #17232(pm:dispatched, p2) and share the mechanism that makes them measurable —packages/spec/src/kernel/repeater-item-titles.test.ts, an exact, shrink-only ledger: a carrier absent from it must be fully titled (a new untitled repeater reds on arrival), and a carrier present in it must still be untitled (a paid debt must be deleted from the ledger).⭐ That ledger is why these are p3 rather than anything worse: the debt cannot grow. A new untitled repeater reddens immediately; what remains is a fixed, enumerated backlog.
⇒ Each card is one carrier set = one slice = one changeset. ⛔ Do not fold them into one dispatch: they touch three different files, and folding gate ② (same package/area, one worktree, one changeset) fails on the file face even though the package is shared.
⚠️ Counts were derived through the platform's own predicate (z.toJSONSchemaovergetMetadataTypeSchema), ⛔ not by regexing source. Re-derive the same way; a source regex will disagree and the ledger will red.priority:p3: raw property keys as column headers in the Studio property panel. Nothing behaves wrongly, no data moves, and the reader is a metadata author looking at a panel — not an end user.type Bug: the panel shows an identifier where the schema means to show a title.Triage seat ·
session_017VGfRocA8VjczSe84fgjY3· R+171 · 2026-09-10T19:31Z (timestamp taken in the same tool call that posts) · comment from the triage seat
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 20, 2026 os-support-ai commented
on Sep 23, 2026 CollaboratorMore actionsClaim: PM loop — Studio shows untitled repeater rows for
view.columns,view.tabsandview.sort: their row properties carry no title, dispatched at 2026-09-23T17:05Z
Session:session_013RDBh5DqXd2xnLwvHLgLFr
Branch:claude/issue-17507-view-repeater-row-titles
Worktree:objectstack-issue-17507
Domain:domain:spec
Seat:domain:spec#1
File surface:packages/spec/src/ui/view.zod.ts, three regions only:ListColumnSchema,ViewTabSchema, and the inline{ field, order }item of the list arm'ssortarray. Alsopackages/spec/src/kernel/repeater-item-titles.test.ts(the shrink-only ledger), whatever the generators rewrite (regenerated, ⛔ never hand-edited), and.changeset/.
Container & model:S,mode:subagent,model: opus (default judgment tier)
Clause-②: no
Thread-read: 5624316224
Serial constraints cleared: census over the open PRs at 2026-09-23T17:05Z.view.zod.tsis also edited by:- PR fix(spec): one row bound per view — retire the unpublished per-kind view
limit#19809: the row-limit region (PaginationConfigSchema, theGrouping/Gallery/Timeline/AddRecord/Kanbanconfigs,rowLimitKey); - PR fix(spec): refuse an absent value on a value-taking view filter operator at authoring time #19861, this seat's:
checkViewFilterRuleValueShape/ViewFilterRuleSchema; - [finding] the form face cannot express an enum whose members carry a hyphen or a capital —
FormSelectOptionSchema.valueis a system identifier, sosystem-data,new-tabandperRecordare unspellable as option values #19678's in-flight round, this seat's:FormSelectOptionSchema.
None of the three regions above is in theirs, so the regions are disjoint. Generated files that also move under PR #19809,
authorable-surface/api-surface/json-schemaunderpackages/spec/andcontent/docs/references/ui/view.mdx, are resolved by regeneration andscripts/pm/os-regen-merge.sh, ⛔ never by hand. The card's old fence (#17360 / PR #17498) cleared on 2026-09-10.Why this qualifies while product P0/P1s are open
A Studio author editing a view's columns, tabs or sort sees untitled rows. This is an authoring-surface defect graded p3 Bug by triage, ⛔ not tooling or contract hygiene.
Generated by Claude Code
- PR fix(spec): one row bound per view — retire the unpublished per-kind view
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsos-dev-report
{
"issue": 17507,
"status": "done",
"branch": "claude/issue-17507-view-repeater-row-titles",
"pr": "#19917",
"session": "session_013RDBh5DqXd2xnLwvHLgLFr",
"premise_still_valid": true,
"summary": "Titled all 25 untitled row properties of the three view repeater carriers in packages/spec/src/ui/view.zod.ts: ListColumnSchema (14), ViewTabSchema (9) and the list arm's INLINE { field, order } sort entry (2, titled Field / Direction like SortItemSchema). Each got a short English .meta({ title }) in the file's existing .describe(...).meta({ title }) convention. The ledger's last three entries (view:columns, view:sort, view:tabs) are deleted, so LEDGER in packages/spec/src/kernel/repeater-item-titles.test.ts is now EMPTY. Measured first on base 44ce049 through the ledger's own derivation (z.toJSONSchema over getMetadataTypeSchema('view'), io input, union arms kept separate, property node before its $ref): 14 / 9 / 2 untitled, the same as 2026-09-10; after the change 0 / 0 / 0 under both io input and io output (the /meta/types derivation). Mechanism correction to the dispatch: the ledger records MEMBERSHIP only (a Set of ids; a listed carrier must have a non-empty untitled list), not an exact untitled count. 'Title' means JSON Schema title, read as raw.title ?? deref(raw).title, emitted from .meta({ title }). No generated file moved: check:generated reports 15 of 15 up to date and authorable-surface is byte-identical, i18n:extract gives zero diff and check:i18n is in sync. Titles never reach the metadata-form bundles because viewForm enumerates no row children for these repeaters. The changeset is @objectstack/spec patch with Clause-②: no, copied from the claim. The assignee was already set (os-support-ai) at pickup and is untouched. The next step after this comment is worktree cleanup (node_modules, then git worktree remove).",
"tests": "All at 7a30204 unless stated; heavy runs under scripts/pm/os-verify-lock.sh (shared-box seconds). (1) FIRING CONTROL, run after the fix was committed: node scripts/ablation-replace.mjs --file packages/spec/src/ui/view.zod.ts --anchor ".meta({ title: 'Display Order' })" --delete -- pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/kernel/repeater-item-titles.test.ts gives: on disk anchor 1 to 0, blob b4451833e711 to a014831e9ce2; RED 'view:tabs: ... expected [ order ] to deeply equal []', Tests 1 failed | 25 passed (26); restored blob == HEAD b4451833e711, git diff HEAD empty. No build was needed: the ledger imports view.zod.ts by a relative src path, not through the dist exports. The first attempt used --replacement with a substring of the anchor; the tool refused it before running (replacement count +0) and restored the file, so it was a no-op with no reading. Green leg on the restored tree: Tests 26 passed (26). (2) pnpm --filter @objectstack/spec build: green at 69da9fe and again at 7a30204; check:dts-emitted 34/34. (3) pnpm --filter @objectstack/spec check:generated at 69da9fe (the only later commit adds the changeset file): 'All 15 generated artifacts are up to date'. (4) pnpm i18n:extract: 11 files written, git status empty (zero diff). pnpm check:i18n: 9 packages in sync. (5) spec local suite, vitest run --project local --maxWorkers=2: Test Files 526 passed (526), Tests 15501 passed | 1 todo. spec repo suite, --project repo: Test Files 35 passed, Tests 602 passed. (6) pnpm --filter @objectstack/spec typecheck: exit 0 (tsc, check:scripts-typecheck, check:test-typecheck). (7) platform-objects full suite, vitest run --passWithNoTests --maxWorkers=2: Test Files 54 passed, Tests 883 passed. That includes repeater-row-properties.test.ts, whose 'view enumerates no repeater row properties yet' dark control still holds. (8) Wire-shape consumers of the served titles: metadata-protocol protocol.meta-types-degenerate-derivation + protocol.meta-types-unauthorable-columns, 26 passed; rest meta-types-schema-titles, 3 passed. Declared narrowing: the rest of those two packages' suites are left to CI. (9) Gates: dispatch-gates --repo objectstack-ai/objectstack --commands, re-derived after the changeset commit, gives 82 families; also ran check-changeset-fixed.mjs (a roster under .changeset). The --ran reconciliation reads '82 derived famil(ies) accounted for — 82 run, 0 NOT-MEASURED (a DERIVED zero)'. check:dual-build-cjs-loads first exited 3 (missing dists); re-run exit 0: 104 entry points across 67 packages load. check:type-check-debt first exited 3 (spec and platform-objects entry points older than sources after this run's own ablation and extract); re-run after rebuilding both, exit 0: 'none above its recorded number'. check:nul-bytes exit 0. (10) ESLint, narrowed and proven: eslint --no-inline-config --format json on the two changed .ts files gives 2 files, 0 errors, 0 warnings. Population: both resolve a config under --print-config (neither ignored), and pnpm lint is 'eslint .'. Invariance: the resolved parserOptions are { ecmaVersion: 'latest', sourceType: 'module' }, with no project or projectService, so linting is not type-aware and this diff cannot change the verdict on any untouched file. (11) Changeset level evidence: dist/ui/index.mjs carries 'Renderer Type' 1x; base src carries it 0x; the nonsense-string negative control reads 0. The published json-schema/ui/ListColumn.json carries the titles. (12) CI at report time, one read of 7a30204 check-runs: 32 total, 8 success, 3 skipped, 21 in_progress, 0 failed. Status in_progress; not awaited.",
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "5 — 3 x git push to claude/issue-17507-view-repeater-row-titles via write-pace --run (the empty-branch probe, the fix commit 69da9fe, the changeset commit 7a30204); 1 x POST /repos/objectstack-ai/objectstack/dispatches (the fleet-write relay op pr_create, i.e. POST /pulls with draft=true, run 35901787905 success, PR #19917 opened by objectstack-fleet[bot]; the body was read back byte-identical to the one sent); 1 x this os-dev-report comment via post-stamped.mjs (POST /issues/17507/comments). No labels, no draft/ready flip, no PATCH of the PR body.",
"open_questions": [
{
"question": "Precedent is split on the semver declaration for this exact shape (a .meta({ title }) added to repeater row properties). The #17505 PR (97233b9) declared Clause-②: yes with a minor changeset, because the published JSON Schema gains title nodes. The #17506 PR (c199772) declared Clause-②: no with patch. This PR follows the claim: Clause-②: no and patch.",
"options": [
"A keep no / patch: no accept set moves, authorable-surface and api-surface are byte-identical, the added nodes are presentation-only",
"B treat JSON Schema title nodes as widening the public surface: yes / minor, and ask for this PR's changeset to be rewritten"
],
"recommendation": "A, because it matches the claim and the newer precedent, and nothing an author can write or a consumer can type against changes. No PR-body change is needed either way; if B is chosen, the changeset frontmatter and the PR's Clause-② line both change."
}
],
"out_of_scope_findings": [
"class: a · evidence: on this PR's tree (7a30204), Studio in zh-CN / ja-JP / es-ES will show the new ENGLISH titles as the column heads of view.columns / view.sort / view.tabs. viewForm (packages/spec/src/ui/view.form.ts) declares no row fields for these three repeaters, so os i18n extract emits no metadataForms.view.fields key for any row property (i18n:extract gives zero diff), and packages/platform-objects/src/apps/translations/repeater-row-properties.test.ts pins view OUTSIDE its population ('view enumerates no repeater row properties yet'). Reachability caveat: on main today the same heads are raw keys, which is this card; the English-in-every-locale form is reachable once #19917 lands. The remedy shape is the one the other carriers already use: enumerate the 25 children in view.form.ts with labels equal to the schema titles, plus the translated leaves. It is outside this card's file surface and not filed by this dev. · dedupe words: view.form.ts repeater row children; view columns sort tabs localisation; metadataForms view columns; repeater-row-properties view; view repeater column heads English",
"carrier: this PR's Acceptance notes · noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsLanding — PR #19917 flipped ready + auto-merge by
domain:specseat 4, 2026-09-24T05:46ZDone on the maintainer's instruction, provenance:
- who: the maintainer;
- words: 「帮我处理」 for a list of twelve PRs "只差一份 at-tier 复核的 PASS 记录", then the landing route 「我直接落地」 chosen in the same exchange;
- where: the chat of session
session_019c3Hi6ZMU1p6m6aA6Bz45d(domain:spec#4).
This does not take over the claim: the claim, the branch and the card stay with the claiming seat, and this seat only lands the PR.
- Contract review: at-tier PASS, record
5808356148on PR fix(spec): title the view.columns, view.tabs and view.sort repeater row properties #19917, on the head the PR carries now. - Landing prechecks, re-read immediately before the flip: head unchanged; every check-run completed
successor skipped by design;check-governed-merges.mjsreads 0 governed paths; the PR is under 5,000 lines;mergeable_stateisclean. - The localization follow-up card is owed after the merge (the maintainer's own note); this seat files it once the PR has merged.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsLanded — PR #19917 →
f5a7250b7a, 2026-09-24T06:28Zdomain:specseat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), landing record for the landing done on the maintainer's instruction (provenance in this seat's landing comment above).- The card closed
completedthroughFixes #17507. The squashf5a7250b7ahas one parent and is an ancestor oforigin/main. - Mis-close check: of the cards closed since 2026-09-24T06:05Z, each was closed by its own PR; none by a stray keyword.
pm:dispatchedremoved. The assignee and the claim belong to the claiming seat and are left untouched.
Generated by Claude Code
- The card closed
25 row properties across three repeater carriers in
packages/spec/src/ui/view.zod.ts.Blocked-by: #17360— ⛔ STRUCK by triage 2026-09-10 (R+171): #17360 is closed/completed and PR #17498 merged 18:55:32Z. This card is NOT blocked.This is the one fence of the three that still stands: PR #17498 is open and still draft against that file (measured 2026-09-10T18:02Z). ⛔ Do not dispatch until #17360 closes — the pin OBSERVES these carriers without editing the file, which is deliberate.
ViewMetadataSchemais a 4-arm union in whichcolumnsis an object array (14 row props) on the list arm and an INTEGER (form body columns) on the form arm. A derivation that MERGES arm properties keeps whichever zod emitted last and answers the integer — which a first pass did, dropping the carrier asNOT_AN_ARRAY. Keep every union arm as a separate candidate.view.sort's array arm is an INLINE{field, order}, not the sharedSortItemSchema. Titling the shared schema (done in PR feat(spec): a repeater's property-panel table has column names, and an untitled item schema is now loud #17500) did not reach it — measured, not assumed.Part of #17232. The mechanism that makes this measurable is
packages/spec/src/kernel/repeater-item-titles.test.ts— an exact, shrink-only ledger: a carrier absent from the ledger must be fully titled (a new untitled repeater reds on arrival), and a carrier present in it must still be untitled (a paid debt must be DELETED from the ledger). So the work here is: add.meta({ title })to each row property, then remove this carrier's ledger entry. ⛔ Do not weaken the ledger to make it pass.Titles are hard-coded English by design, not a gap.
packages/spec/src/system/translation.zod.tsstates it: a row property is rendered fromitems.properties[k].title, andresolveMetadataFormSchemaTitlesoverlays the locale bundle on top of that node. The English default has to live on the schema..meta({ title })on a schema that zod hoists into$defsis emitted as a sibling of the$ref—{"title":"…","$ref":"#/$defs/__schema0"}. Read the property node first, as Studio does; deref'ing first reports genuinely-titled properties as untitled.⛔ Ungraded — triage sets type and priority. Filed by the
domain:specexecution seat 2026-09-10T18:04Z.Generated by Claude Code