+ Three planets leave one line together. The inner one circles
+ four times for every turn of the outer one, and all three meet
+ again at the start.
+
+
+
+ {status === "failed"
+ ? "Select the prompt text and copy it manually."
+ : status === "copied"
+ ? "Ready to paste into your agent."
+ : "Adapt the subject and placement to your brief."}
+
+
+ Adjust ink, stroke widths, and opacity without changing the drawing.
+ Try the shared tokens on W05, then copy the CSS to a wrapper around
+ your inline SVG.
+
+
+
+
+
+
+ {controls.map(({ token, label, hint, max }) => (
+
+ Ink accepts a CSS color. Stroke widths are non-negative, unitless
+ SVG values; opacity ranges from 0 to 1. Preserve the relative
+ emphasis of forms, guides, and hatching.
+
+
+ These variables apply to inline SVG with the packaged role
+ stylesheet, a data-plotter root, and named{" "}
+ data-plotter-role marks. In this catalogue, AG01 and
+ W05 use that foundation. Other drawings keep their existing
+ styles. CSS on a page cannot reach into an SVG loaded as an image.
+
+
+ Start with{" "}
+
+ assets/examples/registration.svg
+ {" "}
+ in the skill ZIP. It already embeds the role stylesheet. Paste its
+ SVG markup inside your .illustration wrapper. To
+ inherit your page’s ink, remove that source SVG’s fixed{" "}
+ color style, or set --plotter-ink to an
+ explicit theme color such as var(--foreground).
+
+
+ Dash patterns, hatch spacing, and arrow geometry express meaning.
+ They belong to the shared standard and helpers, rather than CSS
+ customization. See the{" "}
+
+ visual grammar
+ {" "}
+ to choose the right mark.
+
+
+ For a resolved SVG export, pass your choices to the packaged
+ exporter with --ink and{" "}
+ {`--token=--plotter-opacity-guide=${standard.tokens["--plotter-opacity-guide"]}`}
+ . It cannot read a detached page’s CSS. The{" "}
+
+ downloadable SVGs
+ {" "}
+ already contain resolved appearance values.
+
+
+
+
+
+ );
+}
diff --git a/apps/ui/app/plotter/plotter-usage-content.ts b/apps/ui/app/plotter/plotter-usage-content.ts
new file mode 100644
index 000000000..162edd8e5
--- /dev/null
+++ b/apps/ui/app/plotter/plotter-usage-content.ts
@@ -0,0 +1,16 @@
+export function plotterDownload(filename: string) {
+ const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "/ui";
+ return `${basePath}/plotter/downloads/${filename}`;
+}
+
+export const drawingPrompt = `Use $plotter to illustrate agent registration: an identity, name, and tools become one registry record.
+
+Choose a bundled construction reference. Keep the forms transparent, the labels sparse, and the grid only if it explains the relationship.
+
+Deliver an editable 560 × 440 SVG and a self-contained export. Explain the main relationship and what you inspected.`;
+
+export const animationPrompt = `Use $plotter to animate the attached assembly-line SVG. A core lowers into its carrier, the line advances, and the next core arrives.
+
+Preserve its composition, transparent forms, line hierarchy, and core-only hatching. Keep the hatch attached to each core and give shared contact edges one stroke owner.
+
+Deliver editable HTML with the SVG and styles included, pause/resume and replay, visibility pausing, and a static fallback for reduced motion.`;
diff --git a/apps/ui/app/plotter/plotter-usage.tsx b/apps/ui/app/plotter/plotter-usage.tsx
new file mode 100644
index 000000000..7dd4adcfd
--- /dev/null
+++ b/apps/ui/app/plotter/plotter-usage.tsx
@@ -0,0 +1,113 @@
+import { DownloadIcon, FolderIcon } from "lucide-react";
+import { Button } from "@/registry/default/ui/button";
+import { EngineeringMotionPreview } from "./engineering-motion-preview";
+import { ProductionChainStudy } from "./engineering-studies";
+import { expandedStudies } from "./expanded-studies";
+import { PlotterPrompts } from "./plotter-prompts";
+import { ProductionChainMotionDrawing } from "./production-chain-motion-drawing";
+import { plotterDownload } from "./plotter-usage-content";
+
+export function PlotterUsage() {
+ const registration = expandedStudies.find(
+ (study) => study.id === "agent-registration",
+ );
+ if (!registration)
+ throw new Error("Plotter’s registration example is missing.");
+
+ return (
+
+
+
+ Use Plotter
+
+
+ Give your coding agent the references, drawing rules, and helpers
+ behind this collection. One skill covers still illustrations and
+ animation.
+
+
+
+
+
+ 1. Install the complete skill.
+
+
+ Download and unzip the package. Put the entire{" "}
+ plotter folder in your
+ agent’s skills directory, keeping its files together. Start a task
+ with $plotter.
+
+
+ }
+ >
+
+ Download Plotter .zip
+
+
+ Includes both workflows, seven visual references, the grammar,
+ shared tokens, helpers, and examples. No coss checkout required.
+
+
+
+
+
+ Keep the folder intact
+
+
{`plotter/
+ SKILL.md ← your agent starts here
+ references/ ← workflows and visual grammar
+ assets/ ← shared tokens and examples
+ scripts/ ← generation and static export
+ agents/ ← skill discovery metadata`}
+
+ For Codex, the default destination is{" "}
+
+ ~/.codex/skills/plotter/
+
+ . If you use a custom CODEX_HOME, place it under that
+ directory’s skills/ folder.
+
+
+
+ Use it without installing
+
+
+ Open the extracted folder in your coding agent. Ask it to read{" "}
+ plotter/SKILL.md from the
+ parent folder, then give it your brief. Keep the linked references
+ and assets accessible.
+
+ );
+}
diff --git a/apps/ui/app/plotter/primitives-motion.module.css b/apps/ui/app/plotter/primitives-motion.module.css
new file mode 100644
index 000000000..65202934b
--- /dev/null
+++ b/apps/ui/app/plotter/primitives-motion.module.css
@@ -0,0 +1,13 @@
+.construction {
+ display: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .drawing[data-live] .construction {
+ display: block;
+ }
+
+ .drawing[data-live] .original {
+ visibility: hidden;
+ }
+}
diff --git a/apps/ui/app/plotter/primitives-slider.tsx b/apps/ui/app/plotter/primitives-slider.tsx
new file mode 100644
index 000000000..a3d9006c5
--- /dev/null
+++ b/apps/ui/app/plotter/primitives-slider.tsx
@@ -0,0 +1,87 @@
+import {
+ ConstructionAnnotation,
+ ConstructionMotion,
+} from "./primitives-construction-motion";
+import { secondaryLine } from "./primitives-morph-style";
+import {
+ morphCycleDuration,
+ morphHoldDuration,
+ morphKeyTimes,
+ morphStageDuration,
+} from "./primitives-morph-timing";
+
+const travelGuides = "M204 116V292 M364 116V292 M204 164H364 M204 244H364";
+const scalePositions = [124, 164, 204, 244, 284, 324, 364, 404, 444];
+
+// Remove only coincident grid strokes, so the construction dashes retain gaps.
+export function SliderGridMask() {
+ return (
+
+
+
+
+ );
+}
+
+export function SliderConstruction() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ {scalePositions.map((x, index) => {
+ const halfTick = index % 2 === 0 ? 4 : 2;
+ return ;
+ })}
+
+
+
+
+
+
+
+ 4u
+
+
+ 0%
+
+
+ 50%
+
+
+ 100%
+
+
+ 06 / Slider
+
+
+
+
+ );
+}
diff --git a/apps/ui/app/plotter/primitives-switch.tsx b/apps/ui/app/plotter/primitives-switch.tsx
new file mode 100644
index 000000000..7918526b0
--- /dev/null
+++ b/apps/ui/app/plotter/primitives-switch.tsx
@@ -0,0 +1,112 @@
+import { ConstructionAnnotation } from "./primitives-construction-motion";
+import { secondaryLine } from "./primitives-morph-style";
+import {
+ morphCycleDuration as duration,
+ morphKeyTimes as keyTimes,
+} from "./primitives-morph-timing";
+
+const radiusEndpoint = -60 / Math.sqrt(2);
+
+export function SwitchConstruction() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ 4u
+
+
+ 03 / Switch
+
+
+
+
+ );
+}
+
+export function SwitchThumb() {
+ return (
+
+
+
+ {/* Geometry resets after the whole thumb is hidden. */}
+
+
+
+
+
+
+
+
+ 1.5u
+
+
+
+
+ );
+}
diff --git a/apps/ui/app/plotter/production-chain-hatching.tsx b/apps/ui/app/plotter/production-chain-hatching.tsx
new file mode 100644
index 000000000..1562c4517
--- /dev/null
+++ b/apps/ui/app/plotter/production-chain-hatching.tsx
@@ -0,0 +1,19 @@
+import { hatch } from "../../skills/plotter/assets/foundation/index.mjs";
+
+const coreHatch = hatch({
+ region: { kind: "rect", x: 0, y: 0, width: 48, height: 48 },
+ pattern: "H1",
+});
+
+// H1 identifies only the square core face.
+// Endpoints are clipped to the existing boundaries; no material is implied.
+export function ProductionChainHatching({ x, y }: { x: number; y: number }) {
+ return (
+
+ );
+}
diff --git a/apps/ui/app/plotter/production-chain-motion-drawing.tsx b/apps/ui/app/plotter/production-chain-motion-drawing.tsx
new file mode 100644
index 000000000..3bf92841a
--- /dev/null
+++ b/apps/ui/app/plotter/production-chain-motion-drawing.tsx
@@ -0,0 +1,283 @@
+"use client";
+
+import { useId } from "react";
+import {
+ arrow,
+ standard,
+} from "../../skills/plotter/assets/foundation/index.mjs";
+import { BlueprintDrawing } from "./blueprint-drawing";
+import { ProductionChainHatching } from "./production-chain-hatching";
+
+const cycle = 1.8;
+const duration = `${cycle}s`;
+const pitch = 136;
+const carrierCenters = [8, 144, 280, 416];
+const stations = [144, 280, 416];
+const transferArrow = arrow({
+ kind: "line",
+ from: [256, 288],
+ to: [304, 288],
+});
+
+// Quarter-pitch references sit below every swept part. Station witnesses
+// own their verticals; the field ends above the dimension and its text.
+const grid = Array.from({ length: 11 }, (_, i) => 110 + (i * pitch) / 4)
+ .filter((x) => !stations.includes(x))
+ .map((x) => `M${x} 312V336`)
+ .join(" ");
+
+const dropStart = 0.04;
+const dropTime = 0.38;
+const dropDistance = 80;
+const impact = dropStart + dropTime;
+const gravity = (2 * dropDistance) / dropTime ** 2;
+const reboundHeight = 0.35;
+const reboundSpeed = Math.sqrt(2 * gravity * reboundHeight);
+const settled = impact + (2 * reboundSpeed) / gravity;
+
+function coreDisplacement(time: number) {
+ if (time <= dropStart) return 0;
+ if (time < impact) return (gravity * (time - dropStart) ** 2) / 2;
+ if (time >= settled) return dropDistance;
+ const elapsed = time - impact;
+ return dropDistance - reboundSpeed * elapsed + (gravity * elapsed ** 2) / 2;
+}
+
+const number = (value: number) => Number(value.toFixed(9));
+// Insert wall-crossing times exactly. Linear samples between them approximate
+// constant acceleration within 0.01 SVG unit; every edge uses this same curve.
+const coreTimes = [
+ ...new Set([
+ 0,
+ dropStart,
+ dropStart + Math.sqrt((2 * 24) / gravity),
+ dropStart + Math.sqrt((2 * 72) / gravity),
+ impact,
+ impact + 0.000001,
+ impact + reboundSpeed / gravity,
+ settled,
+ cycle,
+ ...Array.from(
+ { length: Math.ceil((settled - dropStart) * 120) },
+ (_, i) => dropStart + i / 120,
+ ).filter((time) => time < settled),
+ ]),
+].sort((a, b) => a - b);
+const coreFrames = coreTimes.map((time) => {
+ const displacement = coreDisplacement(time);
+ const top = number(128 + displacement);
+ const exposedSideEnd = number(Math.max(top, Math.min(top + 48, 200)));
+ return {
+ bottom: `0 ${number(displacement)}`,
+ outline: `M256 ${top}H304 M256 ${top}V${exposedSideEnd} M304 ${top}V${exposedSideEnd}`,
+ visibility: displacement === dropDistance ? "hidden" : "visible",
+ };
+});
+const coreKeyTimes = coreTimes.map((time) => number(time / cycle)).join(";");
+
+// A motor index: jerk ramps acceleration up/down, cruises, then brakes.
+// Each ramp is an exact cubic; position, speed, and acceleration join smoothly.
+const indexSplines =
+ "0 0 1 1;0.333333333333 0 0.666666666667 0;0.333333333333 0.2 0.666666666667 0.6;0 0 1 1;0.333333333333 0.4 0.666666666667 0.8;0.333333333333 1 0.666666666667 1;0 0 1 1";
+
+function indexMotion(
+ start: number,
+ travelTime: number,
+ accelerate: number,
+ brake: number,
+ distance: number,
+ origin = 0,
+) {
+ const speed = distance / (travelTime - (accelerate + brake) / 2);
+ return {
+ keyTimes: [
+ 0,
+ start,
+ start + accelerate / 2,
+ start + accelerate,
+ start + travelTime - brake,
+ start + travelTime - brake / 2,
+ start + travelTime,
+ cycle,
+ ]
+ .map((time) => number(time / cycle))
+ .join(";"),
+ values: [
+ origin,
+ origin,
+ origin + (speed * accelerate) / 12,
+ origin + (speed * accelerate) / 2,
+ origin + distance - (speed * brake) / 2,
+ origin + distance - (speed * brake) / 12,
+ origin + distance,
+ origin + distance,
+ ]
+ .map((position) => `${number(position)} 0`)
+ .join(";"),
+ };
+}
+
+const belt = indexMotion(settled + 0.02, 1.1, 0.16, 0.2, pitch);
+const feedTravelTime = 0.7;
+const feedToDrop = 0.14;
+const feedArrival = cycle + dropStart - feedToDrop;
+// Feed overlaps indexing; its arrival is tied to the next drop, not the reset.
+const feed = indexMotion(
+ feedArrival - feedTravelTime,
+ feedTravelTime,
+ 0.1,
+ 0.12,
+ pitch * 2,
+ -pitch * 2,
+);
+
+export function ProductionChainMotionDrawing() {
+ const id = useId();
+ const carrierClip = `${id}-carrier-entry-exit`;
+ const feedClip = `${id}-core-feed`;
+
+ return (
+
+
+ {/* Only moving objects are clipped, at their entry/exit apertures. */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Feed guides clear the moving core by 4 units and end before the drop. */}
+
+
+
+
+
+
+
+ {carrierCenters.map((x) => (
+
+
+
+
+ ))}
+
+
+
+
+ frame.bottom).join(";")}
+ />
+
+
+
+ {/* Core sides transfer to the carrier walls at y=200. Each
+ remaining segment stays on the rigid 48×48 core boundary. */}
+
+ frame.outline).join(";")}
+ />
+
+
+ frame.bottom).join(";")}
+ />
+ {/* Contact transfers the bottom to the carrier. The rebound
+ restores it after 1µs, within 0.00005 unit of the seat. */}
+ frame.visibility).join(";")}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Core
+
+
+ 3u
+
+
+ Pitch
+
+
+ Load
+
+
+ Assembled
+
+
+ One line · equal carrier pitch
+
+
+
+ );
+}
diff --git a/apps/ui/app/plotter/service-studies.tsx b/apps/ui/app/plotter/service-studies.tsx
new file mode 100644
index 000000000..4a14025d6
--- /dev/null
+++ b/apps/ui/app/plotter/service-studies.tsx
@@ -0,0 +1,470 @@
+import { BlueprintDrawing } from "./blueprint-drawing";
+import type { IllustrationStudy } from "./study-types";
+
+function CloudflareEdgeStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Visitor
+
+
+ Cache
+
+
+ Origin
+
+
+ Cloudflare edge
+
+
+ Miss
+
+
+ Hit
+
+
+
+
+ 4u
+
+
+ 4u
+
+
+ R44
+
+
+
+ );
+}
+
+function VercelDeploymentStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Preview
+
+
+ Production
+
+
+ Promote
+
+
+ Production URL
+
+
+
+
+ 01
+
+
+ 02
+
+
+ 03
+
+
+ 02
+
+
+ 24
+
+
+ 144
+
+
+ R12
+
+
+
+ );
+}
+
+function StripeSettlementStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {[128, 208, 288, 368].map((x) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+ {[160, 240, 320, 400].map((x) => (
+
+ $25
+
+ ))}
+
+ $100 · Gross payments
+
+
+ Payments
+
+
+
+
+ u
+
+
+ 4u
+
+
+ R8
+
+
+
+ );
+}
+
+function StripePaymentStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Card
+
+
+ Wallet
+
+
+ Bank
+
+
+ Pay
+
+
+ Checkout
+
+
+ $24 · Paid
+
+
+ Business
+
+
+
+
+ 2u
+
+
+ u
+
+
+ R16
+
+
+
+ );
+}
+
+function LinearCycleStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Issue 01
+
+
+ Issue 02
+
+
+ Issue 03
+
+
+ Week 1
+
+
+ Week 2
+
+
+ Cycle end
+
+
+
+
+ Cycle
+
+
+ u
+
+
+ R16
+
+
+
+ );
+}
+
+export const serviceStudies: IllustrationStudy[] = [
+ {
+ id: "service-cloudflare-edge",
+ code: "B01",
+ title: "Cloudflare · Between visitor and origin",
+ description:
+ "An edge cache between visitor and origin: a hit returns locally, while a miss continues outward. A study of Cloudflare’s CDN role.",
+ category: "Services",
+ illustration: ,
+ },
+ {
+ id: "service-vercel-deploy",
+ code: "B02",
+ title: "Vercel · From preview to production",
+ description:
+ "Preview 02 is selected for production. Matching outlines identify the same source version; promotion rebuilds it for the production environment.",
+ category: "Services",
+ illustration: ,
+ },
+ {
+ id: "service-stripe-settlement",
+ code: "B03",
+ title: "Stripe · Payments, combined",
+ description:
+ "Four equal payments combine into one gross payment total, before fees, refunds, or availability. Their conserved width makes aggregation a visible construction rule.",
+ category: "Services",
+ illustration: ,
+ },
+ {
+ id: "service-linear-cycle",
+ code: "B04",
+ title: "Linear · Work aligned to a cycle",
+ description:
+ "Issue modules share a two-week construction grid. Their measured spans align the work within a common cycle boundary.",
+ category: "Services",
+ illustration: ,
+ },
+ {
+ id: "service-stripe-payment",
+ code: "B05",
+ title: "Stripe · Many ways to pay",
+ description:
+ "Card, wallet, and bank payments share one checkout aperture. A selected route carries a customer payment through to a paid purchase for the business.",
+ category: "Services",
+ illustration: ,
+ },
+];
diff --git a/apps/ui/app/plotter/shared-frame.tsx b/apps/ui/app/plotter/shared-frame.tsx
new file mode 100644
index 000000000..4e1ff8c4a
--- /dev/null
+++ b/apps/ui/app/plotter/shared-frame.tsx
@@ -0,0 +1,58 @@
+import { BlueprintDrawing } from "./blueprint-drawing";
+
+export function SharedFrameStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Shared frame
+
+
+ A
+
+
+ B
+
+
+
+
+ 14u
+
+
+ u
+
+
+ 4u
+
+
+ u
+
+
+ 7u
+
+
+ u
+
+
+
+
+ );
+}
diff --git a/apps/ui/app/plotter/state-studies.tsx b/apps/ui/app/plotter/state-studies.tsx
new file mode 100644
index 000000000..effa4058c
--- /dev/null
+++ b/apps/ui/app/plotter/state-studies.tsx
@@ -0,0 +1,22 @@
+import {
+ MissingDestinationBlueprint,
+ MissingDestinationCompact,
+} from "./missing-destination";
+import type { IllustrationStudy } from "./study-types";
+
+export const stateStudies: IllustrationStudy[] = [
+ {
+ id: "state-missing-page",
+ code: "S01",
+ title: "The missing destination",
+ description:
+ "A paired route reaches a closed end one unit before an unbuilt page. The address remains projected, but the connection is absent.",
+ category: "Empty states",
+ illustration: ,
+ emptyState: {
+ title: "Page not found",
+ description: "The page may have moved or no longer exists.",
+ illustration: ,
+ },
+ },
+];
diff --git a/apps/ui/app/plotter/study-types.ts b/apps/ui/app/plotter/study-types.ts
new file mode 100644
index 000000000..8502b6c11
--- /dev/null
+++ b/apps/ui/app/plotter/study-types.ts
@@ -0,0 +1,33 @@
+import type { ReactNode } from "react";
+
+export type StudyCategory =
+ | "Foundations"
+ | "Interfaces"
+ | "Scheduling"
+ | "Services"
+ | "Systems"
+ | "Objects"
+ | "Empty states"
+ | "Agents"
+ | "Mechanisms"
+ | "Architecture"
+ | "Nature"
+ | "Music"
+ | "Maps"
+ | "Typography"
+ | "Craft"
+ | "Play";
+
+export type IllustrationStudy = {
+ id: string;
+ code: string;
+ title: string;
+ description: string;
+ category: StudyCategory;
+ illustration: ReactNode;
+ emptyState?: {
+ title: string;
+ description: string;
+ illustration: ReactNode;
+ };
+};
diff --git a/apps/ui/app/plotter/system-state-studies.tsx b/apps/ui/app/plotter/system-state-studies.tsx
new file mode 100644
index 000000000..eab53be46
--- /dev/null
+++ b/apps/ui/app/plotter/system-state-studies.tsx
@@ -0,0 +1,582 @@
+import { BlueprintDrawing, CompactBlueprintDrawing } from "./blueprint-drawing";
+import { HorizontalDimension, VerticalDimension } from "./dimensions";
+import {
+ DisconnectedBlueprint,
+ DisconnectedCompact,
+} from "./disconnected-plug";
+import type { IllustrationStudy } from "./study-types";
+
+// The cloud’s three generating circles share the y = 200 baseline. The route
+// has a 40-unit centerline radius and stops 32 units before its continuation.
+function CloudForm() {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function CloudGap({ dash }: { dash: string }) {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function OfflineStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This device
+
+
+ No connection
+
+
+
+
+ R40
+
+
+
+ );
+}
+
+function OfflineCompact() {
+ return (
+
+
+
+
+ );
+}
+
+function LockForm() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
+
+function AccessRoute({ dash }: { dash: string }) {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function LockInset() {
+ return ;
+}
+
+function AccessStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Request
+
+
+ Protected
+
+
+
+
+ R40
+
+
+ R16
+
+
+ 16
+
+
+ Ø20
+
+
+ 8
+
+
+ Closed boundary
+
+
+
+
+ );
+}
+
+function AccessCompact() {
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+function EnvelopeForm() {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function EnvelopeFolds({ dash }: { dash: string }) {
+ return (
+
+
+
+
+ );
+}
+
+function EmailStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fold
+
+
+ One crease / two positions
+
+
+
+ );
+}
+
+function EmailCompact() {
+ return (
+
+
+
+
+ );
+}
+
+const gearModule = 14;
+const meshAngle = Math.atan2(78.4, 58.8);
+
+type Gear = {
+ cx: number;
+ cy: number;
+ teeth: number;
+ phase: number;
+ hub: number;
+};
+
+function gearOutline({ cx, cy, teeth, phase }: Gear) {
+ const pitch = (teeth * gearModule) / 2;
+ const root = pitch - 0.65 * gearModule;
+ const tip = pitch + 0.45 * gearModule;
+ const step = (2 * Math.PI) / teeth;
+ const point = (radius: number, angle: number) => ({
+ x: cx + radius * Math.cos(angle),
+ y: cy + radius * Math.sin(angle),
+ });
+ const corners = Array.from({ length: teeth }, (_, index) => {
+ const angle = phase + index * step;
+ return [
+ point(root, angle - 0.27 * step),
+ point(tip, angle - 0.17 * step),
+ point(tip, angle + 0.17 * step),
+ point(root, angle + 0.27 * step),
+ ];
+ }).flat();
+ const serialize = (p: { x: number; y: number }) =>
+ `${p.x.toFixed(3)} ${p.y.toFixed(3)}`;
+ // Short quadratic joins soften the four corners of each broad tooth.
+ // This is a schematic profile, not a generated involute or a cutting plan.
+ const joins = corners.map((corner, index) => {
+ const previous = corners[(index + corners.length - 1) % corners.length];
+ const next = corners[(index + 1) % corners.length];
+ if (!previous || !next) return { before: corner, corner, after: corner };
+ const inset = (neighbor: { x: number; y: number }) => {
+ const length = Math.hypot(neighbor.x - corner.x, neighbor.y - corner.y);
+ return {
+ x: corner.x + ((neighbor.x - corner.x) * 1.4) / length,
+ y: corner.y + ((neighbor.y - corner.y) * 1.4) / length,
+ };
+ };
+ return { before: inset(previous), corner, after: inset(next) };
+ });
+ const first = joins[0];
+ if (!first) return "";
+ return `M${serialize(first.before)}${joins
+ .map(
+ ({ before, corner, after }, index) =>
+ `${index ? `L${serialize(before)}` : ""}Q${serialize(corner)} ${serialize(after)}`,
+ )
+ .join("")}Z`;
+}
+
+// Conceptual maintenance diagram. All pitch radii derive from module 14.
+// The small pair's pitch circles touch at (383.6, 236.8); a tooth faces a
+// valley there. The main drive is separated by 48 between pitch circles.
+// The shallow, rounded profiles indicate engagement in this static pose;
+// they do not assert conjugate motion or manufacturability.
+const gears: Gear[] = [
+ { cx: 176, cy: 192, teeth: 10, phase: 0, hub: 18 },
+ { cx: 350, cy: 192, teeth: 8, phase: meshAngle, hub: 14 },
+ {
+ cx: 408.8,
+ cy: 270.4,
+ teeth: 6,
+ phase: meshAngle + Math.PI - Math.PI / 6,
+ hub: 10.5,
+ },
+];
+
+function GearsForm() {
+ return (
+ <>
+ {gears.map((gear) => (
+
+
+
+
+ ))}
+ >
+ );
+}
+
+function MaintenanceStudy() {
+ return (
+
+
+ {gears.map((gear) => (
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ Drive disengaged
+
+
+
+
+ Common pitch · 10 : 8 : 6
+
+
+
+ );
+}
+
+function MaintenanceCompact() {
+ return (
+
+
+
+
+ );
+}
+
+// R40 sides meet a tip 76 below the head center. The tangent points sit
+// 21.0526 below that center and 34.0116 to either side.
+function PinForm() {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function CoordinateField({
+ dash,
+ arrowheads = false,
+}: {
+ dash: string;
+ arrowheads?: boolean;
+}) {
+ return (
+
+
+
+
+ {arrowheads && (
+
+ )}
+
+ );
+}
+
+function LocationStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Not set
+
+
+
+
+ R40
+
+
+ x
+
+
+ y
+
+
+ 0
+
+
+
+ );
+}
+
+function LocationCompact() {
+ return (
+
+
+
+
+
+ );
+}
+
+export const systemStateStudies: IllustrationStudy[] = [
+ {
+ id: "state-offline",
+ code: "S04",
+ title: "A cloud, out of reach",
+ description:
+ "A curved connection stops before the cloud. Its separated continuation and measured gap make the missing route visible.",
+ category: "Empty states",
+ illustration: ,
+ emptyState: {
+ title: "You’re offline",
+ description: "Changes will sync when your connection returns.",
+ illustration: ,
+ },
+ },
+ {
+ id: "state-access",
+ code: "S08",
+ title: "A boundary, locked",
+ description:
+ "A closed lock interrupts the access route. Its corner radius sets the inset, while a separate construction field locates the keyhole without crossing it.",
+ category: "Empty states",
+ illustration: ,
+ emptyState: {
+ title: "Access restricted",
+ description: "Ask a workspace owner to grant you access.",
+ illustration: ,
+ },
+ },
+ {
+ id: "state-email",
+ code: "S09",
+ title: "An envelope, unfolded",
+ description:
+ "An open flap mirrors its folded position across one crease. Equal offsets and the pocket’s diagonals explain the envelope as a flat construction.",
+ category: "Empty states",
+ illustration: ,
+ emptyState: {
+ title: "Check your email",
+ description: "We sent a sign-in link to your inbox.",
+ illustration: ,
+ },
+ },
+ {
+ id: "state-maintenance",
+ code: "S10",
+ title: "A gear train, interrupted",
+ description:
+ "Three gears share a common pitch. A separated drive interrupts the mechanism.",
+ category: "Empty states",
+ illustration: ,
+ emptyState: {
+ title: "Under maintenance",
+ description: "Settings are temporarily unavailable. Check back soon.",
+ illustration: ,
+ },
+ },
+ {
+ id: "state-disconnected",
+ code: "S11",
+ title: "A connection, apart",
+ description:
+ "A mobile plug aligns with a separate socket recessed behind a thin mounting flange. The hatched support, matching contact spacing, and open gap describe the interrupted connection.",
+ category: "Empty states",
+ illustration: ,
+ emptyState: {
+ title: "Integration disconnected",
+ description: "Reconnect your account to resume syncing.",
+ illustration: ,
+ },
+ },
+ {
+ id: "state-location",
+ code: "S12",
+ title: "A pin, unplaced",
+ description:
+ "A compact pin sits just above an unresolved point in a broad coordinate field. The measured horizontal offset locates the proposed position.",
+ category: "Empty states",
+ illustration: ,
+ emptyState: {
+ title: "No location set",
+ description: "Add an address so attendees know where to meet.",
+ illustration: ,
+ },
+ },
+];
diff --git a/apps/ui/app/plotter/system-studies.tsx b/apps/ui/app/plotter/system-studies.tsx
new file mode 100644
index 000000000..595495628
--- /dev/null
+++ b/apps/ui/app/plotter/system-studies.tsx
@@ -0,0 +1,447 @@
+import { BlueprintDrawing } from "./blueprint-drawing";
+import { ParticlesBlueprint } from "./particles-blueprint";
+import { SharedFrameStudy } from "./shared-frame";
+import type { IllustrationStudy } from "./study-types";
+
+function Registration() {
+ return (
+
+
+
+
+ );
+}
+
+function TangencyStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2r
+
+
+ r
+
+
+ d
+
+
+ Common tangent
+
+
+
+
+ );
+}
+
+function CornerRuleStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ r
+
+
+ r
+
+
+ u
+
+
+ R104
+
+
+ offset
+
+
+
+
+ );
+}
+
+function ProportionStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ u
+
+
+ 1.5u
+
+
+ 2.25u
+
+
+ 3.375u
+
+
+ × 1.5
+
+
+ 2
+
+
+ 1
+
+
+
+
+ );
+}
+
+function AssemblyStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Workspace
+
+
+ Workspace
+
+
+
+
+ Ready
+
+
+ Ready
+
+
+ 01
+
+
+ 02
+
+
+ 03
+
+
+ 3 parts / 1 composition
+
+
+
+
+ );
+}
+
+function ModularTrayStudy() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Design system
+
+
+ Preview
+
+
+ Active
+
+
+
+
+ 12u
+
+
+ 5u
+
+
+ 6u
+
+
+ u
+
+
+
+
+ );
+}
+
+export const systemStudies: IllustrationStudy[] = [
+ {
+ id: "primitives-tangency",
+ code: "P01",
+ title: "Tangency",
+ description:
+ "Two radii share a baseline and an inclined tangent. The contact geometry connects unequal circles through one construction rule.",
+ category: "Foundations",
+ illustration: ,
+ },
+ {
+ id: "primitives-radius",
+ code: "P02",
+ title: "The corner rule",
+ description:
+ "One enlarged corner reveals the radius, offset, and tangent points behind a familiar interface shape.",
+ category: "Foundations",
+ illustration: ,
+ },
+ {
+ id: "primitives-proportion",
+ code: "P03",
+ title: "A system of proportions",
+ description:
+ "Four squares grow from one shared unit. A single diagonal connects the scale into a structural rhythm.",
+ category: "Foundations",
+ illustration: ,
+ },
+ {
+ id: "particles-assembly",
+ code: "C01",
+ title: "An assembly of parts",
+ description:
+ "An icon, a label, and a status pill align into one row, shown as an exploded technical drawing.",
+ category: "Interfaces",
+ illustration: ,
+ },
+ {
+ id: "particles-slots",
+ code: "C02",
+ title: "A modular tray",
+ description:
+ "Different interface fragments occupy a shared grid, like precisely fitted pieces in an architectural plan.",
+ category: "Interfaces",
+ illustration: ,
+ },
+ {
+ id: "particles-recombination",
+ code: "C03",
+ title: "Recombination",
+ description:
+ "Identity, visibility, and action combine into one compact panel. Each of the three fragments keeps its role in the assembled result.",
+ category: "Interfaces",
+ illustration: ,
+ },
+ {
+ id: "particles-shared-frame",
+ code: "C04",
+ title: "Shared frame",
+ description:
+ "Two unequal modules fit one frame. A shared unit defines the edge clearances and the space between them.",
+ category: "Interfaces",
+ illustration: ,
+ },
+];
diff --git a/apps/ui/app/plotter/visual-grammar-content.ts b/apps/ui/app/plotter/visual-grammar-content.ts
new file mode 100644
index 000000000..9f16b3159
--- /dev/null
+++ b/apps/ui/app/plotter/visual-grammar-content.ts
@@ -0,0 +1,78 @@
+export const visualGrammarEntries = [
+ {
+ id: "outlines",
+ title: "Primary and secondary outlines",
+ description:
+ "Identity, name, and tools assemble into one record. Primary outlines hold the loose fragments and finished form together; quieter face details and construction lines support them.",
+ omit: "Keep supporting marks subordinate. Giving every stroke equal weight makes the construction compete with the subject.",
+ caption: "The fragments and completed record read first.",
+ },
+ {
+ id: "hatching",
+ title: "Diagonal hatching",
+ description:
+ "Hatching singles out one bounded region. Here it identifies a visible iris blade while the aperture remains clear. Its direction and spacing stay consistent across that region.",
+ omit: "Omit hatching when no region needs distinguishing. It is not a lighting effect or a material label.",
+ caption:
+ "One blade is selected; its boundaries and the central opening stay clear.",
+ },
+ {
+ id: "grids",
+ title: "Background grids",
+ description:
+ "A grid makes a shared construction module visible. This letter’s stem, counter, and bowl radii all follow the same 32-unit field.",
+ omit: "Omit a background grid when local guides already explain the relationship. Geometric subject matter alone is not a reason to add one.",
+ caption: "The grid reveals one module across the stem, counter, and bowl.",
+ },
+ {
+ id: "arrows",
+ title: "Movement and transfer arrows",
+ description:
+ "The curved dashed line and its solid open arrowhead show a rigid part turning from its current position to the proposed one. The nearby “Quarter turn” text is a callout, not part of the arrow.",
+ omit: "Use an arrow only when a change or direction matters; alignment and equal size need different marks.",
+ caption: "On hover, only the dashed shaft and solid arrowhead remain.",
+ },
+ {
+ id: "callouts",
+ title: "Callout leaders and labels",
+ description:
+ "A callout connects a short label to the feature it names. Here the bent solid leader anchors “Quarter turn” to the curved movement path. The leader and label form one annotation.",
+ omit: "Omit labels that add no meaning. Keep the leader subordinate and end it on the named feature; use movement arrows for direction and dimensions for measurements.",
+ caption: "On hover, only the leader and “Quarter turn” label remain.",
+ },
+ {
+ id: "projections",
+ title: "Projection guides",
+ description:
+ "A whole task separates into two assigned scopes. Each projection connects the same width witness before and after separation, so the parts retain a visible relationship to the whole.",
+ omit: "Omit guides that cannot name both corresponding features. Nearby endpoints are not enough: the alignment must be exact.",
+ caption: "Four quiet guides preserve the same two allocations.",
+ },
+ {
+ id: "dimensions",
+ title: "Dimensions and radius leaders",
+ description:
+ "Dimensions reveal the proportions that construct a form. Here a concentric bend keeps a constant offset, while its width and radius use the same unit.",
+ omit: "Omit measurements that do not explain the construction. Every witness and radius leader must reach the feature it measures.",
+ caption:
+ "One unit system connects the width, offset, and actual circular radius.",
+ },
+ {
+ id: "centerlines",
+ title: "Centerlines and loci",
+ description:
+ "Centerlines and a pitch circle reveal how repeated features share one center. Six curved slots follow the same circle at equal angular intervals.",
+ omit: "Omit axes and loci that do not explain symmetry, rotation, or placement. A dashed guide must pass through the geometry it claims to organize.",
+ caption:
+ "On hover, the center mark, radial axes, and pitch circle remain; the slots and callout disappear.",
+ },
+ {
+ id: "states",
+ title: "Hidden, absent, and proposed outlines",
+ description:
+ "The dashed C-shaped boundary represents a channel behind the plate’s visible front face. The channel exists, but this view cannot see it directly. Only that hidden boundary remains on hover.",
+ omit: "Hidden means present but concealed; absent means missing; proposed means a possible future form. Label the intended state. The solid line pointing to “Hidden rear channel” is a callout leader, a separate annotation that disappears on hover.",
+ caption:
+ "This example shows hidden geometry: the channel’s boundary behind the face.",
+ },
+] as const;
diff --git a/apps/ui/app/plotter/visual-grammar-drawings.tsx b/apps/ui/app/plotter/visual-grammar-drawings.tsx
new file mode 100644
index 000000000..a20abee19
--- /dev/null
+++ b/apps/ui/app/plotter/visual-grammar-drawings.tsx
@@ -0,0 +1,1770 @@
+// Generated from skills/plotter/references/grammar/*.svg by scripts/build-plotter-grammar.py.
+import { type ReactNode, useId } from "react";
+import type { visualGrammarEntries } from "./visual-grammar-content";
+
+function GrammarArrows() {
+ return (
+
+ );
+}
+
+function GrammarCallouts() {
+ return (
+
+ );
+}
+
+function GrammarCenterlines() {
+ const id = useId();
+ return (
+
+ );
+}
+
+function GrammarDimensions() {
+ const id = useId();
+ return (
+
+ );
+}
+
+function GrammarGrids() {
+ return (
+
+ );
+}
+
+function GrammarHatching() {
+ const id = useId();
+ return (
+
+ );
+}
+
+function GrammarOutlines() {
+ return (
+
+ );
+}
+
+function GrammarProjections() {
+ return (
+
+ );
+}
+
+function GrammarStates() {
+ return (
+
+ );
+}
+
+export const visualGrammarDrawings: Record<
+ (typeof visualGrammarEntries)[number]["id"],
+ ReactNode
+> = {
+ arrows: ,
+ callouts: ,
+ centerlines: ,
+ dimensions: ,
+ grids: ,
+ hatching: ,
+ outlines: ,
+ projections: ,
+ states: ,
+};
diff --git a/apps/ui/app/plotter/visual-grammar.tsx b/apps/ui/app/plotter/visual-grammar.tsx
new file mode 100644
index 000000000..9594cd5f5
--- /dev/null
+++ b/apps/ui/app/plotter/visual-grammar.tsx
@@ -0,0 +1,80 @@
+import { GrammarComparison } from "./grammar-comparison";
+import { visualGrammarEntries } from "./visual-grammar-content";
+import { visualGrammarDrawings } from "./visual-grammar-drawings";
+
+export function VisualGrammar() {
+ return (
+
+
+
+ Visual grammar
+
+
+ A glossary of the marks these drawings use: what they mean, when they
+ help, and when to leave them out. Hover over a drawing to isolate the
+ marks being explained, then move away to see the whole construction.
+
+
+ );
+}
diff --git a/apps/ui/next.config.ts b/apps/ui/next.config.ts
index cdd60e02d..069903578 100644
--- a/apps/ui/next.config.ts
+++ b/apps/ui/next.config.ts
@@ -7,6 +7,16 @@ const nextConfig: NextConfig = {
basePath: process.env.NEXT_PUBLIC_BASE_PATH || "/ui",
async redirects() {
return [
+ {
+ source: "/illustration-lab",
+ destination: "/plotter",
+ permanent: false,
+ },
+ {
+ source: "/illustration-lab-prototype",
+ destination: "/plotter",
+ permanent: false,
+ },
{
destination: "/docs/radix-migration",
permanent: true,
diff --git a/apps/ui/package.json b/apps/ui/package.json
index c5163683f..4640850a2 100644
--- a/apps/ui/package.json
+++ b/apps/ui/package.json
@@ -49,6 +49,7 @@
"format": "biome format --write .",
"format:all": "biome check --write --unsafe --no-errors-on-unmatched",
"lint": "biome lint .",
+ "plotter:check": "bun scripts/check-plotter.tsx",
"postinstall": "fumadocs-mdx",
"registry:build": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts",
"registry:validate-deps": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/validate-registry-deps.mts",
diff --git a/apps/ui/plotter-evals/2026-10-31/README.md b/apps/ui/plotter-evals/2026-10-31/README.md
new file mode 100644
index 000000000..ab0c74232
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/README.md
@@ -0,0 +1,19 @@
+# Plotter evaluation · 2026-10-31
+
+`cases.json` preserves the three original briefs, SVG hashes, catalog IDs, and user feedback. `modules.svg`, `buffers.svg`, and `search.svg` are the original self-contained outputs, including their embedded font. The user liked all three and preferred them without the additional background grids tried afterward.
+
+Their catalog equivalents are C04, K09, and S15. Those components inherit the website’s font and theme colors; the shared-frame hatch is analytically clipped instead of using an SVG clip ID. The original geometry, measurements, and labels are retained.
+
+These were three separate fresh sessions using the skill at `aeebda6b`. There was no without-skill control or repeat trial. The results demonstrate those particular outcomes and preferences, not a measured reliability rate or proof that the skill improved performance.
+
+## Repeating an exercise
+
+Copy the complete skill directory into a temporary location. Start a fresh agent session with only that copy, the brief, and an empty output directory. Preserve the resulting SVG, the exact prompt, the package’s file hashes, and the review outcome. Keep generated candidates outside the catalog until selected.
+
+Review recognition, construction relationship, hierarchy, geometry, and readability at the intended size separately. A metadata or rendering check cannot decide visual quality. When an instruction changes, repeat the affected brief and an unrelated brief before drawing conclusions.
+
+Run `bun run plotter:check` from `apps/ui` for package links, bundled reference hashes, catalog identifiers, and SVG rendering. The skill README describes the current experimental scope.
+
+## Clean-install result
+
+The consolidated package completed one fresh-session inscribed-circle exercise. `clean-install/` preserves the brief, exact package hashes, output, agent notes, and independent geometry residuals. No checkout or conversation context was supplied. This passed a bounded portability check; the new drawing has not been selected for the catalog or approved as a style reference.
diff --git a/apps/ui/plotter-evals/2026-10-31/buffers.svg b/apps/ui/plotter-evals/2026-10-31/buffers.svg
new file mode 100644
index 000000000..bb2cb2e23
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/buffers.svg
@@ -0,0 +1,58 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/cases.json b/apps/ui/plotter-evals/2026-10-31/cases.json
new file mode 100644
index 000000000..7b6214893
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/cases.json
@@ -0,0 +1,61 @@
+{
+ "date": "2026-10-31",
+ "skill_commit": "aeebda6ba387149db564dc774c1909eef0e06c9e",
+ "purpose": "Preserved first-pass outputs and prompts. Catalog implementations remove embedded fonts and global styles while retaining the drawings.",
+ "cases": [
+ {
+ "id": "modules",
+ "request": "Create a standalone static blueprint illustration showing two differently sized rounded modules fitting into one shared frame, with a consistent gap. Intended display is 560 × 440.",
+ "output": "modules.svg",
+ "sha256": "ada7356d43edbc252ec895ea330451780a0a68fe55b0da46a7893d27472733f4",
+ "catalog_code": "C04",
+ "catalog_id": "particles-shared-frame",
+ "condition": "Fresh agent session with Plotter; no without-skill control",
+ "user_verdict": "Liked; original version preferred over added background-grid variant"
+ },
+ {
+ "id": "buffers",
+ "request": "Create a standalone static blueprint illustration explaining a 30-minute appointment with a 10-minute buffer before and after. Intended display is 560 × 440.",
+ "output": "buffers.svg",
+ "sha256": "0ab4cc7f653d7ff68dc596f52d37bab8c432233ecd45e88de5084a3fef334733",
+ "catalog_code": "K09",
+ "catalog_id": "cal-appointment-buffers",
+ "condition": "Fresh agent session with Plotter; no without-skill control",
+ "user_verdict": "Liked; original version preferred over added background-grid variant"
+ },
+ {
+ "id": "search",
+ "request": "Create a standalone static blueprint illustration for No matching results: a query has no matching entry in a small indexed collection. Intended display is 560 × 440.",
+ "output": "search.svg",
+ "sha256": "40ce15fc9113808a87be7518e1847c751cdc78f6951f150747af6461a4f1cc05",
+ "catalog_code": "S15",
+ "catalog_id": "state-no-matching-results",
+ "condition": "Fresh agent session with Plotter; no without-skill control",
+ "user_verdict": "Liked; original version preferred over added background-grid variant"
+ }
+ ],
+ "feedback": [
+ {
+ "speaker": "user",
+ "text": "I like them all."
+ },
+ {
+ "speaker": "user",
+ "text": "i honestly prefer these examples to be without grids i'd add them only if necessary"
+ },
+ {
+ "speaker": "user",
+ "text": "i think the rule of thumb should be: if it's pure geometric then a grid would work eg M01 or P00."
+ }
+ ],
+ "interpretation": "Consider a grid when geometric construction is the subject; retain it when it reveals the construction. The three original examples remain preferred.",
+ "limitations": [
+ "Three single runs with the skill; no causal improvement claim or reliability estimate.",
+ "User approval of these examples does not automatically add them to the earlier 27-reference set."
+ ],
+ "original_geometry_checks": [
+ "Shared-frame horizontal clearances are 24 units.",
+ "Appointment intervals 72:216:72 represent 10:30:10 minutes.",
+ "Indexed record centers follow a 60-unit pitch."
+ ]
+}
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/notes.md b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/notes.md
new file mode 100644
index 000000000..3d0cbf0a6
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/notes.md
@@ -0,0 +1,25 @@
+# Reference-led catalogue expansion
+
+After rejecting the initial 78-drawing batch, the user explicitly liked the rebuilt registration drawing, then the delegation and iris follow-up. Their next instruction was: “love them, continue with all the remaining examples, and also want them to be added to the catalogue”.
+
+The initial expansion included the three liked revisions unchanged in appearance, plus 75 revised subjects, bringing the catalogue to 136. The user subsequently removed 36 entries, leaving **100 illustrations**, including 42 from this expansion. Active editable SVGs live in [`../../../app/plotter/drawings`](../../../app/plotter/drawings); the [manifest](../../../app/plotter/drawings/manifest.json) records stable IDs, codes, descriptions, reference choices, and construction details. SVG metadata supplies the same construction context. AG01, AG02, and ME01 retain the positive feedback above; the other 39 retained revisions await individual user feedback. None changes the approved 27-reference selection.
+
+## Subsequent curation
+
+The user found 33 of the additions “not visually interesting”, considered AR01 and GE02 too similar to existing studies, and then explicitly requested OB04’s removal too. The complete code list and reasons are recorded in [direction and feedback](../../../skills/plotter/references/history/direction.md#catalogue-removals-after-expansion--2026-10-31). Removed SVGs and metadata are preserved in [`removed/`](removed/) as review evidence, outside the active catalogue and its generated components.
+
+Repeated mentions of OB03 and AB05 were counted once. Retained codes and IDs have not been renumbered. These decisions apply to the named drawings, not whole subject categories, and retaining a drawing does not establish approval.
+
+## Drawing process
+
+The revision pass proceeded by subject group after the three liked examples established the direction. Each new drawing was compared at native 560 × 440 against a relevant existing reference. The comparisons considered main-form proportions, the relationship carrying the idea, useful guides, and label clearance. Refinements included shared-edge ownership, actual tangent connections, corrected clipping for negative space, measured intervals, and clearer labels. The integrated React drawings were also rendered together to inspect their appearance after conversion.
+
+The initial collection contained eight agent concepts and five subjects each in geometry, mechanisms, objects, architecture, nature, music, maps, typography, interfaces, software, time, absence, craft, and play. Conceptual metaphors are identified in source metadata; dimensions describe the drawn geometry rather than asserting physical performance. Background grids were omitted where local geometry already explained the relationship.
+
+This is an authored illustration revision, not a clean-install skill evaluation. No test suites, type checks, or automated audit campaigns were run, following the user’s request. Rendering and visual comparison informed the drawings; they do not establish engineering accuracy or broad skill reliability.
+
+## Related requests
+
+W05 now has H1 hatching only on its square cores, following the user’s correction. Static and animated views preserve the existing outlines and timing; carriers remain unhatched.
+
+The skill’s [illustrated visual grammar](../../../skills/plotter/references/visual-grammar.md) explains mark selection with paired examples. It supplements the drafting standard without changing the approved style references.
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-connectors-gap.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-connectors-gap.svg
new file mode 100644
index 000000000..766c28c27
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-connectors-gap.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-empty-position.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-empty-position.svg
new file mode 100644
index 000000000..e4760f311
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-empty-position.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-matching-slot.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-matching-slot.svg
new file mode 100644
index 000000000..a63426b85
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-matching-slot.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-missing-piece.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-missing-piece.svg
new file mode 100644
index 000000000..1bc9eb0c0
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-missing-piece.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-unbuilt-destination.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-unbuilt-destination.svg
new file mode 100644
index 000000000..8765e8319
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-unbuilt-destination.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-door-clearance.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-door-clearance.svg
new file mode 100644
index 000000000..7646955cd
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-door-clearance.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-proportional-window.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-proportional-window.svg
new file mode 100644
index 000000000..e9363536a
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-proportional-window.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-sliding-partitions.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-sliding-partitions.svg
new file mode 100644
index 000000000..04945efcd
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-sliding-partitions.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/geo-shared-tangent.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/geo-shared-tangent.svg
new file mode 100644
index 000000000..f951279f6
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/geo-shared-tangent.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-nested-spacing.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-nested-spacing.svg
new file mode 100644
index 000000000..fb1067a42
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-nested-spacing.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-shared-selection.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-shared-selection.svg
new file mode 100644
index 000000000..396013acd
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-shared-selection.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/manifest.json b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/manifest.json
new file mode 100644
index 000000000..93a76a8ff
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/manifest.json
@@ -0,0 +1,398 @@
+[
+ {
+ "id": "geo-shared-tangent",
+ "code": "GE02",
+ "title": "Two radii, one tangent",
+ "description": "Unequal circles meet the same baseline and inclined tangent; their radii explain both contacts.",
+ "category": "Foundations",
+ "file": "geo-shared-tangent.svg",
+ "reference": "P00",
+ "construction": "Geometric construction. Radii80 and48, centers(196,244),(380,276), baseliney324. The upper tangent normal is derived from the center vector and radius difference; lower normals are vertical. Dimension184 measures horizontal center separation.",
+ "removal_reason": "Redundant with existing catalogue studies."
+ },
+ {
+ "id": "mech-scissor-linkage",
+ "code": "ME03",
+ "title": "Equal links, changing span",
+ "description": "Three crossing link pairs share pivots and repeat one geometry across an expanding linkage.",
+ "category": "Mechanisms",
+ "file": "mech-scissor-linkage.svg",
+ "reference": "C02",
+ "construction": "Conceptual planar linkage. u=48. Each bay spans96 horizontally and160 vertically. All diagonals have equal length. Link edges are offset4 from their centerline and terminate analytically on radius5 end pivots and radius7 center pivots. The arrow suggests extension, not a verified travel range.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "object-camera-lens",
+ "code": "OB01",
+ "title": "One optical center",
+ "description": "Concentric lens and retaining-ring boundaries share one optical center.",
+ "category": "Objects",
+ "file": "object-camera-lens.svg",
+ "reference": "P00",
+ "construction": "Geometric front-view concept. Radii120,108,80,56 share center(280,220). H1 identifies the retaining annulus108≤r≤120, not a material or shading. Axis lines are split around the center and its identifying label. No specific lens construction or optical performance asserted.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "object-umbrella-ribs",
+ "code": "OB02",
+ "title": "Eight ribs, one center",
+ "description": "Eight equal radial ribs hold a scalloped canopy around one shared hub.",
+ "category": "Objects",
+ "file": "object-umbrella-ribs.svg",
+ "reference": "P00",
+ "construction": "Geometric umbrella plan. Ribs radius120 on45° pitch; neighboring canopy tips joined by concave radius100 arcs. Hub radius6. H1 identifies one canopy panel bounded by two ribs, the scalloped edge and hub. Flat illustrative construction, not a fabrication plan.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "object-hinge-panels",
+ "code": "OB03",
+ "title": "Two panels, one axis",
+ "description": "Alternating hinge leaves connect two panels around a common axis.",
+ "category": "Objects",
+ "file": "object-hinge-panels.svg",
+ "reference": "C02",
+ "construction": "Conceptual front elevation. Panels144×168; edge gap64; barrel16×128. Open leaf paths share the barrel boundary without retracing it. The centerline denotes the hinge axis, not an electrical connection. No load, clearances or actual hardware standard asserted.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "object-key-lock",
+ "code": "OB05",
+ "title": "A key meets its profile",
+ "description": "A stepped key approaches a receiver with the same profile and alignment.",
+ "category": "Objects",
+ "file": "object-key-lock.svg",
+ "reference": "C01",
+ "construction": "Conceptual fit diagram. Key bow radius36 with15-radius opening; shank profile translated128 units forms the receiver cavity. Tooth starts220,256,292 share pitchu=36. This illustrates a matching profile and does not claim a working lock or authentication system.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "arch-door-clearance",
+ "code": "AR01",
+ "title": "The space a door needs",
+ "description": "The door and its swing envelope share one pivot and one radius.",
+ "category": "Architecture",
+ "file": "arch-door-clearance.svg",
+ "reference": "E01",
+ "construction": "Conceptual architectural plan. u=24; opening and leaf length6u=144. Quarter-circle clearance radius144 is centered at hinge(224,276). Dashed horizontal line denotes the closed leaf edge. H1 identifies wall regions. Illustrative dimensions, not building-code or construction guidance.",
+ "removal_reason": "Redundant with existing catalogue studies."
+ },
+ {
+ "id": "arch-sliding-partitions",
+ "code": "AR04",
+ "title": "A room opens between panels",
+ "description": "Two equal partitions slide apart while a faint former seam locates the closed position.",
+ "category": "Architecture",
+ "file": "arch-sliding-partitions.svg",
+ "reference": "E01",
+ "construction": "Conceptual elevation of sliding partitions. Each panel128×160. Closed leading edges meet atx280; panels translate by-28 and+28, giving56 opening. Missing outlines indicate former meeting edges only. H1 identifies the fixed frame, not a material. This is a spatial-change illustration, not hardware documentation.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "arch-proportional-window",
+ "code": "AR05",
+ "title": "A window in shared proportions",
+ "description": "One module divides a window into four panes with related widths and heights.",
+ "category": "Architecture",
+ "file": "arch-proportional-window.svg",
+ "reference": "C02",
+ "construction": "Geometric elevation. u=48. Clear opening5u×6u, divided horizontally into2u+3u and vertically into2u+4u. Thus the upper-left pane is2u square. Outer frame inset12. Each mullion and shared boundary has one owner; no ornamental grid or material claim.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "nature-offset-rings",
+ "code": "NA03",
+ "title": "Growth shifts the center",
+ "description": "Successive rings expand while their centers move, leaving the earliest core visibly off-center.",
+ "category": "Nature",
+ "file": "nature-offset-rings.svg",
+ "reference": "P00",
+ "construction": "Six circular growth contours: center=(250+5i,220), radius=20i, i=1..6. Center drift25=5u with u5. H1 selects the southwest sector of the newest annular band, between the two actual circular boundaries. This is a geometric growth analogy, not a dated tree section.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "nature-tributaries",
+ "code": "NA04",
+ "title": "Small streams, one river",
+ "description": "Two bounded channels merge at one confluence and continue as a single downstream course.",
+ "category": "Nature",
+ "file": "nature-tributaries.svg",
+ "reference": "E02",
+ "construction": "Continuous banks and a single inner junction define a Y-shaped channel. Arrows mean conceptual downstream movement. The width w of the lower straight reach spans56 SVG units; no discharge, scale, or hydraulics claim. Cubic banks are intentionally organic and carry no radius labels.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "nature-seed-spiral",
+ "code": "NA05",
+ "title": "An angle becomes a field",
+ "description": "A constant turn and a growing radius organize eighty seeds around one center.",
+ "category": "Nature",
+ "file": "nature-seed-spiral.svg",
+ "reference": "P00",
+ "construction": "Golden angle = pi*(3-sqrt(5)). Plotted seed indices20..99 have radius12sqrt(n); each next angle advances137.507764 degrees. The omitted inner indices leave space to explain the generating turn. Circlesr3.8 represent seeds; outer128 circle is a reference envelope, not a flower edge. The drawn central arc uses the exact angle; display label rounds to137.5 degrees.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "music-rhythm-intervals",
+ "code": "MU01",
+ "title": "Time between notes",
+ "description": "A four-beat measure makes one half-beat onset visible between two regular beats.",
+ "category": "Music",
+ "file": "music-rhythm-intervals.svg",
+ "reference": "K07",
+ "construction": "Beat pitch72 SVG units. Onsets at positions0,72,108,144,216 relative to136. The measure ends at424. Outlined noteheads are onset markers; the drawing does not assert note durations or a complete score. Beat and half-beat dimensions share the same72-unit scale.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "music-tuning-fork",
+ "code": "MU03",
+ "title": "A symmetric tuning fork",
+ "description": "Equal-width prongs and a centered handle grow from two concentric circular bends.",
+ "category": "Music",
+ "file": "music-tuning-fork.svg",
+ "reference": "P00",
+ "construction": "u=24 prong width. Inner and outer U bends have radii40 and64 about(280,232). Outer arc-to-handle intersections are derived at x268 and292. The central axis is a construction reference. This stylized silhouette makes no resonance-frequency or manufacturability claim.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "music-missing-beat",
+ "code": "MU04",
+ "title": "The absent beat",
+ "description": "A repeated pulse sequence holds one step at the baseline, leaving a deliberate rest.",
+ "category": "Music",
+ "file": "music-missing-beat.svg",
+ "reference": "E02",
+ "construction": "Eight40-unit step intervals. Each present pulse is16 wide and80 high. Step5 is absent; its expected outline uses3 5, while the real signal remains at the baseline. u=40 step period, not a physical time unit. On/off are conceptual sequencer states.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "music-meeting-rhythms",
+ "code": "MU05",
+ "title": "Three against two",
+ "description": "Two repeating rhythms meet at the same cycle boundary after three pulses in one and two in the other.",
+ "category": "Music",
+ "file": "music-meeting-rhythms.svg",
+ "reference": "B04",
+ "construction": "Common cycle192=6u with u32. Upper pulses every64=2u, lower every96=3u. Two cycles span88..472; the common boundary at280 owns one continuous primary stroke. Baselines and cycle witnesses are distinct from pulse marks.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "map-transit-platform",
+ "code": "MP01",
+ "title": "A shared platform",
+ "description": "Two routes align their stops on opposite sides of one platform.",
+ "category": "Maps",
+ "file": "map-transit-platform.svg",
+ "reference": "E02",
+ "construction": "u24; track spacing72=3u, platformheight24. Two R24 tangent bends lift routeB onto the shared alignment. Stop-circle interiors are clear of track strokes. The generating circle excludes the actual curved route segment. Conceptual transit interchange, not a real station plan.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "map-road-radii",
+ "code": "MP02",
+ "title": "The radius of a turn",
+ "description": "Four equal circular curb returns organize the meeting of two equal-width roads.",
+ "category": "Maps",
+ "file": "map-road-radii.svg",
+ "reference": "P00",
+ "construction": "Road width64=2u withu32. Four R40 tangent arcs join horizontal and vertical straight curbs. The northwest circle guide shows only its unused270-degree portion, so no guide retraces the actual curb. This is illustrative junction geometry, not a road safety or vehicle-turning assessment.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "map-missing-bridge",
+ "code": "MP03",
+ "title": "A route without its span",
+ "description": "A road reaches both riverbanks, but its bridge remains an absent part of the route.",
+ "category": "Maps",
+ "file": "map-missing-bridge.svg",
+ "reference": "E02",
+ "construction": "Road width32; missing span88 between x236 and324. Road endcaps own the riverbank coincidence across y204..236; bank paths stop and restart at those boundaries. Dashed edges indicate absent bridge geometry. This conceptual plan does not assert real topography or bridge dimensions.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "interface-nested-spacing",
+ "code": "UI04",
+ "title": "Space within space",
+ "description": "Equal insets repeat through three nested boundaries; one hatched strip makes the spacing visible.",
+ "category": "Interfaces",
+ "file": "interface-nested-spacing.svg",
+ "reference": "C02",
+ "construction": "Geometric construction. u=24. Frames288x240,240x192,192x144 share center280,220 and constant24 insets. H1 region is the middle-to-content inset along the left edge, not material.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "interface-shared-selection",
+ "code": "UI05",
+ "title": "One selection, three forms",
+ "description": "One bounding selection encloses three independent forms, with handles and a shared inset.",
+ "category": "Interfaces",
+ "file": "interface-shared-selection.svg",
+ "reference": "C02",
+ "construction": "Conceptual UI selection. Circle diameter64, pill112x64, square64x64; bounds x128..432,y196..260. Selection x104..456,y172..284 gives24 inset. Continuous selection edges stop at their corner handles. Dimensions use SVG units.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "software-queue-slot",
+ "code": "SW01",
+ "title": "A queue meets a slot",
+ "description": "Equal work units wait for one matching processing opening. The leading unit and the available slot share one size.",
+ "category": "Systems",
+ "file": "software-queue-slot.svg",
+ "reference": "C02",
+ "construction": "Conceptual allocation. u=48. Queue units48x48 at72 pitch; receiver opening48 high and48 deep. Shapes indicate queue order and capacity, not processor hardware. No inference about throughput.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "software-common-key",
+ "code": "SW04",
+ "title": "Two records, one key",
+ "description": "A shared identifier relates two rows at different positions, carried by a single measured connection.",
+ "category": "Systems",
+ "file": "software-common-key.svg",
+ "reference": "C02",
+ "construction": "Conceptual relation, with approved B09 informing the offset-table layout. u=24; both tables144 wide, rows32 high. Key42 links the selected rows. Shared row boundaries have one stroke owner; ports interrupt outside borders. The curved relation usesR24 and carries no transfer or causal claim.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "software-shape-filter",
+ "code": "SW05",
+ "title": "Only what fits",
+ "description": "A measured opening admits the smaller circles while the larger profiles remain outside.",
+ "category": "Systems",
+ "file": "software-shape-filter.svg",
+ "reference": "P00",
+ "construction": "Geometric filter analogy. Opening48 high between y208/256; circular items diameter40 leave4 vertical clearance each side. Held square64 and triangleheight56 exceed opening height. Profiles remain unrotated. This is a size-filter metaphor, not a physical sorting machine.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "time-availability-overlap",
+ "code": "TM01",
+ "title": "Where availability meets",
+ "description": "Two named availability windows project onto their common two-hour interval.",
+ "category": "Scheduling",
+ "file": "time-availability-overlap.svg",
+ "reference": "K07",
+ "construction": "Conceptual schedule using96 SVG units/hour. Ana09:00–12:00 spans120..408; Lee10:00–13:00 spans216..504. Their10:00–12:00 intersection216..408 is hatched. Hatching means shared availability. Example schedules only.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "time-schedule-handoff",
+ "code": "TM04",
+ "title": "One schedule takes over",
+ "description": "Two schedules meet at one time boundary; the handoff aligns the end of one with the start of the other.",
+ "category": "Scheduling",
+ "file": "time-schedule-handoff.svg",
+ "reference": "K07",
+ "construction": "Conceptual schedule at84 SVG units/hour. TeamA12:00–14:00 spans112..280; TeamB14:00–16:00 spans280..448. Both intervals168wide. Hatch marks the matching handoff edges, not a duration claim. The shared boundary is drawn once within each row.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "time-unfinished-deadline",
+ "code": "TM05",
+ "title": "The deadline arrives first",
+ "description": "A fixed deadline interrupts a sequence with completed work, an active step, and unfinished steps still ahead.",
+ "category": "Scheduling",
+ "file": "time-unfinished-deadline.svg",
+ "reference": "B04",
+ "construction": "Conceptual sequence. u=56 is the step pitch, each step40x40. Four completed steps are hatched, the active step partially hatched, two absent/unfinished steps dashed. Deadline x372 lies between active and unfinished steps. No time estimate or progress percentage is claimed.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "absence-missing-piece",
+ "code": "AB01",
+ "title": "The unfilled quarter",
+ "description": "Three interlocking quarters leave one necessary part absent, while its outer edges remain implied.",
+ "category": "Empty states",
+ "file": "absence-missing-piece.svg",
+ "reference": "P00",
+ "construction": "Geometric assembly metaphor, with E00 absence informing the composition. u=40. Square240x240; missing upper-right quadrant120x120. R16 circular tabs along mating seams. Existing outer boundary and shared interior seams are drawn once; dashed edges are absent, not hidden. No puzzle manufacturability claim.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "absence-empty-position",
+ "code": "AB02",
+ "title": "A place in the collection",
+ "description": "One empty position retains the dimensions and spacing of the surrounding records.",
+ "category": "Empty states",
+ "file": "absence-empty-position.svg",
+ "reference": "C02",
+ "construction": "Conceptual allocation. u=24; six positions form3 columns and2 rows. Cards96x64; all gaps and frame insets24. The missing lower-middle item is dashed. The existing frame is a shared collection boundary, not a full application screen.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "absence-connectors-gap",
+ "code": "AB03",
+ "title": "Almost connected",
+ "description": "A matching tongue and receiver share an axis but remain separated by one visible gap.",
+ "category": "Empty states",
+ "file": "absence-connectors-gap.svg",
+ "reference": "E02",
+ "construction": "Conceptual fit study. u=56 is the gap from tongue endpoint x272 to receiver mouth x328. Tongue and recess48 high and48 deep. H1 identifies the receiver body, bounded by its actual profile and excluding its open recess. No electrical function or working hardware claim.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "absence-unbuilt-destination",
+ "code": "AB04",
+ "title": "A route to what comes next",
+ "description": "A complete route reaches the opening reserved for a destination that has not yet been built.",
+ "category": "Empty states",
+ "file": "absence-unbuilt-destination.svg",
+ "reference": "E02",
+ "construction": "Conceptual absence. Route width16; centerline bends R64 and R48; corresponding boundary radii56/72 and40/56. The destination is absent and dashed. Route centerline and generating radius are guides, not additional route edges. No network architecture claim.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "absence-matching-slot",
+ "code": "AB05",
+ "title": "An opening with a match",
+ "description": "A separate element and an open receiver share the same profile, width, and generating radius.",
+ "category": "Empty states",
+ "file": "absence-matching-slot.svg",
+ "reference": "P00",
+ "construction": "Geometric fit metaphor. u=48; both D profiles are96wide and96high, made from a48-wide rectangle and a right semicircle R48. Profile translation is216 units horizontally. The right outline is an existing vacant opening, not an absent part; its support frame is secondary. No tolerance or hardware claim.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "play-tangram",
+ "code": "PL01",
+ "title": "Seven parts, one square",
+ "description": "Seven complementary pieces retain a single square silhouette; one square piece is identified within it.",
+ "category": "Play",
+ "file": "play-tangram.svg",
+ "reference": "P00, C01",
+ "construction": "Planar tangram partition of a 240-unit square; u=40. Two large triangles, one medium triangle, two small triangles, one square and one parallelogram share their seams. Hatching identifies the square piece, whose horizontal diagonal is 3u. The dashed center projection explains the half-width relationship.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "play-maze",
+ "code": "PL02",
+ "title": "The interrupted route",
+ "description": "One closed passage stops a route whose continuation remains visible.",
+ "category": "Play",
+ "file": "play-maze.svg",
+ "reference": "E02, W03",
+ "construction": "Illustrative planar maze. Wall pitch 56, outer extent224, highlighted route bendsR8. The route enters and exits through actual gaps. The vertical closure at(336,196) interrupts the intended path. Dashed continuation is a hypothetical route, not a hidden wall. No claim of a unique maze solution is made.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "play-almost-fit",
+ "code": "PL05",
+ "title": "Almost aligned",
+ "description": "The matching circular profiles remain separated by one deliberate vertical offset.",
+ "category": "Play",
+ "file": "play-almost-fit.svg",
+ "reference": "P00, P02",
+ "construction": "Planar fit study. Both interface profiles are semicirclesR32. Their centers are(248,212) and(312,236), giving a24-unit vertical mismatch. Both body widths128 are shown. The source tab and receiving recess would match after translation(64,24); no clearance or manufacturing claim is implied.",
+ "removal_reason": "The user found this revision not visually interesting."
+ },
+ {
+ "id": "object-zipper",
+ "code": "OB04",
+ "title": "Two edges become one",
+ "description": "Two tooth rows meet at a slider and continue as one alternating seam.",
+ "category": "Objects",
+ "file": "object-zipper.svg",
+ "reference": "C01",
+ "construction": "Conceptual zipper front view. Incoming tape edges route to slider boundary. Incoming tooth roots belong to tape edges, so roots are not redrawn. Joined tooth pitchu=12; the shown span3u is36. Slider upper boundary is split at pull tab. The geometry conveys joining, not a textile or tooth manufacturing specification.",
+ "removal_reason": "The user explicitly requested removal after the first curation pass."
+ }
+]
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-missing-bridge.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-missing-bridge.svg
new file mode 100644
index 000000000..7f6c6ddfb
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-missing-bridge.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-road-radii.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-road-radii.svg
new file mode 100644
index 000000000..44523e204
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-road-radii.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-transit-platform.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-transit-platform.svg
new file mode 100644
index 000000000..275ce13a7
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-transit-platform.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/mech-scissor-linkage.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/mech-scissor-linkage.svg
new file mode 100644
index 000000000..ac6c6daba
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/mech-scissor-linkage.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-meeting-rhythms.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-meeting-rhythms.svg
new file mode 100644
index 000000000..2d8fcbb98
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-meeting-rhythms.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-missing-beat.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-missing-beat.svg
new file mode 100644
index 000000000..26b9fb5c6
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-missing-beat.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-rhythm-intervals.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-rhythm-intervals.svg
new file mode 100644
index 000000000..150367acf
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-rhythm-intervals.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-tuning-fork.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-tuning-fork.svg
new file mode 100644
index 000000000..0927744cc
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-tuning-fork.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-offset-rings.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-offset-rings.svg
new file mode 100644
index 000000000..95cf635a9
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-offset-rings.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-seed-spiral.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-seed-spiral.svg
new file mode 100644
index 000000000..534bbc620
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-seed-spiral.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-tributaries.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-tributaries.svg
new file mode 100644
index 000000000..841ce3fd0
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-tributaries.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-camera-lens.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-camera-lens.svg
new file mode 100644
index 000000000..7fbb09d08
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-camera-lens.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-hinge-panels.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-hinge-panels.svg
new file mode 100644
index 000000000..bc8ed02d2
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-hinge-panels.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-key-lock.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-key-lock.svg
new file mode 100644
index 000000000..7653fa116
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-key-lock.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-umbrella-ribs.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-umbrella-ribs.svg
new file mode 100644
index 000000000..5a21d3926
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-umbrella-ribs.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-zipper.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-zipper.svg
new file mode 100644
index 000000000..1c105b28f
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-zipper.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-almost-fit.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-almost-fit.svg
new file mode 100644
index 000000000..99cefec8c
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-almost-fit.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-maze.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-maze.svg
new file mode 100644
index 000000000..f5f4fd431
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-maze.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-tangram.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-tangram.svg
new file mode 100644
index 000000000..b232210c0
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-tangram.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-common-key.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-common-key.svg
new file mode 100644
index 000000000..4bba198c9
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-common-key.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-queue-slot.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-queue-slot.svg
new file mode 100644
index 000000000..214c32e7c
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-queue-slot.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-shape-filter.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-shape-filter.svg
new file mode 100644
index 000000000..788510c89
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-shape-filter.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-availability-overlap.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-availability-overlap.svg
new file mode 100644
index 000000000..56cbec084
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-availability-overlap.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-schedule-handoff.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-schedule-handoff.svg
new file mode 100644
index 000000000..0e49621a7
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-schedule-handoff.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-unfinished-deadline.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-unfinished-deadline.svg
new file mode 100644
index 000000000..1c5623c9e
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-unfinished-deadline.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/clean-install/agent-notes.json b/apps/ui/plotter-evals/2026-10-31/clean-install/agent-notes.json
new file mode 100644
index 000000000..79719168c
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/clean-install/agent-notes.json
@@ -0,0 +1,92 @@
+{
+ "subject": "Circle inscribed in an equilateral triangle",
+ "mode": "geometric construction",
+ "candidate_count": 1,
+ "output": "output/inscribed-circle.svg",
+ "size": {
+ "width": 560,
+ "height": 440,
+ "viewBox": "0 0 560 440"
+ },
+ "references": [
+ {
+ "path": "plotter/references/drawings/P00.svg",
+ "reason": "Closest bundled relationship: circle, straight boundaries, radii, and tangency. Borrowed hierarchy and restrained geometry, not historical background fills.",
+ "inspection": "Source read and rendered on a light surface via html_preview."
+ }
+ ],
+ "decisions": [
+ "Triangle and incircle share the primary 0.95-unit stroke. Radii, perpendicular markers, and equality ticks use the standard 0.65-unit stroke at 0.50 opacity.",
+ "Solid point markers are explicitly declared geometric-point symbols. T₁, T₂, T₃ identify tangencies; O identifies the center. Three right-angle markers and the equal-radius equation explain the relationship.",
+ "Matching side ticks identify the equilateral triangle. No physical dimension or standards-compliance claim is made.",
+ "No grid, hatch, or area fill: the three radii already explain the entire construction. Enclosed forms and canvas remain transparent.",
+ "Cal Sans, Arial, sans-serif fallback stack; automatic optical sizing, no letter-spacing, no synthesized styles, GEOM 25 for the single text role. No font files required.",
+ "Accessible title and description included. No external resources or scripts."
+ ],
+ "construction": {
+ "center": [280.0, 246.0],
+ "radius": 90.0,
+ "vertices": [[280.0, 66.0], [124.115427, 336.0], [435.884573, 336.0]],
+ "tangent_points": [
+ [202.057714, 201.0],
+ [357.942286, 201.0],
+ [280.0, 336.0]
+ ],
+ "equations": [
+ "altitude = 3r",
+ "side = 2√3r",
+ "tangent points are the midpoints of the three sides"
+ ]
+ },
+ "checks": {
+ "xml_parse": "PASS",
+ "geometry_source": "Actual serialized SVG coordinates recovered with Python XML parser.",
+ "side_lengths_svg_units": [
+ 311.7691455217984, 311.7691455217984, 311.769146
+ ],
+ "equal_side_max_difference_svg_units": 4.78201570786041e-7,
+ "tangency": [
+ {
+ "circle_incidence_residual_svg_units": 2.949678048480564e-7,
+ "side_incidence_residual_svg_units": 4.3301269656642006e-7,
+ "radius_side_angle_degrees": 90.00000005767743,
+ "right_angle_residual_degrees": 5.767742550233379e-8,
+ "side_parameter": 0.49999999919812466
+ },
+ {
+ "circle_incidence_residual_svg_units": 2.9496777642634697e-7,
+ "side_incidence_residual_svg_units": 4.330126732287751e-7,
+ "radius_side_angle_degrees": 90.00000005767741,
+ "right_angle_residual_degrees": 5.7677411291479075e-8,
+ "side_parameter": 0.4999999991981247
+ },
+ {
+ "circle_incidence_residual_svg_units": 0.0,
+ "side_incidence_residual_svg_units": 0.0,
+ "radius_side_angle_degrees": 90.0,
+ "right_angle_residual_degrees": 0.0,
+ "side_parameter": 0.5
+ }
+ ],
+ "tolerances": {
+ "position_svg_units": 0.05,
+ "angle_degrees": 0.1
+ },
+ "geometry_verdict": "PASS",
+ "visual": {
+ "tool": "html_preview",
+ "sizes": ["560 × 440", "280 × 220 thumbnail"],
+ "surface": "white",
+ "console_messages": [],
+ "result": "The circle-in-triangle gesture remains clear at both sizes. At intended size, labels, radius lines, right-angle markers, and equality ticks are separated and visible; thumbnail preserves the form and contact relationship, while the equation is secondary small text.",
+ "iterations": 1
+ },
+ "html_render_called": false
+ },
+ "missing_dependencies": [],
+ "blockers": [],
+ "limitations": [
+ "Font fallback is allowed and no Cal Sans file was supplied; the exact browser-resolved font was not measured.",
+ "This is a standalone light-surface geometric illustration, not a responsive host integration or a physical engineering drawing."
+ ]
+}
diff --git a/apps/ui/plotter-evals/2026-10-31/clean-install/inscribed-circle.svg b/apps/ui/plotter-evals/2026-10-31/clean-install/inscribed-circle.svg
new file mode 100644
index 000000000..f3aac5738
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/clean-install/inscribed-circle.svg
@@ -0,0 +1,36 @@
+
+
diff --git a/apps/ui/plotter-evals/2026-10-31/clean-install/package-hashes.json b/apps/ui/plotter-evals/2026-10-31/clean-install/package-hashes.json
new file mode 100644
index 000000000..50a265970
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/clean-install/package-hashes.json
@@ -0,0 +1,25 @@
+{
+ "README.md": "fc5015c1dda14fa30cfd35b2ed7eca7fbf7c4342adfcd2a03b4ccc577510b504",
+ "SKILL.md": "27d953450e19af4a50b3f58baf4dd350b3c7d8315b57dfd0a559f3916a1e4473",
+ "agents/openai.yaml": "60cd55400869654c90fd25f6d8b92c75f1cfc32cc902e2aa2051b0ddee93914a",
+ "references/approved-references.md": "e17a1ab5f56eddb55fa64ee88e6af110a78e5ef19db290bc809998eb685ea158",
+ "references/coss-product-concepts.md": "f77ae6e1af2f8e0a79d0ad05ccf41075554651d0d32e4126755f4f649104e377",
+ "references/direction.md": "637b08c6d16d94299e37266028c61642b9e1686caa874008352a93dd9095e4f1",
+ "references/drafting-standard.md": "a65fe5b1f88f44196bc340acedd294fb61e0150de6a69711cf158bd1c17b0f3b",
+ "references/drawings/B04.svg": "3635d28fa90efa36cbb4c9374274df930eafa65bbd50c938508c243ab24ca294",
+ "references/drawings/C02.svg": "21763dddf898e52c29f616aa897792307336914d3f034ddd3a18aafc8fe9f89c",
+ "references/drawings/E02.svg": "595d5174194ac7cfef182eb86488a8dda7ee53a20b0b0ed0b3197f60b04b0f66",
+ "references/drawings/I06.svg": "47118d15b7f0787dcf8c4a88abe3348ce1f5ce3b8cffe47b38694cd6dc710a02",
+ "references/drawings/K07.svg": "4f1c3e771ff163a2de9bfcf44800892dc549fa0a1347c4caac78cf6fbb7fd057",
+ "references/drawings/P00.svg": "4082ec9a1000107cb1580fcd6826cff15cc15c2c3d919a79291d1d7070cce1d4",
+ "references/drawings/manifest.json": "5e4773509da7aeb974bd400470e9969e474d66c1791dad7068ca29e8b5492c9d",
+ "references/implementation.md": "1098415d3d68c993171fb1b80318bdd794a0f71fae17bbaf036e9daed25131b5",
+ "references/linear.png": "8bdb6dd8bf343bb92ee3cb7fc34bd7fb71f712d8f9c9ca37b2be026112fd9d4f",
+ "references/not-found-interrupted-path.png": "ee3f7c33506fda5fb4cbb86600dc9b65b7dc6e3ea40c6c4c3a911f44c33fdfd4",
+ "references/particles-recombination.png": "df932415d657cc7710b70770f97b28ae5db15abe6cf3d9b2fe59316b549b7442",
+ "references/particles.png": "fe1a662f99f25f95b992ac634ba73dd50637e56ffa2428bfb0fd30fd833e3ebc",
+ "references/primitives.png": "72c0331f1861d687639f72ee46861e8e1e526151a6c6f0b717d1ce07143db0ec",
+ "references/remaining-source-audit.md": "2ff697dc69f1e29a212bbb95a2ae2fb892f12ab041d5927e68a321038b27e030",
+ "references/source-audit.md": "97d9479c541cf4f5479469534b87d1d1ae9d5d57ffd4691fa75ededb68707c7b",
+ "references/verification-prompt.md": "1e4d13f5ebcddd8648c3af3fe9095e57ac0d07065e3965c7f4a41feaefef4d9a"
+}
diff --git a/apps/ui/plotter-evals/2026-10-31/clean-install/result.json b/apps/ui/plotter-evals/2026-10-31/clean-install/result.json
new file mode 100644
index 000000000..d94cd2d86
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/clean-install/result.json
@@ -0,0 +1,18 @@
+{
+ "brief": "Create a standalone 560 \u00d7 440 SVG illustration of a circle inscribed in an equilateral triangle. Make the relationship between the circle, the sides, and their tangent points understandable.",
+ "condition": "Fresh agent session, copied skill package only; repository and prior conversation excluded by task instructions. This is a scoped agent exercise, not an OS-enforced isolation boundary.",
+ "package_revision": "Exact packaged files identified by package-hashes.json; based on this change set before its commit.",
+ "result": "Passed one bounded standalone-use smoke test. Agent found bundled P00, produced editable SVG with no external dependencies, inspected native/thumbnail output, and explained its grid decision.",
+ "independent_geometry_residuals_svg_units": {
+ "equal_side_lengths": 4.78201570786041e-7,
+ "center_to_sides_radius": 1.3804489640278916e-7,
+ "tangent_points_on_circle": 2.949678048480564e-7,
+ "tangent_points_on_sides": 4.3301269656642006e-7
+ },
+ "parent_render_review": "Native-size rendering inspected with fallback font. Clear tangencies and labels; no console errors. Aesthetic approval remains with the user.",
+ "limitations": [
+ "One task and one run; no reliability estimate or without-skill comparison.",
+ "Geometric subject does not test service-product research, every subject category, or animation.",
+ "Candidate is evaluation evidence, not an approved reference or catalog addition."
+ ]
+}
diff --git a/apps/ui/plotter-evals/2026-10-31/follow-up/agent-delegation.svg b/apps/ui/plotter-evals/2026-10-31/follow-up/agent-delegation.svg
new file mode 100644
index 000000000..607550d51
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/follow-up/agent-delegation.svg
@@ -0,0 +1,46 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/follow-up/before/agent-delegation.svg b/apps/ui/plotter-evals/2026-10-31/follow-up/before/agent-delegation.svg
new file mode 100644
index 000000000..841d26256
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/follow-up/before/agent-delegation.svg
@@ -0,0 +1,23 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/follow-up/before/iris-aperture.svg b/apps/ui/plotter-evals/2026-10-31/follow-up/before/iris-aperture.svg
new file mode 100644
index 000000000..ab3c15231
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/follow-up/before/iris-aperture.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/follow-up/iris-aperture.svg b/apps/ui/plotter-evals/2026-10-31/follow-up/iris-aperture.svg
new file mode 100644
index 000000000..e9a1aee13
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/follow-up/iris-aperture.svg
@@ -0,0 +1,35 @@
+
diff --git a/apps/ui/plotter-evals/2026-10-31/follow-up/notes.md b/apps/ui/plotter-evals/2026-10-31/follow-up/notes.md
new file mode 100644
index 000000000..9ba02d2ab
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/follow-up/notes.md
@@ -0,0 +1,23 @@
+# Delegation and iris follow-up
+
+The user explicitly liked agent-registration revision 2, then accepted the proposal to work on agent delegation followed by the iris aperture, one at a time. After seeing both drawings, the user said “love them, continue with all the remaining examples, and also want them to be added to the catalogue”. These exact revisions are preserved here and included as AG02 and ME01; registration is AG01. This feedback does not add them to the earlier 27-reference set.
+
+## Agent delegation
+
+[Editable SVG](agent-delegation.svg) · [rejected batch version](before/agent-delegation.svg) · study `agent-delegation`, revision 2.
+
+Primary reference: K07’s proportional relationship between a whole and its parts. C01 supplies the idea of preserving fragment geometry; C02 supplies allocation. The drawing reverses assembly: one task becomes two separately assigned scopes. Unlike the earlier stepped-piece draft, the task and the roles are visible together, and the retained widths connect the two states.
+
+The source is 336 units wide, partitioned into 144 and 192. The lower forms retain those profiles, separated by 24 units. Dimension labels use `u = 24`. Widths encode an illustrative scope allocation, not time, effort, cost, or dependencies. The projections connect corresponding width witnesses; the arrows mean assignment. No background grid is needed.
+
+## Iris aperture
+
+[Editable SVG](iris-aperture.svg) · [rejected batch version](before/iris-aperture.svg) · study `mech-iris-aperture`, revision 2.
+
+Primary reference: P00’s circle/tangency relationship, focal geometry, and bounded hatch. Six equally spaced tangent edges create a central hexagonal opening within a 240-unit disc. A faint incircle explains the edges, while H1 hatching identifies one visible blade region. Axes and the incircle provide the useful construction; no background grid is added.
+
+This is an illustrative geometric iris study. Its visible T-junctions suggest overlapping blades, but it makes no claim about hidden blade profiles, pivots, or verified mechanical movement. The SVG metadata records the actual generating geometry.
+
+## Review scope
+
+Each candidate was rendered beside its approved reference at native 560 × 440 size. Visual review covers hierarchy, construction, labels, and visible intersections. This is neither a fresh-session skill experiment nor a technical certification. No test suite was run. Positive user feedback applies to these exact two revisions; the liked registration version remains unchanged.
diff --git a/apps/ui/plotter-evals/2026-10-31/modules.svg b/apps/ui/plotter-evals/2026-10-31/modules.svg
new file mode 100644
index 000000000..8450a1d0a
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/modules.svg
@@ -0,0 +1,43 @@
+
\ No newline at end of file
diff --git a/apps/ui/plotter-evals/2026-10-31/search.svg b/apps/ui/plotter-evals/2026-10-31/search.svg
new file mode 100644
index 000000000..a03d4c8b9
--- /dev/null
+++ b/apps/ui/plotter-evals/2026-10-31/search.svg
@@ -0,0 +1,55 @@
+
diff --git a/apps/ui/plotter-plan.md b/apps/ui/plotter-plan.md
new file mode 100644
index 000000000..307f9524f
--- /dev/null
+++ b/apps/ui/plotter-plan.md
@@ -0,0 +1,244 @@
+# Plotter shared foundation
+
+Status: the first complete AG01/W05 foundation slice is implemented in the working tree. It includes the shared standard, three geometry helpers, opt-in role styles, compiler integration, portable static export, and usage notes. Before/after renders and exported SVGs were inspected visually; no automated checks or tests were run. The original planning pass changed only this document.
+
+## Review baseline and scope
+
+- Repository: `cosscom/ui`.
+- Branch: `codex/blueprint-illustration-studies`.
+- Baseline commit: **`093067a29cfd48bce213b3fe6627ac1154447a20`** (`feat(ui): add three-speed orbit animation to Plotter`).
+- The working tree was clean when the baseline was recorded.
+- Later implementation review should use `git diff 093067a29cfd48bce213b3fe6627ac1154447a20...HEAD` and this document as its specification. That command covers committed work; include staged and unstaged diffs separately when reviewing work in progress.
+
+The requested outcome is one source for drawing roles and CSS tokens, reusable arrows/dimensions/hatching, a clear drawing–animation separation, and editable SVG output that works outside coss. Preserve existing visual rules and illustrations. Release planning, publication, a catalogue redesign, and a catalogue-wide migration are outside this work.
+
+Use the codebase-design vocabulary: a **module** owns an implementation behind an **interface**; an interface includes inputs, invariants, errors, and ordering requirements. A **seam** is where that interface lives. An **adapter** connects it to a concrete caller. **Depth** means useful capability with little interface knowledge, rather than a large implementation or many abstractions.
+
+## What exists and what it implies
+
+| Current source | Finding | Design consequence |
+| --- | --- | --- |
+| [Drafting standard](skills/plotter/references/drafting-standard.md) | Precise role values, dash meanings, arrow geometry, hatch pitch, and clearance rules already exist. | Encode those values; retain the explanatory prose and its exceptions. |
+| [Drawing wrapper](app/plotter/blueprint-drawing.tsx) | Provides canvas, host foreground, and typography; its inherited stroke width is `0.8`. | Do not globally change that default to the standard's `0.95`: unclassified legacy paths could change. Adopt roles explicitly. |
+| [Editable catalogue](app/plotter/drawings/), [catalogue builder](scripts/build-plotter-drawings.py), and [grammar builder](scripts/build-plotter-grammar.py) | Editable SVG is compiled into React, alongside hand-written JSX drawings. The catalogue builder currently supplies a `0.95` fallback. | Preserve both authoring paths and their existing source ownership. |
+| [Dimensions](app/plotter/dimensions.tsx) | Reusable horizontal/vertical dimensions already serve several drawings, but do not accept actual feature witnesses or an inline label gap. Some labels are descriptive words. | Add the richer geometry helper without silently changing existing callers or interpreting every label as a measurement. |
+| [W05 hatching](app/plotter/production-chain-hatching.tsx) | The same seven clipped segments serve the static and animated core faces. | This is an existing, useful seam; retain it while replacing its repeated drafting knowledge. |
+| [SVG serializer](scripts/plotter_svg.py) | Rewrites defs IDs and strips metadata/title/description for decorative JSX output. CSS property conversion does not preserve `--*` names; stylesheet ID replacement does not distinguish selector IDs from fragment URLs. | Extend the supported conversion contract deliberately. Standalone output needs a different accessibility treatment. |
+| [Engineering preview](app/plotter/engineering-motion-preview.tsx) | Owns playback controls, visibility pausing, reduced motion, and static fallback, separate from the child SVG timeline. | Preserve that division; geometry helpers need no playback state. |
+| [W05 timeline](app/plotter/production-chain-motion-drawing.tsx), [O07 timeline](app/plotter/newtons-cradle-motion-drawing.tsx), and [P00 morph](app/plotter/primitives-morph.tsx) | Motion includes different mechanisms: sampled displacement/contact, pendulum motion, and matching path topology with changing labels. | A universal physics or animation-track model is not justified by this task. |
+| [Skill package](skills/plotter/README.md) | Complete-directory installation already works without the coss checkout. | Keep new portable resources inside that directory. |
+| [Grammar isolation](app/plotter/grammar-comparison.module.css) | Focus markers and supplemental grid segments preserve the original element's treatment during isolation. | Drawing roles must remain independent from teaching/focus annotations. |
+
+The existing [integrity script](scripts/check-plotter.tsx) checks catalogue rendering, IDs, links, and reference hashes. It does not establish visual quality, correct animation, or complete export portability. It was read, not run.
+
+## Design choice
+
+Keep ordinary SVG as the editable document. Add a portable drafting module, generated role styles, and narrowly scoped export tooling. The agent continues to compose paths, groups, text, and native SVG shapes. Helpers concentrate the calculations and conventions that would otherwise recur in each drawing.
+
+Three alternatives were considered:
+
+| Design | Depth and locality | Cost | Decision |
+| --- | --- | --- | --- |
+| Pure mark helpers plus native SVG/JSX | Arrow, dimension, and hatch knowledge has one implementation; composition remains editable. | Authors still arrange geometry and choose meaningful marks. | Use this foundation. |
+| A declarative scene document compiled into React/SVG/motion | Could connect constraints, semantic parts, and multiple renderers. | Introduces a second authoring format, extensive schema knowledge, and migration of existing SVGs and motion details. | Defer until concrete callers need those capabilities. |
+| Python-only fragment generation | Fits existing SVG builders and file-based authoring with little infrastructure. | Live JSX calculations would need generated fragments or another implementation of the same geometry. | Retain Python for document/build tooling; use portable JavaScript for shared geometry. |
+
+The geometry module will be dependency-free ESM JavaScript with type declarations. It can run in a standalone authoring script or be imported by the existing React drawings. Python's existing XML tooling remains useful for local document preparation and SVG-to-JSX conversion. Do not port all generators or duplicate the geometry algorithms in Python.
+
+Dependencies are in-process for geometry and role resolution, local filesystem I/O for authoring/export tools, and React/browser facilities for host rendering and playback. There are no remote dependencies requiring ports or network adapters. Runtime calculation and file writing stay separate; do not introduce an injectable filesystem framework.
+
+## Ownership and proposed files
+
+All paths below are relative to `apps/ui`. New files are proposed, not created by this plan.
+
+| Source | Responsibility |
+| --- | --- |
+| `skills/plotter/assets/foundation/standard.json` | Sole editable source of numeric role defaults, token names, dash patterns, and shared drafting constants. |
+| `skills/plotter/assets/foundation/index.mjs` and `index.d.mts` | Pure `arrow`, `dimension`, and `hatch` helpers; public role data re-export; no React, DOM, filesystem, or clocks. Keep private calculations local until actual complexity warrants splitting them. |
+| `skills/plotter/assets/foundation/standard.mjs` and `roles.css` | Generated runtime values and scoped role styles, derived from `standard.json`. Never maintain these independently. |
+| `skills/plotter/scripts/build-standard.py` | Generate the runtime values, CSS, and marked numeric tables in the drafting standard. Preserve authored explanatory prose. |
+| `skills/plotter/scripts/export-svg.py` | Prepare supported authored SVG as a self-contained static document, using the same standard. Python standard library; no coss imports. |
+| `skills/plotter/references/foundation.md` | Document the small interface, examples, override rules, and export limitations. |
+| `skills/plotter/references/motion.md` | Separate motion workflow and its invariants; route here only for animation work. |
+| Existing `scripts/plotter_svg.py` and `build-plotter-*.py` | coss adapters from native SVG to JSX. Consume shared defaults and preserve the supported document semantics. |
+| Existing `app/plotter/*` sources | Own compositions, catalogue metadata, host integration, and drawing-specific timelines. |
+
+The skill folder is the portable source, rather than a snapshot of a new workspace package. coss imports those resources directly or consumes their generated output. Generated files carry their source/generation notice. Do not add an npm package, duplicate source tree, or network installation requirement for the foundation.
+
+## One standard, with explicit adoption
+
+The JSON stores two related kinds of data:
+
+1. **Appearance:** named stroke roles, widths, opacity, caps/joins, and meaningful dash patterns.
+2. **Geometry constants:** arrow leg length/angle, dimension slash extent, hatch pitch/orientation, and label clearance. These are consumed by geometry functions, not presented as CSS controls that magically regenerate paths.
+
+Preserve the current defaults:
+
+| Role | Width | Opacity | Dash |
+| --- | ---: | ---: | --- |
+| Primary | 0.95 | 1 | none |
+| Secondary; dimension; callout leader | 0.65 | 0.50 | none |
+| Guide | 0.60 | 0.24 | none |
+| Grid | 0.60 | 0.10 | none |
+| Hatch | 0.65 | 0.27 | none |
+| Section cut | 1.30 | 1 | none |
+| Centerline / locus | 0.60 | 0.24 | `8 3 1 3` |
+| Hidden / absent / proposed outline | 0.65 | 0.45 | `3 5` |
+| Movement / transfer shaft | 0.65 | 0.50 | `4 6` |
+
+Different semantic roles may reference the same underlying token values. Labels still distinguish hidden, absent, and proposed geometry. A matching number does not establish a role: assign roles from the mark's meaning, never by automated numeric inference.
+
+Use a scoped `data-plotter-role` vocabulary for native SVG and JSX. Generate CSS with fallbacks, for example:
+
+```css
+[data-plotter] [data-plotter-role="guide"] {
+ stroke-width: var(--plotter-stroke-guide, 0.6);
+ opacity: var(--plotter-opacity-guide, 0.24);
+ stroke-dasharray: none;
+}
+```
+
+Public overrides initially cover monochrome ink, stroke widths, and role opacities. Examples are `--plotter-ink`, `--plotter-stroke-primary`, `--plotter-stroke-secondary`, `--plotter-opacity-guide`, `--plotter-opacity-grid`, and `--plotter-opacity-hatch`. Keep dash meanings and geometry constants in the standard rather than offering arbitrary style presets. The default ink remains host `currentColor`; resolved standalone output chooses an explicit ink.
+
+Important interface invariants:
+
+- Scope styles to opted-in drawings and named marks; native unclassified paths retain their existing treatment. Avoid broad `svg path` selectors.
+- Use variable fallbacks in consuming declarations. Declaring public defaults on every SVG root would mask values inherited from a host ancestor.
+- CSS declarations outrank SVG presentation attributes. Historical exceptions must remain unclassified or use an explicit documented override; adding a role is not visually neutral by itself.
+- Preserve the grouping level of `opacity`, and distinguish it from `stroke-opacity`. Moving opacity from a group to its children changes overlap compositing; nesting styled opacity groups multiplies it. New helpers should avoid accidental double application.
+- Continuous mark roles clear inherited dash patterns. An arrowhead is solid even inside a transfer construction. Join/cap choices must preserve the baseline; do not normalize legacy miter/round choices through a broad rule.
+- SVG user units remain the basis of widths and geometry. Do not add `non-scaling-stroke` globally or change the native 560 × 440 gallery presentation.
+- Text retains its own filled-glyph treatment, font sizes, opacity, and role-specific GEOM values. Stroke-role CSS must not erase labels or apply line opacity twice to them.
+- `data-grammar-focus` and `data-grammar-isolation-only` remain teaching annotations. Isolation hides unrelated marks; it does not recolor or amplify focused marks. Supplemental grid segments remain supported.
+
+Keep historical approved SVG snapshots and their manifest hashes unchanged. They remain composition references with documented exceptions. The standard owns current numeric defaults; the visual grammar and feedback explain meaning and intent.
+
+## Drafting interface
+
+Expose three geometry helpers and read-only standard data. Their outputs are ordinary SVG path strings, attribute records, and optional defs descriptions. They are not a general scene model. JSX authors render them with native elements; file-based authors serialize them into editable SVG. Rendering adapters translate attribute spelling and escape text, without reimplementing geometry.
+
+Illustrative calls:
+
+```ts
+const transfer = arrow({ kind: "line", from: [268, 186], to: [268, 212] });
+// { shaftD, headD }; shaft begins at the tip for dash attachment.
+
+const width = dimension({
+ axis: "x",
+ witnesses: [[112, 340], [448, 340]],
+ position: 390,
+ extensions: { gap: 8, overshoot: 8 },
+ label: {
+ text: "14u",
+ anchor: [280, 393],
+ clearance: { x: 264, y: 380, width: 32, height: 20 },
+ },
+});
+// Separate witness, dimension-line, and slash paths; positioned label.
+
+const face = hatch({
+ region: { kind: "rect", x: 0, y: 0, width: 48, height: 48 },
+ pattern: "H1",
+});
+// Exact seven non-zero diagonal segments used by W05; no boundary stroke.
+```
+
+These are the intended interface shapes; the implementation may refine names without widening scope. An extension requires a real drawing that cannot use the existing interface.
+
+### Arrows
+
+Start with straight transfer arrows used by AG01 and W05. A circular-arc variant can follow when migrating a current curved-arrow caller; it should take a center, radius, start angle, and signed sweep, not parse arbitrary path strings. Use SVG coordinates with positive y downward and document the angular convention.
+
+The implementation owns six-unit legs at 45°, orientation, butt-capped `4 6` shafts, and a solid head attached to the terminal dash. Geometry remains separate from the caller's decision that a movement or transfer is meaningful. Reject non-finite inputs and zero-length arrows; never fabricate a direction. A future arc variant rejects ambiguous/unsupported sweeps explicitly.
+
+### Dimensions
+
+Horizontal and vertical dimensions use actual feature witnesses, the dimension-line position, extension gap/overshoot, and the supplied label placement. They return witnesses separately so the drawing can retain the appropriate role and shared-segment ownership. The helper derives the 8 × 8 slash geometry and splits the dimension line around a supplied clearance box where necessary.
+
+The caller supplies verified text bounds/clearance: pure geometry cannot measure the host font accurately. Preserve existing hand-tuned label positions and larger gaps. New label clearances must allow at least three units around actual text bounds. Reject invalid witness order, non-finite geometry, and a clearance box that consumes the usable dimension without an explicit alternative placement.
+
+Labels may be words, local units, or measurements. Keep their text editable. Numeric labels must derive from the same drawing parameters and declared unit as the witnesses; the helper must not infer real-world units or turn an arbitrary label into a technical claim. Radius dimensions and existing special constructions remain authored SVG until a concrete migration warrants another helper.
+
+### Hatching
+
+H1/H2 use the standard's ±45° direction and perpendicular pitch `12 / √2`; a 48 × 48 H1 face must reproduce W05's existing seven segments. The rectangle case returns analytically clipped segments without defs or IDs.
+
+For a later curved or holed region, accept an authored native path, bounds, fill/clip rule, and an explicit unique clip ID. Generate a line field and use SVG clipping to the declared region. Do not introduce a general Bézier intersection solver. Bounds control line coverage; they do not replace the region or fill its holes. Phase/origin is explicit when multiple regions must align.
+
+Hatching does not draw a region boundary, fill an area, shade an object, or imply a material. The caller names its meaning. W05 continues to hatch only cube/core faces, never carriers or machinery.
+
+## Drawing and animation seams
+
+The drawing module contains geometry and stable semantic groups. It has no timers, React hooks, pointer listeners, playback flags, or visibility policy. Ordinary SVG remains valid without any animation module.
+
+The motion workflow consumes an established drawing and records:
+
+- Which named parts change and which remain fixed.
+- Pivots, constraints, contact/edge ownership, and label attachments.
+- Timing phases, easing or physical approximation, and loop/restart behavior.
+- A deliberately selected static fallback, plus reduced-motion and offscreen behavior.
+
+Keep W05's displacement sampling, feed/index timing, clip apertures, and coincident-edge handoff private to that drawing. Keep O07's trajectory and P00's topology decisions local as well. Their calculations should consume shared drawing parameters where appropriate, not a new universal timeline schema.
+
+`EngineeringMotionPreview` remains the browser/React playback adapter for the pilot. Other previews repeat some visibility/reduced-motion behavior, but extracting a shared controller is a subsequent change only if their concrete requirements align. Do not merge their page layouts or make a new shared production hero.
+
+Route static requests through the existing `plotter` entrypoint and motion requests to the dedicated motion reference. A companion skill can later expose that workflow independently; this foundation does not require a second installation or duplicated rules. Both workflows use the same packaged standard and helpers.
+
+## Self-contained SVG output
+
+The portable output is a complete static SVG document with editable paths and text. The export tool accepts native SVG plus explicit appearance/accessibility options. It supports Plotter-authored markup and generated role styles; it is not a general browser CSS engine or an arbitrary website-to-SVG converter.
+
+Default resolved export:
+
+1. Preserve the viewBox, dimensions, document order, groups, geometry, clipping, and semantic metadata.
+2. Resolve role values and an explicitly supplied theme/token set into presentation attributes at the same grouping level. Preserve local overrides. Do not guess the active coss theme from a detached SVG.
+3. Resolve ink/currentColor and supported inline styles so no Tailwind class, CSS module, external stylesheet, or coss import is required. Preserve the source's explicit standalone ink when no override is requested.
+4. Keep defs local. Prefix IDs and rewrite supported fragment references together, including `url(#…)`, `href`/`xlink:href`, and `aria-labelledby`/`aria-describedby` token lists. Document unsupported reference forms and report them rather than emitting broken references.
+5. Preserve or supply an accessible title and description for meaningful output. Decorative React rendering may intentionally use `aria-hidden`; standalone export must not inherit that decision blindly.
+6. Keep text editable, with a declared font stack, automatic optical sizing, no added tracking or synthetic styles, GEOM25 for ordinary illustration text and GEOM0 for identifiers. Preserve other deliberately authored text roles. A sans-serif fallback removes a required font download, but exact typography requires the same font; supplied embedded-font support can follow separately.
+
+An optional themeable export can retain the generated, scoped role stylesheet with literal fallbacks. Embed it in the document; do not leave a dependency on the application stylesheet. The default resolved artifact is simpler for external SVG consumers.
+
+File writing and XML handling live in the export script; the geometry module remains pure. Export reports unsupported stylesheet constructs, unresolved external resources, missing IDs, and unsupported active animation dependencies. It must not silently drop them. The initial export path is static: export the selected static source rather than treating animation time zero as equivalent or removing tracks from an arbitrary frame. Standalone animated HTML/player packaging is subsequent work.
+
+For hand-written JSX, a coss-only authoring adapter may render the selected static drawing to SVG before passing it to the same exporter. That adapter can use the existing React/Bun environment; a recipient opening the resulting SVG needs neither.
+
+## Compiler changes required before role adoption
+
+The two existing Python builders remain the adapters for catalogue and grammar output. Fix only the document handling required by the pilot:
+
+- Preserve `--custom-property` names verbatim when serializing inline styles.
+- Treat CSS selector IDs and fragment URLs separately; the existing broad `#id` substitution cannot safely serve both.
+- Carry required root attributes, styles, and role scope into generated drawings. Explicitly distinguish standalone ink from host foreground so AG01's source color does not accidentally lock the coss gallery to light mode.
+- Prefix defs and accessibility references consistently, with React `useId` for repeated instances. Keep titles/descriptions only for meaningful renderings; preserve their original source for export.
+- Ensure generated CSS cannot affect neighboring SVGs. Prefer simple generated role selectors; do not add a general CSS rewriting subsystem for unsupported authored styles.
+- Keep `expanded-studies.tsx` and `visual-grammar-drawings.tsx` generated. Their editable SVG sources remain authoritative.
+
+Do not rewrite unchanged catalogue entries merely to normalize formatting or numeric defaults.
+
+## Implementation sequence and the first pilot
+
+Each stage should produce a reviewable diff. This sequence is an implementation proposal, not permission to run tests during the present planning pass.
+
+1. **Standard and opt-in roles.** Add the portable numeric source, generation script, generated CSS/runtime values, and concise interface documentation. Preserve the wrapper's legacy default and all unmarked artwork. Generate only the numeric portions of the drafting standard; keep this plan's baseline values as review evidence, not another live source.
+2. **Helpers and static SVG pilot.** Implement straight arrows, axis-aligned dimensions, and rectangular H1/H2. Migrate only AG01's [agent-registration.svg](app/plotter/drawings/agent-registration.svg) initially. Preserve its composition, labels, gaps, fragment locations, and standalone ink. Its transfer arrow and width dimension exercise two helpers; keep descriptive annotations untouched. Regenerate its catalogue output through the existing builder with the necessary compiler fixes.
+3. **Animation caller.** Replace W05's shared `coreHatch` calculation through the hatch helper, and adopt exact-matching roles/straight arrows in [its motion drawing](app/plotter/production-chain-motion-drawing.tsx). The static W05 consumer necessarily shares the hatch change and belongs in the review scope. Keep the existing 1.8-second timeline, movement, edge ownership, and controls unchanged.
+4. **Standalone artifact.** Produce a resolved AG01 SVG and a static W05 SVG through the export seam. Include enough packaged usage information to generate another standalone drawing without the coss checkout. Do not make the reference snapshots generated outputs.
+5. **Workflow documentation.** Point the drawing skill at the foundation when implementing marks, and move portable animation guidance into its own reference. Keep host integration details in `implementation.md`. Document supported helper cases and the native-SVG path for everything else.
+
+This exercises both authoring paths: SVG compiled into React, and hand-written React using a shared geometric calculation. It does not migrate every dimension caller, add curved clipping prematurely, change the grammar examples, or redesign a drawing.
+
+## Acceptance criteria for later implementation review
+
+No tests or automated checks are requested now. These are observable criteria for reviewing subsequent work; any automated verification can be scoped separately when requested.
+
+- A numeric role change has one editable home; runtime values, CSS, and documented defaults derive from it. Installed skill resources include everything they reference.
+- AG01 and W05 retain their baseline appearance at native size in the relevant host themes. Geometry equivalence, grouping/opacity, label clearance, arrowhead solidity, and W05's cube-only hatching are inspected explicitly. Equivalent path serialization is acceptable; changed composition is not.
+- Geometry calculations retain full precision until serialization. Any later numerical verification uses the existing `0.05`-unit and `0.1°` tolerances and states what was actually checked; matching the palette does not prove geometric meaning.
+- W05's timing, contact transitions, full-loop seam, pause/resume, replay, reduced-motion fallback, and offscreen behavior remain unchanged. A static picture alone cannot establish this.
+- The pilot's standalone artifacts retain editable labels, local defs, explicit appearance, and accessible meaning, with no required coss styles, scripts, imports, or font download. Font fallback differences are stated rather than mistaken for exact visual parity.
+- Repeated React instances do not collide in IDs or leak role styles. Supported references survive export and compilation.
+- Existing catalogue IDs, approved snapshots, gallery layout, grammar isolation, and untouched drawings remain intact. The work does not promote a revised drawing into the approved reference set automatically.
+- The skills explain when to use each helper and when native SVG is appropriate. They do not promise that helpers solve composition, semantic clarity, or physical accuracy.
+
+Review the implemented pilot against the recorded baseline before widening adoption. The packaged skill now routes to separate drawing and animation workflows, with shared direction, grammar, and tokens, and portable implementation examples. Those new examples received source review only; no automated checks, tests, or fresh-session evaluation were run. Further catalogue migration remains subsequent work.
diff --git a/apps/ui/public/plotter/downloads/agent-registration.svg b/apps/ui/public/plotter/downloads/agent-registration.svg
new file mode 100644
index 000000000..9f52b1574
--- /dev/null
+++ b/apps/ui/public/plotter/downloads/agent-registration.svg
@@ -0,0 +1,77 @@
+
diff --git a/apps/ui/public/plotter/downloads/assembly-line.html b/apps/ui/public/plotter/downloads/assembly-line.html
new file mode 100644
index 000000000..4a4a3eeba
--- /dev/null
+++ b/apps/ui/public/plotter/downloads/assembly-line.html
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+ Plotter · An assembly line
+
+
+
+
+
An assembly line
+
A core seats in its carrier. The line advances and the next assembly begins.
+
+
+
+
+
+
+
+
+
+
Static drawing. Animation controls appear when supported.
+
This editable HTML includes its SVG, drawing styles, and player. Open it directly in a modern browser. Cal Sans is optional; labels use sans-serif when it is unavailable.
+
+
+
+
diff --git a/apps/ui/public/plotter/downloads/plotter.zip b/apps/ui/public/plotter/downloads/plotter.zip
new file mode 100644
index 000000000..cf3771c2a
Binary files /dev/null and b/apps/ui/public/plotter/downloads/plotter.zip differ
diff --git a/apps/ui/public/plotter/downloads/production-chain.svg b/apps/ui/public/plotter/downloads/production-chain.svg
new file mode 100644
index 000000000..b96e6a0ec
--- /dev/null
+++ b/apps/ui/public/plotter/downloads/production-chain.svg
@@ -0,0 +1 @@
+
diff --git a/apps/ui/scripts/build-plotter-downloads.tsx b/apps/ui/scripts/build-plotter-downloads.tsx
new file mode 100644
index 000000000..da351840b
--- /dev/null
+++ b/apps/ui/scripts/build-plotter-downloads.tsx
@@ -0,0 +1,169 @@
+import { execFileSync } from "node:child_process";
+import { mkdirSync, writeFileSync } from "node:fs";
+import { resolve } from "node:path";
+import { renderToStaticMarkup } from "react-dom/server";
+import { ProductionChainStudy } from "../app/plotter/engineering-studies";
+import { ProductionChainMotionDrawing } from "../app/plotter/production-chain-motion-drawing";
+import { roleCss } from "../skills/plotter/assets/foundation/index.mjs";
+
+// Regenerate the page's downloads from the current skill and illustration sources.
+const root = resolve(import.meta.dir, "..");
+const destination = resolve(root, "public/plotter/downloads");
+mkdirSync(destination, { recursive: true });
+
+execFileSync(
+ process.execPath,
+ [resolve(root, "scripts/export-plotter-pilots.tsx"), destination],
+ { stdio: "inherit" },
+);
+
+function standaloneMarkup(markup: string, id: string, description: string) {
+ const source = markup.replace(
+ /
+
+
+
+
An assembly line
+
A core seats in its carrier. The line advances and the next assembly begins.
+
+
${still}
+
${motion}
+
+
+
+
+
+
+
Static drawing. Animation controls appear when supported.
+
This editable HTML includes its SVG, drawing styles, and player. Open it directly in a modern browser. Cal Sans is optional; labels use sans-serif when it is unavailable.