Skip to content

Data pillar: the object header moves into the content column, Records / Form / Validations / Hooks / Actions / API / Settings become one underline tab row, and the Records toolbar and grid lose their nested containers #12450

Closed

Description

@objectstack-fleet

Source

Epic #12446, decisions 1 and 3, open decision B. Measured on objectui main a2225f91 at /studio/app.objectstack.hotcrm/data?surface=object:crm_account: the object header ("Account · crm_account · 37 fields") is a full-width row above the Objects rail, so the title sits over the rail while the content it names starts 208 px to the right; the content column opens with a muted pill group (Records / Form / Advanced ▾) floating in 16 px padding, then a bordered rounded card holding the grid's own toolbar and borders — three nested containers before the first row; the toolbar mixes four labelled buttons with four icon-only buttons and sits right-aligned over an empty left half; the record count is in the footer only.

Target

The content column (right of the 240 px rail) becomes:

  1. Object header row, 18 px top padding, 24 px side padding: a 32 px primary-soft tile with the object's icon, the label at 18 px / 600, the api name as an 11 px mono chip, "N fields" at 12 px muted; at the right an Object settings icon button and a more button. The read-only card adds its link here.
  2. Underline tab row, 36 px, 2 px primary underline on the active tab, 13 px: Records, Form, Validations, Hooks, Actions, API, Settings — the five Advanced entries flattened. Decision B was answered on 2026-10-15: as designed, seven flat tabs (the ruling comment on the epic quotes the maintainer's words). Below a min-width of 1000 px (container query on the content column) the last five fold back into "Advanced ▾" with the objectui#11794 behavior; above it they are flat.
  3. Records toolbar, 12 px vertical padding: left — "N records" at 13 px muted, then labelled outline chips (28 px) Hide fields, Filter, Group, Sort with their glyphs; right — search and refresh as 28 px icon buttons with aria-labels, a divider, then "Add record" as a primary-soft chip. The grid's own header toolbar is suppressed (renderStudioGridList passes the host's toolbar) or, if plugin-grid cannot hide it, this card touches packages/plugin-grid only to expose that option.
  4. Grid, flush with the column: a 1 px top border, a sidebar-tinted header row (36 px, 12 px / 500 muted), 40 px body rows, a 48 px row-number column with 24 px left padding, 1 px vertical dividers, the row actions kebab pinned as the last column. Lookup cells render a 20 px initials avatar plus the display value instead of a primary-colored link. Mono font for autonumber columns.
  5. Footer, 10 px padding, 12 px muted: "N records" at the left, the range at the right.

The nested card and the pill group are removed. No metadata behavior changes: column inspector, hidden system fields (objectui#11780), grid projection and the Form tab's panels work as before.

File surface

packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (DataPillar: header, tab strip, Records tab body, renderStudioGridList), ObjectListViewInspector.tsx only if its trigger moves; packages/plugin-grid only for the toolbar-suppression option; packages/app-shell/src/views/metadata-admin/i18n.ts for tab labels already present under engine.studio.data.*.

Acceptance

  1. At 1280 px the object title's left edge equals the grid's row-number column's left edge (24 px inside the content column), pinned by a test comparing offsets or classes.
  2. Seven tabs visible at 1280 px; "Advanced ▾" returns below the breakpoint, with the objectui#11794 name rule; DataPillar.advancedKeepsName-11794.test.tsx is kept for the folded state and a new test pins the flat state.
  3. Every icon-only toolbar button has an aria-label; the toolbar has no unlabelled button.
  4. DataPillar.gridProjection, gridColumns, hiddenSystemFields-11780, inspectorBelongsToView-11781, surfaceRequest, StudioDesignSurface.listViewInspector-11823 stay green.
  5. Before/after screenshots at 1280 px, light and dark.

Constraints

  • Tailwind utilities and theme tokens only; no inline style.
  • Existing data-testids and i18n keys stay.
  • Gates: pnpm --filter @object-ui/app-shell test, lint, build; pnpm --filter @object-ui/plugin-grid test if touched.
  • Lands after the layout skeleton card (the Blocked-by line below).

Reference artboard

docs/design/studio-redesign/data-records.html on objectui main (landed in #12458); data-records.png is its 1280 px render. The folder's README carries the shared numbers and what a pull request against this card must show: two screenshots per artboard it touches (light and dark, 1280 px, cropped to the artboard's region), a deviation list with a reason per line, and the card's named tests green or updated. Where this card and the artboard disagree, this card's numbers win and the disagreement is reported here.

Blocked-by: #12459

Body refreshed 2026-10-15T09:52Z by the epic PM session: a Reference artboard section added now that #12458 is on main. Earlier refresh 2026-10-15T09:13Z: decision B answered; the earlier refresh of 2026-10-15T08:17Z pointed the Blocked-by line at #12459.

Parent: #12446

Activity

  1. added
    enhancementNew feature or request
    pm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    on Oct 15, 2026
  2. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Decision B answered · 2026-10-15

    Maintainer, in the epic PM session on 2026-10-15, verbatim:

    A、B、C 三项全按设计稿裁定:权限进导航、More 退役;Advanced 扁平成七个标签;权限页改五个标签。记到专题卡上。

    Decision B is the second clause: the seven flat tabs ship. The Target section's item 2 stands as written with the breakpoint at 1000 px (not infinity): flat at 1000 px and above, "Advanced ▾" with the objectui#11794 name rule below it. The full record, with the premise to re-check (the Advanced menu holds exactly the five entries named), is on the epic #12446. This card stays blocked on #12459 (#12460, enqueued).

    Written 2026-10-15T09:12Z by the epic PM session session_cdb7a717-def7-4e71-9988-f089fbaa18ff.

  3. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (epic #12446 subtree)
    Session: session_01SJn1GEkrbbBQkhGC26wEDw
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12450-data-pillar-layout
    Worktree: objectui-issue-12450
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (region: DataPillar and renderStudioGridList only), new tests beside it, ObjectListViewInspector.tsx (trigger only, if it moves), packages/plugin-grid (only to expose a toolbar-suppression option), packages/app-shell/src/views/metadata-admin/i18n.ts (engine.studio.data.* keys), the screenshots under docs/design/studio-redesign/after/ named 12450-*.png (the light and dark pairs the reference README asks for, filed as the skeleton PR filed its own), one .changeset/12450-*.md (stop on breach; explain in the report)
    Container & model: L, mode:subagent, model: opus
    Clause-②: no
    Responsibility: n/a — not a defect card
    Thread-read: 6188628732
    Serial constraints cleared: objectui#12459 merged as PR objectui#12460 (cf430cd), the skeleton this card builds on. In flight on the same file at claim time: objectui#12452 (shell header nav region) and objectui#12454 (AutomationsPillar region), both this seat, regions disjoint from DataPillar. objectui#12451 (Form tab) waits on this card. objectui#12418 (seat 1) may touch packages/plugin-grid only where column widths are reported, a different region from the toolbar option. No other open PR touches studio-design/ (3 open PRs read).
    Ruling-ref: 6188628732

  4. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 12450,
    "status": "needs_decision",
    "branch": "claude/issue-12450-data-pillar-layout",
    "pr": "#12477",
    "session": "session_01SJn1GEkrbbBQkhGC26wEDw",
    "premise_still_valid": true,
    "summary": "Draft PR objectui#12477 (head 4c06d3a, assignee os-sales) is complete for the part the card's file surface reaches, and its local verification is done. Object header: an Object settings icon button (opens the Settings panel) and a More actions menu; Add field moved into More, because the artboard has no header Add field. Tab row: the seven flat tabs of ruling B. A CSS container query on the content column (@container plus @min-[1000px]: classes in the new dataTabsFold.ts, no JS width read) folds the last five into Advanced below 1000 px. Measured in Chromium: column 1040 px gives seven flat tabs; 940 px, or the 1280 px viewport with the field inspector open (740 px), folds them. Records body: the framed card and body padding are removed, so the list runs flush with the column. The Studio list now uses the wide toolbar form (compactToolbar: false: Hide fields, Filter, Group and Sort labelled, then Refresh, Search, Add record; density off). The toggles moved from the retired bare show-flag spellings to userActions. One producer fix: an aria-label on plugin-list's icon-only Search trigger (card acceptance 3). Premise B held on cf430cd: Advanced held exactly rules, hooks, actions, api and settings. PM mechanism assumptions: 1, 2, 3 and 5 confirmed. 4 is partly falsified: the Records toolbar belongs to plugin-list's ListView, not plugin-grid. A host toolbar cannot replace it, because its controls own ListView-internal state (filters, sort, grouping, hidden fields, search keyword, refresh key), so suppression alone would remove the controls. What the card still asks for is outside its file surface: the artboard's toolbar arrangement, grid metrics (36 px tinted header, 40 px rows, 24 px row-number inset, column dividers, avatar lookups, mono autonumber) and footer range. These are ListView and ObjectGrid/data-table internals, so card acceptance 1 (title edge equals row-number edge at 24 px) is not met. That remainder is the one open question below. The PR's first line is Fixes #12450, as the dispatch asked. Every option for the remainder lands in other packages under another card. If the seat prefers to keep #12450 open while the question stands, the line becomes Part of #12450 (a seat-side body edit).",
    "tests": "All from the worktree root through os-verify-lock (slot os-dev-12450), at HEAD 4c06d3a unless noted. (1) pnpm exec vitest run --maxWorkers=3 over the 98 studio-design test files that are not StudioDesignSurface.*: Test Files 98 passed (98), Tests 773 passed (773), VERDICT command-exit 0. (2) pnpm exec vitest run --maxWorkers=3 packages/app-shell/src/views/studio-design/StudioDesignSurface.: Test Files 76 passed (76), Tests 511 passed (511), VERDICT command-exit 0. (3) pnpm exec vitest run --maxWorkers=3 packages/plugin-list/: Test Files 133 passed (133), Tests 1432 passed (1432), VERDICT command-exit 0. Run after the plugin-list commit cb56e47; plugin-list has not changed since. (4) The 6 app-shell and plugin-list files that read the search trigger: Test Files 6 passed (6), Tests 214 passed (214). New test: DataPillar.flatTabs-12450.test.tsx, 9 tests, pins the flat state and the header buttons. Ablation, once: via /home/user/objectstack/scripts/ablation-replace.mjs, anchor className={DATA_TAB_FLAT_ONLY} x1 to x0, replacement className={undefined} x0 to x1, blob 12a4c43efdae to 0c671a29edcd. Run: Tests 1 failed | 8 passed (9); the failure is the fold-class pin. Restored: blob == HEAD (12a4c43efdae), git diff HEAD empty. The subject is imported from source by a relative path, so no dist build was involved. Kept for the folded state, unmodified and green: DataPillar.advancedKeepsName-11794. Harness-only edits (assertions unchanged) to 14 existing suites: field adds go through More; power panels open by their flat tab; the 'is Add field offered' reads use the More trigger. Three of these suites are on the stay-green list (gridProjection, gridColumns, inspectorBelongsToView-11781); hiddenSystemFields-11780, surfaceRequest and listViewInspector-11823 are unmodified. All are green in runs (1) and (2). Browser: own stack, hotcrm objectstack dev --fresh --seed-admin -p 4150 plus the console vite on :5250, Chromium at /opt/pw-browsers/chromium, 1280x900 light and dark, plus 1180x900. Both servers were stopped by their recorded PID trees, and the hotcrm and objectui worktrees were removed.",
    "gates": "Local subset, each exit code captured before any pipe. pnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 exit 0 (28 tasks, 27 cached). pnpm --filter @object-ui/app-shell --filter @object-ui/plugin-list type-check exit 0, both scripts echoed type-check; tsc -p tsconfig.test.json --listFiles lists DataPillar.flatTabs-12450.test.tsx, tests/dataPillarAddField.ts and dataTabsFold.ts. eslint on the 19 touched app-shell files and plugin-list src/ListView.tsx: exit 0, 0 errors (existing warnings only). pnpm --filter @object-ui/plugin-list --filter @object-ui/app-shell build exit 0; the aria-label is in plugin-list dist/index.js and the fold classes are in app-shell dist dataTabsFold.js. At 4c06d3a, exit 0 each: node scripts/check-changeset-presence.mjs (20 source files of 2 released packages, 1 changeset), node scripts/check-changeset-no-major.mjs, node scripts/check-changeset-fixed.mjs, pnpm check:changeset-claims, pnpm check:i18n-keys, pnpm check:i18n-drift (2 keys added, 0 en values changed), pnpm check:i18n-designer-parity, pnpm check:control-bytes, pnpm check:new-line-citations (0 new), pnpm check:test-path-roots. node scripts/check-governed-queue-guard.mjs --test on the 24 paths: NOT GOVERNED. objectui has no dispatch-gates derivation; this list was hand-derived from objectui's package.json files and the diff. CI on PR objectui#12477 (head 4c06d3a), read once at report time, 36 check-runs: label success; dependabot, Test (coverage) and Test (coverage shard 1..8) skipped. In progress, no conclusion yet: Lint, Type Check, Test (shard 1/16 through 16/16), Test (dist pins), Build & E2E, Build Docs, Bundle Analysis, Repo Gates, Line Citation Gate, Spec Main Shape Gate, Governed Surface Queue Guard, Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report, Changeset Claim Re-read, Live E2E (informational). Not waited on, per the contract; mergeable_state read 'behind' (main moved three commits, none touching this diff).",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes through scripts/pm/fleet-write/dispatch.mjs, each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create, which issued POST /repos/objectstack-ai/objectui/pulls (draft) then POST /repos//issues/12477/assignees [os-sales]; read-back: 11041 bytes sent, 11041 stored, identical; (2) this os-dev-report comment, POST /repos//issues/12450/comments. Zero label writes. git push is not a REST write. The scratch backend writes (package create, object draft, publish, 8 records) went to my own localhost:4150 stack, not GitHub.",
    "deviations": [
    "File surface: packages/plugin-list/src/ListView.tsx (one aria-label on the Search trigger) is outside the claim's surface. The claim names packages/plugin-grid, but the toolbar's producer is plugin-list. The dispatch's producer clause authorizes the fix; it is declared in the PR body.",
    "Harness edits to 14 existing suites, assertions unchanged, including 3 on the stay-green list (gridProjection, gridColumns, inspectorBelongsToView-11781). Reasons: Add field moved into the More menu, and the flat tabs duplicate the names that three suites' passthrough menu mocks draw as buttons. New shared helper: studio-design/tests/dataPillarAddField.ts.",
    "Card acceptance 1 is not met, and the artboard's toolbar arrangement, grid metrics and footer range are not built. Reason: ListView, ObjectGrid and data-table internals, outside the card's surface. Listed as deviations 1-3 in the PR; the decision is the open question.",
    "Screenshots use a writable 'Acme CRM' base seeded on the local backend, not HotCRM. HotCRM is read-only under objectstack dev, the server refuses to duplicate it (DUPLICATE_SOURCE_NOT_A_BASE), and a read-only package draws no More button. A third screenshot, 12450-data-records-folded-light.png, shows the fold.",
    "Conflict noted, not resolved silently: the dispatch asked for Fixes #12450, while part of the card's acceptance waits on the open question. I kept Fixes because every option puts the remainder on another card. The seat switches the line to Part of #12450 if it keeps the card open instead."
    ],
    "files_changed": [
    ".changeset/12450-data-pillar-layout.md",
    "docs/design/studio-redesign/after/12450-data-records-dark.png",
    "docs/design/studio-redesign/after/12450-data-records-folded-light.png",
    "docs/design/studio-redesign/after/12450-data-records-light.png",
    "packages/app-shell/src/views/metadata-admin/i18n.ts",
    "packages/app-shell/src/views/studio-design/DataPillar.authorRefusal-11785.test.tsx",
    "packages/app-shell/src/views/studio-design/DataPillar.choiceWithoutOptions-11253.test.tsx",
    "packages/app-shell/src/views/studio-design/DataPillar.designerRegistryPopulated.test.tsx",
    "packages/app-shell/src/views/studio-design/DataPillar.flatTabs-12450.test.tsx",
    "packages/app-shell/src/views/studio-design/DataPillar.gridProjection.test.tsx",
    "packages/app-shell/src/views/studio-design/DataPillar.heldIncomplete-11786.test.tsx",
    "packages/app-shell/src/views/studio-design/DataPillar.inspectorBelongsToView-11781.test.tsx",
    "packages/app-shell/src/views/studio-design/DataPillar.panelGate.test.tsx",
    "packages/app-shell/src/views/studio-design/DataPillar.picklistServedOptions-10202.test.tsx",
    "packages/app-shell/src/views/studio-design/StudioDesignSurface.autosaveInFlight-11204.test.tsx",
    "packages/app-shell/src/views/studio-design/StudioDesignSurface.autosaveSwitch-11232.test.tsx",
    "packages/app-shell/src/views/studio-design/StudioDesignSurface.cancelledLoad-11331.test.tsx",
    "packages/app-shell/src/views/studio-design/StudioDesignSurface.draftVersionConflict-11773.test.tsx",
    "packages/app-shell/src/views/studio-design/StudioDesignSurface.formFields.test.tsx",
    "packages/app-shell/src/views/studio-design/StudioDesignSurface.gridColumns.test.tsx",
    "packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx",
    "packages/app-shell/src/views/studio-design/tests/dataPillarAddField.ts",
    "packages/app-shell/src/views/studio-design/dataTabsFold.ts",
    "packages/plugin-list/src/ListView.tsx"
    ],
    "line_budget": "n/a — no skills/** surface",
    "open_questions": [
    {
    "question": "The rest of the artboard's Records body belongs to packages the card's file surface excludes: the toolbar arrangement (left-aligned 28 px chips after an 'N records' count, Search and Refresh, a divider, a primary-soft Add record), the grid metrics (36 px header on the sidebar surface, 40 px rows, a 48 px row-number column with the 24 px inset that acceptance 1 pins, column dividers, initials-avatar lookup cells, mono autonumber) and the footer range. They are ListView (plugin-list) and ObjectGrid/data-table (plugin-grid, components) internals. A host-side toolbar cannot stand in for them, because ListView's controls own its internal filter, sort, grouping, hidden-fields, search and refresh state. Which way does the Studio Records body go?",
    "options": [
    "A — Restyle the shared runtime list and grid to the artboard, so every console list page changes. Cost: this reopens what landed today in objectui#12423 (the compact Display toolbar and the data-table's leading row cell). It touches three published packages and every list page's look and its pins. Workload L-XL, on a list-page card of its own.",
    "B — Studio-only presentation: new React host props on ListView and ObjectGrid (toolbar layout, header surface, row metrics, row-number inset), passed only by renderStudioGridList. Cost: one renderer gets two looks, and the Studio preview stops showing what end users see (renderStudioGridList's stated same-renderer principle). It adds public props to three published packages with no pull beyond this artboard. Workload M-L, one card across plugin-list, plugin-grid and components.",
    "C — Keep the runtime list look inside Studio, which is what objectui#12477 ships: Records draws the list exactly as a list page does, in its wide form. The artboard's grid details and acceptance 1's 24 px alignment stand as recorded deviations, and the card closes. Cost: the Records body differs from the artboard in those details. Workload zero."
    ],
    "recommendation": "C. 实际业务需求 (real business need): the Records tab is the author's preview of what users get. B shows authors a grid no user ever sees, and nothing besides this artboard asks for a Studio-only look. Measured: renderStudioGridList is the only host that renders the Studio grid, and the console list pages moved the other way today (objectui#12423). 项目长远合理性 (long-term soundness): A and C keep one renderer and one look. B forks presentation across three published packages, a standing cost with no contract gain. 防 AI 写错 (preventing AI mistakes): as long as B stays on React host props, no option adds a metadata key, so this axis barely separates them; B does add host-prop surface an agent could mistake for node keys. 创业阶段不扩散需求 (no scope creep at this stage): C adds nothing; B adds unpulled public props; A is the largest. If the maintainer does want the artboard's grid on every list, that is A, on its own list-page card weighed against objectui#12423 — not on this card. Recommend recording the answer on the epic, objectui#12446, so #12450 is not itself held in the decision box."
    }
    ],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed — In Studio on HotCRM crm_account at 1280x900, the Records grid drew 6 of the 9 rows its footer counts, and the last drawn row stretched to the bottom of the column. The skeleton's before-screenshot (cf430cd) shows the same shape, and an 8-row object does not reproduce it. Not root-caused and no location named, so this is not a class-(a) filing; it is in the PR's Acceptance notes. Dedupe words: studio records grid stretched last row, rows missing, crm_account grid gap.",
    "carrier: none · noted, not filed (read from source, not measured) — Below the sm breakpoint, ListView's Filter and Sort triggers hide their label spans and carry no aria-label, in both toolbar forms, so on a phone they would have no accessible name. Not measured in a browser, so not filed; in the PR's Acceptance notes. Dedupe words: list toolbar filter sort aria-label mobile, icon-only unnamed button."
    ]
    }

  5. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #12477 comment 6190857251 (head 4c06d3a, read 2026-10-15T11:21Z). The artboard's grid remainder (acceptance 1) goes to decision D on the epic #12446; every option carries it on another card. Finding filed from this review: #12480.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #12477 merged as 7a5f4c1 on main · read 2026-10-15T12:40Z

    probe on origin/main 7a5f4c1 reading control
    studio-design/dataTabsFold.ts present —
    DATA_TAB_FLAT_ONLY in StudioDesignSurface.tsx 2 hits (import + flat tabs) —
    data-object-settings (header button) 1 hit —
    Records body overflow-auto bg-background with data-records-body 1 hit, no frame the other framed body at the next tab (… rounded-lg border bg-background p-6) still present, 1 hit
    aria-label={searchTerm in plugin-list/src/ListView.tsx 1 hit —
    DataPillar.flatTabs-12450.test.tsx present —
    merge-queue branch for PR 12477 gone —

    State change: the card closed as completed through Fixes #12450. pm:dispatched is removed in this act; pm:epic, domain:ui and area:studio stay. objectui#12451's last Blocked-by: target is now closed, so it is next in this seat's queue. The artboard's grid remainder stays with decision D on the epic (6190886772).


    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 portaldomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestpm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtreepriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions