Skip to content

feat(plugin-chatbot,app-shell): Preview for any copilot draft kind, its deep-link table, the console wiring and the preview bar's pending line (objectui#12968) - #12973

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-12968-draft-preview-any
Oct 18, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-12968-draft-preview-any

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #12968
Clause-②: yes — ChatbotEnhancedProps gains the optional onPreviewDraft, so @object-ui/plugin-chatbot is at minor.

objectstack-ai/objectstack#24324 ruling A (preview-first). This pull request lands the half of the card that lives inside the claimed file surface. The console wiring of the new Preview callback lives in packages/app-shell/src/console/ai/AiChatPage.tsx (the ChatPane that renders ChatbotEnhanced for the console, the Studio copilot and the chat dock) and in LiveCanvas.tsx, which is not in the claim. So the card stays open, and merging this pull request must not close it.

What changes

  • @object-ui/plugin-chatbot. A drafted tool result whose items include no app now draws Preview beside Publish and Review. It draws when the host passes the new optional onPreviewDraft(items, { appSegment }) and the result names its owning packageId. The click hands the host the drafted { type, name } items and the package id, which is the app's route segment (ADR-0048). A draft set that includes an app keeps onPreviewDraftApp, unchanged. A host that passes no onPreviewDraft draws exactly what it drew before. Preview publishes nothing.
  • @object-ui/app-shell, preview/draftSurface.ts. This file maps a draft to the surface that shows it, in both directions.
    • draftPreviewPath(items, appSegment) is the deep-link table, always under ?preview=draft: a page → that page; a view → the object's list on that view; a field or object → the object's default list; an app, or a kind with no surface of its own → the app (the URL the Live Canvas already opens). A batch previews its most specific surface. Paths come from @object-ui/layout's resolveHref, the console's one nav-to-URL mapping. No second URL scheme is spelled.
    • draftSurfaceOf and draftsOnSurface map the route the preview shows back to the pending drafts that belong to it. They match the route against APP_ROUTE_METADATA_READS, the table of every route AppContent declares.
  • DraftPreviewBar. When the surface it shows has pending drafts of its own, the message names them on one line, from the entries usePendingDrafts already reads: 「草稿预览 · 本页 2 项未发布」 / "Draft preview · 2 unpublished changes on this page". Everything else in the bar is unchanged: the package-wide count stays on Changes, and Exit and Publish are untouched. No new request is made.
  • @object-ui/i18n. preview.draftBar.pendingHere is a plural family in all ten packs, with each pack's CLDR categories, following preview.changes.summaryItems.

Measurements (the dispatch's mechanism assumptions)

  • A1 (before the change), held. On the unmodified tree (43efd078aa), ChatbotEnhanced.draftPreviewAny-12968.test.tsx rendered an add_field drafted result (type: 'object', changedKeys: ['fields'], packageId: 'app.crm') with the console's onPreviewDraftApp wired. It drew no Preview: Unable to find an element by: [data-testid="draft-preview"], 4 failed and 3 passed. The draft-preview-app control also found no element. View and page drafts were the same.
  • A2, partly held. A drafted result carries { type, name } items and packageId (detectDraftResult). changedKeys is not lifted, and no record id is carried.
    • The object of a view or field draft comes from its qualified name: the spec's ViewItemNameSchema says the object "can be recovered from the prefix", and a field row is keyed by the object and field name. No new request is needed.
    • A field added to an existing object is drafted as the object itself.
    • Not derivable: the record row ("the record page when the draft names a record"). No drafted envelope this chat lifts names a record, so that row is not implemented. See the open questions in the report.
  • A3, held, with a rule chosen. The bar sits above the app's nested routes. It reads the splat of /apps/:appName/* (useParams()['*']) and matches it against APP_ROUTE_METADATA_READS. The rule: an object route (its list, a view, a record) counts the drafts of type object with that name, plus the view and field drafts under that object. A page, dashboard or report route counts the draft of that name. A route with no single surface (the app landing, metadata admin, search) shows today's message.
  • A3, the field-draft sentence: not derivable. _drafts rows (ListDraftsResponseSchema) carry type, name, member, label, packageId, updatedAt and updatedBy. They carry no changedKeys, and a field draft is an object draft. So "this pending draft adds a field" cannot be read from the count usePendingDrafts already reads. The sentence is not shipped. See the open questions in the report.

Tests

The run is at f9a8e54cb0, with vitest --maxWorkers=1, under the shared lock.

  • New: packages/plugin-chatbot/src/__tests__/ChatbotEnhanced.draftPreviewAny-12968.test.tsx (7 tests). It covers field, view and page drafts → Preview with items and segment; the app row unchanged; CONTROL with no onPreviewDraft; CONTROL with no package; and that Preview makes no publish call or request.
  • New: packages/app-shell/src/preview/__tests__/draftPreview-12968.test.tsx (18 tests). It covers the deep-link table, one row per kind with the app row unchanged; the surface filter; the bar's line on an object list and on a page; CONTROL on a surface with nothing pending, on the app landing, and outside preview (nothing drawn, nothing read).
  • Families that name the Preview button, DraftPreviewBar, usePendingDrafts, PreviewModeContext or ?preview=draft, plus the locale-content i18n tests: 43 files / 350 tests passed, and 35 files / 363 tests passed. The existing DraftPreviewBar.test.tsx and ChatbotEnhanced.test.tsx are unchanged and green.
  • Ablation, once. ablation-replace mutated return onPreviewDraft && appSegment ? ( to return false && onPreviewDraft && appSegment ? (, which restores the app-only condition. The anchor went from 1 to 0 hits and the blob from 2e650291b551 to 65a99e6ea4b5. The new chatbot pin went red with 4 failed and 3 passed. The restore was proved by blob hash: the blob after restore equals the HEAD blob 2e650291b551, and git diff HEAD was empty.
  • Declared to CI: the full test directories of packages/plugin-chatbot, packages/app-shell and packages/i18n.

Gates

  • Closure build turbo run build --filter='@object-ui/app-shell^...' reported 28 of 28 tasks successful.
  • type-check: @object-ui/plugin-chatbot exit 0 and @object-ui/i18n exit 0. @object-ui/app-shell: the result is in the dev report (the shared lock queue).
  • Lint, narrowed to the 15 changed files (eslint --no-inline-config --format json): 15 files, 0 errors. All 11 warnings are on ChatbotEnhanced.tsx lines this diff does not touch. The config enables no type-aware linting, so the diff cannot move a verdict on an untouched file.
  • Each of these exited 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, check-hand-rolled-comment-mask.mjs, check-changeset-fixed.mjs, check-changeset-presence.mjs and check:new-line-citations.

Not in this pull request (named, so the card keeps them)

  1. Console wiring. ChatPane must pass onPreviewDraft and open draftPreviewPath(items, appSegment). For example, the Live Canvas needs to accept a path beside the app segment, or the handler navigates. The files are AiChatPage.tsx (objectui#12965's claimed surface) and LiveCanvas.tsx, both outside this claim. Until then the console still draws Preview only for app drafts.
  2. The 确认修改 applied card. Copilot edits that pass the confirm gate land on the applied-card path (replayOutcome drafted). That path lifts no drafted items (detectReplayOutcome in mapMessages.ts, outside the claim), so it draws no Preview yet.
  3. The field-draft sentence on the bar, and the record row. These need a decision (see above).

Acceptance notes

  • Nothing publishes and no publish path is added. /data is not read. AiChatPage.tsx is untouched. The published-only and member-view tests are unchanged.
  • draftSurface.ts is not exported from the @object-ui/app-shell barrel, so the package's public face does not grow.
  • Adds no product-specific widget, dialog, hook or string (see AGENTS.md › Product-neutral renderer)

The session is https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB.

Patch round (seat edit; head 58d0559, seat rulings 6251864146, os-dev-report 6252456713)

  • origin/main 32a171e565 (objectui#12965 / PR fix(app-shell): the Studio copilot draws no pending-drafts bar over its composer (objectui#12965, part of objectui#12725) #12975) merged in as 174146b444, no conflicts; AiChatPage.tsx's PendingDraftsBar region is byte-for-byte main's.
  • ChatPane passes onPreviewDraft: the Live Canvas opens draftPreviewPath(items, appSegment)'s target under ?preview=draft beside the conversation (the host stays on the conversation, as onPreviewDraftApp does and objectui#12724 rules). LiveCanvas takes an optional full path beside the app segment; the app-draft path is unchanged.
  • detectReplayOutcome lifts a drafted replay's drafted[] into ReplayOutcome.items (shared draftedItemsOf), so the confirm-gated applied card, and the drafted badge an older cloud sends, draw Preview through the same door (draftPreviewAction: an app goes to onPreviewDraftApp, anything else to onPreviewDraft).
  • ChatbotEnhanced.tsx carries the applied-card and badge Preview buttons (within the claim's original surface). Two exact-shape mapMessages pins gained the new items key.
  • Ablation: dropping onPreviewDraft in ChatPane turns 4 pins red (the app-path control stays green); restore proven by blob hash e81282cedec4.
  • Rulings 2 and 3 stand: no field-draft sentence on the bar (the applied card already says it), no record row.

Generated by Claude Code

… any draft kind (objectui#12968)

A drafted tool result whose items include no app now draws Preview when the
host passes the new `onPreviewDraft`; the click hands the host the drafted
items and the package id (the app's route segment). A draft set that
includes an app keeps `onPreviewDraftApp`, unchanged. Preview publishes
nothing.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
…es what is pending on the surface it shows (objectui#12968)

`preview/draftSurface.ts` maps drafted items to the router path of the
affected surface in draft preview (a page, the object's list on a view, the
object's default list, else the app), built with `@object-ui/layout`'s
`resolveHref`; and maps the route the preview shows back to the pending
drafts that belong to it, matched against `APP_ROUTE_METADATA_READS`.
The DraftPreviewBar's message names the count pending on that surface,
through `preview.draftBar.pendingHere` in all ten packs.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
…the any-draft Preview (objectui#12968)

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3162.2 KB 3744.1 KB
Main entry chunk (gzip) 80.8 KB 350 KB
Entry file index-DsUOglU3.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) 21.41KB 7.88KB
app-shell (runtime-config.js) 24.52KB 8.48KB
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) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 612.39KB 149.02KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 291.72KB 74.47KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.33KB 11.96KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
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.04KB 3.92KB
plugin-calendar (index.js) 55.04KB 16.10KB
plugin-charts (index.js) 85.06KB 23.39KB
plugin-chatbot (index.js) 231.24KB 54.35KB
plugin-dashboard (index.js) 147.53KB 39.95KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 266.04KB 70.50KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 181.16KB 46.84KB
plugin-gantt (index.js) 178.46KB 45.13KB
plugin-grid (index.js) 272.87KB 75.68KB
plugin-kanban (index.js) 63.37KB 19.51KB
plugin-list (index.js) 139.82KB 36.04KB
plugin-map (index.js) 27.17KB 9.00KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.31KB 11.88KB
plugin-tree (index.js) 15.27KB 5.40KB
plugin-view (index.js) 94.68KB 24.36KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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) 125.37KB 41.04KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.71KB 2.11KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 1.13KB 0.65KB
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) 5.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
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 (product-blocks.js) 0.20KB 0.19KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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

…e change drafted (objectui#12968)

detectReplayOutcome lifts a drafted replay's drafted[] items into
ReplayOutcome.items (one helper shared with detectDraftResult), and the applied
card and the bare drafted badge draw Preview through the same onPreviewDraft /
onPreviewDraftApp rule the drafted result uses (draftPreviewAction).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
…e Canvas (objectui#12968)

ChatPane passes onPreviewDraft: a field, view or page draft's Preview opens the
Live Canvas at draftPreviewPath's target (?preview=draft). LiveCanvas accepts a
full preview path beside the app segment; the app draft path is unchanged.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3163.6 KB 3744.1 KB
Main entry chunk (gzip) 80.2 KB 350 KB
Entry file index-Tr0R3vdj.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 (buildIdentity.js) 0.80KB 0.49KB
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.41KB 7.88KB
app-shell (runtime-config.js) 24.52KB 8.48KB
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) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 612.39KB 149.02KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 292.36KB 74.70KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.33KB 11.96KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
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.04KB 3.92KB
plugin-calendar (index.js) 55.04KB 16.10KB
plugin-charts (index.js) 85.06KB 23.39KB
plugin-chatbot (index.js) 232.44KB 54.60KB
plugin-dashboard (index.js) 147.53KB 39.95KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 266.04KB 70.50KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 181.16KB 46.84KB
plugin-gantt (index.js) 178.46KB 45.13KB
plugin-grid (index.js) 272.87KB 75.68KB
plugin-kanban (index.js) 63.37KB 19.51KB
plugin-list (index.js) 140.49KB 36.24KB
plugin-map (index.js) 27.17KB 9.00KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.31KB 11.88KB
plugin-tree (index.js) 15.27KB 5.40KB
plugin-view (index.js) 94.68KB 24.36KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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) 125.37KB 41.04KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.71KB 2.11KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 1.13KB 0.65KB
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) 5.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
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 (product-blocks.js) 0.20KB 0.19KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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

… items; changeset names the wiring (objectui#12968)

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3163.6 KB 3744.1 KB
Main entry chunk (gzip) 80.3 KB 350 KB
Entry file index-jQLh7gZ7.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 (buildIdentity.js) 0.80KB 0.49KB
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.41KB 7.88KB
app-shell (runtime-config.js) 24.52KB 8.48KB
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) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 612.39KB 149.02KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 292.36KB 74.70KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.33KB 11.96KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
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.04KB 3.92KB
plugin-calendar (index.js) 55.04KB 16.10KB
plugin-charts (index.js) 85.06KB 23.39KB
plugin-chatbot (index.js) 232.44KB 54.60KB
plugin-dashboard (index.js) 147.53KB 39.95KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 266.04KB 70.50KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 181.16KB 46.84KB
plugin-gantt (index.js) 178.46KB 45.13KB
plugin-grid (index.js) 272.87KB 75.68KB
plugin-kanban (index.js) 63.37KB 19.51KB
plugin-list (index.js) 140.49KB 36.24KB
plugin-map (index.js) 27.17KB 9.00KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.31KB 11.88KB
plugin-tree (index.js) 15.27KB 5.40KB
plugin-view (index.js) 94.68KB 24.36KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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) 125.37KB 41.04KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.71KB 2.11KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 1.13KB 0.65KB
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) 5.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
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 (product-blocks.js) 0.20KB 0.19KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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: 58d05593c87cd494658333bda25b777af3c2efe9
Local-runs: none

Inputs read: card #12968 (body and all four comments: claim 6251068724, os-dev-reports 6251840024 and 6252456713, seat rulings 6251864146), PR #12973 (body, 24-file list, the whole net diff against main, 1,593 lines read in full), the 35 check-runs on the head, and, read-only at the head sha, the sources the diff calls: @object-ui/layout resolveHref, APP_ROUTE_METADATA_READS, the app-shell and plugin-chatbot barrels, usePendingDrafts, PreviewModeContext, detectReplayOutcome, ConsoleLayout / AppContent (where the bar mounts), .changeset/config.json, the changeset gate headers, and objectstack ADR-0033 / ADR-0037.

① Derived judgments

Public-surface changes the diff implies (complete list, each judged):

  1. @object-ui/plugin-chatbot — ChatbotEnhancedProps.onPreviewDraft?(items, { appSegment }), exported through the barrel (export type { ChatbotEnhancedProps … } from './ChatbotEnhanced'). Additive optional prop. Right, and correctly declared as the Clause-② trigger.
  2. @object-ui/plugin-chatbot — ReplayOutcome.items?, exported through the barrel (ReplayOutcome is in the export type { … } from './mapMessages' list). Additive optional key; detectReplayOutcome now emits it on every drafted outcome whose envelope carries drafted[] or a type/name pair (every cloud sends one), so any consumer deep-equal on the drafted shape moves — the two in-repo exact-shape pins were updated in the same diff, no other consumer exists in the barrel's surface. Right, covered by the minor.
  3. @object-ui/plugin-chatbot — DOM: draft-preview testid for a non-app drafted result, applied-card-preview on the applied card, proposed-changes-preview on the bare drafted badge; draft-preview-app kept for the app row. Not a typed contract; the app row's testid and callback are byte-identical to before. Right.
  4. @object-ui/app-shell — preview/draftSurface.ts (draftPreviewPath, draftSurfaceOf, draftsOnSurface, DraftItem, DraftSurface): not in the barrel (confirmed by reading src/index.ts at the head; the package's exports map is . and ./styles.css only). LiveCanvasProps.path?: LiveCanvas is not in the barrel. DraftPreviewBar's pending line: DraftPreviewBar is not in the barrel (mounted only by ConsoleLayout). ChatPane's onPreviewDraft handler is internal wiring. No public type of @object-ui/app-shell grows. Right, so patch is the semver-correct level for the package's published face (see ② for the card's wording).
  5. @object-ui/i18n — preview.draftBar.pendingHere plural family in all ten packs (en/zh/ja/ko one+other; de one+other; es/fr/pt one+many+other; ru one+few+many+other; ar zero…other). Additive keys on the published locale objects; check:i18n-keys / i18n-drift / designer-parity are inside the green Repo Gates and Lint runs. Right.

Those are the only public-surface changes. The PR body's "draftSurface.ts is not exported from the barrel" claim is true at the head.

A host that passes neither callback is unchanged. draftPreviewAction returns undefined when the set is empty; when the set includes an app it needs onPreviewDraftApp (and ignores onPreviewDraft); otherwise it needs both onPreviewDraft and packageId. The drafted-result button went from onPreviewDraftApp && !draftIsPublished ? (app ? button : null) to !draftIsPublished ? (preview ? button : null) — the same truth table for app drafts, and nothing drawn for a non-app draft with no onPreviewDraft. The applied card's action row adds onPreview to its onReview || onPublish || onDiscard gate only; with no door it is undefined. The outer draftReview gate widened from (onPublishDrafts && packageId) to ((onPublishDrafts || onPreviewDraft) && packageId), which is identical when onPreviewDraft is absent. An app draft still goes through onPreviewDraftApp only, with the same (app.name, { materialized, appSegment }) arguments — pinned by an app draft keeps the app button and its callback, unchanged. Right.

draftPreviewPath builds every target with the console's existing route builder, always under ?preview=draft. It spells no route: page / dashboard / report / view / field / object become a NavigationItem and go through @object-ui/layout's resolveHref(entry, '/apps/' + appSegment); an app or an unranked kind takes the bare app path. Read against resolveHref at the head: object without recordId/filters gives /apps/SEG/OBJ or /apps/SEG/OBJ/view/VIEW; page gives /apps/SEG/page/NAME (no params passed, so no querystring); then PREVIEW_QUERY_FLAG=PREVIEW_QUERY_VALUE (preview=draft, the same constants PreviewModeContext reads) is set on the parsed query, so a page href that ever carried params would keep them and still gain the flag. Table: page → that page; view → the object's list on that view (object recovered from the object.view prefix); field / object → the object's default list; app or unknown kind → the app. A batch takes the most specific surface by SURFACE_RANK. LiveCanvas.canvasSrc prefixes spaBase() and the materialized branch still wins, so the real app shows once built. Right. (Minor, not a defect: appSegment is interpolated unencoded, as the console's own nav links do; package ids are URL-safe.)

Nothing on the Preview path publishes, reads /data, or changes the member view (ADR-0033 §2, ADR-0037 "publish remains the only commit point; the canvas is a read-only window"). The click chain is draftPreviewAction.run → onPreviewDraft → ChatPane.setCanvasApp({ …, path }) → iframe src. No fetch, no publish callback (pinned: Preview publishes nothing: no publish callback, no request). The bar's new line filters pendingEntries that usePendingDrafts already fetched from /api/v1/meta/_drafts (env-wide scope, as before); usePendingDrafts.ts is untouched and no second request is added (pinned: outside preview the bar draws nothing and reads nothing). PreviewModeContext is untouched, so every target is a ?preview=draft route and the member view (/apps/... without the flag) renders exactly as before; the added useParams/useMemo sit before the existing if (!preview) return null, hook order intact. Right.

Lifting the replay's drafted[], not draftState.items, is the right item set. draftState.items is the package's whole pending set (cloud#2924's pending/items/others), so previewing it would pick the most specific surface of some unrelated pending draft; drafted[] is what this confirmed change staged, which is the surface the builder just asked for. The lift reuses detectDraftResult's rule via the shared draftedItemsOf (including the single type/name fallback a one-item stage reports), and a published outcome returns before it, so no items leak onto a published card. Right.

The bar's per-surface count is derived soundly from data it already has. draftSurfaceOf matches the splat below /apps/:appName against APP_ROUTE_METADATA_READS with matchRoutes (no second route table): :objectName, :objectName/view/:viewId, :objectName/record/:recordId… yield objectName; page/:pageName, dashboard/:dashboardName, report/:reportName yield their name; the landing, metadata/..., search yield no surface → today's message. DraftPreviewBar is mounted by ConsoleLayout inside AppContent, which is the /apps/:appName/* route element, so useParams()['*'] is that splat (the test's /apps/:appName/* harness mirrors the real mount). draftsOnSurface counts object drafts by name and view/field drafts by their object prefix, page/dashboard/report by name. Right. Two properties to know, neither a defect: the entries are env-wide (the bar never passed a packageId), so a same-named object drafted in another package would count — the same scope the existing total already shows; and _drafts lists one entry per member, so two members' drafts of one object read as 2 changes, which is what is pending.

Seat rulings honoured at the head: ruling 1 (wiring after objectui#12965; the diff's AiChatPage.tsx hunks are the canvas state, the onPreviewDraft handler and two path= props only), ruling 2 (no field-draft sentence anywhere in the diff), ruling 3 (no record row; draftSurfaceOf recognises record routes only to count the object's drafts), ruling 4 (mapMessages.ts lift). The round's declared surface deviation (ChatbotEnhanced.tsx edited for the applied-card and badge buttons) is inside the claim's original surface.

Check-runs on the head (the gate verdicts): completed success — Lint, Repo Gates, Build & E2E, Build Docs, Test (dist pins), Test shards 7/9/11/13/15, Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Claim Re-read, Changeset Overwrite Report, Line Citation Gate, Governed Surface Queue Guard, Bundle Analysis, Live E2E (informational), label; skipped — coverage, dependabot. Still in progress at review time: Type Check and Test shards 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16. None has failed. Not awaited; the adopting seat reads them before the queue.

② Semver level

Changeset .changeset/12968-draft-preview-any.md: @object-ui/plugin-chatbot: minor, @object-ui/app-shell: patch, @object-ui/i18n: patch; no major (AGENTS.md: never declare major in the fixed group). check-changeset-presence requires a declaration for any src/ change in a released package — present; Changeset Bump Policy, Changeset Fixed Group Check and Changeset Declaration are green on the head.

  • plugin-chatbot: minor — matches: two exported types widen (items 1 and 2 above). Under the fixed group all 39 packages move together, so the group bump is minor.
  • app-shell: patch — matches the package's published face: no barrel export changes (item 4). One mismatch to name: the card's acceptance line reads "minor on @object-ui/plugin-chatbot (and @object-ui/app-shell if the bar changes)", and the bar changed. Because the group is fixed, app-shell ships at the same minor version either way; the per-package level only chooses the CHANGELOG heading ("Patch Changes" vs "Minor Changes"). Escalated in ③ as a one-line seat decision, not a gate.
  • i18n: patch — matches: additive keys.
  • The changeset body's claims (what ChatPane passes, the LiveCanvas path, the ReplayOutcome.items lift, the ten packs) are all true of the head diff; Changeset Claim Re-read is green.

Clause-②: yes — right. The claim was amended in place from no on the dev's measurement, the PR body and the changeset both carry Clause-②: yes, and this is the one CONTRACT_REVIEW_TIER review the amendment owed.

③ Boundary flags

Round-1 open_questions (report 6251840024) — all four ruled in 6251864146 and each ruling is implemented at the head (see ① "Seat rulings honoured"). Nothing left open from that round.

Round-2 flags (report 6252456713):

  1. PR body edit the seat owes (A vs B): A, already done. The PR body at review time opens with Fixes #12968 and carries a "Patch round (seat edit; head 58d0559 …)" section naming the wiring, the lift, the merge of 32a171e565, the ablation and the ChatbotEnhanced.tsx deviation. Residue, cosmetic only: the body's "Not in this pull request" items 1–2 and the title's "part 1" describe the pre-patch state; the Patch-round section supersedes them. Seat's call whether to trim; not a gate.
  2. In-canvas open vs host navigation (A vs B): A — answered, no escalation needed. The card says the button "deep-links to the affected page or object in ?preview=draft"; the Live Canvas iframe opens exactly that URL, beside the conversation. ADR-0037 Phase 1 wrote "navigating to the drafted artifact's route", but Phase 2 made the canvas the host for that route, onPreviewDraftApp already behaves this way, and objectui#12724's stay-on-conversation ruling governs. The AiChatPage.previewDraft-12968 pin asserts the router stays on /ai/build/conv-1. The director seat can overrule, as the rulings comment says.
  3. Declared surface deviation (ChatbotEnhanced.tsx in the patch round): accepted. The applied card and the drafted badge are drawn in that file; it is in the claim's original file surface; the edit is the shared helper, the two buttons and the prop doc.
  4. Out-of-scope finding (canvas header reads the package id for a non-app surface; ExcelImportBar.defaultObjectName reads canvasApp.name): noted, not blocking. Pre-existing posture for the app path; a label for the surface would be a follow-up, not this card.
  5. Escalated — app-shell level vs the card's parenthetical. The card's acceptance asks minor on @object-ui/app-shell when the bar changes; the changeset says patch. Effect is CHANGELOG heading only (fixed group). Options: (A) amend the one line to minor so the card's acceptance reads verbatim-true; (B) rule the card's parenthetical satisfied by the group bump and leave patch, which is the semver-correct level for a package whose public face did not grow. Either is a seat ruling; recommend A for the cheapest literal match.
  6. Gates still running. Type Check and 11 of 16 test shards were in_progress when this record was rendered; none failed. The seat reads their conclusions before the queue; this record does not stand in for them.

Implemented-by: claude/issue-12968-draft-preview-any
Reviewed-by: session_01XVp19E1MYUTFM3dnkwz8gB

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet objectstack-fleet Bot changed the title feat(plugin-chatbot,app-shell): Preview for any copilot draft kind, its deep-link table, and the preview bar's pending line (objectui#12968, part 1) feat(plugin-chatbot,app-shell): Preview for any copilot draft kind, its deep-link table, the console wiring and the preview bar's pending line (objectui#12968) Oct 18, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 18, 2026 16:54
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 18, 2026 16:54
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit 76f91f8 Oct 18, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12968-draft-preview-any branch October 18, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants