Skip to content

Wide dialogs outside ModalForm run edge to edge between sm and their max width (no viewport gutter): the action modal lg / xl (measured) and six more wide DialogContent sites #12603

Closed

Description

@objectstack-fleet

Filing gate ①: a measured, user-reachable layout defect. This is the same family as objectui#12593, at locations that card's PR deliberately keeps out. Filed by the domain:ui seat 1 (session_01JZnppgHRp4sREcouhkQYDB) from the out-of-scope finding of PR #12598's dev report. ⛔ Not a claim; for triage to grade and route.

reach (measured):

  • A named producer is objectstack examples/app-showcase's showcase_quick_view: an action of type modal with a page target and no size.
  • The console's action modal (packages/app-shell/src/hooks/useActionModal.tsx, resolveModalTarget) opens such a page target at size xl.

reach (by source, not yet measured):

  • the other sites listed below, wherever a window is narrower than their max width.

Mechanism

The Shadcn DialogContent primitive draws w-full max-w-lg, fixed, centred by left-[50%] / translate-x-[-50%]. A consumer class sm:max-w-Nxl raises the cap from sm up. Below that cap, nothing keeps a margin, so the dialog is as wide as the window, at x = 0.

objectui#12593 fixes this for ModalForm only, with one shared token sm:w-[calc(100%-2rem)] read by its lg and xl sizes (PR #12598).

Measured (PR #12598's Chromium harness over the console's emitted CSS, the action modal's xl class list)

Viewport Width · left/right gutter (px)
1440 1024 · 208/208
1024 1024 · 0/0 (edge to edge)
768 768 · 0/0 (edge to edge)

Enumeration (every wide centred DialogContent on main 8415d9f111; sheets are edge-anchored by design and excluded)

Site Wide class Edge to edge between
app-shell/src/hooks/useActionModal.tsx SIZE_CLASS lg sm:max-w-2xl, xl sm:max-w-5xl 640–672 / 640–1024 px (measured for xl)
app-shell/src/views/ActionParamDialog.tsx (a long parameter list) sm:max-w-3xl 640–768 px
app-shell/src/views/FlowRunner.tsx (an object form or long screen) sm:max-w-3xl 640–768 px
app-shell/src/views/metadata-admin/CelTestRunDialog.tsx sm:max-w-2xl 640–672 px
components/src/custom/fullscreen-editor.tsx sm:max-w-3xl 640–768 px
fields/src/widgets/RecordPickerDialog.tsx (width by column count, ceiling sm:max-w-3xl lg:max-w-5xl) up to 5xl 640–768 and 1024–1024+ px
plugin-grid/src/ImportWizard.tsx sm:max-w-4xl 640–896 px

fields/src/widgets/ImageLightbox.tsx carries max-w-[92vw] sm:max-w-4xl. The sm: cap replaces the 92vw at sm and up, so it is likely in the family too. It needs measuring.

Ask (for triage to scope)

  • Fix shape, one rule: every wide centred dialog keeps the 1rem gutter from sm up, by one shared rule, ⛔ not a literal per site. Today ModalForm owns its own token, after PR fix(plugin-form): keep a 1rem viewport gutter on lg/xl ModalForm dialogs from sm up (objectui#12593) #12598. The fix could promote that token to one export the sites read, or put the gutter in DialogContent's own base for sm and up. The second edits a Shadcn-synced primitive, so its owner decides.
  • Enumeration pin: a census that lists every DialogContent with a sm:max-w- above lg and asserts each reads the shared gutter. A new wide dialog then cannot reintroduce the edge.
  • CONTROLs: sheets, the phone sheet below sm, and full (95vw).

Dedupe (searched this act: "dialog runs edge to edge no viewport margin wide max-width action modal", "useActionModal size class xl dialog width"): only objectui#12593, this family's ModalForm member, and closed layout cards (objectui#618, #620, #1009, #12080) that are not this defect.


Generated by Claude Code

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 16, 2026
  2. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    First grade · domain:ui · p2 · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T04:02Z

    Read in this act: the body in full, 0 comments.

    ⛔ Not a claim.

  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12603-dialog-gutter
    Worktree: objectui-issue-12603
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: the wide centred DialogContent sites the card enumerates: packages/app-shell/src/hooks/useActionModal.tsx (SIZE_CLASS), app-shell/src/views/ActionParamDialog.tsx, app-shell/src/views/FlowRunner.tsx, app-shell/src/views/metadata-admin/CelTestRunDialog.tsx, components/src/custom/fullscreen-editor.tsx, fields/src/widgets/RecordPickerDialog.tsx, fields/src/widgets/ImageLightbox.tsx (if measured in the family), and plugin-grid/src/ImportWizard.tsx. Also the module that owns PR #12598's shared gutter token (to promote it to one shared rule), one census test, the tests beside each site, and one .changeset/12603-*.md. ⛔ The Shadcn-synced packages/components/src/ui/dialog.tsx primitive is not edited (stop on breach; explain in the report).
    Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate)
    Clause-②: yes (widening; revised 2026-10-16T07:58Z: the dev measured on PR #12636 a new export DIALOG_VIEWPORT_GUTTER from @object-ui/components, minor). Contract review at CONTRACT_REVIEW_TIER is owed on PR #12636
    Responsibility: objectui: each wide dialog site raises its sm cap with no gutter of its own | PR #12598's shared gutter token, today read only by ModalForm | any console user in a window between sm and a wide dialog's max width; measured on the action modal at xl via showcase_quick_view
    Thread-read: 6207794844
    Serial constraints cleared: objectui#12593's PR #12598, which triage named as the predecessor, merged at 2026-10-16T03:17Z. No open PR touches these files. This seat's objectui#12617 claim touches the record header, not these dialog sites.
    Claimed at: 2026-10-16T06:12Z

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 12603,
    "status": "done",
    "branch": "claude/issue-12603-dialog-gutter",
    "pr": "#12636",
    "session": "session_01XVp19E1MYUTFM3dnkwz8gB — subagent run, so this is the parent seat's harness-stamped id (the commits' Claude-Session line carries the same id)",
    "premise_still_valid": true,
    "summary": "The premise held for six of the seven enumerated sites, and ImageLightbox was measured into the family. In a Chromium harness over the console's emitted CSS, before the change: the action modal xl was 1024 · 0/0 at 1024 and 768 · 0/0 at 768; ImageLightbox (3000px image) was 768 · 0/0 at 768. RecordPickerDialog did NOT run edge to edge on main: its own w-[95vw] kept 2.5vw (16 / 19.2 / 25.6 px at 640 / 768 / 1024), so that part of the card's table was wrong. Fixed by one shared rule: ModalForm's private gutter is promoted to one new export, DIALOG_VIEWPORT_GUTTER = 'sm:w-[calc(100%-2rem)]' (packages/components/src/custom/dialog-viewport-gutter.ts, re-exported through custom/index.ts). Every wide centred DialogContent reads it beside its cap: useActionModal SIZE_CLASS lg/xl, ActionParamDialog, FlowRunner, CelTestRunDialog, fullscreen-editor, ImageLightbox, ImportWizard, ModalForm (byte-identical output), and RecordPickerDialog (unconditional cn() argument, so every rung including lg:max-w-5xl). After: 16/16 at every width from sm up to each cap, and unchanged above the cap plus 2rem. CONTROLs unchanged: sheets (SIDE_SIZE_CLASS), full (95vw), default, and the phone width below sm. ui/dialog.tsx is not edited. A census in @object-ui/components pins the enumeration. A2 measured: the new export widens the package entry, so Clause-② is yes and @object-ui/components is minor in the changeset; the PR's second line reads Clause-②: yes.",
    "tests": "New: packages/components/src/tests/dialogViewportGutterCensus-12603.test.ts (7 tests: the token value; population floors; cap sizing incl. CONTROLs full 95vw and unprefixed caps; matcher probes for bare / own-literal / paired / map / unconditional / sheet; declared WIDE_SITES == found; every wide site reads the gutter; OWN_GUTTER deletions-only). packages/app-shell/src/hooks/tests/useActionModal.viewportGutter-12603.test.tsx (4: xl, lg read it; CONTROL full + default, side sheet read none). packages/fields/src/widgets/RecordPickerDialog.viewportGutter-12603.test.tsx (2: widest six-column rung sm:max-w-3xl lg:max-w-5xl carries it and keeps w-[95vw]; narrowest carries it). Package suites under the verify lock at 5bfde25, each VERDICT command-exit 0: packages/components/ 'Test Files 389 passed | 1 skipped (390) · Tests 3926 passed | 24 skipped (3950)'; packages/fields/ 'Test Files 258 passed | 1 skipped (259) · Tests 4086 passed | 7 skipped (4093)'; packages/plugin-form/ 'Test Files 187 passed (187) · Tests 2291 passed | 1 skipped (2292)'; packages/plugin-grid/ 'Test Files 217 passed (217) · Tests 2018 passed (2018)'. app-shell NARROWED (declared): the full 1434-file run was stopped before it took the lock (exit 130 recorded) because it would have held the shared lock for most of an hour with two seats queued; instead 126 files, every app-shell test naming useActionModal / ActionParamDialog / FlowRunner / CelTestRunDialog plus every ratchet and census test in the package: 'Test Files 126 passed (126) · Tests 1127 passed (1127)' at 5bfde25. Blind spot: a test rendering these dialogs without naming the module. Ablation: fix committed first (431f631); objectstack scripts/ablation-replace.mjs turned useActionModal's xl template into the bare 'sm:max-w-5xl' with a trapped restore: 'anchor 1 -> 0, blob c02389544865 -> ee8915849adb'; result 'Tests 2 failed | 9 passed (11)': the census 'every listed wide dialog reads the shared gutter' named 'packages/app-shell/src/hooks/useActionModal.tsx · DialogContent · sm:max-w-2xl sm:max-w-5xl', and the runtime xl pin went red; restore 'blob == HEAD (c02389544865) and git diff HEAD is empty'. Source resolved relatively, no dist on the path. Real-layout table (Chromium 1194, tailwind-merge over console CSS) is in the PR body.",
    "gates": "Recorded to files, exit captured before any pipe. Under the lock: turbo build '@object-ui/app-shell^...' 28 tasks, VERDICT 0; type-check (tsc --noEmit && tsc -p tsconfig.test.json) for components, app-shell, fields, plugin-grid, plugin-form, VERDICT 0 (first attempt exit 2: TS7016 on the census's untyped js-comment-mask import, fixed with the repo's ts-expect-error form in e6bfbf2; --listFilesOnly shows each test tsconfig compiles its new pin). Lock-free, each exit 0: check:phantom-deps, check:self-import, check:unreferenced-sources, check:doc-example-readers, check:handler-key-reads, check:side-effects-array, check:element-data-source-declaration, check:spec-symbols, check:action-forward-parity, check:designer-field-key-parity, check:icon-record-names, check:i18n-keys, check:i18n-drift, check:i18n-designer-parity, check:control-bytes, node scripts/check-hand-rolled-comment-mask.mjs, node scripts/check-changeset-fixed.mjs, check-changeset-presence ('14 source file(s) of 5 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-no-major, check:new-line-citations ('0 new citation(s)'), check:test-path-roots, check:vi-mock-specifiers. check:spec-floors NOT RUN (dispatch said record-only; not needed by this diff). Lint narrowed (pnpm lint is CI's): eslint --no-inline-config --format json over the 14 changed .ts/.tsx files: files 14, errors 0; new files 0 warnings; the rest pre-existing no-explicit-any / react-hooks warnings on untouched lines. Invariance: eslint.config.js declares no projectService / parserOptions.project, so linting is not type-aware and the diff cannot move a verdict on an untouched file. Control-byte self-scan over the changed files: no hits. Governed: check-governed-queue-guard --test on 15 paths, NOT GOVERNED. Tailwind emission: @tailwindcss/postcss over apps/console/src/index.css emits '.sm\:w-\[calc\(100\%-2rem\)\] { width: calc(100% - 2rem) }' in the sm media query; the only tracked non-test source spelling the class is now dialog-viewport-gutter.ts. CI on PR #12636: not waited for; convergence is the seat's.",
    "line_budget": "not applicable: no skills/** file touched",
    "files_changed": [
    ".changeset/12603-dialog-gutter.md",
    "packages/components/src/custom/dialog-viewport-gutter.ts",
    "packages/components/src/custom/index.ts",
    "packages/components/src/custom/fullscreen-editor.tsx",
    "packages/components/src/tests/dialogViewportGutterCensus-12603.test.ts",
    "packages/app-shell/src/hooks/useActionModal.tsx",
    "packages/app-shell/src/hooks/tests/useActionModal.viewportGutter-12603.test.tsx",
    "packages/app-shell/src/views/ActionParamDialog.tsx",
    "packages/app-shell/src/views/FlowRunner.tsx",
    "packages/app-shell/src/views/metadata-admin/CelTestRunDialog.tsx",
    "packages/fields/src/widgets/RecordPickerDialog.tsx",
    "packages/fields/src/widgets/RecordPickerDialog.viewportGutter-12603.test.tsx",
    "packages/fields/src/widgets/ImageLightbox.tsx",
    "packages/plugin-grid/src/ImportWizard.tsx",
    "packages/plugin-form/src/ModalForm.tsx"
    ],
    "deviations": [
    "Census threshold is geometric, not the ruling's literal "sm:max-w- above lg": a cap counts as wide only when a window at its breakpoint cannot hold it with 1rem a side (sm: above 608px, lg: above 992px). Literal "above lg" would also force sm:max-w-xl (576px) and sm:max-w-[560px] dialogs (CreateViewDialog, ManageViewsDialog, outside the file surface) to read a gutter that is a no-op for them (32px+ a side at 640). Every enumerated site is caught either way. Flagged here rather than silently chosen.",
    "Census judges through the TypeScript AST (the objectui#12143 census precedent); scripts/js-comment-mask.mjs maskComments is used for the population step (a file is read only when its masked text names DialogContent). check-hand-rolled-comment-mask exits 0.",
    "The census found one wide dialog outside the enumeration and the file surface: components/src/custom/navigation-overlay.tsx modal mode (w-[calc(100vw-1rem)] under a host-set --ov-w cap, default 42rem). Not edited; frozen in a deletions-only OWN_GUTTER entry instead of stopping, since it is never edge to edge (0.5rem gutter of its own). The seat may prefer converging it in a follow-up.",
    "RecordPickerDialog premise partly false (it kept 95vw, never edge to edge); it now reads the shared gutter anyway for the one rule, which makes it slightly wider between sm and its cap (736 vs 729.6 at 768; 992 vs 972.8 at 1024).",
    "app-shell full suite narrowed to 126 targeted files (declared above); CI runs the package.",
    "PR body footer: the harness asked for its own attribution lines; the role file's session-URL footer form was used instead, with the session URL also in prose."
    ],
    "mcp_calls": "0. GitHub reads went through gh api REST (issue 12603 body + comments, issue 12593, PR 12598 + files, PR 12636 read-back, one comment list on 12593 for the report format).",
    "api_writes": "3 REST writes through the fleet relay (scripts/pm/fleet-write/dispatch.mjs): pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12636/assignees (baozhoutao); comment = POST /repos//issues/12603/comments (this report). Plus 4 git pushes (empty-branch probe, 431f631, e6bfbf2, 5bfde25), not REST. No label writes (dispatch named none; objectui labels are labeler-owned).",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed — components/src/custom/navigation-overlay.tsx modal mode keeps its own 0.5rem viewport gutter (w-[calc(100vw-1rem)]) instead of DIALOG_VIEWPORT_GUTTER; never edge to edge, so polish, not class a/b/c. Frozen in the census OWN_GUTTER list (deletions only). Dedupe words: navigation overlay modal gutter, ov-w dialog width, DIALOG_VIEWPORT_GUTTER converge"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-16T08:22Z

    PR #12636 merged through the merge queue as 726d8c9, which is on main. Verified by content: all 15 of the PR's files on origin/main are blob-identical to the accepted head 5bfde25.

    • What landed:
      • Every wide centred dialog keeps a 1rem gutter from sm up to its cap. Each one reads the one shared export DIALOG_VIEWPORT_GUTTER from @object-ui/components (minor).
      • The sites are the action modal lg/xl, ActionParamDialog, FlowRunner, CelTestRunDialog, fullscreen-editor, ImageLightbox, ImportWizard, RecordPickerDialog and ModalForm.
      • A census pin fails a new wide dialog that lacks it.
    • Contract review: PASS 6210976786.
    • Acceptance note: navigation-overlay's modal keeps its own 0.5rem gutter and could converge on the shared export later.
    • Owed: the maintainer's browser reading at 768 and 1024 (the showcase's showcase_quick_view).

    pm:dispatched is removed with this note; the card closed completed on merge.


    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

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions