@agent-native/core 0.84.52 → 0.84.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/skills.ts +34 -16
  5. package/corpus/core/src/client/AssistantChat.tsx +140 -114
  6. package/corpus/core/src/client/blocks/library/diagram.config.ts +13 -2
  7. package/corpus/core/src/client/blocks/library/diagram.tsx +27 -3
  8. package/corpus/core/src/client/blocks/library/server-specs.ts +2 -2
  9. package/corpus/core/src/client/blocks/library/wireframe.config.ts +7 -1
  10. package/corpus/core/src/client/blocks/library/wireframe.tsx +42 -3
  11. package/corpus/core/src/client/blocks/types.ts +9 -0
  12. package/corpus/core/src/client/chat/message-components.tsx +1 -1
  13. package/corpus/core/src/styles/agent-native.css +44 -0
  14. package/corpus/core/src/styles/blocks.css +18 -0
  15. package/corpus/templates/plan/.agents/skills/visual-plan/references/document-quality.md +10 -6
  16. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +20 -9
  17. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +4 -1
  18. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +20 -9
  19. package/corpus/templates/plan/actions/create-ui-plan.ts +1 -1
  20. package/corpus/templates/plan/actions/create-visual-plan.ts +1 -1
  21. package/corpus/templates/plan/actions/create-visual-recap.ts +1 -1
  22. package/corpus/templates/plan/actions/get-plan-blocks.ts +2 -0
  23. package/corpus/templates/plan/app/components/plan/planBlocks.tsx +2 -0
  24. package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +38 -4
  25. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +18 -0
  26. package/corpus/templates/plan/app/global.css +1 -2
  27. package/corpus/templates/plan/changelog/2026-07-02-plan-chat-keeps-the-composer-anchored-when-connect-ai-appears.md +6 -0
  28. package/corpus/templates/plan/changelog/2026-07-02-wireframe-and-diagram-blocks-can-now-show-or-hide-their-outer-frame.md +6 -0
  29. package/corpus/templates/plan/server/plan-mdx.ts +3 -0
  30. package/corpus/templates/plan/shared/plan-content.ts +8 -0
  31. package/dist/cli/skills.d.ts +3 -3
  32. package/dist/cli/skills.d.ts.map +1 -1
  33. package/dist/cli/skills.js +34 -16
  34. package/dist/cli/skills.js.map +1 -1
  35. package/dist/client/AssistantChat.d.ts.map +1 -1
  36. package/dist/client/AssistantChat.js +32 -25
  37. package/dist/client/AssistantChat.js.map +1 -1
  38. package/dist/client/blocks/library/diagram.config.d.ts +3 -1
  39. package/dist/client/blocks/library/diagram.config.d.ts.map +1 -1
  40. package/dist/client/blocks/library/diagram.config.js +10 -1
  41. package/dist/client/blocks/library/diagram.config.js.map +1 -1
  42. package/dist/client/blocks/library/diagram.d.ts.map +1 -1
  43. package/dist/client/blocks/library/diagram.js +13 -7
  44. package/dist/client/blocks/library/diagram.js.map +1 -1
  45. package/dist/client/blocks/library/server-specs.js +2 -2
  46. package/dist/client/blocks/library/server-specs.js.map +1 -1
  47. package/dist/client/blocks/library/wireframe.config.d.ts +3 -1
  48. package/dist/client/blocks/library/wireframe.config.d.ts.map +1 -1
  49. package/dist/client/blocks/library/wireframe.config.js +4 -0
  50. package/dist/client/blocks/library/wireframe.config.js.map +1 -1
  51. package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
  52. package/dist/client/blocks/library/wireframe.js +20 -9
  53. package/dist/client/blocks/library/wireframe.js.map +1 -1
  54. package/dist/client/blocks/types.d.ts +8 -0
  55. package/dist/client/blocks/types.d.ts.map +1 -1
  56. package/dist/client/blocks/types.js.map +1 -1
  57. package/dist/client/chat/message-components.js +1 -1
  58. package/dist/client/chat/message-components.js.map +1 -1
  59. package/dist/notifications/routes.d.ts +2 -2
  60. package/dist/observability/routes.d.ts +6 -6
  61. package/dist/progress/routes.d.ts +1 -1
  62. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  63. package/dist/styles/agent-native.css +44 -0
  64. package/dist/styles/blocks.css +18 -0
  65. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2044
31
- - template files: 5001
31
+ - template files: 5003
@@ -1,5 +1,19 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.84.54
4
+
5
+ ### Patch Changes
6
+
7
+ - a259f96: Keep centered chat composers stable when the Connect AI setup card appears.
8
+ - df3fcfd: Allow wireframe and diagram blocks to opt in or out of their outer visual frame.
9
+
10
+ ## 0.84.53
11
+
12
+ ### Patch Changes
13
+
14
+ - 899ebf1: Hide the diff "Show all lines" footer when annotation anchoring already renders every line.
15
+ - c1e18fb: Make session replay uploads retry failed batches without advancing sequence ids.
16
+
3
17
  ## 0.84.52
4
18
 
5
19
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.84.52",
3
+ "version": "0.84.54",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -858,15 +858,26 @@ occurrence. The result is re-sanitized. In local-files privacy mode, do not call
858
858
  hosted Plan tools; edit the local MDX source directly and rerun the local
859
859
  check/serve or verify command for \`<plan-dir>\`.
860
860
 
861
- **Treat the wireframe border as part of the visible design.** Always wrap HTML
862
- wireframe content in a root container with real inner padding before drawing
863
- cards, fields, pills, labels, or controls. Use at least 14-16px of padding,
864
- \`box-sizing: border-box\`, \`height: 100%\`, and \`gap\` between child rows on the
865
- root node itself so the first row never sits flush against the screen border. Do
866
- not rely on padding on a nested page section as the first visible inset; the
867
- outermost element must create the breathing room. Keep text away from borders:
868
- every container, field, button, menu item, and annotation needs enough padding
869
- and line-height to read cleanly in the rendered Plan view.
861
+ **Choose the outer frame deliberately.** Wireframe and diagram data accept
862
+ \`frame: "auto" | "show" | "hide"\` in block data (\`<Screen frame="hide">\` in
863
+ MDX wireframes, \`<Diagram frame="hide">\` for MDX diagrams). Leave it unset or
864
+ \`auto\` when the host context should decide: Plan and recap surfaces default to a
865
+ drawn outer frame; docs surfaces default to no outer frame. Use \`show\` for
866
+ standalone product screens, before/after recap comparisons, screenshot-like
867
+ artifacts, and visuals that need containment from surrounding prose. Use \`hide\`
868
+ when a docs page, tab, column, card, canvas artboard, or the visual's own
869
+ internal chrome already supplies the boundary. Do not use \`hide\` to compensate
870
+ for cramped content; fix the layout instead.
871
+
872
+ **Inner padding and borders still matter.** Always wrap HTML wireframe content
873
+ in a root container with real inner padding before drawing cards, fields, pills,
874
+ labels, or controls. Use at least 14-16px of padding, \`box-sizing: border-box\`,
875
+ \`height: 100%\`, and \`gap\` between child rows on the root node itself so the
876
+ first row never sits flush against the screen edge. Do not rely on padding on a
877
+ nested page section as the first visible inset; the outermost element must
878
+ create the breathing room. Keep text away from borders: every container, field,
879
+ button, menu item, and annotation needs enough padding and line-height to read
880
+ cleanly in the rendered Plan view.
870
881
 
871
882
  **For feature-cloud or abundance visuals, optimize the composition over line-by-line
872
883
  reading.** Some marketing/product sections need to feel like a large surface area
@@ -1244,12 +1255,16 @@ so you never emit a block the editor cannot render or round-trip:
1244
1255
  \`--wf-paper\`, \`--wf-card\`, \`--wf-accent\`, \`--wf-accent-soft\`, \`--wf-warn\`, and
1245
1256
  \`--wf-ok\`, and switch to Excalifont plus rough.js outlines in sketchy mode. Do not
1246
1257
  set \`font-family\` and do not hard-code hex, rgb, or hsl colors in diagram HTML
1247
- or CSS. Leave room for the sketch font: keep labels short, give nodes generous
1248
- width, and place boundary/annotation labels in unused space instead of over
1249
- nodes; labels must not overlap nodes, connectors, or each other. For small
1250
- text/SVG changes to an existing HTML diagram, use \`patch-diagram-html\` with a
1251
- unique \`find\`/\`replace\` snippet instead of resending the whole \`data.html\`
1252
- string. Use legacy \`nodes\` / \`edges\` only for small previews or truly
1258
+ or CSS. Choose the outer \`frame\` intentionally: use \`show\` when the diagram
1259
+ stands alone in a recap, comparison, or prose section; use \`hide\` when the
1260
+ diagram sits inside docs chrome, columns, tabs, cards, a canvas surface, or
1261
+ already has visible \`.diagram-panel\` / \`.diagram-box\` structure. Leave room
1262
+ for the sketch font: keep labels short, give nodes generous width, and place
1263
+ boundary/annotation labels in unused space instead of over nodes; labels must
1264
+ not overlap nodes, connectors, or each other. For small text/SVG changes to an
1265
+ existing HTML diagram, use \`patch-diagram-html\` with a unique
1266
+ \`find\`/\`replace\` snippet instead of resending the whole \`data.html\` string.
1267
+ Use legacy \`nodes\` / \`edges\` only for small previews or truly
1253
1268
  sequential flows. In architecture/code plans, prefer a repeated section rhythm:
1254
1269
  recommendation title, confidence and category badges, code-path evidence, a
1255
1270
  local before/after or current/target spatial diagram, then concise
@@ -2426,7 +2441,10 @@ tags — resolve every conceptual name to its exact tag + prop schema with the
2426
2441
  Author diagram HTML/CSS with the renderer-owned \`.diagram-*\` primitives
2427
2442
  (\`.diagram-panel\`, \`.diagram-node\`, \`.diagram-pill\`, \`[data-rough]\`, …) and
2428
2443
  the same \`--wf-*\` theme tokens \`references/wireframe.md\` defines — never
2429
- \`font-family\`, hex, rgb/hsl literals, or one-off dark/light palettes.
2444
+ \`font-family\`, hex, rgb/hsl literals, or one-off dark/light palettes. Choose
2445
+ the outer \`frame\` intentionally: recap diagrams usually benefit from
2446
+ \`frame: "show"\` when they stand alone, but use \`frame: "hide"\` when columns,
2447
+ tabs, a card, or the diagram's own panels already provide the boundary.
2430
2448
  - **Outcome-first narrative** → \`rich-text\` for the "what changed and why" prose:
2431
2449
  the objective the diff served, the key decisions visible in it, and the risks a
2432
2450
  reviewer should weigh. This is the only place the model writes freely.
@@ -3452,6 +3452,15 @@ const AssistantChatInner = forwardRef<
3452
3452
  queryKey: ["guided-questions"],
3453
3453
  ...(browserTabId ? { browserTabId } : {}),
3454
3454
  });
3455
+ const hasComposerAccessoryAboveStack = Boolean(
3456
+ composerError ||
3457
+ showComposerSlot ||
3458
+ showCenteredEmptyThreadFooterSlot ||
3459
+ (guidedQuestions && guidedQuestions.length > 0) ||
3460
+ showScrollToBottom ||
3461
+ composerContextItems.length > 0 ||
3462
+ showPlanModeCallout,
3463
+ );
3455
3464
 
3456
3465
  // Human-in-the-loop approvals: when the user approves a paused `needsApproval`
3457
3466
  // tool call, re-issue the turn carrying the call's approval key so the server
@@ -3877,7 +3886,6 @@ const AssistantChatInner = forwardRef<
3877
3886
  onSwitchToAct={handleSwitchToAct}
3878
3887
  />
3879
3888
  )}
3880
- <SelectionAttachedPill />
3881
3889
  {/* Inline attachment / body-size error */}
3882
3890
  {composerError && (
3883
3891
  <div
@@ -3896,124 +3904,142 @@ const AssistantChatInner = forwardRef<
3896
3904
  </button>
3897
3905
  </div>
3898
3906
  )}
3899
- {missingApiKey &&
3900
- !authError &&
3901
- missingApiKeySetupAboveComposer ? (
3902
- <BuilderSetupCard
3903
- onConnected={handleBuilderConnected}
3904
- bouncePulse={missingKeyBouncePulse}
3905
- layout={missingApiKeySetupLayout}
3906
- />
3907
- ) : null}
3908
- {/* Input area */}
3909
- <AgentComposerFrame
3910
- layoutVariant={composerLayoutVariant}
3911
- className={cn(
3912
- composerAreaClassName,
3913
- missingApiKey && "cursor-pointer",
3914
- isComposerDisabled && "opacity-70",
3915
- )}
3916
- onClick={
3917
- missingApiKey
3918
- ? () => setMissingKeyBouncePulse((p) => p + 1)
3919
- : undefined
3907
+ <div
3908
+ className="agent-composer-stack"
3909
+ data-agent-composer-adjacent-ui={
3910
+ hasComposerAccessoryAboveStack ? "true" : undefined
3920
3911
  }
3921
3912
  >
3922
- <ComposerAttachmentPreviewStrip />
3923
- <TiptapComposer
3924
- focusRef={tiptapRef}
3925
- disabled={isComposerDisabled}
3926
- placeholder={
3913
+ <SelectionAttachedPill />
3914
+ {missingApiKey &&
3915
+ !authError &&
3916
+ missingApiKeySetupAboveComposer ? (
3917
+ <div
3918
+ className="agent-composer-setup-card"
3919
+ data-agent-composer-setup-position="above"
3920
+ >
3921
+ <BuilderSetupCard
3922
+ onConnected={handleBuilderConnected}
3923
+ bouncePulse={missingKeyBouncePulse}
3924
+ layout={missingApiKeySetupLayout}
3925
+ />
3926
+ </div>
3927
+ ) : null}
3928
+ {/* Input area */}
3929
+ <AgentComposerFrame
3930
+ layoutVariant={composerLayoutVariant}
3931
+ className={cn(
3932
+ composerAreaClassName,
3933
+ missingApiKey && "cursor-pointer",
3934
+ isComposerDisabled && "opacity-70",
3935
+ )}
3936
+ onClick={
3927
3937
  missingApiKey
3928
- ? missingApiKeySetupAboveComposer
3929
- ? "Connect AI above to start chatting..."
3930
- : "Connect AI below to start chatting..."
3931
- : composerDisabled
3932
- ? (composerDisabledPlaceholder ??
3933
- "Open Desktop to use this chat.")
3934
- : isRunning
3935
- ? queuedMessages.length > 0
3936
- ? `${queuedMessages.length} queued — send a follow-up...`
3937
- : "Send a follow-up..."
3938
- : composerPlaceholder
3939
- }
3940
- onSubmit={
3941
- isRunning || composerContextItems.length > 0
3942
- ? (text, references, attachments, options) =>
3943
- void addToQueue(
3944
- text,
3945
- undefined,
3946
- references.length > 0 ? references : undefined,
3947
- attachments,
3948
- undefined,
3949
- resolveAssistantChatSubmitIntent({
3950
- isRunning,
3951
- requestedIntent: options?.intent,
3952
- }),
3953
- undefined,
3954
- true,
3955
- )
3938
+ ? () => setMissingKeyBouncePulse((p) => p + 1)
3956
3939
  : undefined
3957
3940
  }
3958
- onSlashCommand={onSlashCommand}
3959
- onBeforeSubmit={ensureAgentEngineReadyForSubmit}
3960
- execMode={execMode}
3961
- onExecModeChange={onExecModeChange}
3962
- planModeDisabled={planModeDisabled}
3963
- planModeDisabledReason={planModeDisabledReason}
3964
- selectedModel={selectedModel ?? defaultModel}
3965
- selectedEffort={selectedEffort}
3966
- availableModels={availableModels}
3967
- onModelChange={onModelChange}
3968
- onEffortChange={onEffortChange}
3969
- imageModelMenu={imageModelMenu}
3970
- onConnectProvider={onConnectProvider}
3971
- toolbarSlot={composerToolbarSlot}
3972
- contextItems={composerContextItems}
3973
- onRemoveContextItem={removeComposerContextItem}
3974
- plusMenuMode={plusMenuMode}
3975
- layoutVariant={composerLayoutVariant}
3976
- providerConnectStatusEnabled={providerStatusChecksEnabled}
3977
- draftScope={threadId || tabId}
3978
- interceptBuildRequestsForBuilder
3979
- onAttachmentError={setComposerError}
3980
- extraActionButton={
3981
- contextXRayEnabled ||
3982
- composerExtraActionButton ||
3983
- showRunningInUI ? (
3984
- <>
3985
- {contextXRayEnabled && (
3986
- <ContextMeter threadId={threadId} />
3987
- )}
3988
- {composerExtraActionButton}
3989
- {showRunningInUI && (
3990
- <Tooltip>
3991
- <TooltipTrigger asChild>
3992
- <button
3993
- type="button"
3994
- onClick={stopActiveRun}
3995
- className="shrink-0 flex h-7 w-7 items-center justify-center rounded-md bg-muted text-foreground hover:bg-muted/80"
3996
- >
3997
- <IconPlayerStop className="h-3.5 w-3.5" />
3998
- </button>
3999
- </TooltipTrigger>
4000
- <TooltipContent>Stop generating</TooltipContent>
4001
- </Tooltip>
4002
- )}
4003
- </>
4004
- ) : undefined
4005
- }
4006
- />
4007
- </AgentComposerFrame>
4008
- {missingApiKey &&
4009
- !authError &&
4010
- !missingApiKeySetupAboveComposer ? (
4011
- <BuilderSetupCard
4012
- onConnected={handleBuilderConnected}
4013
- bouncePulse={missingKeyBouncePulse}
4014
- layout={missingApiKeySetupLayout}
4015
- />
4016
- ) : null}
3941
+ >
3942
+ <ComposerAttachmentPreviewStrip />
3943
+ <TiptapComposer
3944
+ focusRef={tiptapRef}
3945
+ disabled={isComposerDisabled}
3946
+ placeholder={
3947
+ missingApiKey
3948
+ ? missingApiKeySetupAboveComposer
3949
+ ? "Connect AI above to start chatting..."
3950
+ : "Connect AI below to start chatting..."
3951
+ : composerDisabled
3952
+ ? (composerDisabledPlaceholder ??
3953
+ "Open Desktop to use this chat.")
3954
+ : isRunning
3955
+ ? queuedMessages.length > 0
3956
+ ? `${queuedMessages.length} queued — send a follow-up...`
3957
+ : "Send a follow-up..."
3958
+ : composerPlaceholder
3959
+ }
3960
+ onSubmit={
3961
+ isRunning || composerContextItems.length > 0
3962
+ ? (text, references, attachments, options) =>
3963
+ void addToQueue(
3964
+ text,
3965
+ undefined,
3966
+ references.length > 0 ? references : undefined,
3967
+ attachments,
3968
+ undefined,
3969
+ resolveAssistantChatSubmitIntent({
3970
+ isRunning,
3971
+ requestedIntent: options?.intent,
3972
+ }),
3973
+ undefined,
3974
+ true,
3975
+ )
3976
+ : undefined
3977
+ }
3978
+ onSlashCommand={onSlashCommand}
3979
+ onBeforeSubmit={ensureAgentEngineReadyForSubmit}
3980
+ execMode={execMode}
3981
+ onExecModeChange={onExecModeChange}
3982
+ planModeDisabled={planModeDisabled}
3983
+ planModeDisabledReason={planModeDisabledReason}
3984
+ selectedModel={selectedModel ?? defaultModel}
3985
+ selectedEffort={selectedEffort}
3986
+ availableModels={availableModels}
3987
+ onModelChange={onModelChange}
3988
+ onEffortChange={onEffortChange}
3989
+ imageModelMenu={imageModelMenu}
3990
+ onConnectProvider={onConnectProvider}
3991
+ toolbarSlot={composerToolbarSlot}
3992
+ contextItems={composerContextItems}
3993
+ onRemoveContextItem={removeComposerContextItem}
3994
+ plusMenuMode={plusMenuMode}
3995
+ layoutVariant={composerLayoutVariant}
3996
+ providerConnectStatusEnabled={providerStatusChecksEnabled}
3997
+ draftScope={threadId || tabId}
3998
+ interceptBuildRequestsForBuilder
3999
+ onAttachmentError={setComposerError}
4000
+ extraActionButton={
4001
+ contextXRayEnabled ||
4002
+ composerExtraActionButton ||
4003
+ showRunningInUI ? (
4004
+ <>
4005
+ {contextXRayEnabled && (
4006
+ <ContextMeter threadId={threadId} />
4007
+ )}
4008
+ {composerExtraActionButton}
4009
+ {showRunningInUI && (
4010
+ <Tooltip>
4011
+ <TooltipTrigger asChild>
4012
+ <button
4013
+ type="button"
4014
+ onClick={stopActiveRun}
4015
+ className="shrink-0 flex h-7 w-7 items-center justify-center rounded-md bg-muted text-foreground hover:bg-muted/80"
4016
+ >
4017
+ <IconPlayerStop className="h-3.5 w-3.5" />
4018
+ </button>
4019
+ </TooltipTrigger>
4020
+ <TooltipContent>Stop generating</TooltipContent>
4021
+ </Tooltip>
4022
+ )}
4023
+ </>
4024
+ ) : undefined
4025
+ }
4026
+ />
4027
+ </AgentComposerFrame>
4028
+ {missingApiKey &&
4029
+ !authError &&
4030
+ !missingApiKeySetupAboveComposer ? (
4031
+ <div
4032
+ className="agent-composer-setup-card"
4033
+ data-agent-composer-setup-position="below"
4034
+ >
4035
+ <BuilderSetupCard
4036
+ onConnected={handleBuilderConnected}
4037
+ bouncePulse={missingKeyBouncePulse}
4038
+ layout={missingApiKeySetupLayout}
4039
+ />
4040
+ </div>
4041
+ ) : null}
4042
+ </div>
4017
4043
  </div>
4018
4044
  </TextStreamingContext.Provider>
4019
4045
  </ChatRunningContext.Provider>
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { childCodeFenceFields, serializeChildCodeFenceFields } from "../mdx.js";
4
- import type { BlockMdxConfig } from "../types.js";
4
+ import type { BlockMdxConfig, BlockVisualFrame } from "../types.js";
5
5
 
6
6
  /**
7
7
  * Pure (React-free) part of the shared `diagram` block: its data schema and MDX
@@ -45,6 +45,8 @@ export interface DiagramData {
45
45
  html?: string;
46
46
  css?: string;
47
47
  caption?: string;
48
+ /** Outer surface frame. `auto` lets the host choose the right default. */
49
+ frame?: BlockVisualFrame;
48
50
  /**
49
51
  * Legacy compatibility path for older/simple node graphs. New plans should use
50
52
  * `html`/`css` when layout quality matters.
@@ -120,6 +122,8 @@ const diagramNoteSchema = z.object({
120
122
  y: z.number().min(0).max(100).optional(),
121
123
  }) as z.ZodType<DiagramNote>;
122
124
 
125
+ const visualFrameSchema = z.enum(["auto", "show", "hide"]);
126
+
123
127
  /**
124
128
  * The block can be a flexible HTML/SVG fragment or a legacy positional
125
129
  * node/edge/note graph, so it ships a custom `Edit` rather than relying on the
@@ -144,6 +148,7 @@ export const diagramSchema = z
144
148
  })
145
149
  .optional(),
146
150
  caption: z.string().trim().max(600).optional(),
151
+ frame: visualFrameSchema.optional(),
147
152
  nodes: z.array(diagramNodeSchema).max(80).optional(),
148
153
  edges: z.array(diagramEdgeSchema).max(120).optional(),
149
154
  notes: z.array(diagramNoteSchema).max(40).optional(),
@@ -167,7 +172,9 @@ function graphDataForAttr(data: DiagramData): DiagramData | undefined {
167
172
  if (data.edges?.length) graph.edges = data.edges;
168
173
  if (data.notes?.length) graph.notes = data.notes;
169
174
  if (Object.keys(graph).length > 0) return graph;
170
- return hasChildFenceData(data) ? undefined : data;
175
+ if (hasChildFenceData(data)) return undefined;
176
+ const { frame: _frame, ...dataForAttr } = data;
177
+ return Object.keys(dataForAttr).length > 0 ? dataForAttr : undefined;
171
178
  }
172
179
 
173
180
  /**
@@ -185,12 +192,16 @@ export const diagramMdx: BlockMdxConfig<DiagramData> = {
185
192
  | Record<string, unknown>
186
193
  | undefined,
187
194
  caption: data.caption,
195
+ frame: data.frame,
188
196
  }),
189
197
  fromAttrs: (attrs) => ({
190
198
  ...(attrs.object<DiagramData>("data") ?? {}),
191
199
  ...(attrs.string("caption") !== undefined
192
200
  ? { caption: attrs.string("caption") }
193
201
  : {}),
202
+ ...(attrs.string("frame") !== undefined
203
+ ? { frame: attrs.string("frame") as BlockVisualFrame }
204
+ : {}),
194
205
  }),
195
206
  serializeChildren: (data) =>
196
207
  serializeChildCodeFenceFields(data, { html: "html", css: "css" }),
@@ -89,6 +89,7 @@ function HtmlDiagram({
89
89
  const ref = useRef<HTMLDivElement>(null);
90
90
  const isDark = useIsDark();
91
91
  const style = useWireframeStyle();
92
+ const showFrame = resolveVisualFrame(data.frame, ctx);
92
93
  const scopeId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
93
94
  // Sanitize author HTML/CSS at the render point (defense-in-depth against
94
95
  // stored XSS). Self-contained in core via the shared block sanitizer (DOM-based
@@ -114,6 +115,7 @@ function HtmlDiagram({
114
115
  dir={ctx.textDirection}
115
116
  data-theme={isDark ? "dark" : "light"}
116
117
  data-style={style}
118
+ data-frame={showFrame ? "show" : "hide"}
117
119
  data-text-direction={ctx.textDirection}
118
120
  data-plan-diagram-scope={scopeId}
119
121
  >
@@ -126,7 +128,7 @@ function HtmlDiagram({
126
128
  <RoughOverlay
127
129
  scopeRef={ref}
128
130
  enabled={style === "sketchy"}
129
- drawFrame={false}
131
+ drawFrame={showFrame}
130
132
  selector={DIAGRAM_ROUGH_SELECTOR}
131
133
  />
132
134
  {data.caption && !compact && (
@@ -138,6 +140,15 @@ function HtmlDiagram({
138
140
  );
139
141
  }
140
142
 
143
+ function resolveVisualFrame(
144
+ frame: DiagramData["frame"],
145
+ ctx: BlockRenderContext,
146
+ ): boolean {
147
+ const resolved =
148
+ frame && frame !== "auto" ? frame : (ctx.visualFrame ?? "show");
149
+ return resolved !== "hide";
150
+ }
151
+
141
152
  /* -------------------------------------------------------------------------- */
142
153
  /* Legacy node-graph paths */
143
154
  /* -------------------------------------------------------------------------- */
@@ -186,11 +197,13 @@ function PositionedDiagram({
186
197
  compact,
187
198
  markerId,
188
199
  direction,
200
+ showFrame,
189
201
  }: {
190
202
  data: DiagramData;
191
203
  compact?: boolean;
192
204
  markerId: string;
193
205
  direction?: BlockRenderContext["textDirection"];
206
+ showFrame: boolean;
194
207
  }) {
195
208
  const nodes = (data.nodes ?? []).map((node) => ({
196
209
  ...node,
@@ -228,7 +241,10 @@ function PositionedDiagram({
228
241
 
229
242
  return (
230
243
  <div
231
- className="plan-sketch rounded-[16px] border border-border bg-muted p-5"
244
+ className={cn(
245
+ "plan-sketch",
246
+ showFrame ? "rounded-[16px] border border-border bg-muted p-5" : "p-0",
247
+ )}
232
248
  dir={direction}
233
249
  data-text-direction={direction}
234
250
  >
@@ -426,10 +442,12 @@ function SequenceDiagram({
426
442
  data,
427
443
  compact,
428
444
  direction,
445
+ showFrame,
429
446
  }: {
430
447
  data: DiagramData;
431
448
  compact?: boolean;
432
449
  direction?: BlockRenderContext["textDirection"];
450
+ showFrame: boolean;
433
451
  }) {
434
452
  const edges = data.edges ?? [];
435
453
  const nodes = orderDiagramNodes(data.nodes ?? [], edges);
@@ -443,7 +461,10 @@ function SequenceDiagram({
443
461
  const visualNodes = direction === "rtl" ? [...nodes].reverse() : nodes;
444
462
  return (
445
463
  <div
446
- className="plan-sketch rounded-[16px] border border-border bg-muted p-5"
464
+ className={cn(
465
+ "plan-sketch",
466
+ showFrame ? "rounded-[16px] border border-border bg-muted p-5" : "p-0",
467
+ )}
447
468
  dir={direction}
448
469
  data-text-direction={direction}
449
470
  >
@@ -524,6 +545,7 @@ function DiagramBody({
524
545
  compact?: boolean;
525
546
  }) {
526
547
  const markerId = useId().replace(/:/g, "");
548
+ const showFrame = resolveVisualFrame(data.frame, ctx);
527
549
  if (data.html?.trim()) {
528
550
  return <HtmlDiagram data={data} ctx={ctx} compact={compact} />;
529
551
  }
@@ -534,6 +556,7 @@ function DiagramBody({
534
556
  compact={compact}
535
557
  markerId={markerId}
536
558
  direction={ctx.textDirection}
559
+ showFrame={showFrame}
537
560
  />
538
561
  );
539
562
  }
@@ -542,6 +565,7 @@ function DiagramBody({
542
565
  data={data}
543
566
  compact={compact}
544
567
  direction={ctx.textDirection}
568
+ showFrame={showFrame}
545
569
  />
546
570
  );
547
571
  }
@@ -211,7 +211,7 @@ export const libraryBlockConfigs: BlockSpec<any>[] = [
211
211
  placement: ["block"],
212
212
  label: "Diagram",
213
213
  description:
214
- "A flexible inline architecture/code diagram. Prefer html/css with SVG or semantic HTML for polished two-dimensional layouts; use .diagram-* primitives and --wf-* tokens for theme/sketch compatibility. Legacy nodes/edges are only for simple previews.",
214
+ "A flexible inline architecture/code diagram. Prefer html/css with SVG or semantic HTML for polished two-dimensional layouts; use .diagram-* primitives and --wf-* tokens for theme/sketch compatibility. Set frame to show for standalone artifacts that need containment, hide when surrounding docs/canvas chrome already supplies the boundary. Legacy nodes/edges are only for simple previews.",
215
215
  }),
216
216
  defineBlock<WireframeData>({
217
217
  type: "wireframe",
@@ -221,7 +221,7 @@ export const libraryBlockConfigs: BlockSpec<any>[] = [
221
221
  placement: ["block"],
222
222
  label: "Wireframe",
223
223
  description:
224
- "A sketch wireframe of one screen built from kit primitives (or an HTML mockup), rendered in a chosen surface frame (desktop/mobile/popover/panel/browser).",
224
+ "A sketch wireframe of one screen built from kit primitives (or an HTML mockup), rendered in a chosen surface (desktop/mobile/popover/panel/browser). Set frame to show for standalone screens and recap comparisons, hide only when surrounding chrome already supplies the boundary.",
225
225
  }),
226
226
  defineBlock<MermaidData>({
227
227
  type: "mermaid",
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { prop, attributeValue } from "../mdx.js";
4
- import type { BlockMdxConfig } from "../types.js";
4
+ import type { BlockMdxConfig, BlockVisualFrame } from "../types.js";
5
5
 
6
6
  /**
7
7
  * Pure (React-free) part of the shared `wireframe` block: its data shape, zod
@@ -138,6 +138,8 @@ export interface WireframeData {
138
138
  /** `design` renders full-fidelity branded HTML/CSS instead of a sketch. */
139
139
  renderMode?: WireframeRenderMode;
140
140
  caption?: string;
141
+ /** Outer surface frame. `auto` lets the host choose the right default. */
142
+ frame?: BlockVisualFrame;
141
143
  /**
142
144
  * Neutral, textless loading register. The renderer drops borders, the sketch
143
145
  * outline, and color, rendering soft placeholder geometry only.
@@ -205,6 +207,7 @@ const elNameSchema = z.enum(
205
207
  WIREFRAME_EL_NAMES as [WireframeElName, ...WireframeElName[]],
206
208
  );
207
209
  const idSchema = z.string().trim().min(1).max(120);
210
+ const visualFrameSchema = z.enum(["auto", "show", "hide"]);
208
211
 
209
212
  /**
210
213
  * Reject full-document HTML (html/head/body/script/style tags) in the `html` /
@@ -281,6 +284,7 @@ export const wireframeSchema = z
281
284
  surface: z.enum(["desktop", "mobile", "popover", "panel", "browser"]),
282
285
  renderMode: z.enum(["wireframe", "design"]).optional(),
283
286
  caption: z.string().trim().max(400).optional(),
287
+ frame: visualFrameSchema.optional(),
284
288
  skeleton: z.boolean().optional(),
285
289
  html: z
286
290
  .string()
@@ -387,6 +391,7 @@ function serializeScreen(data: WireframeData): string {
387
391
  prop("surface", data.surface),
388
392
  prop("renderMode", data.renderMode),
389
393
  prop("caption", data.caption),
394
+ prop("frame", data.frame),
390
395
  prop("html", data.html),
391
396
  prop("css", data.css),
392
397
  prop("skeleton", data.skeleton),
@@ -494,6 +499,7 @@ function parseScreen(node: WireframeMdxNode, idContext: string): WireframeData {
494
499
  (stringAttr(node, "surface") as WireframeData["surface"]) ?? "desktop",
495
500
  renderMode: stringAttr(node, "renderMode") as WireframeData["renderMode"],
496
501
  caption: stringAttr(node, "caption"),
502
+ frame: stringAttr(node, "frame") as WireframeData["frame"],
497
503
  html: requiredStringAttr(node, "html"),
498
504
  css: requiredStringAttr(node, "css"),
499
505
  skeleton: boolAttr(node, "skeleton"),