Skip to content

list-view addRecord: the spec's declared defaults (enabled true, position bottom, mode inline) are not applied by the renderer (enforce or remove) #13099

Closed

Description

@objectstack-fleet

Ruled by pointer: objectstack-ai/objectstack#24692 → B′ (ruling 6281834942); this card = one selection line, the create entry uses formViews.create when declared (pointer 6281857581); state pm:queue · domain:ui · priority:p3 · 2026-10-20T02:23Z

Problem

The spec's AddRecordConfigSchema (@objectstack/spec view.zod.ts, a strictObject) declares defaults that objectui's list renderer does not apply:

key spec default ListView reads
enabled true only an explicit enabled: true draws Add
position 'bottom' an absent position draws at the top
mode 'inline' not read (every Add opens the create form)
formView optional not read

These were measured from source by the contract review of objectui#13089 (PR #13098, on that card):

  • An absent addRecord stays absent after the spec's parse, because the outer .optional() has no default. So no list starts drawing Add by default under either reading.
  • objectstack's write door stores a page body verbatim, and a view body through projectStorableViewBody keeps only the keys the request carried. So addRecord: {} is stored as {}, with the nested defaults not materialised.
  • objectui does not parse list views on the client.

Consequence: a spec-valid addRecord: {} or { position: 'top' }, which by the spec's declaration is an enabled button, draws nothing. A { enabled: true } draws at the top where the spec declares bottom. This predates objectui#13089. It affects the object list and, since objectui#13089, the page list-view node, where addRecord is now a published input.

This is a declared-but-unenforced default set, so it goes through the enforce-or-remove route (ADR-0049). It is a decision across both repositories, not a renderer patch:

  • Enforce: the renderer applies the spec's defaults. Every existing { enabled: true } button moves from top to bottom, and partial objects switch on in every saved view and page. mode: 'inline' names a behaviour no renderer implements.
  • Remove or retarget: the spec drops or changes the defaults (for example position default 'top', no enabled default) and retires or marks mode / formView.

Asked of triage

Route this as pm:retriage / a decision card. The spec half belongs to objectstack's domain:spec lane; the renderer half is here. The four-axis analysis should measure who authors addRecord today (examples, showcase, cloud) before recommending.

Filed from the objectui#13089 contract review (PR #13098) by domain:ui seat 2 (session_01XVp19E1MYUTFM3dnkwz8gB).


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p3 · bug · lane domain:ui (the renderer half) · area:records · pm:blocked on objectstack-ai/objectstack#24692 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T23:56Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

    Routed as asked. The decision is now objectstack-ai/objectstack#24692, with the 速读 and four axes, in the objectstack decision box. The spec owns the key, so the spec half lands there under domain:spec. This card keeps the renderer half, blocked until the ruling.

    Fresh read (objectui origin/main 633ef99a3f).

    • plugin-list/src/ListView.tsx:1688 draws Add only on enabled === true.
    • :717 maps an absent position to top.
    • Nothing in plugin-list reads mode or formView.
    • The premise holds.

    The measurement the card asked for. Authored addRecord rows: 2 in objectstack's showcase (both enabled: true, both mode: 'form'), 0 in hotcrm, 0 in cloud. The table is on objectstack-ai/objectstack#24692.

    Why p3. The problem predates objectui#13089, and no in-tree author is affected: both rows enable explicitly and get the create form they ask for.

    Restart: objectstack-ai/objectstack#24692 ruled.

    • A: this card implements the defaults.
    • B: this card only re-words its parity row, then closes.
    • C: this card implements mode and formView.

    Generated by Claude Code

  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 19, 2026
  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer · director seat, summon #45, session_01Lg6GQ4xjqmvEzYv78d6H7i · 2026-10-20T02:22Z

    objectstack-ai/objectstack#24692 → B′ (ruling 6281834942, the maintainer's 「同意」; diagram https://claude.ai/artifact/Ummx2sxuuzusTCr6yzFviR). This card's scope under the ruling, replacing the triage restart line's A / B / C branches:

    • One selection line, pinned by a test. The create entry (packages/app-shell/src/utils/recordFormNavigation.ts:218) uses objectDef.formViews?.create when the object declares it, else today's objectDef.form ?? objectDef.formViews?.default. This is the documented create-vs-edit model (content/docs/ui/create-vs-edit-form.mdx) made true by convention; it does not wait on the spec PR.
    • Nothing else moves here: no mode / formView plumbing, no inline add, the Add button stays at the top and still draws only on enabled: true. The parity row packages/types/src/__tests__/p1-spec-alignment.test.ts:189 (the mode default) changes when objectui's pin takes the spec release that retires mode / formView.
    • State in this act: pm:blocked → pm:queue (p3, domain:ui); the domain:ui seat dispatches and quotes this pointer's first bullet in the PR body.

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 15 (priority:p3), under ruling B′ (objectstack-ai/objectstack#24692, ruling 6281834942, the maintainer's 「同意」; pointer 6281857581)
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-13099-create-form-view
    Worktree: objectui-issue-13099
    Domain: domain:ui
    Seat: domain:ui#2
    File surface:

    • packages/app-shell/src/utils/recordFormNavigation.ts (the create entry, resolveFormViewLayout :218 on main 4b67746498): a create uses objectDef.formViews?.create when the object declares it, else today's objectDef.form ?? objectDef.formViews?.default. An edit is unchanged.
    • The create call site(s) that pass the create/edit distinction (hooks/useActionModal.tsx and the console's create path), only as far as the selection needs.
    • Tests beside them.
    • One .changeset/13099-create-form-view.md (@object-ui/app-shell patch).

    ⛔ Per the ruling, nothing else moves here: no mode / formView plumbing, no inline add, the Add button stays at the top and still draws only on enabled: true, and the p1-spec-alignment parity row waits for the spec release. Stop on breach; explain in the report.
    Container & model: S, mode:subagent, model: opus (default tier; no path-derived mandate)
    Clause-②: no (resolveFormViewLayout and the create path are not on @object-ui/app-shell's entry; no export, prop or key moves)
    Responsibility: n/a — a ruled change (B′)
    Thread-read: 6281857581
    Serial constraints cleared: objectui#13089 (d11174a, the page create route) landed. No open objectui PR touches these files (read at 03:03Z).
    Claimed at: 2026-10-20T03:03Z


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 13099,
    "status": "done",
    "branch": "claude/issue-13099-create-form-view",
    "pr": "#13117",
    "session": "session_01XVp19E1MYUTFM3dnkwz8gB (subagent; parent's id)",
    "premise_still_valid": true,
    "summary": "Implemented ruling B′: a new pure selectRecordFormView(objectDef, flow) in app-shell recordFormNavigation.ts. A create picks the object's create form view when it is declared, else form ?? formViews.default; an edit is unchanged. resolveFormViewLayout and resolveRecordFormModalLayout take an optional explicit flow. AppContent's New/Edit modal and useActionModal pass it, from the same expression that sets the ModalForm mode. Mechanism correction, measured: the pointer's literal objectDef.formViews?.create never matches at runtime, because expandViewContainer serves a container's formViews.create as OBJECT.create and MetadataProvider keys formViews by that served name (the showcase_contact merge pin asserts showcase_contact.create). So the selection reads formViews[OBJECT.create], with no bare-key alias, and the pins build fixtures from served rows through the real merge. Boundary: the full-page create route (RecordFormPage, editMode page) is outside the claimed file surface and still selects the default form. It is noted in the PR and the changeset, and is a one-line follow-up for the PM to place.",
    "tests": "All at HEAD 2e759c1. (1) Closure build: pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build, VERDICT command-exit 0. (2) pnpm --filter @object-ui/app-shell type-check (tsc --noEmit plus tsc -p tsconfig.test.json): exit 0; --listFiles counts createFormView-13099.test.ts in the test project (1). An earlier run at 13c7's parent was red, TS2339 on the test's .name reads; fixed. (3) vitest --maxWorkers=1 under the lock: the new pin plus all 99 test files naming recordFormNavigation, useActionModal or AppContent, in 3 batches: 34 files / 260 tests, 31 / 235, 35 / 338, all passed. Full packages/app-shell/ directory DECLARED TO CI (the dispatch bars whole-dir runs). (4) Ablation via objectstack scripts/ablation-replace.mjs: anchor 'if (createView) return createView;' x1 to x0, blob df4f0b15 to abecb1d2. Pin (a) red ('expected [ identity, notes ] to deeply equal [ quick ]'), (b) and (c) green, 1 failed | 2 passed. Restore: blob df4f0b151ec4b5ebaef09a6aef65424f38e6c873 == HEAD and git diff HEAD empty. Run at feb535e and again at 2e759c1, same result; the source imports relatively, so there is no dist on the path. (5) Lint narrowed: eslint --no-inline-config --format json on the 4 changed ts/tsx files gives 4 files, 0 errors. Warnings 43/15/5 on AppContent/useActionModal/recordFormNavigation, equal to their base-commit counts measured via --stdin; 0 on the new test. Population: the 4 changed files. Invariance: eslint.config.js has 0 hits for parserOptions/projectService/TypeChecked, so linting is not type-aware. (6) Battery, exit 0 each: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes, new-line-citations; node scripts/check-hand-rolled-comment-mask.mjs, check-changeset-fixed.mjs, check-changeset-presence.mjs. check:spec-floors exit 1 'no-artifact' (workspace not fully built): NOT MEASURED, recorded, not fixed. CI on the PR: in_progress at report time (6 completed / 24 in_progress).",
    "mcp_calls": "0",
    "api_writes": "2 relay writes: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft) plus POST /repos//issues/13117/assignees (baozhoutao), one stroke, read back 6783/6783 bytes identical; (2) this os-dev-report comment, POST /repos//issues/13099/comments. Plus 4 git pushes of the branch (the empty-branch probe and 3 commits). No labels written: objectui path labels belong to labeler.yml and the dispatch named none.",
    "gates": {
    "app-shell^... build": 0,
    "app-shell type-check": 0,
    "vitest new pin + 99 family files": 0,
    "eslint narrowed (4 files)": 0,
    "check:phantom-deps": 0,
    "check:self-import": 0,
    "check:unreferenced-sources": 0,
    "check:doc-example-readers": 0,
    "check:handler-key-reads": 0,
    "check:side-effects-array": 0,
    "check:element-data-source-declaration": 0,
    "check:spec-symbols": 0,
    "check:action-forward-parity": 0,
    "check:designer-field-key-parity": 0,
    "check:icon-record-names": 0,
    "check:i18n-keys": 0,
    "check:i18n-drift": 0,
    "check:i18n-designer-parity": 0,
    "check:control-bytes": 0,
    "check:new-line-citations": 0,
    "check-hand-rolled-comment-mask.mjs": 0,
    "check-changeset-fixed.mjs": 0,
    "check-changeset-presence.mjs": 0,
    "check:spec-floors": "1 (no-artifact, NOT MEASURED; pre-existing per dispatch)",
    "packages/app-shell full dir": "declared to CI"
    },
    "files_changed": [
    ".changeset/13099-create-form-view.md",
    "packages/app-shell/src/console/AppContent.tsx",
    "packages/app-shell/src/hooks/useActionModal.tsx",
    "packages/app-shell/src/utils/recordFormNavigation.ts",
    "packages/app-shell/src/utils/tests/createFormView-13099.test.ts"
    ],
    "deviations": [
    "Mechanism, not the ruling's intent: the selection reads formViews[OBJECT.create] (the served name) rather than the pointer's literal formViews.create, which no served object carries. Evidence: expandViewContainer in @objectstack/spec/ui, and the MetadataProvider.merge.test.ts pin asserting showcase_contact.create.",
    "The dispatch both asks for 'each touched package's dir under the lock' and bars whole-package-directory runs. I followed the ban and declared packages/app-shell/ to CI."
    ],
    "open_questions": [
    {
    "question": "The full-page create route (RecordFormPage, objects with editMode 'page') still selects form ?? formViews.default, so a page-mode create ignores a declared create form view. It is outside this claim's file surface. Should it follow?",
    "options": [
    "A: a follow-up one-liner, RecordFormPage formDef = selectRecordFormView(objectDef, mode), plus a pin",
    "B: leave it; no in-tree producer pairs editMode page with a create form view"
    ],
    "recommendation": "A, as a follow-up on this card's ruling. The documented model says the create form view wins for create, regardless of open mode. B leaves a split the docs do not describe; zero producers today is the only cost saving."
    }
    ],
    "out_of_scope_findings": [
    "carrier: none named · RecordFormPage page-mode create does not use the create form view (no producer: editMode appears nowhere under objectstack examples/, so no reach) · noted in the PR's Acceptance notes and the changeset, not filed · dedupe words: RecordFormPage, formViews create, editMode page, create form view"
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat ruling on the dev's report (domain:ui seat 2, session_01XVp19E1MYUTFM3dnkwz8gB)

    • Mechanism correction: accepted. expandViewContainer serves a container's formViews.create as OBJECT.create, and MetadataProvider keys formViews by that served name (the showcase_contact.create merge pin). So the selection reads formViews[OBJECT.create], with no bare-key alias. The pointer's intent ("the create form view when declared") is what matters, and the dev measured how it is served.
    • Open question (the full-page create route): A, in this PR. The documented create-vs-edit model (content/docs/ui/create-vs-edit-form.mdx) says the create form view wins for a create whatever the open mode. A page-mode create choosing the default form would be a split the docs do not describe. It is the same ruling and one call (RecordFormPage's formDef = selectRecordFormView(objectDef, flow)).
      • Claim amended: packages/app-shell/src/views/RecordFormPage.tsx joins the file surface for that selection only.
      • Add a pin: a page-mode create uses the declared create form view, and a page-mode edit is unchanged.
      • Drop the "not followed" note from the changeset and the PR's Acceptance notes.

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 13099,
    "status": "done",
    "branch": "claude/issue-13099-create-form-view",
    "pr": "#13117",
    "head": "2d6c7546faa16aacbfa4ad218a314132a1953138",
    "session": "session_01XVp19E1MYUTFM3dnkwz8gB (subagent; parent's id)",
    "premise_still_valid": true,
    "summary": "Patch round on the seat ruling 6282790090 (answer A, in this PR). RecordFormPage now selects its form view with formDef = selectRecordFormView(objectDef, mode), the same selection as the New/Edit modal, so a page-mode create renders the declared create form view (served as OBJECT.create) and a page-mode edit keeps the default form. New pin RecordFormPage.createFormView-13099.test.tsx: a real RecordFormPage mount over served rows through the real merge. The changeset's page-route note is dropped, and the PR body's matching Acceptance note is removed (one PATCH, read back identical). As the ruling scoped it, only the selection moved: the page's separate subforms read (form.subforms, else formViews.default.subforms) is unchanged. So a page-mode create with a create view still takes the default form's subforms, while the modal takes the create view's. That is noted here and not changed.",
    "tests": "All at HEAD 2d6c754, from a fresh worktree (pnpm run setup). (1) pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build && pnpm --filter @object-ui/app-shell type-check: VERDICT command-exit 0. (2) vitest --maxWorkers=1 under the lock: createFormView-13099.test.ts plus the RecordFormPage family (all 26 test files naming RecordFormPage, the new pin among them): 27 files / 329 tests passed. (3) Ablation A, ablation-replace.mjs on recordFormNavigation.ts: anchor 'if (createView) return createView;' x1 to x0, blob df4f0b15 to abecb1d2. Both pins red: (a) 'expected [ identity, notes ] to deeply equal [ quick ]', and the page create 'expected tabbed:identity,notes to be simple:quick'. Page edit and the controls stayed green: 2 failed | 4 passed. Restored blob df4f0b151ec4b5ebaef09a6aef65424f38e6c873 == HEAD, git diff HEAD empty. (4) Ablation B, the page wiring, on RecordFormPage.tsx: anchor 'selectRecordFormView(objectDef as any, mode) ?? {};' replaced with the old form ?? formViews.default read, blob b10a9e6a to 656875d3. The page-mode create pin went red; edit and the control stayed green: 1 failed | 2 passed. Restored blob b10a9e6a98b14c6cf95af7281be6e661425826b8 == HEAD, diff empty. (5) eslint --no-inline-config narrowed to RecordFormPage.tsx and the new test: 0 errors. Warnings 16 against 17 at base on RecordFormPage, 0 on the test. Not type-aware, as before. (6) check:control-bytes, new-line-citations, unreferenced-sources, self-import, check-changeset-presence, check-changeset-fixed and check-hand-rolled-comment-mask all exit 0. The earlier round's full battery and the 99-file family stand from 2e759c1; packages/app-shell/ dir declared to CI.",
    "mcp_calls": "0",
    "api_writes": "This round, 2 relay writes: issue_patch PATCH /repos//issues/13117 (PR body, the note removed, 6159/6159 bytes identical, footer kept), and this os-dev-report comment, POST /repos//issues/13099/comments. 1 git push (2d6c754). Earlier round: pr_create plus assignee, 1 report comment, 4 pushes.",
    "gates": {
    "app-shell^... build + type-check": 0,
    "vitest pin + RecordFormPage family (27 files)": 0,
    "ablation A (resolver)": "red as intended, restored",
    "ablation B (page wiring)": "red as intended, restored",
    "eslint narrowed": 0,
    "check:control-bytes": 0,
    "check:new-line-citations": 0,
    "check:unreferenced-sources": 0,
    "check:self-import": 0,
    "check-changeset-presence.mjs": 0,
    "check-changeset-fixed.mjs": 0,
    "check-hand-rolled-comment-mask.mjs": 0,
    "packages/app-shell full dir": "declared to CI"
    },
    "files_changed": [
    ".changeset/13099-create-form-view.md",
    "packages/app-shell/src/console/AppContent.tsx",
    "packages/app-shell/src/hooks/useActionModal.tsx",
    "packages/app-shell/src/utils/recordFormNavigation.ts",
    "packages/app-shell/src/utils/tests/createFormView-13099.test.ts",
    "packages/app-shell/src/views/RecordFormPage.tsx",
    "packages/app-shell/src/views/RecordFormPage.createFormView-13099.test.tsx"
    ],
    "deviations": [
    "The new RecordFormPage pin is a .tsx test using vi.mock, the same harness as the sibling RecordFormPage.*.test.tsx files. The dispatch's vi.mock caution names .test.ts files only."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none named · page-mode create with a create view still reads the default form's subforms, because RecordFormPage's subforms line was outside the ruled 'selection only' scope (no producer pairs editMode page, a create view and subforms) · noted, not filed · dedupe words: RecordFormPage subforms, create form view, editMode page"
    ]
    }


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #13117 @ 2d6c7546faa16aacbfa4ad218a314132a1953138 · domain:ui seat 2 (session_01XVp19E1MYUTFM3dnkwz8gB) · ruling B′ (objectstack-ai/objectstack#24692, 6281834942)

    • Clause-②: no. selectRecordFormView, resolveFormViewLayout and RecordFormPage are not on @object-ui/app-shell's entry. No contract review is owed, and the seat read both reports.
    • Landed scope:
      • one selection, selectRecordFormView(objectDef, flow): a create uses the object's declared create form view, else form ?? formViews.default, and an edit is unchanged;
      • the console's New/Edit modal and useActionModal pass the flow from the same expression that sets the ModalForm mode;
      • under seat ruling 6282790090, the full-page create route (RecordFormPage, editMode: 'page') uses the same selection.
    • Mechanism correction (accepted): expandViewContainer serves a container's formViews.create as OBJECT.create, so the selection reads formViews[OBJECT.create]. The pins build fixtures from served rows through the real merge.
    • Pins:
      • the modal create uses the create view, the modal edit is unchanged, and the control (no create view) is unchanged;
      • a real RecordFormPage mount shows page-mode create using the create view and page-mode edit unchanged.
      • Ablation A (resolver) turned both create pins red, and ablation B (page wiring) turned the page create red. Each restore was proven by blob.
    • Changeset: @object-ui/app-shell patch.
    • CI: all 36 check-runs completed on the head, 33 passed and 3 skipped.
    • Path fork: no governed path, 316 changed lines. Seat landing.
    • Findings: Acceptance notes. A page-mode create with a create view still reads the default form's subforms (the page's separate subforms line, outside "selection only"). No producer pairs editMode: 'page', a create view and subforms. Per the ruling, mode / formView plumbing and the p1-spec-alignment parity row wait for the spec release that retires them.

    Next: ready + auto-merge; landing is verified by content on main.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB

    pm:dispatched is removed.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions