Repository navigation
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
Conversation
… 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
✅ 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
|
…#12965 landed) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
…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
✅ 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
|
… items; changeset names the wiring (objectui#12968) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
✅ 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: Inputs read: card #12968 (body and all four comments: claim ① Derived judgmentsPublic-surface changes the diff implies (complete list, each judged):
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.
Nothing on the Preview path publishes, reads Lifting the replay's The bar's per-surface count is derived soundly from data it already has. Seat rulings honoured at the head: ruling 1 (wiring after objectui#12965; the diff's 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 levelChangeset
③ Boundary flagsRound-1 Round-2 flags (report
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #12968
Clause-②: yes —
ChatbotEnhancedPropsgains the optionalonPreviewDraft, so@object-ui/plugin-chatbotis atminor.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(theChatPanethat rendersChatbotEnhancedfor the console, the Studio copilot and the chat dock) and inLiveCanvas.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 noappnow draws Preview beside Publish and Review. It draws when the host passes the new optionalonPreviewDraft(items, { appSegment })and the result names its owningpackageId. 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 keepsonPreviewDraftApp, unchanged. A host that passes noonPreviewDraftdraws 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'sresolveHref, the console's one nav-to-URL mapping. No second URL scheme is spelled.draftSurfaceOfanddraftsOnSurfacemap the route the preview shows back to the pending drafts that belong to it. They match the route againstAPP_ROUTE_METADATA_READS, the table of every routeAppContentdeclares.DraftPreviewBar. When the surface it shows has pending drafts of its own, the message names them on one line, from the entriesusePendingDraftsalready 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.pendingHereis a plural family in all ten packs, with each pack's CLDR categories, followingpreview.changes.summaryItems.Measurements (the dispatch's mechanism assumptions)
43efd078aa),ChatbotEnhanced.draftPreviewAny-12968.test.tsxrendered anadd_fielddrafted result (type: 'object',changedKeys: ['fields'],packageId: 'app.crm') with the console'sonPreviewDraftAppwired. It drew no Preview:Unable to find an element by: [data-testid="draft-preview"], 4 failed and 3 passed. Thedraft-preview-appcontrol also found no element. View and page drafts were the same.{ type, name }items andpackageId(detectDraftResult).changedKeysis not lifted, and no record id is carried.ViewItemNameSchemasays 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./apps/:appName/*(useParams()['*']) and matches it againstAPP_ROUTE_METADATA_READS. The rule: an object route (its list, a view, a record) counts the drafts of typeobjectwith that name, plus theviewandfielddrafts 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._draftsrows (ListDraftsResponseSchema) carrytype,name,member,label,packageId,updatedAtandupdatedBy. They carry nochangedKeys, and a field draft is anobjectdraft. So "this pending draft adds a field" cannot be read from the countusePendingDraftsalready 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.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 noonPreviewDraft; CONTROL with no package; and that Preview makes no publish call or request.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).DraftPreviewBar,usePendingDrafts,PreviewModeContextor?preview=draft, plus the locale-content i18n tests: 43 files / 350 tests passed, and 35 files / 363 tests passed. The existingDraftPreviewBar.test.tsxandChatbotEnhanced.test.tsxare unchanged and green.ablation-replacemutatedreturn onPreviewDraft && appSegment ? (toreturn false && onPreviewDraft && appSegment ? (, which restores the app-only condition. The anchor went from 1 to 0 hits and the blob from2e650291b551to65a99e6ea4b5. 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 blob2e650291b551, andgit diff HEADwas empty.packages/plugin-chatbot,packages/app-shellandpackages/i18n.Gates
turbo run build --filter='@object-ui/app-shell^...'reported 28 of 28 tasks successful.type-check:@object-ui/plugin-chatbotexit 0 and@object-ui/i18nexit 0.@object-ui/app-shell: the result is in the dev report (the shared lock queue).eslint --no-inline-config --format json): 15 files, 0 errors. All 11 warnings are onChatbotEnhanced.tsxlines this diff does not touch. The config enables no type-aware linting, so the diff cannot move a verdict on an untouched file.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.mjsandcheck:new-line-citations.Not in this pull request (named, so the card keeps them)
ChatPanemust passonPreviewDraftand opendraftPreviewPath(items, appSegment). For example, the Live Canvas needs to accept a path beside the app segment, or the handler navigates. The files areAiChatPage.tsx(objectui#12965's claimed surface) andLiveCanvas.tsx, both outside this claim. Until then the console still draws Preview only for app drafts.replayOutcomedrafted). That path lifts no drafted items (detectReplayOutcomeinmapMessages.ts, outside the claim), so it draws no Preview yet.Acceptance notes
/datais not read.AiChatPage.tsxis untouched. The published-only and member-view tests are unchanged.draftSurface.tsis not exported from the@object-ui/app-shellbarrel, so the package's public face does not grow.The session is
https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB.Patch round (seat edit; head
58d0559, seat rulings6251864146, os-dev-report6252456713)origin/main32a171e565(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 as174146b444, no conflicts;AiChatPage.tsx'sPendingDraftsBarregion is byte-for-bytemain's.ChatPanepassesonPreviewDraft: the Live Canvas opensdraftPreviewPath(items, appSegment)'s target under?preview=draftbeside the conversation (the host stays on the conversation, asonPreviewDraftAppdoes and objectui#12724 rules).LiveCanvastakes an optional fullpathbeside the app segment; the app-draft path is unchanged.detectReplayOutcomelifts a drafted replay'sdrafted[]intoReplayOutcome.items(shareddraftedItemsOf), so the confirm-gated applied card, and the drafted badge an older cloud sends, draw Preview through the same door (draftPreviewAction: an app goes toonPreviewDraftApp, anything else toonPreviewDraft).ChatbotEnhanced.tsxcarries the applied-card and badge Preview buttons (within the claim's original surface). Two exact-shapemapMessagespins gained the newitemskey.onPreviewDraftinChatPaneturns 4 pins red (the app-path control stays green); restore proven by blob hashe81282cedec4.Generated by Claude Code