Repository navigation
app-shell: the two Create View doors build different view bodies, and "Save as view" omits kanban.columns, so a kanban saved from the data page is refused by spec 17.6.0 (closing card: one builder, a per-type parse pin over both doors) #11581
Description
Activity
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p2·domain:ui·area:records·pm:blocked. The Create View family's closing card: one builder for both doors, and a per-type parse pinTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T22:52Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: #11576
Why blocked. PR objectui#11580 (objectui#11576, open) is in flight on
CreateViewDialog.tsxand pins both doors forchart. A one-builder refactor of both doors' bodies lands after it, so the chart fix is not rebased under a moving refactor. The unlock scan returns the card when objectui#11576 closes.Measured now (objectui
origin/main2f54dca531):ObjectDataPage.tsx'sbuildSaveAsViewSpec(:171, used at:371) mirrors nothing into a type subconfig.ObjectView.tsx'shandleViewCreatemirrors the column list intokanban.columns(comment about:1538–:1540).- Two doors, two bodies.
Why p2. "Save as view" with Kanban on the data page is refused by the published contract. Until objectui#11578 lands, it is also silent. It has objectui#11576's grade, for the second occurrence.
Routing:
packages/app-shell, sodomain:ui.Direction: the card's lead, confirmed.
- Both doors build the persisted view through ONE builder. ⛔ No type-specific mirror lives at only one door.
- Acceptance, the enumeration pin: for every view type
CreateViewDialogoffers, the body each door persists parses against the installedListViewSchema(andViewItemSchemawhere the door wraps it). A newly offered type with no parse row turns the pin red. - Not folded with objectui#11578. That card fixes how a refusal is surfaced, and this one fixes what is sent. The fixes differ, and both touch the two doors' files, so they are serial. The claim reads objectui#11578's PR file list at claim time.
Generated by Claude Code
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Oct 3, 2026 objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionspm:blockedstays; the blocker is re-derived from objectui#11576 to objectui#11578 ·domain:uiseat 1 ·session_01FjqrwXPfSMkSfkKYDSRkN2· 2026-10-03T23:28Z · unlock scan.- The old blocker closed: triage
5974334182recordedBlocked-by: #11576. objectui#11576 closedcompletedat 2026-10-03T22:56Z: PR objectui#11580 merged asd0097afand was verified by content (landing note5974368920). - Re-derived before release, not reflexive: objectui#11578 (
pm:dispatched, claim5974379591) is in flight on the same two doors,ObjectDataPage.tsxandObjectView.tsx, and onCreateViewDialog.tsx; its branchclaude/issue-11578-view-save-refusalalready carries commits there. Triage's direction ("Not folded with objectui#11578 … serial") holds, so this card cannot be dispatched until that one lands. - The line moves to the body, where the unlock scan reads it:
Blocked-by: #11578. The body edit adds only an## Unlocksection; nothing else in it changed. - Next: when objectui#11578 closes, the unlock scan releases this card to
pm:queue. It is p2, inside the maintainer's current p0–p2 scope, so this seat claims it then and reads that PR's merged shape first.
Generated by Claude Code
- The old blocker closed: triage
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsTriage: unlocked. objectui#11576 closed through PR objectui#11580, so
pm:blocked→pm:queue. It stays serial with objectui#11578 on the two doors' filesTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T23:58Z. ⛔ Not a claim, ⛔ not a dispatch.- Measured now: PR objectui#11580 merged at 2026-10-03T22:56Z as
d0097af2e9. The dialog's chart payload is fixed and pinned at both doors. - Serial: objectui#11578 (the silent-failure surfacing) is
pm:dispatchedon the same two files (ObjectDataPage.tsx,ObjectView.tsx). The claim reads that PR's file list at claim time and goes after it. - The direction in
5974334182stands: one builder for both doors, and a per-type parse pin over every type the dialog offers.
Generated by Claude Code
- Measured now: PR objectui#11580 merged at 2026-10-03T22:56Z as
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsClaim: PM loop round 6
Session:session_01FjqrwXPfSMkSfkKYDSRkN2
Account:os-zhuang(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-11581-one-view-builder
Worktree:objectui-issue-11581
Domain:domain:ui
Seat:domain:ui#1
File surface: triage5974334182(direction confirmed; unlock5974794649), read at objectuiorigin/mainf1c9379as leads:packages/app-shell/src/views/ObjectDataPage.tsx(buildSaveAsViewSpecandhandleSaveAsView's body build) andpackages/app-shell/src/views/ObjectView.tsx(handleViewCreate's inline body and its kanban / gallery mirror): both build the persisted view through one builder.- the builder's home:
packages/app-shell/src/views/runtime-metadata-persistence.tsor a new module beside it;CreateViewDialog.tsxonly to export a pure helper that lists the offered types. - new pins (the per-type enumeration over both doors), existing
buildSaveAsViewSpecpins kept green; one@object-ui/app-shellpatchchangeset.
The dev stops on a breach and explains it in the report.
Container & model:M,mode:subagent,model: opus(default judgment tier;dispatch-gates --tier --repo objectstack-ai/objectuiprinted "no path-derived mandate" for these paths)
Clause-②: no
Why no: two internal doors share one body builder, and "Save as view" stops persisting a body the spec refuses. Nothing is added topackages/app-shell/src/index.ts(the package exports only.). If that changes, it becomesyesand the PR says so.
Thread-read: 5974794649
Serial constraints cleared: read 2026-10-04T00:33Z.- objectui#11578's PR objectui#11582 (the triage's serial note, same two doors) merged as
f1c9379; objectui#11576's PR objectui#11580 merged asd0097af. This card keeps both as they are. - No open PR touches
ObjectDataPage.tsx,ObjectView.tsx,CreateViewDialog.tsxorruntime-metadata-persistence.ts. - objectui#11583 (filed, not graded) names other write sites in
ObjectView.tsx; they are not this card's and are not touched. - This seat's in flight: objectui#10872's README item is claimed in this act (
packages/plugin-detail/README.md), with disjoint files.
Generated by Claude Code
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 11581, "status": "done", "branch": "claude/issue-11581-one-view-builder", "pr": "https://github.com/objectstack-ai/objectui/pull/11589", "session": "session_01FjqrwXPfSMkSfkKYDSRkN2 (mode:subagent run of the domain:ui seat 1 session; resumed once after an HTTP 429 weekly-limit stop)", "premise_still_valid": true, "summary": "Premise reproduced at f1c9379 with the installed @objectstack/spec 17.6.0: \"Save as view\" (buildSaveAsViewSpec) persists a kanban that ListViewSchema and ViewItemSchema refuse (invalid_type at kanban.columns), and a gallery without gallery.visibleFields that the add-view door writes; the other 7 offered types parse at both doors. Both doors now build the spec through ONE builder, buildNewViewSpec (new packages/app-shell/src/views/newViewSpec.ts). Each door resolves only its inputs (door 1: page columns plus URL triples folded to spec filter rules; door 2: buildAddViewSpec, extracted from handleViewCreate, with defaultListColumnsFromObject(objectDef, 5)), and the builder owns the kanban.columns and gallery.visibleFields mirrors through a total record over ListViewVisualization. The add-view door's body is byte-identical; \"Save as view\" gains both mirrors. The per-type enumeration pin is driven by a new pure CreateViewDialog.offeredViewTypes(). It submits the real dialog for each offered type, runs the payload through both doors, parses with ListViewSchema (envelope config) and ViewItemSchema (envelope), and requires the two doors to build the same envelope. A door-1 page case runs against the replayed view gate, and a door-2 page case is added. The objectui#11578 toasts and await and the objectui#11576 chart binding are unchanged. Draft PR #11589 is open. The PR assignee write was refused by the auto-mode classifier (see deviations); the seat owns it.", "tests": "HEAD 168f819. Red before (1a8dc9a: the pin, door 2 moved unchanged into buildAddViewSpec, no builder): pnpm exec vitest run CreateViewDialog.viewTypeParse-11581.test.tsx → 3 failed / 10 passed: kanban row 'DOOR 1 ... refused by the spec: invalid_type path [kanban, columns]'; gallery row 'the two doors built different bodies from one payload'; Save-as-view page case 'expected 422 to be 200'. Green after: that file 17 passed (168f819); ObjectView.createKanbanView-11581.test.tsx 1 passed. Narrowed union at 168f819: the 75 test files that import a changed module by name (git grep across packages/** and apps/** tests, minus the 46 packages/plugin-view hits, which import plugin-view's own ObjectView) → 'Test Files 75 passed (75) / Tests 857 passed (857)'; it includes every CreateViewDialog.*, ObjectDataPage.* and ObjectView.create* suite. Ablation 1 (the builder's kanban mirror set to null; anchor count 1 to 0 and injection 0 to 1 on disk; no build needed because the suites import app-shell modules by relative path, so src): 5 failed / 13 passed. The kanban row fails at both doors on both schemas (4 soft refusals), plus both builder kanban pins and both page cases. Restored with git checkout HEAD -- absolute path; blob accf26f1db913d7d71121a5c1aa68b29e5502c56 equals the HEAD blob; git diff HEAD empty. Ablation 2 (fake_ablation_type appended to buildViewTypeMeta's record; injection 0 to 1 on disk): 2 failed / 16 passed: 'PARSE_ROWS names exactly the types offeredViewTypes() lists' and the row fake_ablation_type 'no parse row for the offered view type'. Restored; blob aa96b493d04b7c051b2e0d489cad608d0b52c93b equals the HEAD blob. In ablation 2 the replace helper's own 'anchor gone' self-check printed WRITE NOT ON DISK by construction, because an append keeps the anchor; the on-disk proof is the count line (anchor=1 injected=1). Both ablations finished and were restored before the rate-limit stop, and both were re-verified by blob hash on resume. NOT MEASURED: the whole packages/app-shell suite (988 files), whose run died with its process without a verdict about 20 min in; CI's pnpm test owns it.", "mcp_calls": "0", "api_writes": "2 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectui/pulls, draft #11589, read back 7811 bytes identical; (2) this os-dev-report → POST /repos/objectstack-ai/objectui/issues/11581/comments via post-stamped.mjs. Not REST: 3 git pushes of claude/issue-11581-one-view-builder (empty probe at f1c9379, then 9b7f039, then 168f819). Refused before sending, 0 requests: label-write.mjs --repo objectstack-ai/objectui --issue 11589 --assign os-zhuang (auto-mode classifier).", "open_questions": [], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted, not filed: the grid type card's test id create-view-type-grid equals the test id of the type-grid container in CreateViewDialog; test affordance only, no user effect; the new pin reads cards by tag. Recorded in the PR's Acceptance notes.", "carrier: none (承接者:无) · noted, not filed: the dialog's key pattern accepts a one-character key while ListViewSchema name needs two. Not a refusal anywhere: viewEnvelope strips name from config, and ViewItemSchema accepts the qualified object-dot-key (measured with key k). Observation only." ], "gates": [ { "command": "pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build", "exit": 0, "verdict": "os-verify-lock: VERDICT command-exit 0 (29 packages)" }, { "command": "pnpm --filter @object-ui/app-shell type-check", "exit": 0, "verdict": "os-verify-lock: VERDICT command-exit 0 at 168f819 (tsc --noEmit && tsc -p tsconfig.test.json; the latter includes src/**/*.test.tsx)" }, { "command": "pnpm exec vitest run --maxWorkers=2 --reporter=dot (75 importer test files)", "exit": 0, "verdict": "Test Files 75 passed (75), Tests 857 passed (857) at 168f819" }, { "command": "pnpm exec eslint --no-inline-config --format json (7 changed ts/tsx files)", "exit": 0, "verdict": "files 7, errors 0. Narrowing proof: population = the config's **/*.{ts,tsx} rule objects; count from the JSON = 7; invariance = tseslint.configs.recommended without type-aware parser options, and no eslint-rules/* rule reads another file" }, { "command": "pnpm check:control-bytes", "exit": 0, "verdict": "check-control-bytes: OK (scanned 10209 tracked text file(s))" }, { "command": "pnpm check:new-line-citations", "exit": 0, "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s)" }, { "command": "pnpm check:changeset-claims", "exit": 0, "verdict": "report-only; 2 pending bodies (7070-no-invented-gantt-date-fields, app-shell-page-header-subtitle-4761) name ObjectView.tsx / ObjectDataPage.tsx, both re-read and still true" }, { "command": "pnpm check:pending-changeset-literals", "exit": 0, "verdict": "No test source names a pending changeset." }, { "command": "pnpm check:metadata-write-doors", "exit": 0, "verdict": "OK 17 metadata write door(s) derived" }, { "command": "pnpm check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape", "exit": 0, "verdict": "OK x3" }, { "command": "pnpm check:test-path-roots / check:unreferenced-sources / check:self-import / check:esm-specifiers / check:phantom-deps / check:unused-deps", "exit": 0, "verdict": "OK x6" }, { "command": "node scripts/check-changeset-presence.mjs", "exit": 0, "verdict": "7 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)" }, { "command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "verdict": "No changeset declares a major bump." }, { "command": "node scripts/check-governed-queue-guard.mjs --test (8 diff paths)", "exit": 0, "verdict": "NOT GOVERNED" }, { "command": "pnpm check:eager-closure", "exit": 2, "verdict": "NOT MEASURED: PREREQUISITE NOT MET (reads a built apps/console bundle); CI owns it" }, { "command": "pnpm exec vitest run packages/app-shell/ (whole package)", "exit": null, "verdict": "NOT MEASURED: the process died without a verdict about 20 min in; CI owns it" } ], "files_changed": [ "packages/app-shell/src/views/newViewSpec.ts (new)", "packages/app-shell/src/views/ObjectDataPage.tsx", "packages/app-shell/src/views/ObjectView.tsx", "packages/app-shell/src/views/CreateViewDialog.tsx", "packages/app-shell/src/views/runtime-metadata-persistence.ts (comment only)", "packages/app-shell/src/views/CreateViewDialog.viewTypeParse-11581.test.tsx (new)", "packages/app-shell/src/views/ObjectView.createKanbanView-11581.test.tsx (new)", ".changeset/11581-one-view-builder.md (new, @object-ui/app-shell patch)" ], "deviations": [ "PR assignee NOT written: node scripts/pm/label-write.mjs --repo objectstack-ai/objectui --issue 11589 --assign os-zhuang was refused by the Claude Code auto-mode classifier ([External System Writes]) before sending anything. It was not re-sent by any other route; the seat writes it.", "No path labels written, per the brief (labeler.yml owns them).", "origin/main moved f1c9379 → 0c012a7 (objectui#11584 plugin-detail README, objectui#11585 skills guide plus a changeset). Neither touches this diff's files, so the branch was NOT merged with main; the gates are on 168f819 over base f1c9379.", "On resume the coordinator's note said ablation 1 was in flight. Measured otherwise: both ablations had finished and been restored (blob hashes re-verified equal to HEAD). The step in flight was the whole-package app-shell vitest run, which died without a verdict and is reported NOT MEASURED.", "Gallery behaviour change at \"Save as view\": it now writes gallery.visibleFields = columns when the gallery declares none. That is the add-view door's existing rule, now shared because the ruling allows no type-specific mirror at only one door. Stated in the PR and the changeset.", "The new test-only exports (offeredViewTypes in CreateViewDialog.tsx, buildAddViewSpec in ObjectView.tsx) add react-refresh/only-export-components warnings, the same as those files' existing exports. 0 errors." ], "line_budget": "n/a (no skills/** touched)" }
Generated by Claude Code
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actions✅ ACCEPT: PR objectui#11589 at head
168f819c3(dev report5975846910); it lands through the merge queue when every check is greendomain:uiseat 1 ·session_01FjqrwXPfSMkSfkKYDSRkN2· 2026-10-04T02:39Z. Reviewed against GitHub, not the report.- Shape: draft, base
main. The first line isFixes #11581, with no other closing keyword.Clause-②: nois at line start, with its reason. 8 files, +694 / −44, all inside the claim's surface.check-governed-merges: NOT governed. Clause-②: nore-checked:@object-ui/app-shellexports only..views/index.tsre-exportsObjectViewandCreateViewDialogby name (noexport *). So the new module exportsbuildNewViewSpec,buildAddViewSpecandofferedViewTypesare internal, marked@internal. The type-only import ofListViewVisualizationcomes from@object-ui/core, already a dependency.patch✓.- Triage
5974334182, carried out:- ONE builder,
buildNewViewSpecin the newviews/newViewSpec.ts, owns every type-specific rule. The column mirror is a TOTALRecord<ListViewVisualization, …>, so a visualization added to the spec fails the build until someone decides its block.kanban.columnsis always written, because the spec requires it.gallery.visibleFieldsis written only when the payload carries none. - Each door resolves only its own inputs. "Save as view" supplies the page's columns and the URL conditions folded to spec rules (objectui#3419's fold, unchanged; an all-dropped fold still writes no
filter). The add-view door suppliesdefaultListColumnsFromObject(objectDef, 5). - The add-view door's body is unchanged: the removed inline block and the builder compute the same object for every type. "Save as view" gains both mirrors.
- The objectui#11578 toasts and dialog await, and the objectui#11576 chart binding, are untouched in the diff.
- ONE builder,
- The behaviour change at "Save as view" for gallery (it now writes
gallery.visibleFields), accepted. The ruling allows no type-specific mirror at only one door, so the gallery rule had to be shared. The changeset says so in user terms. - Pins:
CreateViewDialog.viewTypeParse-11581.test.tsxis driven byofferedViewTypes()(the picker's own rows, not a hand-typed copy). It submits the real dialog once per offered type and runs each payload through both doors. It parses each spec withListViewSchemaand each envelope withViewItemSchema, and requires both doors to build the same envelope. There is also a door-1 page case against the replayed view gate, andObjectView.createKanbanView-11581.test.tsxcovers door 2's page.- Red first (pin before the builder): 3 failed, 10 passed: the kanban row (
invalid_typeatkanban.columns), the gallery row ("the two doors built different bodies") and the "Save as view" page case (422). - Ablation 1 (no kanban mirror): 5 red. Ablation 2 (a fake offered type): exactly the enumeration's 2 rows red. Both were restored by blob hash and re-verified on resume.
- Gates: build, type-check and the 75 test files that import a changed module (857 tests) are green, plus lint and the write-door, mock, changeset and governance gates. Not measured locally: the whole
app-shellsuite, whose run died without a verdict. CI's eightTestshards run it, and this seat lands only on an all-green head. - Out-of-scope readings: the grid card's duplicated test id (test affordance only), and the dialog's one-character key (not refused anywhere, because the envelope uses the qualified name). Both are dropped: no user effect, no
reach:. - Deviation, not acted on by this seat: the dev's
label-write --assign os-zhuangfor the PR was refused by its classifier before any request went out. ⛔ This seat does not re-run a refused write, so the PR assignee stays unset and is listed for the maintainer. mainmoved to0c012a7(objectui#11584 and docs(skills): drop the four retired CLI rows from project-setup.md's command table (objectui#11511) #11585); neither touches these files, and the merge queue builds on the currentmain.- ⇒ On merge, the unlock scan releases objectui#11583 (
Blocked-by: #11581).
Generated by Claude Code
- Shape: draft, base
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsLanded and verified by content: closed
completedby PR objectui#11589 ·domain:uiseat 1 ·session_01FjqrwXPfSMkSfkKYDSRkN2· 2026-10-04T03:09Z.-
The merge: PR objectui#11589 went through the merge queue to
mainasb65aa5eat 2026-10-04T03:08Z (seat ACCEPT5975860090). -
Content check of
packages/app-shell/src/views/onmainb65aa5eagainst its first parent5a2ca6b:mainparent newViewSpec.ts(the one builder) in the treeyes no newViewSpec.ts: the kanban column mirror1 0 ObjectDataPage.tsx:return buildNewViewSpec(config1 0 ObjectView.tsx:export function buildAddViewSpec1 0 ObjectView.tsx: the old inlinespec.kanban = { ...(spec.kanban || {}), columns: incomingColumns }0 1 CreateViewDialog.tsx:export function offeredViewTypes1 0 Both pins (
CreateViewDialog.viewTypeParse-11581,ObjectView.createKanbanView-11581) and the changeset11581-one-view-builder.mdare in the tree. The diff is exactly the reviewed 8 files, +694 / −44. -
In that window objectui#11586 also closed, through its own PR objectui#11587; it is not this seat's card, and nothing closed unexpectedly.
-
Released by this landing: objectui#11583 (
Blocked-by: #11581, the silent-refused-write family's closing card). The unlock scan takes it next. -
pm:dispatchedis removed in the same act.domain:ui,area:records, the type and the priority stay.
Generated by Claude Code
-
- added a commit that references this issue
on Oct 7, 2026
Filing gate: class (b), a producer writing a shape the contract refuses.
reach:named producer, measured:ObjectDataPage.buildSaveAsViewSpec("Save as view" on the object data page), with its persisted body parsed by@objectstack/spec17.6.0'sListViewSchema. Reader: triage grades and routes it.Filed by
domain:uiseat 1 (session_01FjqrwXPfSMkSfkKYDSRkN2, seat post objectui#5560). The objectui#11576 dev raised it as an unmeasured inference in report5974135085; this seat measured it. ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.Measured (this seat, npm
@objectstack/spec17.6.0 installed in a scratch directory,ListViewSchema.safeParse)buildSaveAsViewSpecpersists for a kanban:{ type: 'kanban', label, name, columns: [...], kanban: { groupByField: 'stage' } }invalid_typeatkanban.columns, "expected array, received undefined"columnsmirrored intokanban.columns(control)buildSaveAsViewSpecpersists for a gallery:{ type: 'gallery', …, gallery: { coverField } }KanbanConfigSchema.columnsisz.array(z.string()): required, "Fields to show on cards".Where it lands (read at objectui
main0903617)packages/app-shell/src/views/ObjectDataPage.tsx,buildSaveAsViewSpec(about:171):{ ...config, columns, filter? }. It mirrors nothing into a type subconfig.packages/app-shell/src/views/ObjectView.tsx,handleViewCreate(about:1536–:1545): "certain view types nest their card/field list inside their type-specific subconfig (e.g. kanban.columns, gallery.visibleFields) … we mirror the resolved column list into the subconfig here so the spec validator accepts the row." The data page's door never got that mirror.Family: this is the closing card
Likely landing (a lead, not a ruling)
CreateViewDialogoffers, the body each door persists parses against the installedListViewSchema(andViewItemSchemawhere the door wraps it). A newly offered type with no parse row turns the pin red.CreateViewDialog.tsxand pins both doors forchart, and objectui#11578 (the silent failure) touches both doors' catch blocks. This card goes after both, or merges with objectui#11578 if triage prefers.Dedupe
save as view kanban columns refused·buildSaveAsViewSpec kanban.columns·two create view doors different bodies·KanbanConfigSchema columns required data page.Unlock
Blocked-by: #11578
objectui#11578 is in flight on the same two doors (
ObjectDataPage.tsx,ObjectView.tsx) and onCreateViewDialog.tsx. This card's one-builder refactor lands after it.Generated by Claude Code