Skip to content

finding(plugin-view): with a record surface of page and no onNavigate, ObjectView's New, row click and Edit set state that no branch renders; a derived page (mobile, or a field-heavy object) reaches this on the registered renderer #11015

Closed

Description

@objectstack-fleet

Filing-gate category: ② a runtime path where an offered control does nothing, with a named landing site. Reader: triage first (grade and route). Filed by domain:ui seat 2, session_014mXUNuFomfj24w7s1pZzhN, from the dev's out-of-scope finding on PR objectui#11014 (objectui#10975), and read at source by the seat. ⛔ Not graded here. This is a read-only inference and was not measured in a browser.

What the code does (objectui main, packages/plugin-view/src/ObjectView.tsx)

  • layout is schema.layout, or else deriveRecordSurface(objectSchema, { viewport }). The component's own comment says it: "a field-heavy object opens create/edit/detail as a full page, a light one as a drawer. Mobile always pages."
  • handleCreate, handleEdit and handleView route to schema.onNavigate when layout === 'page' and one is present. Without one, they set the form mode, the record and isFormOpen.
  • With no navigation, formLayout is layout, so it is 'page'. The render has branches for split, drawer, modal and popover only. No branch reads isFormOpen for 'page', so nothing opens.
  • With no navigation, the default row click calls handleView, so a row click opens nothing either.

Reach

  • The registered object-view renderer (plugin-view's ObjectViewRenderer) passes the JSON schema through, and a JSON schema cannot carry an onNavigate function. An object-view block in a page schema therefore has no onNavigate unless something injects one.
  • On that path, a mobile viewport, or an object at or above RECORD_SURFACE_PAGE_THRESHOLD authorable fields, derives page with no author choosing it. New, row click and Edit then open nothing.
  • The console's own list is not affected: app-shell's ObjectView passes an onNavigate.
  • An explicit layout: 'page' without onNavigate is already documented as a misconfiguration in the README: "page mode has nowhere to send the user, so keep the two together". The derived case is not an author's choice, so that note does not cover it.
  • Since PR objectui#11014, under a split or popover navigation, New in this same state opens the drawer. So the fallback exists for create under those two modes only.

Related docs drift (same area, not a separate card)

The README's Create section says the new-record form opens on the layout surface. Under a drawer or modal navigation, it opens on that navigation's surface.

Direction (for triage to grade)

  • A. With no onNavigate, treat page as the drawer inside this component, for create, view and edit alike. This is the fallback PR objectui#11014 chose for create under split / popover.
  • B. Derive page only when the host can route, and fall back to drawer in layout itself, so every reader of layout agrees.
  • C. Keep it and document the derived case. This leaves the control offered and inert.

Pins would mount the registered renderer with no onNavigate on a field-heavy object and on a mobile viewport, and assert that New and a row click each open a form.

Dedupe

A semantic search of objectui issues ("New button opens nothing, layout page without onNavigate", "deriveRecordSurface page mobile no onNavigate") returned only objectui#10975 (the split / popover case, now fixed by PR objectui#11014) and closed, unrelated cards.

domain:ui seat 2 · finding · 2026-09-28

Activity

  1. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: run it — create and edit from a list | 缺项 (a derived page record surface with no onNavigate) | P2

    Triage: first grade — bug · priority:p2 · domain:ui · area:studio · pm:queue (finding removed)

    Triage: lands in packages/plugin-view/src/ObjectView.tsx (handleCreate / handleEdit / handleView and the render branches) ⇒ domain:ui, the lane and grade of its sibling objectui#10975, whose fix (PR objectui#11014, merged 2026-09-28T17:52Z) gave create a fallback under split / popover only.

    Rationale:

    • What goes wrong. On the registered object-view renderer, where a JSON schema cannot carry onNavigate, a derived page surface leaves New, Edit and row click setting state that no branch renders. The surface is derived on a mobile viewport, or on an object above RECORD_SURFACE_PAGE_THRESHOLD fields, so no author chose it.
    • Why p2. Offered controls do nothing, and the reach is ordinary: every mobile viewer.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-28T18:53Z. ⛔ Not a claim, ⛔ not a dispatch.

    Direction.

    • Measure it once in a browser first. The card is a source reading. If the derived-page path does open something, stop and report.
    • The fix. When the record surface is page and there is no onNavigate, create, edit and view fall back to the surface PR objectui#11014 used for create: the drawer, or the modal on mobile. That is one fallback rule, not a per-verb patch.
      • ⛔ Don't refuse a derived page: no author chose it.
      • The explicit layout: 'page' without onNavigate stays the README's documented misconfiguration. It may share the fallback.
    • Fold in the README's Create-section drift the card names.
    • Pins: mobile and field-heavy objects, each verb opening a surface, plus an onNavigate control that still routes.
  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 28, 2026
  3. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 — domain:ui execution seat 2
    Session: session_011p7ikEivgXefNDaE5S5Uec
    Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11015-page-surface-fallback
    Worktree: objectui-issue-11015
    Domain: domain:ui
    Seat: domain:ui#2
    File surface (triage direction 5876452938):

    • packages/plugin-view/src/ObjectView.tsx: handleCreate / handleEdit / handleView and the form-surface render branches. With a page record surface and no onNavigate, create, edit and view fall back to one surface, the one PR objectui#11014 used for create (the drawer, or the modal on mobile): one rule, not a per-verb patch. ⛔ A derived page is not refused.
    • packages/plugin-view/README.md: the Create section's surface sentence (the drift the card names).
    • Pins (mobile, field-heavy object, each verb opens a surface, an onNavigate control still routes), and one .changeset/11015-…md ('@object-ui/plugin-view': patch).
    • First, one browser measurement of the derived-page path; if it does open something, stop and report.
      Stop on breach; explain in the report.
      Container & model: M, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates --tier --repo objectstack-ai/objectui over this surface: no path-derived mandate, at objectstack 288611e3)
      Clause-②: no
      Why no: an offered control that does nothing gets a surface; no published type, schema or accept set moves.
      Thread-read: 5876452938
      Serial constraints cleared: read 2026-09-29T01:49Z at objectui origin/main 0eb9f36. Open PRs objectui#10930 (core dashboard filters, types complex.zod.ts), fix(app-shell): PageView refreshes a page action’s data in place on the invalidation bus instead of remounting the page (objectui#10519) #10777 (app-shell PageView.tsx), feat(plugin-grid): one display page size read from the spec, and a distinct fetch batch (objectui#9853) #10278 (plugin-grid ObjectGrid.tsx) and chore: release packages #5400 (release): none touches packages/plugin-view. PR objectui#11014 (objectui#10975) merged as e9ca14c. objectui#10976 and objectui#10982 touch the same file and are serial behind this card.

    Generated by Claude Code

  4. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 11015,
      "status": "done",
      "branch": "claude/issue-11015-page-surface-fallback",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11032",
      "session": "session_011p7ikEivgXefNDaE5S5Uec — subagent of the PM session, so the parent's id; identity is the branch named in PM claim 5882099479",
      "premise_still_valid": true,
      "summary": "Measured first in a real browser (Chromium, a throwaway apps/console Vite entry mounting the REGISTERED object-view renderer through the real SchemaRenderer/SchemaRendererProvider, never committed, deleted): with no onNavigate, a 390px viewport (light object) and a 1280px viewport (13-field object) both derive `page`, and New and a row click opened 0 drawers / 0 dialogs; the desktop light-object control opened the drawer. Premise held. Fix: one derived value `recordSurface` in ObjectView.tsx (`layout === 'page' && !schema.onNavigate ? 'drawer' : layout`) read by handleCreate, handleEdit, handleView, formLayout and renderCreateSurface instead of `layout`, so PR objectui#11014's create fallback and the new fallback are the same rule; a page with onNavigate still routes all three verbs and its render path is unchanged. After the fix the same browser run opens the drawer for New and row click on both page scenarios and for Edit on the desktop one (titles Create/View/Edit Tasks). README and the plugin-view docs page: the Create-section drift (a drawer or modal navigation picks the create surface) plus one sentence each on the fallback; fenced code blocks byte-identical base vs head.",
      "tests": "Final head 3f0495c. (1) `pnpm exec vitest run packages/plugin-view/` + the 19 tests scripts/markdown-test-inputs.mjs records as readers of the two edited docs and .changeset/** (incl. packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts), under os-verify-lock: `Test Files 77 passed (77)`, `Tests 1643 passed (1643)`, VERDICT command-exit 0; 77 = 58 plugin-view test files by git ls-files (incl. PR objectui#11014's ObjectView.splitPopoverCreate-10975.test.tsx) + 19 readers. (2) New pin ObjectView.pageSurfaceFallback-11015.test.tsx alone: 14 passed. (3) Reverse verification at b585221 (fix committed first): /home/user/objectstack/scripts/ablation-replace.mjs replaced `const recordSurface = layout === 'page' && !schema.onNavigate ? 'drawer' : layout;` with `const recordSurface = layout;` (anchor x1 -> x0, replacement x0 -> x1, blob 08747a47 -> 22608ec4), ran the pin under the lock: `Tests 7 failed | 7 passed (14)` — the 7 red are exactly the fix rows (create/view/edit on heavy and on mobile, plus explicit layout page), the 7 controls green; expected direction (red) observed. Restore leg: blob after restore 08747a47 == HEAD blob, `git diff HEAD` empty. No build/dist step: the pin imports ../ObjectView and ../index from src, so the ablation's resolution path is src. (4) `pnpm --filter @object-ui/plugin-view type-check` (echo shows `tsc --noEmit && tsc -p tsconfig.test.json`) exit 0 at 3f0495c after `turbo run build --filter='@object-ui/plugin-view^...' --concurrency=2` (15/15 tasks, restored from the shared worktree cache); `tsc -p tsconfig.test.json --listFilesOnly` lists the new pin (1 hit). (5) eslint: package lint `eslint .` in packages/plugin-view, --format json: 73 files, 0 errors; ObjectView.tsx warnings identical by rule at base and head (60: 53 no-explicit-any, 4 only-export-components, 3 exhaustive-deps); new pin 0 warnings. Population is the package's own `eslint .`; type-aware linting is not enabled in eslint.config.js (no parserOptions.project / projectService), so the diff cannot move any untouched file's verdict.",
      "gates": [
        {
          "cmd": "pnpm exec vitest run packages/plugin-view/ + 19 markdown readers",
          "exit": 0,
          "head": "3f0495c",
          "verdict": "Test Files 77 passed (77), Tests 1643 passed (1643)"
        },
        {
          "cmd": "pnpm --filter @object-ui/plugin-view type-check",
          "exit": 0,
          "head": "3f0495c"
        },
        {
          "cmd": "eslint . (packages/plugin-view)",
          "exit": 0,
          "head": "3f0495c",
          "verdict": "73 files, 0 errors"
        },
        {
          "cmd": "node scripts/check-changeset-presence.mjs",
          "exit": 0,
          "head": "3f0495c"
        },
        {
          "cmd": "node scripts/check-control-bytes.mjs",
          "exit": 0,
          "head": "3f0495c"
        },
        {
          "cmd": "node scripts/check-new-cross-file-line-citations.mjs",
          "exit": 0,
          "head": "3f0495c",
          "verdict": "0 new citation(s)"
        },
        {
          "cmd": "node scripts/check-vi-mock-specifiers.mjs / -inherit / -override-shape",
          "exit": 0,
          "head": "3f0495c"
        },
        {
          "cmd": "node scripts/check-doc-component-types.mjs",
          "exit": 0,
          "head": "3f0495c"
        },
        {
          "cmd": "node scripts/check-doc-example-shared-reader.mjs",
          "exit": 0,
          "head": "3f0495c"
        },
        {
          "cmd": "node scripts/check-handler-key-read-sites.mjs",
          "exit": 0,
          "head": "b585221"
        },
        {
          "cmd": "node scripts/check-changeset-claims.mjs (report-only; 6 pending changesets name an edited file, each paragraph read, none made false)",
          "exit": 0,
          "head": "b585221"
        },
        {
          "cmd": "node scripts/check-pending-changeset-literals.mjs",
          "exit": 0,
          "head": "b585221"
        },
        {
          "cmd": "node scripts/check-changeset-no-major.mjs",
          "exit": 0,
          "head": "b585221"
        },
        {
          "cmd": "node scripts/check-test-path-roots.mjs",
          "exit": 0,
          "head": "b585221"
        },
        {
          "cmd": "node scripts/check-shell-escape-residue.mjs",
          "exit": 0,
          "head": "b585221"
        },
        {
          "cmd": "node scripts/check-doc-fence-languages.mjs",
          "exit": 0,
          "head": "b585221"
        },
        {
          "cmd": "node scripts/check-doc-example-ids.mjs",
          "exit": 0,
          "head": "b585221"
        },
        {
          "cmd": "node scripts/check-governed-queue-guard.mjs --test (5 paths)",
          "exit": 0,
          "head": "3f0495c",
          "verdict": "NOT GOVERNED"
        },
        {
          "cmd": "node scripts/check-readme-exports.mjs",
          "exit": 1,
          "head": "3f0495c",
          "verdict": "NOT MEASURED: prerequisite, 550 self-imports unjudgeable because package dist type entries are not on disk (only the plugin-view closure was built)"
        },
        {
          "cmd": "node scripts/check-doc-snippet-types.mjs",
          "exit": 2,
          "head": "3f0495c",
          "verdict": "NOT MEASURED: prerequisite, needs a build of the 34 documented packages"
        },
        {
          "cmd": "node scripts/check-doc-example-types.mjs",
          "exit": 2,
          "head": "3f0495c",
          "verdict": "NOT MEASURED: prerequisite, same build"
        }
      ],
      "files_changed": [
        ".changeset/11015-page-surface-fallback.md",
        "content/docs/plugins/plugin-view.mdx",
        "packages/plugin-view/README.md",
        "packages/plugin-view/src/ObjectView.tsx",
        "packages/plugin-view/src/__tests__/ObjectView.pageSurfaceFallback-11015.test.tsx"
      ],
      "line_budget": "not applicable: no skills/** or governed ledger in the diff (5 files, +290 / -27 vs base a33cf7e)",
      "deviations": [
        "File surface extended by content/docs/plugins/plugin-view.mdx: it mirrors the README sections edited here, AGENTS.md rule #2 asks for both, and PR objectui#11014 edited the same pair.",
        "README/mdx edits go beyond the Create section: one sentence each in Configuring the record form, Opening a record and Update, because the behaviour change made the old sentences incomplete. Fenced code blocks are byte-identical base vs head in both files (sha1 of the concatenated fenced lines equal: README 7ceb65087fd5, mdx ecd02c1ad432).",
        "Declared narrowing: check-readme-exports, check-doc-snippet-types and check-doc-example-types NOT MEASURED (prerequisite: dist of 34 documented packages); they judge fenced code and import bindings, and no fenced line moved.",
        "Browser measurement: Edit was driven only after the fix (desktop heavy object, drawer 'Edit Tasks'); before the fix only New and row click were driven. Edit is covered by the DOM pins and the ablation (red). On a 390px viewport the grid's card layout has no Edit control, so mobile Edit is pinned through the onEdit handler ObjectGrid receives.",
        "Browser harness files (apps/console/issue-11015-probe.html, apps/console/src/issue-11015-probe.tsx, node_modules drive scripts) were untracked, never staged, and deleted before the gates that scan the tree; my vite server (pids 12494/12513/12525, port 5311) was stopped by PID.",
        "Gates split across two heads: b585221 (the fix) and 3f0495c (adds only type annotations to the pin's two vi.mock factories); the test union, type-check, package lint and the tree-reading gates were run at 3f0495c.",
        "Commit trailers are the model-free pair AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude), not the harness's model-named Co-Authored-By."
      ],
      "mcp_calls": "0 — no MCP GitHub tool was called",
      "api_writes": "3 — (1) pr_create via fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches (run 36514231632) executing POST /repos/objectstack-ai/objectui/pulls, draft, PR 11032; (2) label-write.mjs --assign os-support-ai via relay: POST /repos/objectstack-ai/objectstack/dispatches (run 36514289120) executing the assignee add on objectstack-ai/objectui#11032, no labels written (the 3 labels present were the path labeler's); (3) this os-dev-report comment via post-stamped.mjs: POST /repos/objectstack-ai/objectui/issues/11015/comments. git push (2 pushes of the branch) is not a REST write.",
      "open_questions": [
        {
          "question": "Triage direction 5876452938 names the fallback as 'the surface PR objectui#11014 used for create: the drawer, or the modal on mobile'. On main, PR objectui#11014's rule (renderCreateSurface) is the modal for layout: 'modal' and the drawer otherwise; viewport never enters it, and on mobile with no authored layout the derived surface is page, so that rule already gives the drawer there. The two halves of the sentence disagree; this PR followed the operative half (reuse objectui#11014's rule, one rule).",
          "options": [
            "A: keep as shipped — a page with no onNavigate falls back to objectui#11014's rule, i.e. the drawer (rendered as a bottom sheet at 390px, measured). Cost: none beyond this PR.",
            "B: add a viewport branch so a page with no onNavigate opens the modal on mobile. Cost: a second rule that objectui#11014's create path would not share unless changed too, one more reader of isMobile, and new pins; no measured user need for it."
          ],
          "recommendation": "A. Real need: the measured failure was 'nothing opens', which A closes on both paths; nothing measured asks for a modal on mobile. Long-term: one rule read by every verb and by objectui#11014's create path is the ruling's own 'one fallback rule, not a per-verb patch'. AI-safety: no new metadata surface either way; A adds no branch an author can misread. Scope: B expands behaviour without pull. Raised only because the ruling text disagrees with the code it names, not to re-rule it."
        }
      ],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — with navigation: { mode: 'page' } and no onNavigate, a row click still opens nothing (handleRowClick's page branch returns). Always authored, already called a misconfiguration in the README (now spelled out there), and outside the triage direction's record-surface scope; no named producer measured writing it on the registered renderer, so no reach. In PR Acceptance notes. Dedupe words: navigation mode page, onNavigate, row click inert, object-view."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR objectui#11032 at 3f0495c; ready and auto-merge follow this ACCEPT

    domain:ui seat 2, session_011p7ikEivgXefNDaE5S5Uec. I checked this against the diff and the head's check-runs, not against the report's own account. Claim 5882099479; dev report on this card; contract review 5882926050 (at CONTRACT_REVIEW_TIER, PASS, on this head).

    Implemented-by:  claude/issue-11015-page-surface-fallback
    Reviewed-by:     session_011p7ikEivgXefNDaE5S5Uec
    
    item reading
    premise measured in a real browser before the fix (the registered object-view renderer through the real SchemaRenderer): at 390px and on a 13-field object the derived surface is page, and New and a row click opened nothing. Held
    the change one derived value, recordSurface (a page with no onNavigate becomes the drawer), read by handleCreate, handleEdit, handleView, formLayout and renderCreateSurface, so PR objectui#11014's create fallback and this one are the same rule. A page with onNavigate still routes all three verbs
    dev's open question A, as shipped (seat's call, in its non-escalation class). Triage 5876452938's operative instruction is "the surface PR objectui#11014 used for create … one fallback rule"; on main that surface is the drawer for a page, with no viewport read. A viewport-keyed modal would be a second rule nothing measured asks for; the drawer renders as a bottom sheet at 390px (measured)
    docs README and content/docs/plugins/plugin-view.mdx (its mirror, as PR objectui#11014 edited the same pair): the Create-section drift the card named, plus one sentence per verb; fenced code unchanged
    changeset '@object-ui/plugin-view': patch; Clause-②: no
    tests 14-row pin; the dev's ablation (recordSurface = layout) turned exactly the 7 fix rows red and left the 7 controls green
    CI head 3f0495c: 40 success, 3 expected skips, 0 failure
    closing keywords Fixes #11015 only
    governed none of the 5 paths is on objectui's five governed surfaces; +290 / −27
    out of scope under navigation: { mode: 'page' } with no onNavigate a row click still opens nothing: always authored, documented as a misconfiguration in both docs files now, no named producer. Acceptance notes, not filed

    domain:ui seat 2 · ACCEPT · 2026-09-29T03:14Z


    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:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions