@agent-native/core 0.115.1 → 0.115.4
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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +17 -0
- package/corpus/core/src/cli/skills-content/visual-plan-skill.ts +19 -2
- package/corpus/core/src/client/blocks/library/wireframe.tsx +3 -1
- package/corpus/core/src/server/builder-browser.ts +34 -4
- package/corpus/core/src/server/core-routes-plugin.ts +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +10 -1
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +19 -2
- package/corpus/templates/plan/AGENTS.md +10 -0
- package/corpus/templates/plan/actions/create-prototype-plan.ts +27 -3
- package/corpus/templates/plan/actions/get-plan-blocks.ts +1 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +3 -2
- package/corpus/templates/plan/app/components/plan/PlanContentRenderer.tsx +18 -4
- package/corpus/templates/plan/app/components/plan/PlanVisualSurface.tsx +10 -3
- package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +3 -1
- package/corpus/templates/plan/app/i18n/en-US.ts +6 -0
- package/corpus/templates/plan/app/lib/create-plan-routing.ts +39 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +31 -107
- package/corpus/templates/plan/changelog/2026-07-22-high-fidelity-mockup-requests-now-preserve-branded-css-use-d.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +110 -2
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +14 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
- package/dist/cli/skills-content/visual-plan-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/visual-plan-skill.js +19 -2
- package/dist/cli/skills-content/visual-plan-skill.js.map +1 -1
- package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
- package/dist/client/blocks/library/wireframe.js +3 -2
- package/dist/client/blocks/library/wireframe.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/builder-browser.d.ts +1 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +27 -4
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.js +1 -1
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/package.json +1 -1
- package/src/agent/production-agent.ts +17 -0
- package/src/cli/skills-content/visual-plan-skill.ts +19 -2
- package/src/client/blocks/library/wireframe.tsx +3 -1
- package/src/server/builder-browser.ts +34 -4
- package/src/server/core-routes-plugin.ts +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.115.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2154ad1: Fix Builder preview credential relay failing behind a proxy. The relay handler now derives the request origin from `x-forwarded-host`/`x-forwarded-proto` (via `getBuilderBrowserOriginForEvent`) instead of the internal loopback host, so `targetOrigin` verification passes on hosted preview deployments.
|
|
8
|
+
|
|
9
|
+
## 0.115.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- a841109: Teach visual-plan agents to create and promote full-fidelity design surfaces without losing scoped CSS or duplicating an existing plan.
|
|
14
|
+
|
|
15
|
+
## 0.115.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- f0601ec: Collapse duplicate assistant tool-call ids in the shared agent loop so providers do not reject replayed tool-call history.
|
|
20
|
+
|
|
3
21
|
## 0.115.1
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.115.
|
|
3
|
+
"version": "0.115.4",
|
|
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": {
|
|
@@ -3087,6 +3087,21 @@ function normalizeToolCallInputForHistory(
|
|
|
3087
3087
|
return { rawInput: input };
|
|
3088
3088
|
}
|
|
3089
3089
|
|
|
3090
|
+
function dedupeAssistantToolCallsById(
|
|
3091
|
+
content: import("./engine/types.js").EngineContentPart[],
|
|
3092
|
+
): import("./engine/types.js").EngineContentPart[] {
|
|
3093
|
+
const seenToolCallIds = new Set<string>();
|
|
3094
|
+
const deduped: import("./engine/types.js").EngineContentPart[] = [];
|
|
3095
|
+
for (const part of content) {
|
|
3096
|
+
if (part.type === "tool-call") {
|
|
3097
|
+
if (seenToolCallIds.has(part.id)) continue;
|
|
3098
|
+
seenToolCallIds.add(part.id);
|
|
3099
|
+
}
|
|
3100
|
+
deduped.push(part);
|
|
3101
|
+
}
|
|
3102
|
+
return deduped;
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3090
3105
|
function toolInputSchemaErrorResult(
|
|
3091
3106
|
toolName: string,
|
|
3092
3107
|
input: unknown,
|
|
@@ -3996,6 +4011,8 @@ export async function runAgentLoop(opts: {
|
|
|
3996
4011
|
}
|
|
3997
4012
|
}
|
|
3998
4013
|
|
|
4014
|
+
assistantContent = dedupeAssistantToolCallsById(assistantContent);
|
|
4015
|
+
|
|
3999
4016
|
const assistantContentForHistory = assistantContent.map((part) =>
|
|
4000
4017
|
part.type === "tool-call"
|
|
4001
4018
|
? {
|
|
@@ -301,6 +301,18 @@ and screen ids across both surfaces. The canvas is the inspectable static refere
|
|
|
301
301
|
the prototype is the interactive version of that same flow, not a separate
|
|
302
302
|
design direction.
|
|
303
303
|
|
|
304
|
+
Treat “higher fidelity,” “pixel-accurate,” “polished mockup,” “production-like,”
|
|
305
|
+
“real design,” and “not a sketch/wireframe” as design-first language even when
|
|
306
|
+
the request also says “mockup.” For a new plan, use \`create-plan-design\`. For
|
|
307
|
+
an existing plan, keep the same plan id and call \`update-visual-plan\` with a
|
|
308
|
+
\`set-visual-render-mode\` patch using \`renderMode: "design"\` plus the upgraded
|
|
309
|
+
screen HTML/CSS in the same update. Ground the result in the real app shell,
|
|
310
|
+
tokens, typography, spacing, and states, and add stable \`data-design-id\`
|
|
311
|
+
targets. Put scoped styles in each screen's \`css\` field, never in a \`<style>\`
|
|
312
|
+
tag. The viewer-local Clean toggle only changes one browser's wireframe
|
|
313
|
+
preference; it is not a fidelity upgrade. Do not create a duplicate plan to
|
|
314
|
+
handle a fidelity follow-up.
|
|
315
|
+
|
|
304
316
|
## Wireframe quality — read \`references/wireframe.md\`
|
|
305
317
|
|
|
306
318
|
UI recap/plan wireframes must meet a strict quality bar — full-width chrome,
|
|
@@ -349,7 +361,9 @@ directory before authoring a plan.
|
|
|
349
361
|
visual surface, canvas only, or canvas + prototype.
|
|
350
362
|
- \`create-ui-plan\`: start a UI-first plan when the work is primarily product UI.
|
|
351
363
|
- \`create-prototype-plan\`: start a prototype-first plan with a functional top
|
|
352
|
-
review surface.
|
|
364
|
+
review surface. If the interaction itself must also be high fidelity, set each
|
|
365
|
+
screen's \`renderMode\` to \`design\` and pass scoped styles through \`css\`;
|
|
366
|
+
otherwise use \`create-plan-design\` for design-first review.
|
|
353
367
|
- \`create-plan-design\`: start a full-fidelity branded Design-tab plan with an
|
|
354
368
|
optional matching Prototype tab.
|
|
355
369
|
- \`convert-visual-plan-to-prototype\`: convert an existing HTML wireframe canvas
|
|
@@ -357,7 +371,10 @@ directory before authoring a plan.
|
|
|
357
371
|
- \`create-visual-questions\`: use only when the user explicitly asks for a visual
|
|
358
372
|
intake questionnaire, not as \`/visual-plan\` preflight.
|
|
359
373
|
- \`update-visual-plan\`: revise content, status, or comments with targeted
|
|
360
|
-
\`contentPatches\` (see Core Workflow steps 6-7).
|
|
374
|
+
\`contentPatches\` (see Core Workflow steps 6-7). Use
|
|
375
|
+
\`set-visual-render-mode\` with \`renderMode: "design"\` when promoting an
|
|
376
|
+
existing plan to high fidelity, together with deliberate screen HTML/CSS;
|
|
377
|
+
render mode alone only removes sketch treatment. \`replace-blocks\` and full
|
|
361
378
|
\`content\` replacement require \`expectedUpdatedAt\` from a fresh
|
|
362
379
|
\`get-visual-plan\` call.
|
|
363
380
|
- \`read-visual-plan-source\`: read the normalized plan as \`plan.mdx\`,
|
|
@@ -142,7 +142,7 @@ function ArtboardFrame({
|
|
|
142
142
|
const fitRef = useRef<HTMLDivElement>(null);
|
|
143
143
|
const isDark = useIsDark();
|
|
144
144
|
const theme: "light" | "dark" = isDark ? "dark" : "light";
|
|
145
|
-
const
|
|
145
|
+
const preferredStyle = useWireframeStyle();
|
|
146
146
|
const preset = SURFACE_PRESETS[surface] ?? SURFACE_PRESETS.desktop;
|
|
147
147
|
const width = canvasWidth ?? preset.width;
|
|
148
148
|
// AUTO-HEIGHT: with no explicit `canvasSize` the artboard height is driven by
|
|
@@ -164,6 +164,7 @@ function ArtboardFrame({
|
|
|
164
164
|
fixedHeight ?? null,
|
|
165
165
|
);
|
|
166
166
|
const designMode = renderMode === "design";
|
|
167
|
+
const style = designMode ? "clean" : preferredStyle;
|
|
167
168
|
const sketchy = !designMode && style === "sketchy" && !skeleton;
|
|
168
169
|
const roughEnabled = sketchy && roughOverlay;
|
|
169
170
|
const frameBorder = skeleton
|
|
@@ -409,6 +410,7 @@ function KitArtboard({
|
|
|
409
410
|
surface={data.surface}
|
|
410
411
|
compact={compact}
|
|
411
412
|
skeleton={data.skeleton}
|
|
413
|
+
renderMode={data.renderMode}
|
|
412
414
|
showFrame={showFrame}
|
|
413
415
|
selector="[data-rough]"
|
|
414
416
|
caption={data.caption}
|
|
@@ -26,6 +26,8 @@ export const BUILDER_RELAY_STATE_PARAM = "_an_relay";
|
|
|
26
26
|
export const BUILDER_RELAY_SECRET_ENV = "AGENT_NATIVE_BUILDER_RELAY_SECRET";
|
|
27
27
|
export const BUILDER_RELAY_TARGET_ORIGINS_ENV =
|
|
28
28
|
"AGENT_NATIVE_BUILDER_RELAY_TARGET_ORIGINS";
|
|
29
|
+
export const BUILDER_RELAY_TARGET_DOMAIN_SUFFIXES_ENV =
|
|
30
|
+
"AGENT_NATIVE_BUILDER_RELAY_TARGET_DOMAIN_SUFFIXES";
|
|
29
31
|
export const BUILDER_RELAY_TIMESTAMP_HEADER = "x-agent-native-relay-timestamp";
|
|
30
32
|
export const BUILDER_RELAY_FLOW_HEADER = "x-agent-native-relay-flow";
|
|
31
33
|
export const BUILDER_RELAY_SIGNATURE_HEADER = "x-agent-native-relay-signature";
|
|
@@ -151,15 +153,18 @@ export function isTrustedBuilderRelayTargetOrigin(value: string): boolean {
|
|
|
151
153
|
) {
|
|
152
154
|
return false;
|
|
153
155
|
}
|
|
154
|
-
const
|
|
155
|
-
if (!configured) return false;
|
|
156
|
-
return configured
|
|
156
|
+
const exactOriginMatch = (process.env[BUILDER_RELAY_TARGET_ORIGINS_ENV] ?? "")
|
|
157
157
|
.split(",")
|
|
158
158
|
.map((origin) => origin.trim())
|
|
159
159
|
.filter(Boolean)
|
|
160
160
|
.some((origin) => origin === value && !origin.includes("*"));
|
|
161
|
+
return (
|
|
162
|
+
exactOriginMatch ||
|
|
163
|
+
builderRelayTargetDomainSuffixes().some((suffix) =>
|
|
164
|
+
hostname.endsWith(suffix),
|
|
165
|
+
)
|
|
166
|
+
);
|
|
161
167
|
}
|
|
162
|
-
|
|
163
168
|
export function signBuilderPreviewRelayState(input: {
|
|
164
169
|
ownerEmail: string;
|
|
165
170
|
targetOrigin: string;
|
|
@@ -191,6 +196,31 @@ export function signBuilderPreviewRelayState(input: {
|
|
|
191
196
|
return { state: `${encoded}.${builderRelayMac(encoded)}`, payload };
|
|
192
197
|
}
|
|
193
198
|
|
|
199
|
+
function builderRelayTargetDomainSuffixes(): string[] {
|
|
200
|
+
return (process.env[BUILDER_RELAY_TARGET_DOMAIN_SUFFIXES_ENV] ?? "")
|
|
201
|
+
.split(",")
|
|
202
|
+
.map((suffix) => suffix.trim().toLowerCase())
|
|
203
|
+
.filter((suffix) => {
|
|
204
|
+
if (!suffix.startsWith(".") || suffix.includes("*")) return false;
|
|
205
|
+
const hostname = suffix.slice(1);
|
|
206
|
+
if (!hostname.includes(".") || hostname.length > 253) return false;
|
|
207
|
+
if (
|
|
208
|
+
!hostname
|
|
209
|
+
.split(".")
|
|
210
|
+
.every((label) =>
|
|
211
|
+
/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label),
|
|
212
|
+
)
|
|
213
|
+
) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
return new URL(`https://${hostname}`).hostname === hostname;
|
|
218
|
+
} catch {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
194
224
|
export function verifyBuilderPreviewRelayState(
|
|
195
225
|
state: string | null | undefined,
|
|
196
226
|
options: { now?: number } = {},
|
|
@@ -2345,7 +2345,7 @@ export function createCoreRoutesPlugin(
|
|
|
2345
2345
|
timestamp: getHeader(event, BUILDER_RELAY_TIMESTAMP_HEADER),
|
|
2346
2346
|
flowId: getHeader(event, BUILDER_RELAY_FLOW_HEADER),
|
|
2347
2347
|
signature: getHeader(event, BUILDER_RELAY_SIGNATURE_HEADER),
|
|
2348
|
-
requestOrigin:
|
|
2348
|
+
requestOrigin: getBuilderBrowserOriginForEvent(event),
|
|
2349
2349
|
requestBasePath: getAppBasePath(),
|
|
2350
2350
|
},
|
|
2351
2351
|
{
|
|
@@ -2612,7 +2612,7 @@ fn source_asbd(
|
|
|
2612
2612
|
}
|
|
2613
2613
|
|
|
2614
2614
|
/// Decode one audio buffer's raw bytes into f32 samples according to the
|
|
2615
|
-
/// stream's sample format (float32/float64 or signed int16/int32).
|
|
2615
|
+
/// stream's sample format (float32/float64 or signed int16/int24/int32).
|
|
2616
2616
|
fn decode_samples_to_f32(bytes: &[u8], is_float: bool, bits: u32) -> Vec<f32> {
|
|
2617
2617
|
match (is_float, bits) {
|
|
2618
2618
|
(true, 32) => bytes_to_f32_vec(bytes),
|
|
@@ -2624,6 +2624,15 @@ fn decode_samples_to_f32(bytes: &[u8], is_float: bool, bits: u32) -> Vec<f32> {
|
|
|
2624
2624
|
.chunks_exact(2)
|
|
2625
2625
|
.map(|c| i16::from_le_bytes([c[0], c[1]]) as f32 / 32768.0)
|
|
2626
2626
|
.collect(),
|
|
2627
|
+
(false, 24) => bytes
|
|
2628
|
+
.chunks_exact(3)
|
|
2629
|
+
.map(|c| {
|
|
2630
|
+
// Reconstruct a 24-bit little-endian signed integer and sign-extend to i32.
|
|
2631
|
+
let raw = (c[0] as i32) | ((c[1] as i32) << 8) | ((c[2] as i32) << 16);
|
|
2632
|
+
let signed = if raw & 0x800000 != 0 { raw | !0x00FF_FFFFi32 } else { raw };
|
|
2633
|
+
signed as f32 / 8_388_608.0
|
|
2634
|
+
})
|
|
2635
|
+
.collect(),
|
|
2627
2636
|
(false, 32) => bytes
|
|
2628
2637
|
.chunks_exact(4)
|
|
2629
2638
|
.map(|c| i32::from_le_bytes(c.try_into().unwrap()) as f32 / 2_147_483_648.0)
|
|
@@ -301,6 +301,18 @@ and screen ids across both surfaces. The canvas is the inspectable static refere
|
|
|
301
301
|
the prototype is the interactive version of that same flow, not a separate
|
|
302
302
|
design direction.
|
|
303
303
|
|
|
304
|
+
Treat “higher fidelity,” “pixel-accurate,” “polished mockup,” “production-like,”
|
|
305
|
+
“real design,” and “not a sketch/wireframe” as design-first language even when
|
|
306
|
+
the request also says “mockup.” For a new plan, use `create-plan-design`. For
|
|
307
|
+
an existing plan, keep the same plan id and call `update-visual-plan` with a
|
|
308
|
+
`set-visual-render-mode` patch using `renderMode: "design"` plus the upgraded
|
|
309
|
+
screen HTML/CSS in the same update. Ground the result in the real app shell,
|
|
310
|
+
tokens, typography, spacing, and states, and add stable `data-design-id`
|
|
311
|
+
targets. Put scoped styles in each screen's `css` field, never in a `<style>`
|
|
312
|
+
tag. The viewer-local Clean toggle only changes one browser's wireframe
|
|
313
|
+
preference; it is not a fidelity upgrade. Do not create a duplicate plan to
|
|
314
|
+
handle a fidelity follow-up.
|
|
315
|
+
|
|
304
316
|
## Wireframe quality — read `references/wireframe.md`
|
|
305
317
|
|
|
306
318
|
UI recap/plan wireframes must meet a strict quality bar — full-width chrome,
|
|
@@ -349,7 +361,9 @@ directory before authoring a plan.
|
|
|
349
361
|
visual surface, canvas only, or canvas + prototype.
|
|
350
362
|
- `create-ui-plan`: start a UI-first plan when the work is primarily product UI.
|
|
351
363
|
- `create-prototype-plan`: start a prototype-first plan with a functional top
|
|
352
|
-
review surface.
|
|
364
|
+
review surface. If the interaction itself must also be high fidelity, set each
|
|
365
|
+
screen's `renderMode` to `design` and pass scoped styles through `css`;
|
|
366
|
+
otherwise use `create-plan-design` for design-first review.
|
|
353
367
|
- `create-plan-design`: start a full-fidelity branded Design-tab plan with an
|
|
354
368
|
optional matching Prototype tab.
|
|
355
369
|
- `convert-visual-plan-to-prototype`: convert an existing HTML wireframe canvas
|
|
@@ -357,7 +371,10 @@ directory before authoring a plan.
|
|
|
357
371
|
- `create-visual-questions`: use only when the user explicitly asks for a visual
|
|
358
372
|
intake questionnaire, not as `/visual-plan` preflight.
|
|
359
373
|
- `update-visual-plan`: revise content, status, or comments with targeted
|
|
360
|
-
`contentPatches` (see Core Workflow steps 6-7).
|
|
374
|
+
`contentPatches` (see Core Workflow steps 6-7). Use
|
|
375
|
+
`set-visual-render-mode` with `renderMode: "design"` when promoting an
|
|
376
|
+
existing plan to high fidelity, together with deliberate screen HTML/CSS;
|
|
377
|
+
render mode alone only removes sketch treatment. `replace-blocks` and full
|
|
361
378
|
`content` replacement require `expectedUpdatedAt` from a fresh
|
|
362
379
|
`get-visual-plan` call.
|
|
363
380
|
- `read-visual-plan-source`: read the normalized plan as `plan.mdx`,
|
|
@@ -104,6 +104,16 @@ Design tab as the visual source of truth and the Prototype tab as the same
|
|
|
104
104
|
direction made interactive. (`create-plan-design` is an MCP tool reached from
|
|
105
105
|
`/visual-plan`, not a separate slash command.)
|
|
106
106
|
|
|
107
|
+
Treat “higher fidelity,” “pixel-accurate,” “polished mockup,” “production-like,”
|
|
108
|
+
“real design,” and “not a sketch/wireframe” as design-first requests, even when
|
|
109
|
+
the prompt also says “mockup.” For an existing plan, do not create a duplicate:
|
|
110
|
+
call `update-visual-plan` on the same plan id with a
|
|
111
|
+
`set-visual-render-mode` patch using `renderMode: "design"`, and replace or patch
|
|
112
|
+
the affected screen HTML/CSS in the same update. Put scoped styles in each
|
|
113
|
+
screen's `css` field, never in `<style>` tags. Merely switching the viewer-local
|
|
114
|
+
Clean toggle removes rough.js for one browser but does not create a high-fidelity
|
|
115
|
+
artifact.
|
|
116
|
+
|
|
107
117
|
Use `/visual-recap` when the user wants a high-level review surface for a PR,
|
|
108
118
|
commit, branch, or git diff that already changed. Recaps are reverse plans:
|
|
109
119
|
derive blocks from the real diff, call `create-visual-recap` with the recap
|
|
@@ -46,6 +46,9 @@ const prototypeSurfaceSchema = z.enum([
|
|
|
46
46
|
"panel",
|
|
47
47
|
"browser",
|
|
48
48
|
]);
|
|
49
|
+
const prototypeRenderModeSchema = z.enum(["wireframe", "design"]);
|
|
50
|
+
const boundedPrototypeHtml = (value: string) =>
|
|
51
|
+
!/<\/?(?:html|head|body|script|style)\b/i.test(value);
|
|
49
52
|
|
|
50
53
|
const prototypeScreenSchema = z.object({
|
|
51
54
|
id: z.string().optional().describe("Stable screen id"),
|
|
@@ -55,11 +58,32 @@ const prototypeScreenSchema = z.object({
|
|
|
55
58
|
.optional()
|
|
56
59
|
.describe("What the reviewer should inspect on this screen"),
|
|
57
60
|
surface: prototypeSurfaceSchema.optional().default("browser"),
|
|
61
|
+
renderMode: prototypeRenderModeSchema
|
|
62
|
+
.optional()
|
|
63
|
+
.describe(
|
|
64
|
+
'Set to "design" for polished, high-fidelity, production-like, or branded screens. Design mode persists on the artifact and disables the rough.js treatment for every viewer.',
|
|
65
|
+
),
|
|
58
66
|
html: z
|
|
59
67
|
.string()
|
|
68
|
+
.max(40_000)
|
|
69
|
+
.refine(boundedPrototypeHtml, {
|
|
70
|
+
message:
|
|
71
|
+
"Prototype HTML must be a bounded fragment without html/head/body/script/style tags. Put scoped styles in the css field.",
|
|
72
|
+
})
|
|
73
|
+
.optional()
|
|
74
|
+
.describe(
|
|
75
|
+
'Bounded semantic HTML fragment for a real interactive prototype. Use safe Alpine-like directives (x-data, x-model, x-for, x-text, x-show, :class, @click, @keydown.enter) for local behavior; use data-goto="screen-id" only for true screen/route changes. Never include html/body/script/style tags; put visual styling in css.',
|
|
76
|
+
),
|
|
77
|
+
css: z
|
|
78
|
+
.string()
|
|
79
|
+
.max(20_000)
|
|
80
|
+
.refine(boundedPrototypeHtml, {
|
|
81
|
+
message:
|
|
82
|
+
"Prototype CSS must not include document, script, or style tags.",
|
|
83
|
+
})
|
|
60
84
|
.optional()
|
|
61
85
|
.describe(
|
|
62
|
-
|
|
86
|
+
"Scoped CSS for this screen. Required when the request is high fidelity: use real codebase or brand tokens, deliberate typography, spacing, color, and state styling instead of embedding a style tag in html.",
|
|
63
87
|
),
|
|
64
88
|
state: z
|
|
65
89
|
.array(
|
|
@@ -112,7 +136,7 @@ const createPrototypePlanSchema = z.object({
|
|
|
112
136
|
.optional()
|
|
113
137
|
.default([])
|
|
114
138
|
.describe(
|
|
115
|
-
|
|
139
|
+
'Prototype screens. Default to one functional screen when local UI behavior is enough; use 2-4 screens only for true routes/steps. Screen HTML should include working local controls, inputs, toggles, lists, filters, or lightweight flows where relevant. For higher-fidelity requests, prefer create-plan-design; when the interaction itself requires a polished prototype, set each screen renderMode to "design" and pass its styling in css.',
|
|
116
140
|
),
|
|
117
141
|
transitions: z
|
|
118
142
|
.array(prototypeTransitionSchema)
|
|
@@ -143,7 +167,7 @@ const createPrototypePlanSchema = z.object({
|
|
|
143
167
|
|
|
144
168
|
export default defineAction({
|
|
145
169
|
description:
|
|
146
|
-
|
|
170
|
+
'Create a plan whose primary review surface is a running interactive prototype. For a document-first plan use create-visual-plan; for a UI-first wireframe canvas use create-ui-plan; for a recap of an existing diff use create-visual-recap; for full-fidelity branded design use create-plan-design. Prototype screen HTML uses safe Alpine-like directives for local state and data-goto for screen navigation only. If a functional prototype must also be high fidelity, set renderMode to "design" and put scoped styles in css; never embed style tags in html because they are rejected. Publish via this tool; never deliver the plan as inline chat text.',
|
|
147
171
|
schema: createPrototypePlanSchema.refine(
|
|
148
172
|
(args) => Boolean(args.brief || args.goal),
|
|
149
173
|
{ message: "Either brief or goal is required." },
|
|
@@ -49,7 +49,7 @@ const AUTHORING_RULES_NOTE = `
|
|
|
49
49
|
|
|
50
50
|
**API endpoints**: keep \`api-endpoint\` and \`openapi-spec\` blocks in normal single-column document flow. Use \`columns\` only for an explicit before/after contract comparison.
|
|
51
51
|
|
|
52
|
-
**renderMode**: leave unset or set to \`wireframe\`
|
|
52
|
+
**Visual fidelity and renderMode**: leave \`renderMode\` unset or set it to \`wireframe\` for normal wireframes. “Higher fidelity,” “pixel-accurate,” “polished mockup,” “production-like,” “real design,” or “not a sketch/wireframe” requires \`renderMode: "design"\`, substantial branded HTML/CSS grounded in the real app, and stable \`data-design-id\` targets. Put scoped styles in the wireframe/prototype \`css\` field — never in a \`<style>\` tag. On an existing plan, update the same plan id with \`set-visual-render-mode\` plus the upgraded HTML/CSS; do not create a duplicate. The viewer-local Clean toggle is not a fidelity upgrade.\``;
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Expose the live plan block vocabulary to the agent. The list is generated from
|
|
@@ -112,6 +112,7 @@ function contentPatchTargetId(patch: PlanContentPatch) {
|
|
|
112
112
|
if ("blockId" in patch) return patch.blockId;
|
|
113
113
|
if ("screenId" in patch) return patch.screenId;
|
|
114
114
|
if (patch.op === "set-metadata") return "plan-metadata";
|
|
115
|
+
if (patch.op === "set-visual-render-mode") return "visual-render-mode";
|
|
115
116
|
if (patch.op === "set-prototype" || patch.op === "remove-prototype") {
|
|
116
117
|
return "prototype";
|
|
117
118
|
}
|
|
@@ -451,7 +452,7 @@ function contentPatchDetails(input: {
|
|
|
451
452
|
const CONTENT_DESCRIPTION =
|
|
452
453
|
"Destructive full structured content replacement. Read the latest plan first and pass its updatedAt as expectedUpdatedAt. Prefer granular contentPatches for ordinary edits; use this only for intentional broad restructuring.";
|
|
453
454
|
const CONTENT_PATCHES_DESCRIPTION =
|
|
454
|
-
"Targeted structured content edits addressed by stable id. Prefer granular operations for live plans. The destructive replace-blocks operation requires expectedUpdatedAt from a fresh read. patch-visual-plan-source is only for exported MDX folders. Supported ops: set-metadata for title/brief, set-prototype / remove-prototype / update-prototype-screen / patch-prototype-html for live prototype surfaces; update-block / replace-block, update-rich-text, patch-wireframe-html, patch-diagram-html, update-wireframe-node, replace-wireframe-screen, update-canvas-frame, update-canvas-annotation / append-canvas-annotation, append-block / remove-block, update-custom-html.";
|
|
455
|
+
"Targeted structured content edits addressed by stable id. Prefer granular operations for live plans. The destructive replace-blocks operation requires expectedUpdatedAt from a fresh read. patch-visual-plan-source is only for exported MDX folders. Supported ops: set-metadata for title/brief; set-visual-render-mode to persist `design` (high fidelity, authored CSS, no rough.js for any viewer) or `wireframe` across canvas, linked blocks, and prototype screens; set-prototype / remove-prototype / update-prototype-screen / patch-prototype-html for live prototype surfaces; update-block / replace-block, update-rich-text, patch-wireframe-html, patch-diagram-html, update-wireframe-node, replace-wireframe-screen, update-canvas-frame, update-canvas-annotation / append-canvas-annotation, append-block / remove-block, update-custom-html. A fidelity upgrade must also replace or patch the existing screen HTML/CSS with deliberate branded design; changing render mode alone only removes the sketch treatment.";
|
|
455
456
|
|
|
456
457
|
// Named so `agentInputSchema` below can `.extend()` it with compact
|
|
457
458
|
// `content`/`contentPatches` fields instead of duplicating every other key.
|
|
@@ -528,7 +529,7 @@ const updateVisualPlanSchema = z.object({
|
|
|
528
529
|
|
|
529
530
|
export default defineAction({
|
|
530
531
|
description:
|
|
531
|
-
"Update an Agent-Native Plan's structured content blocks, prototype screens, sections, comments, or status. Prefer contentPatches for targeted edits. Use full content only for broad restructuring. Works on plans and recaps alike when you have editor access; with viewer access (common on PR recaps published by CI) only comment-only calls succeed — to change a recap you cannot edit, publish a replacement with create-visual-recap instead of retrying this call.",
|
|
532
|
+
"Update an Agent-Native Plan's structured content blocks, prototype screens, visual fidelity, sections, comments, or status. Prefer contentPatches for targeted edits. When a user asks for higher fidelity on an existing plan, update that same plan in place: use set-visual-render-mode with design and provide polished screen HTML/CSS in the same call instead of creating a duplicate plan or only toggling the viewer-local clean style. Use full content only for broad restructuring. Works on plans and recaps alike when you have editor access; with viewer access (common on PR recaps published by CI) only comment-only calls succeed — to change a recap you cannot edit, publish a replacement with create-visual-recap instead of retrying this call.",
|
|
532
533
|
schema: updateVisualPlanSchema,
|
|
533
534
|
// ADVERTISED-ONLY: same top-level shape, but `content`/`contentPatches`
|
|
534
535
|
// swap the deep per-block-type union for a compact `type`-enum stand-in.
|
|
@@ -652,10 +652,24 @@ export function PlanContentRenderer({
|
|
|
652
652
|
hiddenChangedFileBlockIds,
|
|
653
653
|
hideChangedFiles,
|
|
654
654
|
]);
|
|
655
|
-
const blockLookup = useMemo(
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
655
|
+
const blockLookup = useMemo(() => {
|
|
656
|
+
const blocks = new Map<string, PlanBlock>();
|
|
657
|
+
const visit = (block: PlanBlock) => {
|
|
658
|
+
blocks.set(block.id, block);
|
|
659
|
+
if (block.type === "tabs") {
|
|
660
|
+
for (const tab of block.data.tabs) {
|
|
661
|
+
for (const child of tab.blocks) visit(child);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (block.type === "columns") {
|
|
665
|
+
for (const column of block.data.columns) {
|
|
666
|
+
for (const child of column.blocks) visit(child);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
for (const block of content.blocks) visit(block);
|
|
671
|
+
return blocks;
|
|
672
|
+
}, [content.blocks]);
|
|
659
673
|
const fileToBlockIdMap = useMemo<Map<string, string>>(() => {
|
|
660
674
|
if (!isRecap) return new Map();
|
|
661
675
|
const map = new Map<string, string>();
|
|
@@ -60,7 +60,7 @@ export function PlanVisualSurface({
|
|
|
60
60
|
onDesignElementStyleChange,
|
|
61
61
|
}: PlanVisualSurfaceProps) {
|
|
62
62
|
const t = useT();
|
|
63
|
-
const designCanvas = isDesignCanvas(canvas);
|
|
63
|
+
const designCanvas = isDesignCanvas(canvas, blockLookup);
|
|
64
64
|
const [selectedDesignElement, setSelectedDesignElement] =
|
|
65
65
|
useState<DesignElementSelection | null>(null);
|
|
66
66
|
const selectedDesignElementKey = selectedDesignElement
|
|
@@ -226,10 +226,17 @@ export function PlanVisualSurface({
|
|
|
226
226
|
return null;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
function isDesignCanvas(
|
|
229
|
+
function isDesignCanvas(
|
|
230
|
+
canvas: PlanContent["canvas"] | undefined,
|
|
231
|
+
blockLookup: Map<string, PlanBlock>,
|
|
232
|
+
) {
|
|
230
233
|
return Boolean(
|
|
231
234
|
canvas?.mode === "design" ||
|
|
232
|
-
canvas?.frames.some((frame) =>
|
|
235
|
+
canvas?.frames.some((frame) => {
|
|
236
|
+
if (frame.wireframe) return frame.wireframe.renderMode === "design";
|
|
237
|
+
const block = frame.blockId ? blockLookup.get(frame.blockId) : undefined;
|
|
238
|
+
return block?.type === "wireframe" && block.data.renderMode === "design";
|
|
239
|
+
}),
|
|
233
240
|
);
|
|
234
241
|
}
|
|
235
242
|
|
|
@@ -194,7 +194,7 @@ function ArtboardFrame({
|
|
|
194
194
|
const fitRef = useRef<HTMLDivElement>(null);
|
|
195
195
|
const { resolvedTheme } = useTheme();
|
|
196
196
|
const theme: "light" | "dark" = resolvedTheme === "dark" ? "dark" : "light";
|
|
197
|
-
const
|
|
197
|
+
const preferredStyle = useWireframeStyle();
|
|
198
198
|
const preset = SURFACE_PRESETS[surface] ?? SURFACE_PRESETS.desktop;
|
|
199
199
|
const height = canvasSize ?? preset.height;
|
|
200
200
|
const width = canvasWidth ?? preset.width;
|
|
@@ -202,6 +202,7 @@ function ArtboardFrame({
|
|
|
202
202
|
const maxFrameWidth = compact ? preset.width * baseScale : width;
|
|
203
203
|
const [fitScale, setFitScale] = useState(baseScale);
|
|
204
204
|
const designMode = renderMode === "design";
|
|
205
|
+
const style = designMode ? "clean" : preferredStyle;
|
|
205
206
|
const sketchy = !designMode && style === "sketchy" && !skeleton;
|
|
206
207
|
const roughEnabled = sketchy && roughOverlay;
|
|
207
208
|
const paper = designMode
|
|
@@ -560,6 +561,7 @@ function KitWireframe({
|
|
|
560
561
|
canvasSize={canvasSize}
|
|
561
562
|
canvasWidth={canvasWidth}
|
|
562
563
|
skeleton={data.skeleton}
|
|
564
|
+
renderMode={data.renderMode}
|
|
563
565
|
showFrame={showFrame}
|
|
564
566
|
selector="[data-rough]"
|
|
565
567
|
caption={data.caption}
|
|
@@ -564,6 +564,10 @@ const messages = {
|
|
|
564
564
|
label: "UI flow",
|
|
565
565
|
description: "UI flow - wireframes and states",
|
|
566
566
|
},
|
|
567
|
+
design: {
|
|
568
|
+
label: "High-fidelity design",
|
|
569
|
+
description: "High-fidelity design - polished branded screens",
|
|
570
|
+
},
|
|
567
571
|
questions: {
|
|
568
572
|
label: "Visual questions",
|
|
569
573
|
description: "Visual questions - explicit intake",
|
|
@@ -587,6 +591,8 @@ const messages = {
|
|
|
587
591
|
"# Implementation plan\n\nPaste the existing Codex or Claude Code plan here and turn it into a visual review document.",
|
|
588
592
|
},
|
|
589
593
|
assessment: {
|
|
594
|
+
design:
|
|
595
|
+
"Auto detected a high-fidelity request; the agent will create polished branded screens.",
|
|
590
596
|
ui: "Auto detected UI states or flows; the agent will make a wireframe-first plan.",
|
|
591
597
|
visual:
|
|
592
598
|
"Auto will ask the agent for a rich technical plan with diagrams and implementation detail.",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type CreatePlanKind = "auto" | "design" | "ui" | "questions" | "visual";
|
|
2
|
+
export type ResolvedPlanKind = Exclude<CreatePlanKind, "auto">;
|
|
3
|
+
export type AutoPlanKind = Exclude<ResolvedPlanKind, "questions">;
|
|
4
|
+
|
|
5
|
+
export function isProbablyImportedPlan(prompt: string) {
|
|
6
|
+
const trimmed = prompt.trim();
|
|
7
|
+
const lines = trimmed.split(/\r?\n/).filter((line) => line.trim());
|
|
8
|
+
if (trimmed.length > 900 && lines.length > 8) return true;
|
|
9
|
+
const hasHeading = lines.some((line) => /^#{1,4}\s+\S/.test(line.trim()));
|
|
10
|
+
const checklistCount = lines.filter((line) =>
|
|
11
|
+
/^[-*]\s+\[[ x]\]\s+\S/i.test(line.trim()),
|
|
12
|
+
).length;
|
|
13
|
+
const taskCount = lines.filter((line) =>
|
|
14
|
+
/^([-*]|\d+[.)])\s+\S/.test(line.trim()),
|
|
15
|
+
).length;
|
|
16
|
+
const hasPlanLanguage =
|
|
17
|
+
/\b(implementation plan|acceptance criteria|milestones?|phases?|risks?|open questions?|test plan)\b/i.test(
|
|
18
|
+
trimmed,
|
|
19
|
+
);
|
|
20
|
+
return (
|
|
21
|
+
trimmed.includes("```") ||
|
|
22
|
+
(hasHeading && (taskCount >= 3 || hasPlanLanguage)) ||
|
|
23
|
+
(checklistCount >= 2 && trimmed.length > 220)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function assessPlanPrompt(prompt: string): { kind: AutoPlanKind } {
|
|
28
|
+
const highFidelity =
|
|
29
|
+
/\b(high(?:er)?[- ]fidelity|full[- ]fidelity|hi[- ]fi|polished(?: mockups?)?|production[- ](?:like|ready)|pixel[- ](?:perfect|accurate)|brand(?:ed|[- ]aware)|real design|not (?:a )?(?:sketch(?:y)?|wireframe)|beyond (?:the )?(?:sketch|wireframe))\b/i.test(
|
|
30
|
+
prompt,
|
|
31
|
+
);
|
|
32
|
+
if (highFidelity) return { kind: "design" };
|
|
33
|
+
|
|
34
|
+
const uiDirection =
|
|
35
|
+
/\b(ui|screen|screens|layout|wireframe|mockup|form factor|mobile|desktop|responsive|nav|sidebar|flow|redesign|empty state|loading state|error state)\b/i.test(
|
|
36
|
+
prompt,
|
|
37
|
+
);
|
|
38
|
+
return { kind: uiDirection ? "ui" : "visual" };
|
|
39
|
+
}
|