You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
App pages: block grids declare columns and fold by breakpoint; the page description sits under its title #12624
Walked on objectui main 8415d9f11 at 1024 px, HotCRM's Sales Home page (/page/sales_home_page, src/sales/pages/home.page.ts): the four KPI blocks under "Key Performance Indicators" stack one per row, each a full-width card with one number, so the first screen is four stripes; the page's description "Sales team home page with key metrics, my open work and upcoming events" is drawn as a muted line above the title "Sales Dashboard" (the page header draws description, then title), and the Edit pencil floats at the top right.
Target
A page region's blocks lay out on a 12-column grid: a block declares span (default 12 for lists and tables, 3 for metric tiles, 6 for charts) and the renderer folds spans by breakpoint (≥1280: as declared; 768–1279: tiles 6, charts 12; <768: 12). The page designer in Studio shows the span on the block card.
The page header is the console page header (objectui#12553): icon, title, then the description under it, actions at the right.
If the page spec has no span field, the card says so and files the spec change on objectstack-ai/objectstack; the folding rule still lands with kind-based defaults.
File surface
packages/app-shell/src/views/metadata-admin/previews/PagePreview.tsx and the runtime page renderer, packages/plugin-dashboard tiles when used in pages, the page spec in packages/spec if span is new, tests beside.
Acceptance
Tests: four metric blocks render in one row at 1280 px and two rows at 1024 px; the description is after the title in the DOM.
Screenshots of Sales Home at 1024 and 1280 px.
pnpm --filter @object-ui/app-shell test green.
Evidence
Screenshots on the temporary branch claude/console-home-login-design-20261016 of objectstack-ai/objectui (commit b72c97985, never merged): hotcrm-sales-home-kpi-stacked.jpg.
Filed by the epic PM session session_cdb7a717-def7-4e71-9988-f089fbaa18ff from the 2026-10-16 walk of HotCRM's core pages as a user: objectui main 8415d9f11, HotCRM 63db83e2, the vite console on :5180 against objectstack serve --dev on :4001, the maintainer's browser pane at 1024 × 768 (en). The maintainer asked for the findings to be filed on the repository each belongs to, verbatim: 「相关问题也在对应的项目上创建任务」.
Claim: PM loop round 4 (priority:p2; held serial behind PR #12679 on the page renderer, which landed as f8267df93a)
Session: session_011K9g23W67dy2zwnyzaoB6o
Account: os-tesla
Branch: claude/issue-12624-page-block-grid
Worktree: objectui-issue-12624 (in the cloud session's own container)
Domain: domain:ui
Seat: domain:ui#1
File surface: how an app page lays out a region's blocks, and the order of the page heading.
packages/components/src/renderers/layout/page.tsx: the region block layout (a 12-column grid with spans derived from the block's kind, folded by breakpoint) and where the page description sits relative to the title;
the page:header renderer (PageHeaderRenderer, packages/components/src/renderers/layout/containers.tsx), for the heading order only: icon, title, then the description, with actions at the right;
packages/app-shell/src/views/metadata-admin/previews/PagePreview.tsx, so the designer's block card shows the derived span, read-only;
packages/plugin-dashboard metric tiles, only if a tile placed in a page needs a width fix to fill its span;
tests beside, and one .changeset/12624-*.md.
Outside the surface:
Any span key, prop or type in objectui. PageComponentSchema declares no span at objectstack main; on PageRegionSchema, span is only a refused alias of width. An objectui-only key would be an undeclared dialect on spec-served pages. Target 3 applies: the kind-based defaults land here, and the seat files the spec half after the dev reports whether those defaults meet the card's case.
getRemainingRegions and TEMPLATE_REGISTRY in page.tsx: objectui#12476.
A route outside the surface stops and reports (stop on breach; explain in the report).
Container & model: M, mode:cloud, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui over the planned paths: no path-derived mandate)
Clause-②: no
Clause-② reason: only the renderer's layout and heading order change. No published type, prop, export, schema read or pack key is added or removed, because the spans are derived from block kinds, not authored.
Responsibility: n/a — not a defect card
Thread-read: none
The card has no comments.
Serial constraints cleared: read at objectui mainf8267df93a.
objectui#12476 (p3, pm:queue) edits the same page.tsx. Fold-or-serial, answered: serial, not folded. It fails gate ① (a different defect shape and fix: retiring the unplaced-region stack branch versus the in-region block grid), so it follows this card's merge.
{
"issue": 12624,
"status": "done",
"branch": "claude/issue-12624-page-block-grid",
"pr": "https://github.com/objectstack-ai/objectui/pull/12734",
"session": "session_01NvsorV5JuG3fpRuUjWRHir",
"premise_still_valid": true,
"summary": "Page regions now lay out their blocks on a 12-column grid. Each block's span comes from its kind, set once in packages/components/src/renderers/layout/page-block-span.ts: metric tiles 3, charts 6, everything else 12. The span folds by breakpoint with literal md/xl classes: 768–1279 px tiles 6 and charts 12, under 768 px everything 12. sidebar/aside rails stay at 12, and on the dashboard template each region cell holds its own block grid. When a titled page:header owns the h1, the page description is now drawn inside that header under the title and subtitle (a PageDescriptionContext that every page renderer resets), not above the regions. There is still exactly one h1. The Studio designer's block card (PageBlockCanvas, which PagePreview renders) shows the derived span read-only as a badge (3/12, 6/12, 12/12), read from the same table. H1 is FALSIFIED for the Sales Home case: in the walk screenshot the four KPI tiles sit inside a titled container (page:card shape: the heading is inset by the card's p-6), so a region-level grid does not change that page. See open_questions. H2, H3, H4 and H5 held.",
"tests": "At head 550bf27 (after merging origin/main): `pnpm exec vitest run --maxWorkers=2 packages/components/src/renderers/layout packages/components/src/__tests__ packages/app-shell/src/views/metadata-admin/previews`: Test Files 354 passed | 1 skipped (355), Tests 3768 passed | 7 skipped. `pnpm exec vitest run --maxWorkers=2 scripts/__tests__`: Test Files 186 passed | 2 skipped (188), Tests 5619 passed. New: packages/components/src/__tests__/page-block-grid-12624.test.tsx (9 tests: four tiles fill 1 row at 1280 and 2 rows at 1024, worked out from each cell's real col-span classes by the breakpoint min-width rule; an unnamed kind takes 12; chart 6; rails 12; the dashboard template composes; a hidden block leaves an empty:hidden cell; the description comes after the title and subtitle with exactly one h1; a LIVE CONTROL with no header; a nested page does not inherit the description). New: previews/PageBlockCanvas.blockSpan-12624.test.tsx (4 tests: the badges read 3/6/12; a rail reads 12; a children page shows no badge; every type in the table is registered once plugin-dashboard and plugin-charts load). Ablations: each run commits the fix first, mutates through an anchor-checked python replace (anchor count after = 0, injected = 1), uses a trap restore with `git checkout HEAD -- ABSPATH`, then compares the hash to the HEAD blob and checks that `git diff HEAD` is empty. The tests resolve @object-ui/components through the vitest src alias, so no dist rebuild was needed. grid (span forced to 12 in page.tsx): 5 failed / 4 passed, restored blob 7af52354285516d68cf972479d0b60fd7db6c31e == HEAD. unnamed (default 12 changed to 3 in page-block-span.ts): 1 failed ('a block whose kind the table does not name takes 12'), restored 5baddd435a1a774475577645b3fd82ea8d262f51 == HEAD. desc (headerDrawsDescription = false): 1 failed ('a titled page:header draws the description after the title, with one h1'), restored 7af52354… == HEAD. designer (span={undefined}): 2 failed / 2 passed, restored e436a841ca930e62b18c5e49292ba8a87399b43a == HEAD. Browser (Chromium /opt/pw-browsers/chromium, temporary console harness page, deleted afterwards): after 1280 = 4 cells at top 137, width 296 each (one row); after 1024 = 2+2 rows, width 480; h1s 1, descAfterTitle true. Before (base page.tsx/containers.tsx checked out temporarily, restored by hash): 5 stacked full-width cells, descAfterTitle false. Screenshots are on branch claude/issue-12624-evidence under evidence/12624/ (before-1024, before-1280, after-1024, after-1280, designer-after-1280). HotCRM's real Sales Home was NOT MEASURED (HotCRM is not in this container).",
"gates": "Local, at head 550bf27, each exit code captured to a file before any pipe: `pnpm --filter '@object-ui/app-shell^...' build` 0 (rebuilt after the merge); `pnpm --filter @object-ui/components type-check` 0; `pnpm --filter @object-ui/app-shell type-check` 0 after the closure rebuild (the first post-merge run gave exit 2 only from a stale @object-ui/fields dist the merge brought in; it was green after the rebuild, so that run is not counted as a measurement). `pnpm check:changeset-claims` 0; `pnpm changeset:check` 0; `pnpm check:test-path-roots` 0; `pnpm check:spec-symbols` 0; `pnpm check:new-line-citations` 0 ('0 new citation(s)'); `node scripts/check-changeset-presence.mjs` 0 ('9 source file(s) of 2 released package(s) changed … declares 1 changeset'); eslint on the changed ts/tsx 0 (0 errors; the warnings are all pre-existing any / exhaustive-deps, and I added no `any`). Console build (pre-merge head f36cb9b) `VITE_BASE_PATH=/console/ pnpm --filter @object-ui/console exec vite build` 0. `pnpm check:eager-closure` 0: 'Console eager closure is 3118.1 KB gzipped … budget: 3744.1 KB'; ui-components 281.9 / 338.9 KB. scripts/check-nul-bytes.mjs is objectstack-only (MODULE_NOT_FOUND here, so not a measurement); I ran the control-byte grep over the changed files instead: no hit. Not run locally: the whole @object-ui/app-shell suite (Acceptance 3), `pnpm lint`, and the full farm. CI shards run those; there are no PR check-runs to cite yet, so they are NOT MEASURED here.",
"mcp_calls": "2 — mcp__github__issue_read (get, get_comments), both read-only",
"api_writes": "0 — no REST writes. Two git pushes: claude/issue-12624-page-block-grid and claude/issue-12624-evidence (an orphan commit 273d8597 holding only evidence/12624/*.png)",
"open_questions": [
{
"question": "Sales Home (the card's own case): from the walk screenshot, the four KPI tiles sit INSIDE a titled container block (the 'Key Performance Indicators' heading is inset by a card's padding, which looks like page:card), not directly in the region. The region grid therefore leaves that page stacked. The claim keeps containers.tsx to heading order only, so I stopped at the surface. Should the same block grid also lay out the child list of page:card / page:section?",
"options": [
"A: a follow-up card (or a claim widening) that applies the same pageBlockSpan grid to the children of page:card and page:section. The table and the class map are already shared, so it is a small change in containers.tsx. Keep this PR as Part of #12624 until then",
"B: land this PR as Fixes #12624, and have HotCRM move its tiles into the region directly",
"C: confirm the nesting against HotCRM src/sales/pages/home.page.ts first (not readable from this container)"
],
"recommendation": "C then A. Real business need: the defect the maintainer walked is exactly this page, so a region-only grid does not fix what they saw. Long-term soundness: a page:card holding a block list is a generic composition, so the generic fix belongs in the containers, not in HotCRM. AI-proofing: the authors stay the same, with no new key. Startup focus: A reuses the existing table and adds no surface. If C confirms the nesting, change the PR body's first line to `Part of #12624`."
},
{
"question": "H2: do the kind-based defaults cover the card's case without a declared spec `span`?",
"options": [
"A: yes. Four tiles fill one row at 1280 and two rows at 1024 (measured in Chromium and pinned). No spec `span` is needed for this case",
"B: file the spec half (a declared PageComponentSchema span) now"
],
"recommendation": "A. No author in the card needs a non-default width. A spec key with no pull goes against the startup-focus axis, and a kind-derived span cannot be mis-declared. Revisit only if a named page needs, for example, a 4-wide chart."
},
{
"question": "Clause-② wording: to keep the span table single-source (H4: 'one source, no copy'), @object-ui/components adds runtime exports pageBlockSpan, isPageRailRegion, PAGE_BLOCK_SPANS and the PageBlockSpan type. The claim's Clause-② reason says no export is added. None of these is an authorable key.",
"options": [
"A: keep Clause-② no and correct the reason sentence",
"B: switch to Clause-② yes (widening)"
],
"recommendation": "A, because Clause-② governs what an author writes, and none of these exports is metadata. The changeset is minor and names the exports."
},
{
"question": "Surface drift: the designer's block card is BlockRow in previews/PageBlockCanvas.tsx (which PagePreview renders), not PagePreview.tsx, and the strings are in metadata-admin/i18n.ts (en and zh). I edited those two files for the claim's stated purpose. Add them to the claim's file surface?",
"options": [
"A: add them to the surface after the fact",
"B: treat it as a breach"
],
"recommendation": "A. It is the same directory and the same purpose the claim names."
},
{
"question": "Target 2 names an icon. The spec retires page:header `icon` (retiredKey), and the page renderer draws no page-level `icon` today. Draw the page-level icon in the heading?",
"options": [
"A: a separate card",
"B: leave as is"
],
"recommendation": "A or B. It is outside H3's order fix, and it would add eager bytes to a renderer with tight budget headroom."
}
],
"out_of_scope_findings": [
"carrier: seat — the region grid uses viewport breakpoints (per Target 1), so on the dashboard template a half-width region cell at 1280 px gives tiles about 140 px each. Container-relative spans (Tailwind 4 @container) would fit narrow cells better but would not meet the 'one row at 1280' acceptance inside the console's sidebar layout. Noted, not filed",
"carrier: seat — a page with two titled page:header blocks draws the description in each (it already has two h1s). Noted, not filed",
"carrier: seat — print pages now lay region blocks on the grid too (print width folds to md). page-print's [data-region] > * break-inside rule still matches the cells. Noted, not filed"
]
}
Posted by the domain:ui seat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) verbatim from the cloud session's final message, because a cloud session cannot comment from its container. The seat filled pr with the draft PR it opened, #12734.
The seat's answers:
Sales Home: C, then A, within this PR.
The seat read HotCRM's public src/sales/pages/home.page.ts on main. In the main region, one page:card titled "Key Performance Indicators" holds the four object-metric tiles in properties.children. So the nesting is confirmed.
Spec span: A. The kind defaults meet the case, so no spec card is filed.
Clause-②: B, yes. The seat's criterion is whether a card widens an accept set or the public surface. Four new runtime exports from @object-ui/components widen the published surface even though none is metadata. The PR body's line 2 reads Clause-②: yes, and the seat's own TIER_DEFAULT ACCEPT covers it. The claim's reason sentence is superseded by this note.
Surface: A.previews/PageBlockCanvas.tsx and metadata-admin/i18n.ts join the claim's surface on record, for its stated purpose.
The page-level icon: B, leave it. The spec retired page:headericon, and spec declaration outranks the card's prose.
The three seat-carried notes stay noted, not filed.
os-dev-report (delta, REWORK round 1 on PR #12734)
{
"issue": 12624,
"status": "done",
"round": "rework 1",
"branch": "claude/issue-12624-page-block-grid",
"head": "d643f37",
"pr": "https://github.com/objectstack-ai/objectui/pull/12734",
"session": "session_01NvsorV5JuG3fpRuUjWRHir",
"premise_still_valid": true,
"summary": "page:card and page:section now lay out a list of children on the same 12-column block grid as region blocks. A new shared PageBlockGrid in packages/components/src/renderers/layout/page-block-grid.tsx draws both region blocks and container children, and it uses the one pageBlockSpan table and PAGE_BLOCK_SPAN_CLASS map, with no second copy. A container in a region spans 12 by its own kind, and its children fold the same way as region blocks. A bare node or text child still renders through renderChildren. A rail context set by sidebar and aside regions keeps tiles nested in a container full width. In the Studio designer, the child cards of a page:card or page:section now show their span too. The changeset and page.mdx describe the container grid. Commits this round: 3205254 (feature, tests, docs, changeset) and the merge d643f37 of origin/main (c41ce3f, a real merge commit). Nothing was rebased, amended or force-pushed.",
"tests": "At head d643f37: `pnpm exec vitest run --maxWorkers=2 packages/components/src/renderers/layout packages/components/src/__tests__ packages/app-shell/src/views/metadata-admin/previews` gives Test Files 354 passed | 1 skipped (355), Tests 3773 passed | 7 skipped. page-block-grid-12624.test.tsx now has 13 tests (4 new): (1) a page:card holding four object-metric children: the card cell spans 12, its children each span 3, and the rows come to 1 at 1280 px, 2 at 1024 px and 4 at 390 px; (2) a page:section does the same; (3) a container whose children are not tiles stacks them, spans [12, 12], 2 rows at 1280 px; (4) a container in a sidebar rail keeps its tiles at 12. PageBlockCanvas.blockSpan-12624.test.tsx now has 5 tests (1 new): a page:card's child cards read [card 12, 3, 3, 12]. Ablations ran on the committed fix 3205254, each with a trap restore through `git checkout HEAD -- ABSPATH`, a hash check against the HEAD blob, and an empty `git diff HEAD`. card: renderBlockChildren swapped for renderChildren in the page:card body; 3 failed (the card row test, the stack test, the rail test); restored 276d213efe1db74b254551c940a1fdc1dd41b2b0 == HEAD. section: the same swap in page:section; 1 failed ('a page:section does the same'); restored 276d213e… == HEAD. nestedrail: `isRail = !!rail` with the context read dropped; 1 failed (the rail container test); restored 3e04180f567068d63f486bc4b6d3af2b69fcf4f4 == HEAD. designernested: the badge guard forced to false; 1 failed ('the children of a page:card show the span the card lays them out at'); restored b39f9d41b310b18b246266878d2cb79cf931f522 == HEAD. For every leg the anchor count after the mutation was 0 and the injected text was found on disk. The four round-0 ablations still hold; the region path now runs through PageBlockGrid and its pins stay green. Browser (Chromium /opt/pw-browsers/chromium; a temporary console harness page with the HotCRM shape: page:header with title and subtitle, page description, then one page:card titled 'Key Performance Indicators' holding four object-metric tiles, plugin-dashboard loaded; harness deleted afterwards): after at 1280 px, 4 cells in one row (top 210, width 284 each); after at 1024 px, 2 rows of 2 (width 455); h1s 1; description after the title. Before (base page.tsx and containers.tsx checked out temporarily, then restored to the HEAD blobs ec3696fc and 276d213e): no grid, the four tiles stacked full width inside the card, and the description above the title, which matches the walk screenshot. The tile values read 0 in both legs because the harness's stub data source does not answer the metric query; the screenshots show layout only. PNGs were pushed to claude/issue-12624-evidence at ad7a480 (a child of 273d859): evidence/12624/hotcrm-shape-before-1024.png, hotcrm-shape-before-1280.png, hotcrm-shape-after-1024.png, hotcrm-shape-after-1280.png. scripts/__tests__ was not re-run this round; round 0 ran it green at 550bf27, and this round's diff touches no script.",
"gates": "At d643f37, each exit code captured before any pipe: `pnpm --filter '@object-ui/app-shell^...' build` 0; `pnpm --filter @object-ui/components type-check` 0; `pnpm --filter @object-ui/app-shell type-check` 0; console closure build plus `VITE_BASE_PATH=/console/ pnpm --filter @object-ui/console exec vite build` 0. `pnpm check:eager-closure` 0: 'Console eager closure is 3120.8 KB gzipped across 73 of 2257 chunks (budget: 3744.1 KB, headroom: 623.4 KB)'. ui-components is 282.2 KB against a 338.9 KB ceiling; it was 281.9 KB in round 0, before the main merge. `pnpm check:changeset-claims` 0; `pnpm changeset:check` 0; `pnpm check:test-path-roots` 0; `pnpm check:spec-symbols` 0; `pnpm check:new-line-citations` 0; `node scripts/check-changeset-presence.mjs` 0. eslint on the changed files: exit 0, 0 errors. It reports one new warning: react-refresh/only-export-components on page-block-grid.tsx, because the file exports renderBlockChildren beside the component; it is left as is. Control-byte grep over the changed files: no hit. CI check-runs on PR #12734 are NOT MEASURED here (in progress after the push).",
"mcp_calls": "0 this round",
"api_writes": "0 — git pushes only: claude/issue-12624-page-block-grid (550bf27..d643f37) and claude/issue-12624-evidence (273d859..ad7a480, fast-forward)",
"open_questions": [],
"out_of_scope_findings": [
"carrier: seat — a page:card with one or more listed children now always draws them on the grid. Cards that hold several non-tile blocks gain gap-4 between them, where before they had no gap. page:section already used space-y-4, so its spacing does not change. Noted, not filed"
]
}
Posted by the domain:ui seat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) verbatim from the cloud session's final message. The seat applies the dev's PR-body lines on #12734.
The seat's answer to the one out-of-scope finding: the gap-4 that a card's non-tile children gain is accepted as part of this PR. It is the same gap region blocks have had since round 0, and one gap for every block list is the point of the shared grid. No card.
Verified by content: the merge commit's own diff equals the PR's diff from its merge-base c41ce3f241 to the reviewed head d643f37, changed line for changed line in each of the 13 files (+773/−23). Positive control at the merge commit: containers.tsx names renderBlockChildren 3 times (the import and both containers).
Card: closed completed by Fixes #12624. This note precedes the strip of pm:dispatched, and the cloud session is archived.
Next, outside this card: objectui#12476 (retire the stack-the-rest branch for authored regions) was serial behind this card and is now claimable.
Activity
objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 4 (
priority:p2; held serial behind PR #12679 on the page renderer, which landed asf8267df93a)Session:
session_011K9g23W67dy2zwnyzaoB6oAccount:
os-teslaBranch:
claude/issue-12624-page-block-gridWorktree:
objectui-issue-12624(in the cloud session's own container)Domain:
domain:uiSeat:
domain:ui#1File surface: how an app page lays out a region's blocks, and the order of the page heading.
packages/components/src/renderers/layout/page.tsx: the region block layout (a 12-column grid with spans derived from the block's kind, folded by breakpoint) and where the pagedescriptionsits relative to the title;page:headerrenderer (PageHeaderRenderer,packages/components/src/renderers/layout/containers.tsx), for the heading order only: icon, title, then the description, with actions at the right;packages/app-shell/src/views/metadata-admin/previews/PagePreview.tsx, so the designer's block card shows the derived span, read-only;packages/plugin-dashboardmetric tiles, only if a tile placed in a page needs a width fix to fill its span;.changeset/12624-*.md.Outside the surface:
spankey, prop or type in objectui.PageComponentSchemadeclares nospanat objectstackmain; onPageRegionSchema,spanis only a refused alias ofwidth. An objectui-only key would be an undeclared dialect on spec-served pages. Target 3 applies: the kind-based defaults land here, and the seat files the spec half after the dev reports whether those defaults meet the card's case.packages/app-shell/src/layout/PageHeader.tsx: objectui#12606's PR feat(app-shell): one page header for every Setup page — sys_* lists, metadata types, settings and component pages (objectui#12606) #12662, another seat, in flight.getRemainingRegionsandTEMPLATE_REGISTRYinpage.tsx: objectui#12476.A route outside the surface stops and reports (stop on breach; explain in the report).
Container & model:
M,mode:cloud,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuiover the planned paths: no path-derived mandate)Clause-②: no
Clause-② reason: only the renderer's layout and heading order change. No published type, prop, export, schema read or pack key is added or removed, because the spans are derived from block kinds, not authored.
Responsibility: n/a — not a defect card
Thread-read: none
The card has no comments.
Serial constraints cleared: read at objectui
mainf8267df93a.jsxarm) landed asf8267df93a, which is this card's base.pm:queue) edits the samepage.tsx. Fold-or-serial, answered: serial, not folded. It fails gate ① (a different defect shape and fix: retiring the unplaced-region stack branch versus the in-region block grid), so it follows this card's merge.os-sales) editsapp-shell/src/layout/PageHeader.tsxbut notpage.tsxorcontainers.tsx; this card stays out ofPageHeader.tsx.renderers/layout/**,PagePreview.tsxorplugin-dashboard.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{ "issue": 12624, "status": "done", "branch": "claude/issue-12624-page-block-grid", "pr": "https://github.com/objectstack-ai/objectui/pull/12734", "session": "session_01NvsorV5JuG3fpRuUjWRHir", "premise_still_valid": true, "summary": "Page regions now lay out their blocks on a 12-column grid. Each block's span comes from its kind, set once in packages/components/src/renderers/layout/page-block-span.ts: metric tiles 3, charts 6, everything else 12. The span folds by breakpoint with literal md/xl classes: 768–1279 px tiles 6 and charts 12, under 768 px everything 12. sidebar/aside rails stay at 12, and on the dashboard template each region cell holds its own block grid. When a titled page:header owns the h1, the page description is now drawn inside that header under the title and subtitle (a PageDescriptionContext that every page renderer resets), not above the regions. There is still exactly one h1. The Studio designer's block card (PageBlockCanvas, which PagePreview renders) shows the derived span read-only as a badge (3/12, 6/12, 12/12), read from the same table. H1 is FALSIFIED for the Sales Home case: in the walk screenshot the four KPI tiles sit inside a titled container (page:card shape: the heading is inset by the card's p-6), so a region-level grid does not change that page. See open_questions. H2, H3, H4 and H5 held.", "tests": "At head 550bf27 (after merging origin/main): `pnpm exec vitest run --maxWorkers=2 packages/components/src/renderers/layout packages/components/src/__tests__ packages/app-shell/src/views/metadata-admin/previews`: Test Files 354 passed | 1 skipped (355), Tests 3768 passed | 7 skipped. `pnpm exec vitest run --maxWorkers=2 scripts/__tests__`: Test Files 186 passed | 2 skipped (188), Tests 5619 passed. New: packages/components/src/__tests__/page-block-grid-12624.test.tsx (9 tests: four tiles fill 1 row at 1280 and 2 rows at 1024, worked out from each cell's real col-span classes by the breakpoint min-width rule; an unnamed kind takes 12; chart 6; rails 12; the dashboard template composes; a hidden block leaves an empty:hidden cell; the description comes after the title and subtitle with exactly one h1; a LIVE CONTROL with no header; a nested page does not inherit the description). New: previews/PageBlockCanvas.blockSpan-12624.test.tsx (4 tests: the badges read 3/6/12; a rail reads 12; a children page shows no badge; every type in the table is registered once plugin-dashboard and plugin-charts load). Ablations: each run commits the fix first, mutates through an anchor-checked python replace (anchor count after = 0, injected = 1), uses a trap restore with `git checkout HEAD -- ABSPATH`, then compares the hash to the HEAD blob and checks that `git diff HEAD` is empty. The tests resolve @object-ui/components through the vitest src alias, so no dist rebuild was needed. grid (span forced to 12 in page.tsx): 5 failed / 4 passed, restored blob 7af52354285516d68cf972479d0b60fd7db6c31e == HEAD. unnamed (default 12 changed to 3 in page-block-span.ts): 1 failed ('a block whose kind the table does not name takes 12'), restored 5baddd435a1a774475577645b3fd82ea8d262f51 == HEAD. desc (headerDrawsDescription = false): 1 failed ('a titled page:header draws the description after the title, with one h1'), restored 7af52354… == HEAD. designer (span={undefined}): 2 failed / 2 passed, restored e436a841ca930e62b18c5e49292ba8a87399b43a == HEAD. Browser (Chromium /opt/pw-browsers/chromium, temporary console harness page, deleted afterwards): after 1280 = 4 cells at top 137, width 296 each (one row); after 1024 = 2+2 rows, width 480; h1s 1, descAfterTitle true. Before (base page.tsx/containers.tsx checked out temporarily, restored by hash): 5 stacked full-width cells, descAfterTitle false. Screenshots are on branch claude/issue-12624-evidence under evidence/12624/ (before-1024, before-1280, after-1024, after-1280, designer-after-1280). HotCRM's real Sales Home was NOT MEASURED (HotCRM is not in this container).", "gates": "Local, at head 550bf27, each exit code captured to a file before any pipe: `pnpm --filter '@object-ui/app-shell^...' build` 0 (rebuilt after the merge); `pnpm --filter @object-ui/components type-check` 0; `pnpm --filter @object-ui/app-shell type-check` 0 after the closure rebuild (the first post-merge run gave exit 2 only from a stale @object-ui/fields dist the merge brought in; it was green after the rebuild, so that run is not counted as a measurement). `pnpm check:changeset-claims` 0; `pnpm changeset:check` 0; `pnpm check:test-path-roots` 0; `pnpm check:spec-symbols` 0; `pnpm check:new-line-citations` 0 ('0 new citation(s)'); `node scripts/check-changeset-presence.mjs` 0 ('9 source file(s) of 2 released package(s) changed … declares 1 changeset'); eslint on the changed ts/tsx 0 (0 errors; the warnings are all pre-existing any / exhaustive-deps, and I added no `any`). Console build (pre-merge head f36cb9b) `VITE_BASE_PATH=/console/ pnpm --filter @object-ui/console exec vite build` 0. `pnpm check:eager-closure` 0: 'Console eager closure is 3118.1 KB gzipped … budget: 3744.1 KB'; ui-components 281.9 / 338.9 KB. scripts/check-nul-bytes.mjs is objectstack-only (MODULE_NOT_FOUND here, so not a measurement); I ran the control-byte grep over the changed files instead: no hit. Not run locally: the whole @object-ui/app-shell suite (Acceptance 3), `pnpm lint`, and the full farm. CI shards run those; there are no PR check-runs to cite yet, so they are NOT MEASURED here.", "mcp_calls": "2 — mcp__github__issue_read (get, get_comments), both read-only", "api_writes": "0 — no REST writes. Two git pushes: claude/issue-12624-page-block-grid and claude/issue-12624-evidence (an orphan commit 273d8597 holding only evidence/12624/*.png)", "open_questions": [ { "question": "Sales Home (the card's own case): from the walk screenshot, the four KPI tiles sit INSIDE a titled container block (the 'Key Performance Indicators' heading is inset by a card's padding, which looks like page:card), not directly in the region. The region grid therefore leaves that page stacked. The claim keeps containers.tsx to heading order only, so I stopped at the surface. Should the same block grid also lay out the child list of page:card / page:section?", "options": [ "A: a follow-up card (or a claim widening) that applies the same pageBlockSpan grid to the children of page:card and page:section. The table and the class map are already shared, so it is a small change in containers.tsx. Keep this PR as Part of #12624 until then", "B: land this PR as Fixes #12624, and have HotCRM move its tiles into the region directly", "C: confirm the nesting against HotCRM src/sales/pages/home.page.ts first (not readable from this container)" ], "recommendation": "C then A. Real business need: the defect the maintainer walked is exactly this page, so a region-only grid does not fix what they saw. Long-term soundness: a page:card holding a block list is a generic composition, so the generic fix belongs in the containers, not in HotCRM. AI-proofing: the authors stay the same, with no new key. Startup focus: A reuses the existing table and adds no surface. If C confirms the nesting, change the PR body's first line to `Part of #12624`." }, { "question": "H2: do the kind-based defaults cover the card's case without a declared spec `span`?", "options": [ "A: yes. Four tiles fill one row at 1280 and two rows at 1024 (measured in Chromium and pinned). No spec `span` is needed for this case", "B: file the spec half (a declared PageComponentSchema span) now" ], "recommendation": "A. No author in the card needs a non-default width. A spec key with no pull goes against the startup-focus axis, and a kind-derived span cannot be mis-declared. Revisit only if a named page needs, for example, a 4-wide chart." }, { "question": "Clause-② wording: to keep the span table single-source (H4: 'one source, no copy'), @object-ui/components adds runtime exports pageBlockSpan, isPageRailRegion, PAGE_BLOCK_SPANS and the PageBlockSpan type. The claim's Clause-② reason says no export is added. None of these is an authorable key.", "options": [ "A: keep Clause-② no and correct the reason sentence", "B: switch to Clause-② yes (widening)" ], "recommendation": "A, because Clause-② governs what an author writes, and none of these exports is metadata. The changeset is minor and names the exports." }, { "question": "Surface drift: the designer's block card is BlockRow in previews/PageBlockCanvas.tsx (which PagePreview renders), not PagePreview.tsx, and the strings are in metadata-admin/i18n.ts (en and zh). I edited those two files for the claim's stated purpose. Add them to the claim's file surface?", "options": [ "A: add them to the surface after the fact", "B: treat it as a breach" ], "recommendation": "A. It is the same directory and the same purpose the claim names." }, { "question": "Target 2 names an icon. The spec retires page:header `icon` (retiredKey), and the page renderer draws no page-level `icon` today. Draw the page-level icon in the heading?", "options": [ "A: a separate card", "B: leave as is" ], "recommendation": "A or B. It is outside H3's order fix, and it would add eager bytes to a renderer with tight budget headroom." } ], "out_of_scope_findings": [ "carrier: seat — the region grid uses viewport breakpoints (per Target 1), so on the dashboard template a half-width region cell at 1280 px gives tiles about 140 px each. Container-relative spans (Tailwind 4 @container) would fit narrow cells better but would not meet the 'one row at 1280' acceptance inside the console's sidebar layout. Noted, not filed", "carrier: seat — a page with two titled page:header blocks draws the description in each (it already has two h1s). Noted, not filed", "carrier: seat — print pages now lay region blocks on the grid too (print width folds to md). page-print's [data-region] > * break-inside rule still matches the cells. Noted, not filed" ] }Posted by the
domain:uiseat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) verbatim from the cloud session's final message, because a cloud session cannot comment from its container. The seat filledprwith the draft PR it opened, #12734.The seat's answers:
src/sales/pages/home.page.tsonmain. In themainregion, onepage:cardtitled "Key Performance Indicators" holds the fourobject-metrictiles inproperties.children. So the nesting is confirmed.Part of #12624, the seat widens this claim to the children ofpage:cardandpage:sectionincontainers.tsx, soFixes #12624holds. REWORK round 1 on PR feat(components): page regions lay blocks on a 12-column grid with kind-derived spans; the page description sits under its title #12734 carries it.span: A. The kind defaults meet the case, so no spec card is filed.yes. The seat's criterion is whether a card widens an accept set or the public surface. Four new runtime exports from@object-ui/componentswiden the published surface even though none is metadata. The PR body's line 2 readsClause-②: yes, and the seat's ownTIER_DEFAULTACCEPT covers it. The claim's reason sentence is superseded by this note.previews/PageBlockCanvas.tsxandmetadata-admin/i18n.tsjoin the claim's surface on record, for its stated purpose.page:headericon, and spec declaration outranks the card's prose.The three seat-carried notes stay noted, not filed.
Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
os-dev-report (delta, REWORK round 1 on PR #12734)
{ "issue": 12624, "status": "done", "round": "rework 1", "branch": "claude/issue-12624-page-block-grid", "head": "d643f37", "pr": "https://github.com/objectstack-ai/objectui/pull/12734", "session": "session_01NvsorV5JuG3fpRuUjWRHir", "premise_still_valid": true, "summary": "page:card and page:section now lay out a list of children on the same 12-column block grid as region blocks. A new shared PageBlockGrid in packages/components/src/renderers/layout/page-block-grid.tsx draws both region blocks and container children, and it uses the one pageBlockSpan table and PAGE_BLOCK_SPAN_CLASS map, with no second copy. A container in a region spans 12 by its own kind, and its children fold the same way as region blocks. A bare node or text child still renders through renderChildren. A rail context set by sidebar and aside regions keeps tiles nested in a container full width. In the Studio designer, the child cards of a page:card or page:section now show their span too. The changeset and page.mdx describe the container grid. Commits this round: 3205254 (feature, tests, docs, changeset) and the merge d643f37 of origin/main (c41ce3f, a real merge commit). Nothing was rebased, amended or force-pushed.", "tests": "At head d643f37: `pnpm exec vitest run --maxWorkers=2 packages/components/src/renderers/layout packages/components/src/__tests__ packages/app-shell/src/views/metadata-admin/previews` gives Test Files 354 passed | 1 skipped (355), Tests 3773 passed | 7 skipped. page-block-grid-12624.test.tsx now has 13 tests (4 new): (1) a page:card holding four object-metric children: the card cell spans 12, its children each span 3, and the rows come to 1 at 1280 px, 2 at 1024 px and 4 at 390 px; (2) a page:section does the same; (3) a container whose children are not tiles stacks them, spans [12, 12], 2 rows at 1280 px; (4) a container in a sidebar rail keeps its tiles at 12. PageBlockCanvas.blockSpan-12624.test.tsx now has 5 tests (1 new): a page:card's child cards read [card 12, 3, 3, 12]. Ablations ran on the committed fix 3205254, each with a trap restore through `git checkout HEAD -- ABSPATH`, a hash check against the HEAD blob, and an empty `git diff HEAD`. card: renderBlockChildren swapped for renderChildren in the page:card body; 3 failed (the card row test, the stack test, the rail test); restored 276d213efe1db74b254551c940a1fdc1dd41b2b0 == HEAD. section: the same swap in page:section; 1 failed ('a page:section does the same'); restored 276d213e… == HEAD. nestedrail: `isRail = !!rail` with the context read dropped; 1 failed (the rail container test); restored 3e04180f567068d63f486bc4b6d3af2b69fcf4f4 == HEAD. designernested: the badge guard forced to false; 1 failed ('the children of a page:card show the span the card lays them out at'); restored b39f9d41b310b18b246266878d2cb79cf931f522 == HEAD. For every leg the anchor count after the mutation was 0 and the injected text was found on disk. The four round-0 ablations still hold; the region path now runs through PageBlockGrid and its pins stay green. Browser (Chromium /opt/pw-browsers/chromium; a temporary console harness page with the HotCRM shape: page:header with title and subtitle, page description, then one page:card titled 'Key Performance Indicators' holding four object-metric tiles, plugin-dashboard loaded; harness deleted afterwards): after at 1280 px, 4 cells in one row (top 210, width 284 each); after at 1024 px, 2 rows of 2 (width 455); h1s 1; description after the title. Before (base page.tsx and containers.tsx checked out temporarily, then restored to the HEAD blobs ec3696fc and 276d213e): no grid, the four tiles stacked full width inside the card, and the description above the title, which matches the walk screenshot. The tile values read 0 in both legs because the harness's stub data source does not answer the metric query; the screenshots show layout only. PNGs were pushed to claude/issue-12624-evidence at ad7a480 (a child of 273d859): evidence/12624/hotcrm-shape-before-1024.png, hotcrm-shape-before-1280.png, hotcrm-shape-after-1024.png, hotcrm-shape-after-1280.png. scripts/__tests__ was not re-run this round; round 0 ran it green at 550bf27, and this round's diff touches no script.", "gates": "At d643f37, each exit code captured before any pipe: `pnpm --filter '@object-ui/app-shell^...' build` 0; `pnpm --filter @object-ui/components type-check` 0; `pnpm --filter @object-ui/app-shell type-check` 0; console closure build plus `VITE_BASE_PATH=/console/ pnpm --filter @object-ui/console exec vite build` 0. `pnpm check:eager-closure` 0: 'Console eager closure is 3120.8 KB gzipped across 73 of 2257 chunks (budget: 3744.1 KB, headroom: 623.4 KB)'. ui-components is 282.2 KB against a 338.9 KB ceiling; it was 281.9 KB in round 0, before the main merge. `pnpm check:changeset-claims` 0; `pnpm changeset:check` 0; `pnpm check:test-path-roots` 0; `pnpm check:spec-symbols` 0; `pnpm check:new-line-citations` 0; `node scripts/check-changeset-presence.mjs` 0. eslint on the changed files: exit 0, 0 errors. It reports one new warning: react-refresh/only-export-components on page-block-grid.tsx, because the file exports renderBlockChildren beside the component; it is left as is. Control-byte grep over the changed files: no hit. CI check-runs on PR #12734 are NOT MEASURED here (in progress after the push).", "mcp_calls": "0 this round", "api_writes": "0 — git pushes only: claude/issue-12624-page-block-grid (550bf27..d643f37) and claude/issue-12624-evidence (273d859..ad7a480, fast-forward)", "open_questions": [], "out_of_scope_findings": [ "carrier: seat — a page:card with one or more listed children now always draws them on the grid. Cards that hold several non-tile blocks gain gap-4 between them, where before they had no gap. page:section already used space-y-4, so its spacing does not change. Noted, not filed" ] }Posted by the
domain:uiseat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) verbatim from the cloud session's final message. The seat applies the dev's PR-body lines on #12734.The seat's answer to the one out-of-scope finding: the
gap-4that a card's non-tile children gain is accepted as part of this PR. It is the same gap region blocks have had since round 0, and one gap for every block list is the point of the shared grid. No card.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Landed ·
domain:uiseat 1 ·session_011K9g23W67dy2zwnyzaoB6o· 2026-10-16T17:09Zmainc9e67425ef, with one parent,2d9c81e. It is an ancestor oforigin/main.c41ce3f241to the reviewed headd643f37, changed line for changed line in each of the 13 files (+773/−23). Positive control at the merge commit:containers.tsxnamesrenderBlockChildren3 times (the import and both containers).completedbyFixes #12624. This note precedes the strip ofpm:dispatched, and the cloud session is archived.Generated by Claude Code