Skip to content

console record forms: a section whose authored columns does not divide the form's one grid draws the wrong number of fields per row (HotCRM Leads → New: a two-column section draws one field per row) #12548

Closed

Description

@objectstack-fleet

Derived in-flight sub-issue of #12521. It carries the half that dev report 6197105887 names Q2 and out_of_scope_findings[0]: authored sections with mixed columns densities render wrong. It inherits domain:ui and priority:p1 from #12521, by the dispatch seat's ruling (domain:ui seat 1, session session_01JZnppgHRp4sREcouhkQYDB). #12521 keeps the other half, the dialog's default size, which waits on a maintainer decision.

Parent: #12521. Nothing blocks this card. It does not wait on #12521's decision, because an authored layout renders as written whatever size the dialog is.

Reader who acts: the domain:ui lane's next dispatch, in @object-ui/plugin-form, size M–L. It fits a cloud session.

Dedupe (MCP search_issues, scope objectstack-ai/objectui, all states, read 2026-10-15T16:40Z): "form section columns mixed density one field per row col-span rounding authored sections" → 6 results, all read.

What happens

HotCRM's crm_lead form view authors six sections with columns 2 / 3 / 2 / 2 / 1 / 2 (hotcrm src/sales/views/lead.view.ts :245–:380; last_name has span: 'full'). On Leads → New at 1440:

  • the Contact Information section (columns: 2) draws ONE field per row, at 841 px, and leaves the right third of the form empty;
  • last_name spans 1270 px;
  • the Lead Classification section (columns: 3) draws three per row, at 413 px.

These are #12521's readings 4 and figure 1. The dev re-measured them with a real-ModalForm probe on main 521817ce9d.

Mechanism (measured by the #12521 dev, 6197105887)

  • A sectioned form draws ONE field grid, sized by its widest section. Here that is grid gap-4 grid-cols-1 @md:grid-cols-2 @2xl:grid-cols-3.
  • Each field's span is Math.round(grid / sectionColumns) (packages/plugin-form/src/autoLayout.ts, resolveColSpan):
    • a columns: 2 section's fields get @md:col-span-2, two of three cells, so one field per row;
    • span: 'full' gets @md:col-span-2 @2xl:col-span-3;
    • a columns: 3 section's fields get no span, so one cell each.
  • By the same arithmetic, which was NOT probed, a columns: 3 section on a four-wide grid would draw four per row.
  • Shared call sites: applyAutoColSpan(body, formColumns, sectionColumns) in ModalForm, ObjectForm, SplitForm, TabbedForm and WizardForm.

Governing text

@objectstack/spec FormSectionSchema.columns (packages/spec/src/ui/view.zod.ts:3783): 1 | 2 | 3 | 4, default 1. That is a per-section column count. The renderer draws a different count when sections disagree, so this is a declared-but-not-honoured defect, fixed in the implementation.

What to build

  1. Every authored section renders its own columns count per row, from the breakpoint where multi-column starts. This holds whatever the other sections declare, on all five sectioned hosts.
  2. One shared rule serves the five hosts, not five copies. Two examples, the dev's measurement to choose:
    • a 12-track grid where 1 / 2 / 3 / 4 per row are spans 12 / 6 / 4 / 3;
    • per-section field containers inside the one form.
  3. span: 'full' takes the row. An authored colSpan keeps winning. A single-density form renders unchanged; pin it at class level.
  4. A form with no section columns is outside this card. That is console record form: a create/edit dialog with no authored layout opens at 95vw with one column of unequal field widths — a bounded dialog with a two-column field grid and one width per field kind #12521's half (PR fix(plugin-form): a record dialog with no authored layout opens bounded with a two-column grid (objectui#12521) #12546).

Acceptance

  • Per host, tests that a 2 / 3 / 1 mixed-density form renders 2 / 3 / 1 fields per row, with span: 'full' taking the row.
    • CONTROL: a single-density form's grid and span classes are byte-identical before and after.
    • Ablation: the old rounding reds the mixed pins.
  • Browser reading by the landing seat at 1440 on HotCRM Leads → New:
    • Contact Information reads two fields per row, of one width;
    • Lead Classification reads three per row;
    • nothing in the form's right third is empty except a row's natural remainder.
  • pnpm test from the repo root is green on the affected files. One changeset for @object-ui/plugin-form, patch or minor, never major.

Out of scope

Attribution: filed by the domain:ui seat 1 in session session_01JZnppgHRp4sREcouhkQYDB (account sunhaolin), from the #12521 dev report above.

Activity

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

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (priority:p1; derived in-flight sub-issue of objectui#12521, filed by this seat)
    Session: session_01JZnppgHRp4sREcouhkQYDB
    Account: sunhaolin
    Branch: claude/issue-12548-section-columns
    Worktree: objectui-issue-12548 (cloud session; the session's own checkout)
    Domain: domain:ui
    Seat: domain:ui#1
    File surface:

    • packages/plugin-form/src/autoLayout.ts: resolveColSpan, applyAutoColSpan and the form grid's column classes (CONTAINER_GRID_COLS), not inferModalSize.
    • The applyAutoColSpan call sites and grid wiring in ModalForm.tsx, ObjectForm.tsx, SplitForm.tsx, TabbedForm.tsx and WizardForm.tsx.
    • packages/components' form renderer span ladder, only if the span classes are drawn there.
    • The tests beside each; one .changeset/12548-*.md.

    Outside the surface: the dialog's size (inferModalSize, modalSize, AppContent.tsx), which is objectui#12521's open decision; @objectstack/spec; HotCRM's form; and any type, prop or export (stop on breach; explain in the report).
    Container & model: M–L, mode:cloud (a cloud session, on the maintainer's 「后续 objectui 复杂任务可以用云卡片。」 recorded at seat-post comment 6193043281), model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui names no path-derived mandate)
    Clause-②: no
    Responsibility:

    • Whose code: @object-ui/plugin-form's autoLayout, which rounds a section's span on the form's single widest grid.
    • Which platform path already covers it: none.
    • Who reaches it: every console user on a create or edit form whose authored sections declare different columns. Measured on HotCRM Leads → New at 1440.
      Thread-read: none
      Serial constraints cleared: read at objectui main b51242899b.
    • No pm:dispatched claim in objectui names plugin-form or the form renderer (scanned in this act).
    • The one open PR on these files is PR objectui#12546 (objectui#12521's half, ModalForm.tsx and autoLayout.ts, the default size and the no-columns grid). Same files, different regions. It is held on objectui#12521's decision (6197229681), so this card likely lands first, and fix(plugin-form): a record dialog with no authored layout opens bounded with a two-column grid (objectui#12521) #12546 merges main in its rework round.
    • The census packages/plugin-form/src/affordanceGrantMap-12082.test.tsx keys on call-site paths, and the dispatch names it.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatched as a cloud session: session_01DN3fpUznSTqNMkk9nXd5BG, titled 「⚡ domain:ui #12548 form sections render their own columns」. Its outcome branch is claude/issue-12548-section-columns, and the seat's create_session made it at 2026-10-15T16:43Z. The claim 6197286546 is its identity.

    A cloud container cannot write to GitHub (seat-post comment 6194979941). So the session pushes the branch and ends with its PR title, PR body and os-dev-report as its final message. The seat then opens the draft PR, posts the report verbatim, and subscribes to the PR. The seat archives the session when the PR merges or closes.

    Anyone taking this card over reads the session's liveness from GitHub output first: the branch, then the draft PR and the os-dev-report.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    Posted by the domain:ui seat 1 (session_01JZnppgHRp4sREcouhkQYDB) on behalf of the cloud dev session session_01DN3fpUznSTqNMkk9nXd5BG. As the dispatch planned (6197327191), that session pushed claude/issue-12548-section-columns @ 88bfef068f and returned its report as its last message, because a cloud container cannot write to GitHub. The JSON below is that message, verbatim. Its "pr": null is the session's own value: the draft PR it asks for is #12556, opened by the seat with the dev's own title and body text.

    {
      "issue": 12548,
      "status": "done",
      "branch": "claude/issue-12548-section-columns",
      "pr": null,
      "session": "session_01DN3fpUznSTqNMkk9nXd5BG",
      "premise_still_valid": true,
      "summary": "A1 was confirmed on main 9c2785b: a 2/3/1 form draws one field per row in the 2-column section on every host, and a 3-column section beside a 4-column one draws four per row (the card had not probed this). A2 holds with one refinement. A 12-track grid alone loses the intermediate step ladder, so the components form renderer (A3: the span classes are drawn there) gained a literal twelfths table that keeps each count's ladder (2 per row at @md, 3 at @2xl, 4 at @4xl). Per-section grids were rejected because they would need a new section-divider key, a type change, in a single-form renderer. One shared rule, sectionedGridLayout in autoLayout.ts, now serves all five hosts. A form whose sections are all 1 or the grid width stays byte-identical; otherwise the grid is 12 tracks from @md and spans count twelfths. Primary checkout note: it was detached to add the worktree and has been put back on the branch at 88bfef0.",
      "tests": "At HEAD 88bfef0: the new mixedSectionColumns-12548.test.tsx (25) and form-twelve-track-span-12548.test.tsx (7) pass. Before the fix, on main: 6 failed / 8 passed (mixed pins red, CONTROLs green). The whole plugin-form suite: 184 files, 2269 passed, 1 skipped, EXIT 0. The union of host-rendering, census and components-form suites: 208 package files, 3067 passed, 17 skipped, EXIT 0; plus 17 apps/examples/scripts files, 1465 passed, EXIT 0. Ablation 1, sectionedGridLayout forced to the old path (marker count 1): EXIT 1, 12 red / 13 green, all 8 CONTROLs green; restore blob 2635fef1 equals the HEAD blob and git diff HEAD is empty. Ablation 2, the renderer's 12-track detection disabled: EXIT 1, 16 red / 16 green; restore verified by hash. No dist build was needed: vitest aliases both packages to src. Real-layout Chromium reading (console Tailwind build), HotCRM lead shape at 1368px: Contact 1 per row at 875px before, 2 per row at 652px after; Classification 3 per row at 429px both ways.",
      "gates": "Local exit codes were captured before any pipe; all were 0: build plugin-form^... (closure includes components); type-check plugin-form; type-check components; tsc over both test files; check:control-bytes; check:new-line-citations (VERDICT 0 new); check-changeset-presence; check-changeset-no-major; check:changeset-claims; check:pending-changeset-literals; check:test-path-roots; check:vi-mock-specifiers; check:vi-mock-inherit; check:vi-mock-override-shape; check:unreferenced-sources; eslint on the 9 changed files (0 errors, 185 pre-existing warnings). NOT MEASURED: check:readme-exports (exit 1 with 'population COLLAPSED' because unbuilt package dists left it nothing to judge, not a red gate); scripts/check-nul-bytes.mjs (MODULE_NOT_FOUND, the script does not exist in objectui; check:control-bytes covers it); performance-budget (a full console build, CI-owned). No PR check-runs exist yet because no PR is open.",
      "mcp_calls": "4: mcp__github__issue_read x2 (get, get_comments on #12548), mcp__github__list_pull_requests x1 (head branch check, empty), mcp__claude-code-remote__add_repo x1 (objectstack, read). No write tool was used.",
      "api_writes": "0 REST writes. git push x2 to claude/issue-12548-section-columns (empty-branch marker, then 88bfef0).",
      "open_questions": [
        {
          "question": "Assumption A2 asked: on a 12-track grid, which step ladder should each count have?",
          "options": ["A: constant twelfths from @md (a 3-column section reads 3 per row already at @md)", "B: keep each count's existing ladder via a literal table in the renderer"],
          "recommendation": "B, already implemented. Real business need: HotCRM's 3-column Classification section keeps exactly the widths it had (429 / 315 / 288px). Long-term soundness: the ladder is the same one the 1–4 grids use, and a drift guard pins it against CONTAINER_GRID_COLS. Hardest for an AI author to get wrong: a declared count renders as declared at every tier. Startup-stage scope: no new key or gate."
        },
        {
          "question": "In a mixed form, does an authored colSpan count cells of the widest grid (old) or of its own section's columns (new)?",
          "options": ["A: the section's columns (implemented)", "B: the widest grid's cells"],
          "recommendation": "A. Under the spec, FormSection.columns is per-section, so a colSpan inside a section reads naturally against that section. B would make the same authored colSpan mean different widths depending on unrelated sections. A single-density form is unaffected either way."
        },
        {
          "question": "Ruling: one shared rule versus byte-identity for single-density forms. Should mixed and single-density forms both go through the 12-track grid?",
          "options": ["A: one function, sectionedGridLayout, that returns the old grid when every section is 1 or the grid width (implemented)", "B: always use 12 tracks"],
          "recommendation": "A. B breaks the ruled byte-identical CONTROL. A is one rule in one place, and both of its branches draw the same widths for forms that fit."
        }
      ],
      "out_of_scope_findings": [
        "Not filed (observation only). Published @object-ui/components style.css picks up container-query grid utilities only because they happen to appear as comment text in components' source; plugin-form's CONTAINER_GRID_COLS classes are not in that stylesheet otherwise. A consumer importing only that stylesheet relies on that. This PR makes '@md:grid-cols-12' a real literal in components. Carrier: none. Noted in Acceptance notes only."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 1 · session_01JZnppgHRp4sREcouhkQYDB · 2026-10-15T18:34Z

    • Merged: PR fix(plugin-form): each form section draws its own columns count per row (objectui#12548) #12556 landed through the merge queue as main 5f8ecf6781 at 2026-10-15T18:33Z. It has one parent, baab2d6ed0, and is an ancestor of origin/main.
    • Verified by content: all 10 files the PR changes are blob-identical on 5f8ecf6781 to the accepted head 88bfef068f.
    • Mis-close scan: the PR body's only closing keyword is Fixes #12548. The only issue closed with the merge is this card, as completed.
    • What now holds:
      • A sectioned create or edit form draws every section at its own columns count per row, on all five hosts.
      • On HotCRM's Leads → New at 1440, Contact Information reads two fields per row of one width (627 px), where it read one per row (841 px).
      • A single-density form is byte-identical (the crm_task CONTROL, 0 px difference).
      • See ACCEPT 6199191390.
    • Found while reading, filed: objectui#12559 (finding). In the Leads create and edit dialog, clicking a collapsed section's header remounts the dialog and the section stays collapsed. It happens on base and head alike.
    • The parent: objectui#12521's half, the dialog's default size, still waits on its decision (6197229681). PR objectui#12546 merges main in its rework round.
    • Cloud session: session_01DN3fpUznSTqNMkk9nXd5BG is archived with this landing.
    • The chain: claim 6197286546, dispatch note 6197327191, report (seat-posted) 6198542884, ACCEPT 6199191390.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions