diff --git a/src/renderer/src/ai/mockupWireframe.ts b/src/renderer/src/ai/mockupWireframe.ts
new file mode 100644
index 0000000..d286c08
--- /dev/null
+++ b/src/renderer/src/ai/mockupWireframe.ts
@@ -0,0 +1,142 @@
+// ─── Mockup wireframe generation ────────────────────────────────────────────
+// Generates a low-fidelity SVG wireframe for a Mockup node from the model
+// context around it: its own label / description / screen, the requirements
+// and scenarios it `illustrates`, the container it is `presented-by`, and the
+// screens it `navigates-to`. Like forgeClarify.ts this is a single tool-less
+// call to the provider adapter, not a runAIPrompt loop — the output is one
+// blob of markup, not a sequence of model edits.
+//
+// The SVG is only ever rendered through an
data URI (see MockupNode),
+// which never runs scripts or fetches external resources; sanitizeWireframeSvg
+// still strips the obvious active content so the stored markup is inert even
+// if something later inlines it.
+
+import { getAdapter } from './registry'
+import { textOf, type AISettings, type TokenUsage } from './types'
+import type { C4Node, C4Relation } from '../types/c4'
+
+export const WIREFRAME_WIDTH = 400
+export const WIREFRAME_HEIGHT = 300
+
+/** Upper bound on stored markup — keeps a model full of mockups from turning
+ * into megabytes of eagerly-loaded frontmatter. */
+export const MAX_WIREFRAME_CHARS = 20_000
+
+type NodeMap = Record
+type RelationMap = Record
+
+function prop(node: C4Node, key: string): string {
+ const v = (node as unknown as Record)[key]
+ return typeof v === 'string' ? v.trim() : ''
+}
+
+function describeRequirement(n: C4Node): string {
+ const action = prop(n, 'action')
+ return action ? `${n.label}: the system shall ${action}` : n.label
+}
+
+function describeScenario(n: C4Node): string {
+ const parts = [
+ prop(n, 'given') && `Given ${prop(n, 'given')}`,
+ prop(n, 'when') && `When ${prop(n, 'when')}`,
+ prop(n, 'then') && `Then ${prop(n, 'then')}`,
+ ].filter(Boolean)
+ return parts.length ? `${n.label}: ${parts.join('; ')}` : n.label
+}
+
+export function buildWireframePrompt(mockupId: string, nodes: NodeMap, relations: RelationMap): string {
+ const mockup = nodes[mockupId]
+ if (!mockup) throw new Error(`Unknown mockup node: ${mockupId}`)
+
+ const outgoing = Object.values(relations)
+ .filter((r) => r.sourceId === mockupId)
+ .map((r) => ({ rel: r, target: nodes[r.targetId] }))
+ .filter((x): x is { rel: C4Relation; target: C4Node } => !!x.target)
+
+ const requirements = outgoing.filter((x) => x.target.type === 'requirement').map((x) => describeRequirement(x.target))
+ const scenarios = outgoing.filter((x) => x.target.type === 'scenario').map((x) => describeScenario(x.target))
+ const presentedBy = outgoing
+ .filter((x) => x.rel.relationType === 'presented-by')
+ .map((x) => [x.target.label, x.target.technology].filter(Boolean).join(' — '))
+ const navigatesTo = outgoing
+ .filter((x) => x.target.type === 'mockup')
+ .map((x) => (x.rel.label ? `${x.target.label} (via ${x.rel.label})` : x.target.label))
+
+ const lines = [
+ `Draw a low-fidelity UI wireframe for the screen "${mockup.label}".`,
+ ]
+ const screen = prop(mockup, 'screen')
+ if (screen) lines.push(`Screen / route: ${screen}`)
+ const description = prop(mockup, 'description')
+ if (description) lines.push('', 'Screen description:', '"""', description, '"""')
+ const section = (title: string, items: string[]): void => {
+ if (items.length) lines.push('', title, ...items.map((i) => `- ${i}`))
+ }
+ section('Requirements this screen must illustrate:', requirements)
+ section('Scenarios this screen must support:', scenarios)
+ section('Rendered by:', presentedBy)
+ section('Navigation targets (show as buttons/links leading there):', navigatesTo)
+
+ lines.push(
+ '',
+ 'Output rules:',
+ `- Respond with ONLY one