Repository navigation
fix(app-shell): both Create View doors build the persisted view through one builder, so "Save as view" saves a kanban the spec accepts (objectui#11581) - #11589
Conversation
…d on "Save as view" kanban (objectui#11581)
The Create View dialog's payload is submitted once per type it offers
(`offeredViewTypes()`, a pure read of the picker's rows) and run through
both persisting doors' spec builders: "Save as view"
(`buildSaveAsViewSpec`) and the add-view door, whose inline body moves,
unchanged, into `buildAddViewSpec`. Each envelope must parse against the
installed `ListViewSchema` and `ViewItemSchema`, and the two doors must
build the same body from the same payload.
Red on this commit, as predicted: the kanban row ("Save as view" is
refused at `kanban.columns`), the gallery row (only the add-view door
mirrors `visibleFields`), and the "Save as view" page case (422 at the
replayed view gate). The one builder lands next.
Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2
Co-authored-by: Claude <noreply@anthropic.com>
…gh one builder, so "Save as view" saves a kanban the spec accepts (objectui#11581) `buildNewViewSpec` (new, `views/newViewSpec.ts`) is the one builder of a new view's list-view spec. Each door resolves only its own inputs: "Save as view" (`buildSaveAsViewSpec`) its page columns and the URL conditions folded to spec filter rules; the add-view door (`buildAddViewSpec`, behind `handleViewCreate`) the object's default business columns. The builder owns every type-specific rule: the resolved columns are mirrored into `kanban.columns` (required by `KanbanConfigSchema`) and into `gallery.visibleFields` when the gallery declares none, through a total record over the visualizations, so a new visualization fails the build until its block is decided. "Save as view" therefore stops persisting a kanban the view write door refuses at `kanban.columns`, and a gallery saved there now shows the view's columns on its cards, as the same payload through the tab bar always did. The add-view door's body is unchanged. Pins: the per-type enumeration over both doors turns green, plus the builder's mirror pins and the add-view door through its page (`ObjectView.createKanbanView-11581.test.tsx`). Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
…alued records, not any (objectui#11581) AGENTS.md #6 (no `any`): `buildNewViewSpec` reads its payload as `Record<string, unknown>` and narrows `type` before it indexes the mirror table. Both doors' resolvers keep their `Record<string, any>` signatures, so no caller changes. Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
…, not the first (objectui#11581) Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #11581
Clause-②: no
Why no: the new symbols (
buildNewViewSpecin the newpackages/app-shell/src/views/newViewSpec.ts,buildAddViewSpecinObjectView.tsx,offeredViewTypesinCreateViewDialog.tsx) are module exports for the pins only. Nothing is added topackages/app-shell/src/index.ts(the package exports only.), andviews/index.tsre-exportsCreateViewDialogandObjectViewby name, not these.What changed
The Create View dialog persists through two doors: "Save as view" on the object data page, and the add-view door on the object page (
handleViewCreate: the tab bar's add button and the view-config panel's create mode). They assembled the spec separately from the same payload, and only the add-view door mirrored the columns intokanban.columns/gallery.visibleFields. Measured on the cut (f1c9379) with the installed@objectstack/spec17.6.0: "Save as view" with Kanban is refused,invalid_typeatkanban.columns; every other offered type parses at both doors, but a gallery from "Save as view" carries novisibleFieldswhile the same payload through the tab bar does.buildNewViewSpec(config, { fallbackColumns, filter }). It resolvescolumns(the payload's, else the door's fallback), placesfilter(no rules, no key), and owns every type-specific rule throughCOLUMN_MIRRORS, a TOTAL record overListViewVisualization:kanban(requiredcolumns),gallery(visibleFieldswhen the payload has none),nullfor the other seven, so a new visualization fails the build until its block is decided.buildSaveAsViewSpec(config, columns, urlFilters)keeps its signature and pins; it now resolves only its inputs (page columns, URL triples folded to spec rules) and calls the builder.handleViewCreate's inline body moves tobuildAddViewSpec(config, objectDef), which suppliesdefaultListColumnsFromObject(objectDef, 5)and calls the builder. This door's body is byte-identical to before.CreateViewDialog.offeredViewTypes(): a pure read of the picker's rows (buildViewTypeMetawith an identity translator), so the pin enumerates at collection time.kanban.columns(was a 422 at the view write door); a gallery now persistsgallery.visibleFields= the view's columns when it declares none, as the tab-bar door always did, so its cards show those fields.@object-ui/app-shellpatch changeset:.changeset/11581-one-view-builder.md.Pins
CreateViewDialog.viewTypeParse-11581.test.tsxPARSE_ROWSnames exactlyofferedViewTypes(), andofferedViewTypes()is the type grid the dialog renders;it.each(offeredViewTypes())): the real dialog is rendered and submitted with that row's picks, the payload goes throughbuildSaveAsViewSpecandbuildAddViewSpec, each wrapped byviewEnvelope;ListViewSchemamust accept the envelope'sconfigandViewItemSchemathe envelope, at both doors (soft, so a row names every refusing door), and the two doors must build the same envelope from the same columns;kanban.columns,gallery.visibleFieldskept when declared, nothing for other types or a prototype-chain name);ObjectDataPage, against the spec's view gate replayed at the network boundary: 200,kanban.columnsequal to the persistedcolumns, lands on the draft.ObjectView.createKanbanView-11581.test.tsx: the add-view door through the realObjectViewand dialog; the saved body parses and carries the mirror.Evidence
1a8dc9a, the pin plus door 2 moved unchanged intobuildAddViewSpec, no builder): 3 failed, 10 passed. Kanban row: DOOR 1 refused,invalid_typeatkanban.columns. Gallery row: "the two doors built different bodies from one payload". Page case:expected 422 to be 200.168f819: the new file 17 passed; the door-2 page pin 1 passed.nullin the builder (anchor 1 to 0, injection 0 to 1, on disk): 5 failed, 13 passed. The kanban row fails at BOTH doors, both schemas (kanban.columns/config.kanban.columns, "expected array, received undefined"); both builder kanban pins; both page cases. Restored withgit checkout HEAD; blobaccf26f1db91equals the HEAD blob,git diff HEADempty.buildViewTypeMeta's record (injection 0 to 1 on disk): 2 failed, 16 passed: thePARSE_ROWSenumeration and the rowfake_ablation_type("no parse row for the offered view type"). The rendered-grid check stays green, since the grid andofferedViewTypes()both carry it. Restored; blobaa96b493d04bequals the HEAD blob,git diff HEADempty.Gates (head
168f819)pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build(29 packages): exit 0.pnpm --filter @object-ui/app-shell type-check(tsc --noEmit && tsc -p tsconfig.test.json, which includessrc/**/*.test.tsx): exit 0.git grepforObjectView,ObjectDataPage,CreateViewDialog,runtime-metadata-persistence,newViewSpecacrosspackages/**andapps/**tests, minus the 46 hits inpackages/plugin-view, which import that package's ownObjectView): 75 files, 857 tests passed. It includes everyCreateViewDialog.*,ObjectDataPage.*andObjectView.create*suite..ts/.tsxfiles,--no-inline-config --format json: 7 files, 0 errors. The narrowing is a measurement: the population is the config's**/*.{ts,tsx}rule objects; the rule set istseslint.configs.recommendedwith no type-aware parser options, and noeslint-rules/*rule reads another file, so the diff cannot move a verdict on an untouched file.check:control-bytes,check:new-line-citations(0 new),check:changeset-claims(2 pending bodies nameObjectView.tsx/ObjectDataPage.tsx; both re-read and still true),check:pending-changeset-literals,check:metadata-write-doors,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:test-path-roots,check:unreferenced-sources,check:self-import,check:esm-specifiers,check:phantom-deps,check:unused-deps,check-changeset-presence,check-changeset-no-major,check-governed-queue-guard --test(NOT GOVERNED): all exit 0.packages/app-shellsuite (988 test files). One run died with its process, without a verdict, about 20 minutes in, at--maxWorkers=2; CI'spnpm testruns it.check:eager-closure: PREREQUISITE NOT MET (it reads a built console bundle); CI owns it.Acceptance notes
gridtype card's test id,create-view-type-grid, is also the test id of the grid that holds the cards. The new pin reads cards by tag. No user effect; noted, not filed (carrier: none).ListViewSchemanameneeds two. Not reachable as a refusal:viewEnvelopestripsnamefromconfig, and the envelope'snameis the qualified object-dot-key, whichViewItemSchemaaccepts (measured with a one-character key). Observation only.origin/mainmoved to0c012a7after the cut (objectui#10872's README, objectui#11511's skills guide). Neither touches these files, so the branch is not merged with it.Generated by Claude Code