Skip to content

fix(plugin-form): object-form's modalCloseButton: false hides the modal's close button (objectui#11061) - #11091

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11061-modal-close-button
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11061-modal-close-button

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11061
Clause-②: yes

An author can now hide a modal form's close (X) button. object-form's modalCloseButton was declared on ObjectFormSchema and on the object-form registration, and ObjectForm's modal route forwarded it, but ModalForm never read it. As a result, modalCloseButton: false still drew the X, with no effect and no error. The route is A from the seat's answer on the card (comment 5889172074, amending claim 5888697196): the switch lives on the shared dialog wrapper.

What changes

  • @object-ui/components: MobileDialogContent (in custom/mobile-dialog-content.tsx) gains an optional showCloseButton, default true. false leaves the close button out of the DOM. The name and default match upstream Shadcn's DialogContent, whose parameter is showCloseButton = true in apps/v4/registry/new-york-v4/ui/dialog.tsx on shadcn-ui/ui main. That was read once for this PR, and nothing here re-derives it. The prop is destructured, so it never reaches the Radix content element as a DOM attribute. The props type is exported as MobileDialogContentProps through custom/index.ts's export *. That follows the package's convention: most custom/ wrappers export their props type. For the contract review: the published surface gains one optional member on the component's props, plus that one named type.
  • @object-ui/plugin-form: ModalForm computes showCloseButton = schema.modalCloseButton !== false once, and passes it on BOTH dialog arms: the flat form and the subforms master-detail form. Only an explicit false hides the button; unset and true keep it. The modalCloseButton JSDoc on ModalFormSchema now says this.
  • Registration description: the object-form registration's modalCloseButton description is restored to what is now true. It is honoured by ModalForm on both arms, and the modal still closes on Escape, and on the Cancel action when that is shown. The old wording before objectui#11050 carried a cross-file line address, which is not restored.
  • Docs: one modalCloseButton row is added to the object-form table in content/docs/api/schema-reference.md.
  • Changeset .changeset/11061-modal-close-button.md: @object-ui/components: minor, @object-ui/plugin-form: patch.
  • No change to packages/types.

Pins

  • packages/components/src/__tests__/mobile-dialog-show-close-button-11061.test.tsx
    • The wrapper renders the close button by default and with true.
    • With false, the dialog still renders its content but holds no button, and carries no leaked showclosebutton attribute.
    • Escape still reaches onOpenChange(false) with the button hidden.
  • packages/plugin-form/src/__tests__/modalCloseButton-11061.test.tsx
    • Driven through ObjectForm, so the forwarding hop is inside the pin.
    • The default and true keep the close button (the control rows).
    • false has no close button.
    • With it hidden, Escape reaches onOpenChange(false), and Cancel reaches onOpenChange(false) and onCancel.
    • The subforms arm keeps the button by default, and with false has none and still closes on Escape.

Verification (all at HEAD 6231bf720, branch merged with main a5841be35)

  • Locked run (os-verify-lock, 'VERDICT command-exit 0'; shared-box figures):
    • turbo run build --filter='@object-ui/plugin-form^...' --concurrency=2: 'Tasks: 11 successful, 11 total'.
    • pnpm --filter @object-ui/components type-check: exit 0.
    • pnpm --filter @object-ui/plugin-form type-check: exit 0. Both run tsc --noEmit and then tsc -p tsconfig.test.json.
    • pnpm exec vitest run --maxWorkers=2 covered packages/plugin-form/, the four mobile-dialog-* test files in components, and apps/console/src/__tests__/registry-inputs-spec-parity.test.ts. Result: 'Test Files 148 passed (148)', 'Tests 1918 passed | 1 skipped (1919)'. 148 is the 143 tracked plugin-form test files plus the 5 named files, so both new pins ran.
  • Ablation (locked, 'VERDICT command-exit 0', each leg through ablation-replace.mjs with a trap restore, both pins run each time):
    • Leg 1 changes ModalForm's const showCloseButton = schema.modalCloseButton !== false; to const showCloseButton = true;. The anchor went from 1 to 0 hits, and the blob changed from 62fd6bcd4e06 to afa17b9d7e7a. Result: 'Tests 2 failed | 9 passed (11)', the two false rows of the ModalForm pin.
    • Leg 2 changes the wrapper's {showCloseButton && ( to {true && (. The blob changed from dd2d1daed4fc to a357b2a3d367. Result: 'Tests 3 failed | 8 passed (11)', the wrapper's false row plus the same two ModalForm rows.
    • Restored: blob equals HEAD, git diff HEAD is empty, and 'Tests 11 passed (11)'.
    • As predicted, the Escape and Cancel rows stay green under both mutations: those ways out predate the fix. The pins that turn red are exactly the ones that read the new switch.
  • eslint on the changed files (--format json: 5 files): 0 errors.
    • Warnings on the three modified sources are 2, 20 and 23. Those equal their counts at base a5841be35, linted from git show through --stdin. The two new pins have 0.
    • Narrowing, declared: eslint.config.js configures no type-aware parsing (no projectService, project or TypeChecked config), so these per-file verdicts cannot move on an untouched file. The full pnpm lint is CI's.
  • Other gates, exit 0: node scripts/check-changeset-presence.mjs (1 changeset for 5 published source files of 2 packages), check-control-bytes, check-changeset-no-major, check:new-line-citations (0 new), check:component-surface-parity, check:changeset-claims, check:pending-changeset-literals, check:unreferenced-sources, check:test-path-roots, check:doc-types, check:doc-fences.
  • NOT MEASURED, prerequisite not met and left to CI:
    • check:readme-exports and check:doc-snippets need a whole-tree build.
    • check:sdui-registration-pins needs a console build.

Acceptance notes

  • The docs row in content/docs/api/schema-reference.md is outside the amended file surface. It was added under AGENTS.md Add automated testing infrastructure and CI/CD workflows #2 (docs-driven): one table row, no snippet.
  • The premise was measured at runtime before the fix. With modalCloseButton unset, true and false, the dialog drew 1, 1 and 1 close buttons, and Escape and Cancel already reached onOpenChange(false) in all three cases (report 5889134591 on the card).

Generated by Claude Code

…al's close button (objectui#11061)

`MobileDialogContent` gains an optional `showCloseButton` (default true);
`false` leaves its close button out of the DOM. `ModalForm` passes
`modalCloseButton !== false` to it on both dialog arms (the flat form and
the subforms master-detail form), so the declared and forwarded key now
takes effect. Escape and Cancel still reach onOpenChange(false).

The object-form registration description is restored to state that the
key is honoured by ModalForm, and the schema reference gains its row.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: components plugin tests labels Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 15 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/3917-retire-action-condition-branch.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    • ActionCondition is removed from @object-ui/types (and from the barrel export). - ActionSchema.condition is retyped to the predicate the runtime actually honours: boolean | string | { dialect?: string; source: string } — the same three arms ActionRunner's own ActionDef.condition carries, and the same vocabulary visible and disabled use. - ActionConditionSchema is removed from @object-ui/types/zod (and from the zod barrel); the condition key now validates against that predicate union. - The two teaching sites (content/docs/core/enhanced-actions.mdx Conditional Execution, content/docs/api/schema-reference.md ActionSchema table) are rewritten to the live vocabulary: condition is a gate; a branch is expressed as separate actions with mutually exclusive conditions.

.changeset/6170-retire-timeline-dead-keys.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Also in this change: the in-repo example packages/types/examples/data-display-examples.json (its timeline node authored all three) is migrated to items / variant; the two content/docs/api/schema-reference.md snippets that authored events: [] now author items: []; and the plugin-timeline docs callout says retired rather than deprecated.

.changeset/6896-retire-chart-inline-data.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    ⚠️ One correction to the record the ruling rests on. The ruling states zero authorship of a populated series[].data outside tests across packages/ / apps/ / examples/. The re-measurement finds one such site inside those roots — packages/types/examples/data-display-examples.json (2 series) — plus four outside them, in documentation: content/docs/api/schema-reference.md (3) and content/docs/core/report-schema.mdx (1).

.changeset/6939-kanban-column-cards.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Breaking, deliberately. KanbanColumn declared its card list as items in complex.ts and in the zod mirror complex.zod.ts. Every board reads cards. Measured on origin/main 78a3cc238: KanbanImpl.tsx reads .cards on 12 lines, KanbanEnhanced.tsx on 8, and bucketCardsIntoColumns twice more as col.cards || []; .items had zero read sites in either board (a same-shaped .title control on the same two files returns 8 and 3, so those zeros are readings and not a mis-shaped probe). Both catalog entries, the plugin docs and content/docs/api/schema-reference.md all author cards.

.changeset/6951-tree-view-data-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What was measured, on this branch's base. TreeViewSchema declared two spellings for its one inline-nodes slot — nodes (read second) and data (read third: boundData || schema.nodes || schema.data || [] at renderers/data-display/tree-view.tsx:105), both declared by objectui#6150. data had been REQUIRED until 777e5c6f4 (PR fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) #7533) made it optional, so this retirement starts from a declared-and-optional member on both faces. The in-repo corpus at the retirement: seven tree-view nodes under examples/schema-catalog and packages/types/examples plus one content/docs fence — six on nodes, two on data (packages/types/examples/data-display-examples.json and content/docs/api/schema-reference.md), both rewritten; no package source authored either spelling.

.changeset/7664-kanban-arm-plugin-dialect.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Migration. Author boards in the plugin dialect — objectName + groupBy for an object-bound board, or columns[].cards[] with badges for a static one. Replace DeclarativeKanbanSchema imports with KanbanSchema (from @object-ui/types, or the Zod KanbanSchema from @object-ui/types/zod; @object-ui/plugin-kanban re-exports the same KanbanSchema type). Delete draggable (drag-and-drop is always on) and column color (style a lane through className). content/docs/api/schema-reference.md's kanban section now documents this dialect.

.changeset/7997-detail-view-related-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Documentation. packages/plugin-detail/README.md and content/docs/api/schema-reference.md stop teaching the retired array and gain a migration block each.

.changeset/8268-testid-emitted-as-data-testid.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    The other direction — retiring the promise — was considered and declined. It is what c1fe272ad did for BaseSchema.hidden, but that key had a working behaviour to describe and zero named consumers, and the ruling's decline turned on exactly that. This promise already has carriers outside the type declaration: content/docs/api/schema-reference.md states it as a table row and authors testId in that page's own base-schema example, @object-ui/cli's OBJECTUI_STRUCTURAL_KEYS identifies a file as an ObjectUI schema node by this key, at this change ObjectGridSlotKey / ObjectFormSlotKey pin it, SchemaBuilder.testId() writes it, and ADR-0054 C4 — shipped — reads "the renderer emits data-testid … derived from metadata".

.changeset/8653-listview-title-retired-rowactiondefs-pinned.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    title — retired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/8801-object-kanban-allow-collapse-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

.changeset/9187-record-highlights-layout-two-values.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    ⚠️ The census behind this narrowing covers this repository only, and it found no in-repo authoring to migrate: every in-tree layout: 'grid' belongs to a different component (detail-view in content/docs/api/schema-reference.md and phase2-schemas.test.ts, ai-recommendations in packages/plugin-ai/README.md), and the one in-repo consumer of this interface that writes a layout (p1-spec-alignment.test.ts) writes 'horizontal'. So no document in this repository stops type-checking. A TypeScript consumer outside this repo that wrote grid is not observable from here and gets a compile error (TS2322) naming the key — which is why the FROM/TO is spelled out above.

.changeset/calendar-view-schema-converge.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Runtime renderer behaviour is unchanged. @object-ui/plugin-calendar's README and content/docs/api/schema-reference.md are repaired to the converged surface in the same change, so no copy of the old contradiction survives.

.changeset/issue-5373-retire-crud-schema.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    crud had four declaration faces and no registered renderer, for the whole life of the key: the TS interface (packages/types/src/crud.ts), the zod mirror (packages/types/src/zod/crud.zod.ts), a dedicated branch in validateSchema that affirmatively PASSED it, and CRUDBuilder in @object-ui/core. A node spelling it painted the OBJUI-001 "Unknown component type" panel, and content/docs/api/schema-reference.md published it as reference material — so a reader (or an AI author) who copied the page got a red panel.

  • names api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Authoring crud is now REFUSED BY NAME rather than passed or silently ignored. validateSchema returns an error with code: 'RETIRED_TYPE' on schema.type — at any depth, since it is what validateChildren recurses with — so assertValidSchema throws and isValidSchema answers false. The message names the migration: object-grid for the record table with its toolbar, filters, pagination and row/batch actions, object-form for the create/edit form, and detail for the record view. api/schema-reference.md is rewritten around those shapes.

.changeset/object-view-unmirrored-keys-7779.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    listViews stays unmirrored, on the ruling's own fallback clause. The declaration's value is the local NamedListView — 64 declared top-level members (⚠️ re-taken at objectui#8980, which added the seventeen the protocol declares on this surface to the 47 this entry first measured), of which the renderer reads 21 off a named view. data is one of the 21 now: it used to reach the renderer through an as any cast on the named-view config in packages/plugin-view/src/ObjectView.tsx and be declared nowhere, and the objectui#8980 ruling declared it by name — objectui#7928's open half, answered. The spec's ViewSchema.listViews is a record of the STRICT ObjectListViewSchema, which requires columns and refuses options, ObjectQL tuple filters and default — that is, it refused the named views this package's own README and content/docs/api/schema-reference.md taught when this entry was written ({ label: 'All Users' } fails at columns; filter: [["owner", "=", "..."]] fails at filter.0), and objectui#8255 has since rewritten them in the spec shape. Mirroring the spec value would have lost documented behaviour; mirroring the local value would enforce 43 unread members (64 declared, minus the 21 that are both declared and read) into the contract — the very thing ruling B refused for the six local keys. The key therefore stays in the parity ledger with that measurement, pinned, until the maintainer decides its value type. It is not papered over with z.any().

.changeset/table-renderer-declared-column-contract-5350.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What makes this site different from its three siblings is that the alias was not merely tolerated, it was published. content/docs/api/schema-reference.md §TableSchema shipped a copyable { "name": "id", "label": "#" } example and a property row reading "Column definitions with name, label, …", while packages/types declared the opposite pair. Docs and type disagreed about one type they both call TableColumn, each internally consistent. Retiring the alias without correcting the page would have turned a documented, working example into a silently broken one, so both halves land together: the page now authors accessorKey/header. The same row also advertised a render property that TableColumn has never declared — the renderer's hook is cell — and that claim is dropped rather than re-spelled.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 3e71a9825 (merge-base with origin/main): 6 file(s) changed outside .changeset/, read against 1745 pending declaration(s) that publish a body (2349 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3108.7 KB 3149.4 KB
Main entry chunk (gzip) 149.3 KB 350 KB
Entry file index-D2U9-i3t.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.81KB 133.93KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.99KB 63.22KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.68KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.54KB 61.80KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 169.69KB 43.37KB
plugin-gantt (index.js) 171.88KB 42.61KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 90.43KB 22.76KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 8.61KB 3.45KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.30KB 6.99KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6231bf720414a050668cb25f37929ea0b788cd19
Local-runs: none

Inputs read, and nothing else: card #11061 (body and all five comments: triage 5887233605, claim 5888697196, blocked report 5889134591, seat answer 5889172074, done report 5889821028); PR #11091 (body, 7-file list, net diff against main); the head's check-runs at filter=latest; head and base file reads for context only. Binding direction: triage 5887233605 as amended by 5889172074 (route A).

① Derived judgments

Every accept-set and public-surface change the diff implies, each judged:

  1. @object-ui/components public surface, plus one optional prop. MobileDialogContent gains showCloseButton?: boolean, default true; false leaves DialogPrimitive.Close out of the DOM. Published: custom/index.ts line 27 re-exports the module with export * (already so at base 3e71a98), and src/index.ts line 91 re-exports ./custom. The prop is destructured before the rest spread, so it never reaches the Radix Content element (the components pin asserts no leaked showclosebutton attribute). Default true keeps every existing caller's button. Right: it is the amendment's switch, with its name, shape and default.
  2. @object-ui/components public surface, plus one named type export. MobileDialogContentProps (the former inline React.ComponentPropsWithoutRef argument, intersected with the new member) is exported from the same module and so published through the same two export * hops. Additive; no existing name moves. Collision check at head: ui/dialog.tsx exports no Props-suffixed name, and the barrel's other export * sources were not found to carry the name; the head's Type Check run is the binding ambiguity check. Right; its admissibility under the amendment is judged in ② and ③.
  3. object-form's modalCloseButton accept set (behaviour, not type). ObjectFormSchema.modalCloseButton?: boolean (packages/types/src/objectql.ts line 1917) and the object-view form-slot key (line 2135) are untouched: the claim's ⛔ on packages/types held. ModalFormSchema.modalCloseButton?: boolean is unchanged, JSDoc only. Behaviour: ModalForm computes showCloseButton = schema.modalCloseButton !== false once and passes it on BOTH of its MobileDialogContent render sites. At head exactly two exist (line 1018, the subforms master-detail arm; line 1070, the flat arm), the same two as at base (1010, 1062). The forwarding hop at ObjectForm.tsx line 614 is unchanged. So false hides, and true or unset keeps. Right: the triage pins (false hides, the default shows) exactly, and only an explicit false moves anything.
  4. Another way out stays. The new pins assert that Escape reaches onOpenChange(false) on both arms with the X hidden, and that Cancel reaches onOpenChange(false) and onCancel on the flat arm. The round-1 probe (report 5889134591) measured that those routes already reached onOpenChange(false) before the fix, and the dev's ablation reports them green under both mutations, which is the expected direction: they pin the triage's dismissal requirement, not the switch. Right.
  5. object-form registration description (packages/plugin-form/src/index.tsx line 274): text only; the input's name and type are unchanged, so the registry's accept set does not move. The wording states "honoured by ModalForm" again, as the triage asked, without the pre-fix(plugin-form,plugin-view,types): object-form resolves its seven I18nLabel members (objectui#10993, batch 1) #11050 cross-file line address (Line Citation Gate green). Right.
  6. Pins. packages/components/src/__tests__/mobile-dialog-show-close-button-11061.test.tsx (default, true, false as a real getByRole absence plus a null querySelector('button'), Escape) and packages/plugin-form/src/__tests__/modalCloseButton-11061.test.tsx (driven through ObjectForm, so the forwarding hop is inside the pin; default and true as controls, false, Escape, Cancel, and the subforms arm's default and false). All four amendment pins are covered; the components pin is the amendment's "one new pin" option. Right.
  7. No-touch zone. packages/components/src/ui/** is untouched; the switch lives in custom/, where objectui AGENTS.md 构建一个 **Live Playground (实时演练场)** (用于展示引擎能力) #7 routes dialog behaviour. No new dependency, no new key, no new gate. Right.
  8. Docs row in content/docs/api/schema-reference.md: a documentation change with no code surface; judged in ③.

Nothing else in the 7-file diff implies a surface change.

② Semver level

  • Changeset .changeset/11061-modal-close-button.md: @object-ui/components: minor, @object-ui/plugin-form: patch. Matches what the diff publishes, and matches the amendment.
  • @object-ui/components minor is right: the published surface grows by one optional prop member AND one new named type export; both are additive and backward-compatible, which is the minor class exactly. minor covers the named type export: a new export is additive surface, the same level as the widened member, and nothing below minor would declare either. patch would under-declare; major is not warranted, since no existing caller changes. Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check and Changeset Claim Re-read are green on the head.
  • The new named export is admissible under the amendment: the amendment's Clause-② reasoning (the published props type widens by one optional member, hence @object-ui/components: minor) already places this PR in the additive-surface class at minor; naming that type adds no key, dependency, gate or behaviour, stays inside the amended file, and follows the package's convention. Verified at head by reading each file: 21 of the 27 custom/*.tsx wrappers export a Props-suffixed type, the figure the dev report and the PR body both name for this review. One wording gap and no level gap: the changeset prose describes the widened props type but does not name MobileDialogContentProps as a new export; the PR body does. Not a finding.
  • @object-ui/plugin-form patch is right: a declared and forwarded key starts taking effect; ModalFormSchema and the registration input shape are unchanged; no export moves. plugin-form depends on @object-ui/components at workspace:*, so the release tooling resolves the range to the new minor and no manual range edit is needed.
  • The Clause-②: line: PR body line 2 reads Clause-②: yes, the amendment's value (the claim's original no was amended to yes in 5889172074), and it is the right value for this diff, since a published type moves. Right.

③ Boundary flags

Round 1 open_questions (report 5889134591): ONE entry, the claim-widening question with options A to D. Answered by the seat in 5889172074 (route A, claim amended). The diff implements A as specified: the switch is showCloseButton on MobileDialogContent, default true; ModalForm passes schema.modalCloseButton !== false on both arms; the tests sit under packages/components/src/__tests__/ as one new pin. Closed.

Round 2 open_questions: empty. out_of_scope_findings: empty.

Round 2 deviations, each answered:

  1. Docs row (one modalCloseButton row in the object-form property table of content/docs/api/schema-reference.md): outside the amended file surface, added docs-driven under objectui AGENTS.md Add automated testing infrastructure and CI/CD workflows #2. Admissible: it documents the key exactly as the code now behaves (default true, false hides, Escape and Cancel still close), sits beside its sibling modalSize, and the same file already lists modalCloseButton in its "ObjectUI extensions" bullet, so the row makes the reference self-consistent. No snippet, so no doc-snippet exposure (Doc Snippet Type Check is green anyway). Landing-order note for the seat: the row is the last line of that table, at head line 851, two lines above the #### Spec alignment & extension keys heading at 853, the section objectui#10993 batch 2 also edits. The two changes do not overlap in content, but with git's 3-line context their hunks are adjacent, so whichever of fix(plugin-form): object-form's modalCloseButton: false hides the modal's close button (objectui#11061) #11091 and batch 2 lands second most likely needs a one-hunk rebase. Land in either order; rebase the second.
  2. Named props type MobileDialogContentProps, beyond the amendment's "one optional member" wording: admissible, per ② (additive, same package, same file, same level, package convention 21 of 27, no collision found at head).
  3. The verify lock needed two attempts: process only, no bearing on the diff; the head's check-runs are the gate verdicts here in any case.
  4. Commit trailers in the model-free pair: attribution, no bearing on the diff or the surface.
  5. Worktree removed after the PR opened: process only; the branch and its head 6231bf720 are on the remote as the PR's head.

Dev "NOT MEASURED" gates (check:readme-exports, check:doc-snippets, check:sdui-registration-pins) are answered by the head's check-runs: README Export Check green, Doc Snippet Type Check green, Build & E2E and Test (dist pins) green.

Check-runs on the head (filter=latest): 43 runs, all completed: 40 success, 3 skipped by workflow condition (dependabot and the two coverage jobs), 0 failure, 0 in progress. Every gate family the dev left to CI is green on this head: Type Check, Lint, the eight test shards and the aggregate Test job, Build and E2E, Test (dist pins), Build Docs, Spec Main Shape Gate, Governed Surface Queue Guard, and the changeset, doc, citation and control-byte gates.

Notes for the seat, with no verdict weight: the PR's mergeable_state reads behind (base main at 3e71a98 has moved past the branch's merge with a5841be35); this record names head 6231bf720 only, and an update of the branch makes a new head. The wrapper's new JSDoc also says a backdrop click reaches onOpenChange(false); that is unpinned prose, and the triage's named ways out (Escape, Cancel) are the ones pinned.

Implemented-by: claude/issue-11061-modal-close-button
Reviewed-by: session_011p7ikEivgXefNDaE5S5Uec

VERDICT: PASS


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation package: components plugin tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(plugin-form): object-form's declared modalCloseButton is never read, so modalCloseButton: false still shows the modal's close button

2 participants