Repository navigation
fix(components): every wide centred dialog reads one shared viewport gutter (objectui#12603) - #12636
Conversation
…gutter Promote ModalForm's objectui#12593 gutter to one export, DIALOG_VIEWPORT_GUTTER in @object-ui/components, and have every wide centred DialogContent read it beside its sm cap: the action modal's lg/xl, ActionParamDialog, FlowRunner, CelTestRunDialog, the fullscreen editor, ImageLightbox, RecordPickerDialog and ImportWizard. A census in @object-ui/components enumerates every wide centred dialog and fails one that does not read the gutter. Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
…ensus Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
… to any Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Read in this act: card #12603 (body, triage ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Review: ACCEPT — objectui PR #12636 ·
|
Fixes #12603
Clause-②: yes
What changed
One shared rule for the viewport gutter of every wide centred dialog.
ModalForm's privateBOUNDED_SIZE_VIEWPORT_GUTTER(PR #12598) is promoted to one export of@object-ui/components:DIALOG_VIEWPORT_GUTTER = 'sm:w-[calc(100%-2rem)]', in the new modulepackages/components/src/custom/dialog-viewport-gutter.ts, re-exported throughcustom/index.ts. Every wideDialogContentnow writes its cap and this constant together, and nothing spells the gutter literal of its own.smup the dialog is the smaller of its max width and the window less 2rem. The primitive'sleft-[50%]/translate-x-[-50%]centres it, so it keeps 1rem either side.packages/components/src/ui/dialog.tsxis not edited.Sites:
app-shell/src/hooks/useActionModal.tsxSIZE_CLASS:lgandxl(the size a page-target modal with no size opens at).full,sm,defaultandSIDE_SIZE_CLASS(the sheet) are unchanged.app-shell/src/views/ActionParamDialog.tsx(long list),FlowRunner.tsx(object form or long screen),metadata-admin/CelTestRunDialog.tsx.components/src/custom/fullscreen-editor.tsx.fields/src/widgets/RecordPickerDialog.tsx: an unconditionalcn()argument, so every column-count rung reads it, thelg:max-w-5xlceiling included. Belowsmit stays95vw.fields/src/widgets/ImageLightbox.tsx: measured in the family (below).plugin-grid/src/ImportWizard.tsx.plugin-form/src/ModalForm.tsxreads the export instead of its private copy. Its class output is byte-identical.Census
packages/components/src/__tests__/dialogViewportGutterCensus-12603.test.ts(7 tests):packages/*/srcandapps/*/srcsource file whose comment-masked text (scripts/js-comment-mask.mjs) namesDialogContent.DialogContent/MobileDialogContentclassNamethrough the TypeScript AST:cn()arguments, conditionals, same-file consts and maps, and same-file function returns.sm:above 608px,lg:above 992px, and so on. A cap it cannot size fails closed.WIDE_SITESdeclares every wide centred dialog. Declared and found must agree, and each must read the gutter: a template literal that interpolatesDIALOG_VIEWPORT_GUTTERbeside the cap, or the constant as an unconditionalcn()argument.OWN_GUTTERis deletions-only. It holds one site, below.Runtime pins:
app-shell/src/hooks/__tests__/useActionModal.viewportGutter-12603.test.tsx(4 tests):xlandlgread the gutter. CONTROLs:fullkeepssm:max-w-[95vw] sm:w-full,defaultreads none, and asideplacement (sheet) reads none.fields/src/widgets/RecordPickerDialog.viewportGutter-12603.test.tsx(2 tests): the widest rung (six columns,sm:max-w-3xl lg:max-w-5xl) and the narrowest carry it.w-[95vw]stays for belowsm.Mechanism assumptions, as measured
main: its ownw-[95vw]kept a 2.5vw margin (16px at 640, 19.2px at 768, 25.6px at 1024). Under the one rule it now reads the shared gutter like the rest: 16px at every width fromsmup to the cap, and unchanged above the cap plus 2rem. The widest case is pinned.DIALOG_VIEWPORT_GUTTER, to the@object-ui/componentspackage entry. That is a widening, so the changeset declares@object-ui/componentsminor, and each other touched packagepatch. The second line of this body is corrected toyes.@tailwindcss/postcssoverapps/console/src/index.css, whose@sourcelines coverpackages/components/src. The emitted sheet carries.sm\:w-\[calc\(100\%-2rem\)\] { width: calc(100% - 2rem) }inside thesmmedia query. After this change the only tracked non-test source that spells the class iscomponents/src/custom/dialog-viewport-gutter.ts, so the rule comes from there.ImageLightboxis in the family. Itssm:max-w-4xlreplaces the92vwfromsmup. Measured with a 3000px-wide image, before: 768 gives 768 · 0/0 and 640 gives 640 · 0/0, edge to edge. After: 736 · 16/16 and 608 · 16/16. Folded in.Real-layout reading (headless Chromium 1194, static harness)
Each class list is the primitive's class string merged with the site's class through
tailwind-merge, ascndoes, over the console's emitted CSS. The viewport height is 900. Each cell is width · left/right gutter in px, before → after.xllg/CelTestRunDialog3xlsites (param list, flow, editor)4xlsites (import wizard, lightbox image)CONTROLs, identical before and after at every viewport: the action modal's
full(1368 · 36/36 at 1440, 608 · 16/16 at 640) anddefault(512 wide).Tests and gates
The heavy runs went through the shared verify lock. CI runs the full farm.
pnpm exec vitest run --maxWorkers=2 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).5bfde250b.@object-ui/app-shell, narrowed (declared): the fullpackages/app-shell/run (1434 test files) was stopped before it took the lock, because it would have held the shared lock for most of an hour with other seats queued. In its place, 126 files ran at5bfde250band passed: Test Files 126 passed (126), Tests 1127 passed (1127). They are every app-shell test that namesuseActionModal,ActionParamDialog,FlowRunnerorCelTestRunDialog(git grep -l), plus the package's ratchet and census tests (product-neutral-renderer,no-component-any-cast,actions-envelope,flow-envelopeand the rest). The blind spot: a test that renders one of these dialogs without naming its module is not in the set. CI runs the whole package.pnpm turbo run build --filter='@object-ui/app-shell^...' --concurrency=2exited 0 (28 tasks). Thentype-check(tsc --noEmit && tsc -p tsconfig.test.json) for components, app-shell, fields, plugin-grid and plugin-form exited 0.--listFilesOnlyconfirms that each test tsconfig compiles its new pin..ts/.tsxfiles (--no-inline-config): 0 errors. The new files carry no warnings. Every warning on the touched files is a pre-existingno-explicit-any/react-hookswarning on lines this diff does not touch.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, andnode scripts/check-hand-rolled-comment-mask.mjs.check-changeset-fixed,check-changeset-presence(14 source files of 5 released packages, 1 changeset declared),check-changeset-no-major. Alsocheck:new-line-citations(0 new) andcheck:test-path-roots.check-governed-queue-guard --teston the 15 paths answers NOT GOVERNED.Ablation
The fix was committed first.
ablation-replacemutated the action modal'sxlentry from the template with the gutter to the bare'sm:max-w-5xl', with a trapped restore. On disk the anchor count went from 1 to 0, and the blob changed fromc02389544865toee8915849adb.every listed wide dialog reads the shared gutternamedpackages/app-shell/src/hooks/useActionModal.tsx · DialogContent · sm:max-w-2xl sm:max-w-5xl.xl, the size a page target with no size opens at, reads the gutter beside its cap.git diff HEADis empty.Acceptance notes
lg" taken literally. Asm:max-w-xl(576px) orsm:max-w-[560px]dialog keeps at least 32px a side in the narrowestsmwindow, so it is never in this defect. The census counts a cap as wide only when a window at its breakpoint cannot hold it with 1rem a side. All the card's enumerated sites are caught, and one more as well (next note).OWN_GUTTER. It iscomponents/src/custom/navigation-overlay.tsx's modal mode:w-[calc(100vw-1rem)]under a host-set--ov-wcap, 42rem by default. It is never edge to edge, but it is a 0.5rem literal of its own. It was outside the card's enumeration and this PR's file surface, so it is not converged here. Carrier: none.smand its cap. It used95vw, and is now the window less 2rem: for example 736px instead of 729.6px at 768. That is the one rule replacing a per-site margin.classNamethat arrives from outside the file (a prop,schema.className), an inlinestylewidth, and a wrapper that renders aDialogContentunder another tag name. Each is stated in the census header.Implemented by the os-dev run in session
https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB.Generated by Claude Code