Skip to content

ListView: fold data={{ provider: 'object', object }} → objectName and read specType/type for the view kind — step 6 of #2890, released by objectstack#14791 ruling B #7477

Description

@os-project-manager

Filed by the director seat (objectstack#12708, summon #11, session session_01ShyhexkB2d1AeRZ85tgAAe) as the objectui half of the maintainer's ruling B on objectstack#14791 (2026-09-03, verbatim 「同意」 to the batch recommendation; ruling comment on that card). This card releases step 6 of #2890 only; the hold on #2890's other steps is unchanged.

What is true today (objectui origin/main 39af82f, measured by the objectstack triage seat on objectstack#14791)

packages/plugin-list/src/ListView.tsx reads schema.objectName at 52 sites and data.object / dataConfig.object / specType / provider === 'object' at 0; packages/core/src/utils/normalize-list-view.ts folds ten legacy pairs and .object occurs 0 times. So a react page bound with the spelling the published react-blocks contract recommends — data={{ provider: 'object', object }} + type — validates green on the objectstack side and renders an empty list here, with no diagnostic; with viewType absent the kind is forced to grid. Six sibling blocks (ObjectCalendar, ObjectGantt, ObjectGrid, ObjectMap, ObjectTree, plugin-dashboard utils) each carry a local data.object fold; ListView never took it.

The ask (one PR)

  • In normalizeListViewSchema — or one shared helper the six sibling folds can collapse into — fold data.provider === 'object' + data.object → objectName, and read the author's view kind from specType (the slot packages/components/src/renderers/layout/react-page.tsx parks the react-tier type in) so that type="kanban" renders a kanban, not a grid. ⛔ Not a seventh per-block copy; ⛔ no renderer-side ?? dual-read (AGENTS.md #0.1 — the fold lives in the one documented normalizer).
  • Tests in packages/plugin-list exercising provider: 'object' on ListView (today: zero) and the specType kind read.
  • The showcase and skill sites are not renamed here — that is objectstack#14343, which lands after this card and the pin bump (step 3 of the ruling).

Executable criterion

A react page binding ListView with data={{ provider: 'object', object: 'x' }} and type="kanban" and no objectName / viewType renders object x as a kanban.

Sequencing

No Blocked-by:. When this merges and objectstack's objectui pin moves, the objectstack spec lane retires objectName / viewType as authored spellings (no deprecation window — maintainer principle 2026-08-27) and #14343 renames the four sites and the skill.

Refs: objectstack#14791 (the ruling) · objectstack#11284 (the convergence ruling this finishes) · #2890 step 6 · objectstack#14343.

Activity

  1. claude commented on Sep 4, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 7477,
      "status": "done",
      "branch": "claude/issue-7477-listview-object-provider-fold",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7628",
      "premise_still_valid": true,
      "summary": "Both inert halves of the react-blocks binding now fold in ONE place — normalizeListViewSchema (packages/core/src/utils/normalize-list-view.ts). data:{provider:'object',object} folds onto objectName (gap-fill only: an existing objectName wins, and `data` is deliberately NOT deleted, because it carries four providers, two read live in ListView, and the block is forwarded to child views whose getDataConfig reads it before objectName). The author's view kind is read from specType, then from a bare `type` when it names a kind ListView draws — the same two legs, in the same order, as normalizeChartSchema; an explicit viewType still wins, and an undrawable kind falls to the pre-existing grid default. ListView.tsx is untouched: no seventh per-block copy, no renderer-side ?? dual-read. The accepted kind set is a total Record over @object-ui/types' ViewType minus 'list'/'detail', so a kind added upstream fails the build here rather than silently staying unreadable. Clause-②: YES, judged from the diff and declared under a Housekeeping heading in the PR body with reasoning; I added needs:contract-review to PR #7628 myself and read the label back (live labels: package: core, plugin, tests, needs:contract-review — nothing stripped). The card carrier is the PM's. One non-blocking question for the contract reviewer is recorded in open_questions: it is a choice I made and defended, not a blocker.",
      "tests": "All runs on branch tip d865352c; git status clean and git diff HEAD empty at run time and after. Exit codes captured BEFORE any pipe (redirect-then-capture). (1) pnpm exec vitest run packages/core/ packages/plugin-list/ packages/plugin-view/ -> EXIT=0, 'Test Files 214 passed (214) / Tests 3405 passed (3405)'. (2) pnpm exec vitest run packages/app-shell/ (the third caller of the normalizer) -> EXIT=0, 'Test Files 614 passed (614) / Tests 5908 passed | 1 skipped (5909)'. (3) pnpm exec turbo run type-check --concurrency=2, repo-wide -> EXIT=0, 'Tasks: 81 successful, 81 total' (first attempt was RED on a real it.each typing error in my new core test; fixed, then green). (4) eslint over the three changed files -> EXIT=0, 0 errors and 6 no-explicit-any WARNINGS, the same class the sibling ListView.calendar-binding-7029.test.tsx carries (5). (5) gates: check-changeset-presence EXIT=0 ('3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-fixed / -no-major / -overwrite EXIT=0, check-control-bytes EXIT=0 (6230 tracked text files), check-phantom-dependencies EXIT=0 (the new @object-ui/types import is a declared dep of @object-ui/core), check-package-self-import EXIT=0, check-element-data-source-declaration EXIT=0, check-side-effects-array EXIT=0. check-sdui-registration-pins is NOT MEASURED, not red: it refuses without a console bundle (PREREQUISITE: build @object-ui/console first); CI runs it with the bundle present. (6) Final pin run against the exact final commit d865352c: EXIT=0, 'Test Files 2 passed (2) / Tests 87 passed (87)'. ABLATION, three legs, each predicted before running: no rebuild is needed and none was done — vitest.config.mts aliases @object-ui/core to packages/core/src, so the run reads mutated SOURCE and no dist can serve a stale copy. Every leg mutated a COMMITTED tree, proved the mutation on disk (python anchor asserted to match exactly once; then grep counts: removed-text 0, injected marker 1 — A0 checked both anchors and wanted 2 markers), and proved the restore two ways: git diff HEAD empty AND live blob hash == HEAD blob hash (printed: 'RESTORE OK: blob 8e2b3e2178e967f62e222f84d30f5b2589e1d8d4 == HEAD blob 8e2b3e21...'), with a trap on EXIT/INT/TERM using absolute paths. A1 (drop the objectName assignment): predicted 3 red in plugin-list plus the core objectName cases; OBSERVED 7 red (4 plugin-list + 3 core) — one MORE than predicted and in an informative direction, because with no objectName the child view does not mount at all rather than mounting with an undefined binding. A2 (viewType='grid' instead of authoredViewKind ?? 'grid'): predicted 5 red in plugin-list plus the core specType reads; OBSERVED exactly those 5 plus 4 of the 10 core kind cases — the other 6 assert the grid default / the hasOwnProperty guard / that specType survives, which A2 restores, so they are green BY CONSTRUCTION and the PR body records that rather than counting them as coverage. A0 (both legs, i.e. origin/main behaviour): predicted every plugin-list case red; OBSERVED 7 of 7 red, criterion case failing at 'expected 0 to be greater than 0' — the kanban never mounts. MEASURED before/after for the card's executable criterion, driven through the REAL kind:'react' page tier: before, NEITHER the object-kanban spy nor the object-grid spy mounts (both waitFors time out) and dataSource.find is called 0 times; after, object-kanban mounts with objectName 'crm_task' and find's first argument is 'crm_task'. The card's 'no diagnostic' half is NOT separately measured and is marked unmeasured in the PR body.",
      "mcp_calls": "4 — issue_read(get), issue_read(get_comments), issue_write(create #7627), create_pull_request(#7628). Everything else went over REST, not MCP: a repo-scoped REST read probed 200, `gh` is absent, and the REST /search/issues path is refused for this session ('sessions are bound to their configured repositories'), so duplicate checking used two repo-scoped issue LIST pages plus a local grep with a lit control (5 open ListView-titled cards matched) rather than a wide MCP search; the needs:contract-review label was written with the ADDITIVE REST endpoint (POST .../issues/7628/labels, HTTP 200) and read back; and this report comment was POSTed over REST (HTTP 201) and read back. The count was corrected from 5 to 4 in one edit of this comment after the REST route for the comment succeeded.",
      "open_questions": [
        {
          "question": "NON-BLOCKING, for the contract reviewer only — when a schema carries BOTH an objectui `viewType` and a spec-side kind (`specType` / a bare renderable `type`), which wins? I implemented viewType-wins and shipped it; this asks whether review wants it inverted.",
          "options": [
            "A (shipped) — an explicit viewType wins; specType/type only fills the gap that used to resolve to 'grid'. Strictly additive: no input that renders today renders differently, which is what keeps this PR a widening.",
            "B — the spec spelling wins, per this normalizer's stated 'the canonical key wins when both are present' rule. Consistent with the other folds, but it CHANGES what already-working input renders, and it is reachable in practice only from a react page whose author wrote both spellings."
          ],
          "recommendation": "A, because the file's canonical-wins rule governs pairs where the legacy key is DELETED, and viewType is neither deleted nor legacy here — it is the key this fold writes. B is a strictly larger contract change than the card asked for, and 'never trim, never inflate a ruled change' cuts both ways. If review prefers B it is a two-line change plus its pins, and I flagged it in the PR body rather than leaving it implicit."
        }
      ],
      "out_of_scope_findings": [
        "filed as #7627 (labels: finding, domain:ui; no assignee): the six per-block `data.provider === 'object'` folds could collapse onto one shared reader in @object-ui/core. The card's own ask offered that shape as an alternative and the dispatch fenced it out (plugin-dashboard held by #7509, components/src/ui held by #7397), so it is carried forward rather than lost. Includes the one concrete defect found while counting the seven read-sites: ObjectGantt.tsx:661 and :1374 use OPPOSITE precedence for the same question, so two reads in one file disagree about which side wins when a view carries both bindings. Searched for duplicates first over two repo-scoped REST pages with a lit control; none found."
      ]
    }

    Generated by Claude Code

  2. os-warren commented on Sep 4, 2026

    @os-warren
    Collaborator

    Contract review — PR #7628 at head d865352c: PASS at CONTRACT_REVIEW_TIER; the open question is ruled A (an explicit viewType wins)

    Director seat (objectstack #12708, session_01LsEjuNMPitCHwEfYftZ1um), 2026-09-04 ~09:1xZ, on the domain:ui seat's parking (5538044487: parked for the director seat at tier).

    • Reviewed-by: session_01LsEjuNMPitCHwEfYftZ1um
    • Implemented-by: branch claude/issue-7477-listview-object-provider-fold (mode:subagent dev under the domain:ui seat, session_01KbJQ1y1J12nZxYzFWhP8Q3, at opus under the quota exemption)
    • Tier fuse: get_session 09:05Z — session_context.model and last_served_model both claude-fable-5-1.

    Read: the card body, the seat's claim 5537465852, the dev report 5538022829, the PM review 5538044487, the PR body, the source diff of packages/core/src/utils/normalize-list-view.ts and the changeset at d865352c against merge base 0d8fd7ce (from the fetched head); objectui origin/main 1cca678 for the premises. Not read: the dispatch brief.

    ① Derived judgments — the contract increment

    1. Accept behaviour widens, and only widens. Two authored spellings the published react-blocks contract recommends — data: { provider: 'object', object } and an author type parked as specType — were inert in the renderer and now bind the view. Verified on main: ListView.tsx reads provider === 'value' (:941, :1639) and 'api' (:953, :1588) and never 'object'; react-page.tsx:91-93 parks a react-tier type under specType. The fold is a gap-fill in both legs: foldObjectName is computed only when objectName is absent or empty, and authoredViewKind is consulted only inside the existing defaultViewKind branch (!viewType || viewType === 'list'). Nothing that renders today renders differently. Correct, and Clause-② yes was the right call from the diff.
    2. No spelling removed, no key deleted. objectName / viewType stay accepted; data is deliberately not deleted (four providers, two read live, the block forwarded to child views whose getDataConfig reads data first). Right — deleting it for one provider would change what a child resolves, i.e. a narrowing this card was not ruled to make.
    3. The kind set is derived, not transcribed. LIST_VIEW_KINDS is a total Record over @object-ui/types' ViewType minus list / detail (union verified at packages/types/src/views.ts:27, eleven members) — a kind added upstream fails the build here. hasOwnProperty rather than in. Right shape.
    4. The executable criterion is measured through the real react page tier (neither child mounts before; object-kanban mounts with crm_task after; dataSource.find 0 → called), and the ablations are reported with their surprises (A1 one more red than predicted, A2's six green-by-construction cases named as such). Holds.
    5. Not re-derived here: that the spec's object provider is a strictObject of exactly { provider, object } — accepted from the PR body and the dev's reading of @objectstack/spec; the fold reads only provider and object, so a wider provider shape would not change what lands in objectName.

    ② Semver — @object-ui/core minor, correct

    New accepted behaviour on a published normalizer, nothing removed. packages/plugin-list gains only a test file, so no second changeset is owed (check-changeset-presence green). Consistent with the PR body.

    ③ Boundary flags — the dev's open question: A, as shipped

    When a schema carries both an objectui viewType and a spec-side kind, the explicit viewType wins. Reasoning: the normalizer's "canonical key wins" rule governs pairs where the legacy key is deleted; here viewType is neither legacy nor deleted — it is the key this fold writes. B would change what already-working input renders, a strictly larger change than objectstack#14791's ruling B authorised, and the retirement of viewType / objectName is objectstack's later move (#14343), at which point the question dissolves. The || / ?? nuance the seat noted elsewhere does not arise here (the guards test for a non-empty string).

    Landing — same stroke

    Not governed (four paths under packages/** and .changeset/). needs:contract-review removed here and on PR #7628 (read-modify-write, read back), PR marked ready, squash auto-merge armed; this seat follows it to MERGED and this card closes on Fixes. #7627 (the six-fold collapse + the ObjectGantt.tsx:661 / :1374 precedence disagreement) stays a finding for triage; objectstack#14343 waits on this landing and the pin bump, as the card's sequencing note says.


    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

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions