diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml
index 2317586..f67137d 100644
--- a/.github/workflows/website.yml
+++ b/.github/workflows/website.yml
@@ -3,9 +3,9 @@ name: Website quality
on:
push:
branches: [master]
- paths: ["website/**", ".github/workflows/website.yml"]
+ paths: ["website/**", "src/**", "agent/**", "style.css", "package*.json", "scripts/agent/**", ".github/workflows/website.yml"]
pull_request:
- paths: ["website/**", ".github/workflows/website.yml"]
+ paths: ["website/**", "src/**", "agent/**", "style.css", "package*.json", "scripts/agent/**", ".github/workflows/website.yml"]
workflow_dispatch:
permissions:
@@ -13,7 +13,7 @@ permissions:
jobs:
quality:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
defaults:
run:
working-directory: website
@@ -24,10 +24,22 @@ jobs:
node-version: 22
cache: npm
cache-dependency-path: website/package-lock.json
+ - run: npm ci --ignore-scripts
+ working-directory: .
+ - run: npm run check
+ working-directory: .
+ - run: npm test -- tests/agent/showcase.test.ts
+ working-directory: .
+ - run: npm run build && npm run test:templates
+ working-directory: .
- run: npm ci
- run: npm run check
- run: npx playwright install --with-deps chromium
- run: npm test
+ - uses: actions/upload-artifact@v4
+ with:
+ name: static-showcase
+ path: website/dist/
- uses: actions/upload-artifact@v4
if: failure()
with:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7874bf3..e5c9f73 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## Unreleased
+
+- Add a unified website showcase with six live use cases, Product/UX/Engineering/QE
+ examples for workspace deletion, billing, search, security and import validation, a deterministic agent walkthrough, and
+ inspectable plans, actual source, canonical framework examples and verification scope.
+- Add sticky `offsetX`/`offsetY` and arrow `labelOffsetX`/`labelOffsetY` pixel nudges
+ after automatic placement and before viewport clamping. Defaults preserve existing
+ placement. Core grows by 38 bytes gzip to 6.83 KiB; the 7 KiB guard is unchanged.
+- Generate updated option contracts and snippets; add evidence-based handoff guidance
+ within existing skills without changing routing or adding a skill.
+- Make playground code match its preview, including seed, motion and placement;
+ add reset and use canonical framework templates. Test the site against checkout
+ source, validate bundled plans and add desktop/mobile interaction and visual checks.
+
## 0.1.0 — 2026-09-06
- Add agent authoring tools alongside the typed runtime API with typed annotation plans, generated
diff --git a/README.md b/README.md
index 8d6b476..8ee616e 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,22 @@ Hand-sketched annotations for live UI, built for coding agents and developers.
Add circles, highlights, arrows, notes, and proofreader marks while your
application keeps its controls and layout.
-Agent and developer first: work with your coding agent through project skills,
-validated plans and framework examples, or write annotations directly with the
-typed JavaScript API. Both workflows use the same primitives and lifecycle.
+[Live showcase](https://stetkit.com/use-cases) · [Agent workflow](https://stetkit.com/agent-workflow) · [Agent setup](docs/agent-usage.md)
+
+**Developer:** `circle(button)` adds emphasis while your button keeps working.
+**Agent:** “Annotate what changed and explain the risky parts.” A coding agent
+can turn that request into a validated plan, framework code and a visual handoff.
+
+Try [a dangerous settings action](https://stetkit.com/use-cases/workspace-deletion),
+[an implementation handoff](https://stetkit.com/use-cases/security-handoff), or
+[a reproducible form review](https://stetkit.com/use-cases/form-review).
+Each has a working interface, annotation toggle, actual plan/source and explicit
+verification scope. [Tutorials](https://stetkit.com/use-cases/guided-tutorial) and
+[live documentation](https://stetkit.com/use-cases/live-documentation) work without an agent.
+
+Distinct live examples show Product billing intent, UX search recovery and QE import checks.
+Stet supplies the annotation layer; your existing tools own requirements, design
+and tests. No collaboration backend, accounts or model API required.
The API is under active development and may change
before 1.0. Pin an exact version if you need predictable upgrades.
@@ -80,9 +93,13 @@ annotation.destroy(); // remove the annotation and its subscriptions
Still by default. Seeded when you need repeatability. Optional hover resketching
and stroke boil honor reduced-motion preferences.
-To explore locally: `npm install`, then `python -m http.server 4173` and open
-[the live demo](http://localhost:4173/examples/vanilla/) or
-[the visual specimens](http://localhost:4173/examples/visual/).
+To explore this checkout locally, run `npm ci`, `npm --prefix website ci`, then
+`npm --prefix website run dev`. Open `/use-cases` or `/agent-workflow`.
+The [playground](https://stetkit.com/playground) exposes real options and copyable code;
+framework integration lives in [Docs](https://stetkit.com/docs).
+See [website setup](website/README.md) for production builds and tests. Hosted
+links show the deployed site; this checkout's additions appear after deployment.
+The [fixed visual specimens](examples/visual/) remain the regression matrix.
## Dependencies and framework support
@@ -107,6 +124,7 @@ development dependencies and are not installed as dependencies in your app.
- [Explanation: how stet marks live UI](docs/explanation.md)
- [API reference](docs/reference.md)
- [Framework examples](examples/)
+- [Use cases and visual handoff](docs/visual-handoff.md)
- [Agent usage](docs/agent-usage.md) and [architecture](docs/agent-architecture.md)
- [GitHub Packages and release process](docs/releases.md)
diff --git a/agent/annotation-plan.d.ts b/agent/annotation-plan.d.ts
index bddbf87..01a905b 100644
--- a/agent/annotation-plan.d.ts
+++ b/agent/annotation-plan.d.ts
@@ -85,6 +85,8 @@ export type AnnotationPlan = {
"options"?: {
"label"?: string;
"curvature"?: number;
+"labelOffsetX"?: number;
+"labelOffsetY"?: number;
"seed"?: number;
"roughness"?: number;
"boil"?: number;
@@ -108,6 +110,8 @@ export type AnnotationPlan = {
"options": {
"text": string;
"side"?: "auto" | "top" | "right" | "bottom" | "left";
+"offsetX"?: number;
+"offsetY"?: number;
"seed"?: number;
"roughness"?: number;
"boil"?: number;
diff --git a/agent/capabilities.json b/agent/capabilities.json
index 5e8ea4c..381c554 100644
--- a/agent/capabilities.json
+++ b/agent/capabilities.json
@@ -284,6 +284,12 @@
"curvature": {
"type": "number"
},
+ "labelOffsetX": {
+ "type": "number"
+ },
+ "labelOffsetY": {
+ "type": "number"
+ },
"seed": {
"type": "number"
},
@@ -318,6 +324,8 @@
"defaults": {
"label": "absent",
"curvature": 0.16,
+ "labelOffsetX": 0,
+ "labelOffsetY": 0,
"seed": "random uint32",
"roughness": 1,
"boil": 0,
@@ -356,6 +364,12 @@
"left"
]
},
+ "offsetX": {
+ "type": "number"
+ },
+ "offsetY": {
+ "type": "number"
+ },
"seed": {
"type": "number"
},
@@ -391,6 +405,8 @@
},
"defaults": {
"side": "auto",
+ "offsetX": 0,
+ "offsetY": 0,
"seed": "random uint32",
"roughness": 1,
"boil": 0,
@@ -482,7 +498,7 @@
"motion": "Still by default; reduced motion disables boil and hover resketch live. Fixed seed, dimensions and options reproduce geometry.",
"lifecycle": "DOM Elements in the current document and document.body must exist. Options are snapshots. Handles expose refresh(), resketch(seed?), destroy().",
"targeting": "Targets are source-editing evidence, not executable selectors. Prove uniqueness and readiness in application code. Arrow order is from, to.",
- "placement": "Body overlays; resize/scroll/font tracking. Movement without resize needs refresh(). No collision engine, cross-document targets, top-layer dialogs or transformed/zoomed body/html support. Partial clipping and very long notes need visual review.",
+ "placement": "Body overlays; resize/scroll/font tracking. Movement without resize needs refresh(). No collision engine, cross-document targets, top-layer dialogs or transformed/zoomed body/html support. Sticky offsetX/offsetY and arrow labelOffsetX/labelOffsetY nudge automatic placement before viewport clamping; they do not avoid obstacles. Partial clipping and very long notes need visual review.",
"styling": "Import @funsaized/stet/style.css once; preserve target layout and semantics. CSS variables theme the overlay."
},
"tools": {
diff --git a/agent/catalog.mjs b/agent/catalog.mjs
index 09194b4..9186b3d 100644
--- a/agent/catalog.mjs
+++ b/agent/catalog.mjs
@@ -14,7 +14,7 @@ export const primitives = {
sticky: { type: 'StickyOptions', targets: ['target'], padding: 14, purpose: 'Explain with concise paper-note text', meaningfulText: 'text; describes target' },
mark: { type: 'StetOptions', targets: ['target'], padding: 4, purpose: 'Check right or cross wrong; kind is a separate core argument', meaningfulText: 'description' },
};
-export const defaults = { seed: 'random uint32', roughness: 1, boil: 0, stroke: 'CSS token', fill: 'CSS token', width: 'CSS token', resketchOnHover: false, description: 'absent', label: 'absent', curvature: 0.16, side: 'auto' };
+export const defaults = { seed: 'random uint32', roughness: 1, boil: 0, stroke: 'CSS token', fill: 'CSS token', width: 'CSS token', resketchOnHover: false, description: 'absent', label: 'absent', curvature: 0.16, side: 'auto', offsetX: 0, offsetY: 0, labelOffsetX: 0, labelOffsetY: 0 };
export const strategies = ['ref', 'id', 'data-attribute', 'source', 'stet-attribute', 'css'];
export const tools = { claude: '.claude/skills', cursor: '.agents/skills', opencode: '.agents/skills', codex: '.agents/skills' };
export const constraints = {
@@ -23,6 +23,6 @@ export const constraints = {
motion: 'Still by default; reduced motion disables boil and hover resketch live. Fixed seed, dimensions and options reproduce geometry.',
lifecycle: 'DOM Elements in the current document and document.body must exist. Options are snapshots. Handles expose refresh(), resketch(seed?), destroy().',
targeting: 'Targets are source-editing evidence, not executable selectors. Prove uniqueness and readiness in application code. Arrow order is from, to.',
- placement: 'Body overlays; resize/scroll/font tracking. Movement without resize needs refresh(). No collision engine, cross-document targets, top-layer dialogs or transformed/zoomed body/html support. Partial clipping and very long notes need visual review.',
+ placement: 'Body overlays; resize/scroll/font tracking. Movement without resize needs refresh(). No collision engine, cross-document targets, top-layer dialogs or transformed/zoomed body/html support. Sticky offsetX/offsetY and arrow labelOffsetX/labelOffsetY nudge automatic placement before viewport clamping; they do not avoid obstacles. Partial clipping and very long notes need visual review.',
styling: 'Import @funsaized/stet/style.css once; preserve target layout and semantics. CSS variables theme the overlay.',
};
diff --git a/agent/patterns.mjs b/agent/patterns.mjs
index 3819452..17d5e8d 100644
--- a/agent/patterns.mjs
+++ b/agent/patterns.mjs
@@ -9,7 +9,7 @@ function attachMarks(target: Element, destination: Element | null | undefined, e
const destroy = () => { for (const handle of handles.splice(0).reverse()) handle.destroy(); };
try {
if (enabled) {
-${arrowOnly ? '' : ' handles.push(circle(target, { seed: 42, description: "Review this action before continuing." }));\n handles.push(sticky(target, { seed: 43, text: "Read the consequences before continuing." }));\n'} if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here." }));
+${arrowOnly ? '' : ' handles.push(circle(target, { seed: 42, description: "Review this action before continuing." }));\n handles.push(sticky(target, { seed: 43, text: "Read the consequences before continuing." }));\n'} if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here.", labelOffsetY: -12 }));
}
return destroy;
} catch (error) { destroy(); throw error; }
diff --git a/agent/schemas/annotation-plan.schema.json b/agent/schemas/annotation-plan.schema.json
index bda8494..83a6f72 100644
--- a/agent/schemas/annotation-plan.schema.json
+++ b/agent/schemas/annotation-plan.schema.json
@@ -488,6 +488,12 @@
"curvature": {
"type": "number"
},
+ "labelOffsetX": {
+ "type": "number"
+ },
+ "labelOffsetY": {
+ "type": "number"
+ },
"seed": {
"type": "number"
},
@@ -625,6 +631,12 @@
"left"
]
},
+ "offsetX": {
+ "type": "number"
+ },
+ "offsetY": {
+ "type": "number"
+ },
"seed": {
"type": "number"
},
diff --git a/agent/schemas/capabilities.schema.json b/agent/schemas/capabilities.schema.json
index ffa39bc..dd83c8b 100644
--- a/agent/schemas/capabilities.schema.json
+++ b/agent/schemas/capabilities.schema.json
@@ -288,6 +288,12 @@
"curvature": {
"type": "number"
},
+ "labelOffsetX": {
+ "type": "number"
+ },
+ "labelOffsetY": {
+ "type": "number"
+ },
"seed": {
"type": "number"
},
@@ -322,6 +328,8 @@
"defaults": {
"label": "absent",
"curvature": 0.16,
+ "labelOffsetX": 0,
+ "labelOffsetY": 0,
"seed": "random uint32",
"roughness": 1,
"boil": 0,
@@ -360,6 +368,12 @@
"left"
]
},
+ "offsetX": {
+ "type": "number"
+ },
+ "offsetY": {
+ "type": "number"
+ },
"seed": {
"type": "number"
},
@@ -395,6 +409,8 @@
},
"defaults": {
"side": "auto",
+ "offsetX": 0,
+ "offsetY": 0,
"seed": "random uint32",
"roughness": 1,
"boil": 0,
@@ -486,7 +502,7 @@
"motion": "Still by default; reduced motion disables boil and hover resketch live. Fixed seed, dimensions and options reproduce geometry.",
"lifecycle": "DOM Elements in the current document and document.body must exist. Options are snapshots. Handles expose refresh(), resketch(seed?), destroy().",
"targeting": "Targets are source-editing evidence, not executable selectors. Prove uniqueness and readiness in application code. Arrow order is from, to.",
- "placement": "Body overlays; resize/scroll/font tracking. Movement without resize needs refresh(). No collision engine, cross-document targets, top-layer dialogs or transformed/zoomed body/html support. Partial clipping and very long notes need visual review.",
+ "placement": "Body overlays; resize/scroll/font tracking. Movement without resize needs refresh(). No collision engine, cross-document targets, top-layer dialogs or transformed/zoomed body/html support. Sticky offsetX/offsetY and arrow labelOffsetX/labelOffsetY nudge automatic placement before viewport clamping; they do not avoid obstacles. Partial clipping and very long notes need visual review.",
"styling": "Import @funsaized/stet/style.css once; preserve target layout and semantics. CSS variables theme the overlay."
},
"tools": {
diff --git a/agent/skills/stet-explain-ui/SKILL.md b/agent/skills/stet-explain-ui/SKILL.md
index 33abaed..6845d48 100644
--- a/agent/skills/stet-explain-ui/SKILL.md
+++ b/agent/skills/stet-explain-ui/SKILL.md
@@ -23,3 +23,8 @@ Remove redundant notes before trying to fit several overlays into a narrow layou
Use the [base Stet workflow](../stet/SKILL.md) to inspect installed facts, plan,
validate and implement in the detected framework. Verify a reader can understand
the action while the original controls retain their behavior.
+
+When explaining completed implementation work, use the
+[handoff guidance](../stet/references/handoff.md) to separate actual changes,
+verified behavior and follow-ups. This does not substitute for implementation
+or verification.
diff --git a/agent/skills/stet/SKILL.md b/agent/skills/stet/SKILL.md
index 694e6c4..02810dd 100644
--- a/agent/skills/stet/SKILL.md
+++ b/agent/skills/stet/SKILL.md
@@ -54,4 +54,5 @@ Local edits cause a conflict; reconcile them instead of deleting project config.
| Vue directives | [vue](references/vue.md) |
| Svelte actions | [svelte](references/svelte.md) |
| Angular directives | [angular](references/angular.md) |
+| Explanation of completed implementation | [handoff](references/handoff.md) |
| Errors and verification failures | [troubleshooting](references/troubleshooting.md) |
diff --git a/agent/skills/stet/references/handoff.md b/agent/skills/stet/references/handoff.md
new file mode 100644
index 0000000..f1c8fc7
--- /dev/null
+++ b/agent/skills/stet/references/handoff.md
@@ -0,0 +1,20 @@
+# Explain an implementation handoff
+
+Use after implementation, when the requested explanation is about what changed.
+Inspect the actual diff and working controls. Separate implemented behavior,
+checks you actually ran, and follow-up work. A passing build does not establish
+interaction behavior or visual quality; an annotation is never test evidence.
+
+Annotate meaningful changes only. Use concise delivery copy such as “Exact-name
+confirmation added”; reserve “verified” for behavior supported by your recorded
+checks. Put untested claims and remaining work in the handoff text instead of
+turning them into green checks. Keep safety warnings in the application.
+
+Preserve the new controls and their layout. Prefer a focal circle or underline
+when a note would repeat native copy. Group cleanup in the framework's existing
+lifecycle. Provide the plan, changed source and actual verification results so a
+human can review the working UI with marks on or off.
+
+This is an explanation of completed work. A request to find defects uses the
+review workflow; a request to present product value uses showcase. A handoff
+request alone does not authorize unrelated implementation or deployment.
diff --git a/agent/skills/stet/references/troubleshooting.md b/agent/skills/stet/references/troubleshooting.md
index 1ae42bb..08e7997 100644
--- a/agent/skills/stet/references/troubleshooting.md
+++ b/agent/skills/stet/references/troubleshooting.md
@@ -10,10 +10,14 @@
| Invisible/misplaced chrome | Check CSS import, target visibility, lifecycle and supported placement context | Scroll/resize and inspect in browser |
| Drift after app movement | Call retained core handle.refresh(); inspect adapter lifecycle if applicable | Seed unchanged; overlay follows target |
| Stale annotation | Destroy manual handles; align target and adapter mount lifetimes | Navigate/toggle; no stale overlay/description |
-| Text overlaps UI | Shorten copy, reduce marks, try a supported side; inspect responsive state | Text readable, controls unobscured |
+| Text overlaps UI | Shorten copy and reduce marks first. Inspect installed support for sticky offsetX/offsetY or arrow labelOffsetX/labelOffsetY; nudge after browser inspection. Curvature adjusts the path, not obstacle routing | Text readable, controls unobscured |
| Skill update conflict | Preserve and reconcile named local files; rerun update | No unrelated config changed |
Exit codes: 1 invalid plan; 2 usage; 3 file/JSON error; 4 installation conflict.
JSON failure output contains errors with path/code/message. Recovery must address
that cause. A successful validation or build is not visual verification. If a
browser cannot run, state that limitation and the exact checks that did run.
+
+Placement nudges use viewport x/y pixels (positive right/down), after automatic
+placement and before viewport clamping. They do not detect collisions or force a
+preferred side. Recheck at narrow widths; remove redundant labels when possible.
diff --git a/agent/snippets.mjs b/agent/snippets.mjs
index 7d21f01..f38ad84 100644
--- a/agent/snippets.mjs
+++ b/agent/snippets.mjs
@@ -6,7 +6,7 @@ export function snippet(primitive, framework) {
if (!Object.hasOwn(primitives, primitive) || !Object.hasOwn(frameworks, framework)) throw new Error('Unknown primitive or framework');
if (primitive === 'arrow' && ['vue', 'svelte'].includes(framework)) return lifecyclePattern(framework, true);
const title = primitive[0].toUpperCase() + primitive.slice(1);
- const options = { seed: 42, ...(primitive === 'sticky' ? { text: 'Review the consequences before continuing.' } : primitive === 'arrow' ? { label: 'Review this action.' } : { description: 'Review this action before continuing.' }) };
+ const options = { seed: 42, ...(primitive === 'sticky' ? { text: 'Review the consequences before continuing.', offsetY: 8 } : primitive === 'arrow' ? { label: 'Review this action.', labelOffsetY: -12 } : { description: 'Review this action before continuing.' }) };
const adapterOptions = { ...options, ...(primitive === 'mark' ? { kind: 'wrong' } : {}) };
const js = value => JSON.stringify(value);
const css = 'import "@funsaized/stet/style.css";';
@@ -18,5 +18,5 @@ export function snippet(primitive, framework) {
}
if (framework === 'vue') return { extension: 'vue', code: `\n\n\n${primitive === 'arrow' ? ' Consequences of this action
\n \n' : ''} \n\n` };
if (framework === 'svelte') return { extension: 'svelte', code: `\n\n${primitive === 'arrow' ? '
Consequences of this action
\n\n{#if destination}\n' : ''}\n${primitive === 'arrow' ? '{/if}\n' : ''}` };
- return { extension: 'ts', code: note + `import { Component } from "@angular/core";\nimport { Stet${title}Directive } from "@funsaized/stet/angular";\n// Add @import "@funsaized/stet/style.css"; to the application's global stylesheet.\n@Component({\n selector: "app-annotated-action",\n standalone: true,\n imports: [Stet${title}Directive],\n template: \`\n${primitive === 'arrow' ? ' Consequences of this action
\n' : ''} \n \`,\n})\nexport class AnnotatedAction {}\n` };
+ return { extension: 'ts', code: note + `import { Component } from "@angular/core";\nimport { Stet${title}Directive } from "@funsaized/stet/angular";\n// Add @import "@funsaized/stet/style.css"; to the application's global stylesheet.\n@Component({\n selector: "app-annotated-action",\n standalone: true,\n imports: [Stet${title}Directive],\n template: \`\n${primitive === 'arrow' ? ' Consequences of this action
\n' : ''} \n \`,\n})\nexport class AnnotatedAction {}\n` };
}
diff --git a/agent/templates/angular/arrow.ts b/agent/templates/angular/arrow.ts
index 8c9967e..4fc32cf 100644
--- a/agent/templates/angular/arrow.ts
+++ b/agent/templates/angular/arrow.ts
@@ -8,7 +8,7 @@ import { StetArrowDirective } from "@funsaized/stet/angular";
imports: [StetArrowDirective],
template: `
Consequences of this action
-
+
`,
})
export class AnnotatedAction {}
diff --git a/agent/templates/angular/lifecycle.ts b/agent/templates/angular/lifecycle.ts
index a8f9dbf..1d4ec95 100644
--- a/agent/templates/angular/lifecycle.ts
+++ b/agent/templates/angular/lifecycle.ts
@@ -8,7 +8,7 @@ function attachMarks(target: Element, destination: Element | null | undefined, e
if (enabled) {
handles.push(circle(target, { seed: 42, description: "Review this action before continuing." }));
handles.push(sticky(target, { seed: 43, text: "Read the consequences before continuing." }));
- if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here." }));
+ if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here.", labelOffsetY: -12 }));
}
return destroy;
} catch (error) { destroy(); throw error; }
diff --git a/agent/templates/angular/sticky.ts b/agent/templates/angular/sticky.ts
index 999a8de..6607b3d 100644
--- a/agent/templates/angular/sticky.ts
+++ b/agent/templates/angular/sticky.ts
@@ -7,7 +7,7 @@ import { StetStickyDirective } from "@funsaized/stet/angular";
standalone: true,
imports: [StetStickyDirective],
template: `
-
+
`,
})
export class AnnotatedAction {}
diff --git a/agent/templates/react/arrow.tsx b/agent/templates/react/arrow.tsx
index aa1e12d..c6ccd5c 100644
--- a/agent/templates/react/arrow.tsx
+++ b/agent/templates/react/arrow.tsx
@@ -10,6 +10,6 @@ export function AnnotatedAction({ enabled = true }: { enabled?: boolean }) {
return <>
Consequences of this action
- {enabled && }
+ {enabled && }
>;
}
diff --git a/agent/templates/react/lifecycle.tsx b/agent/templates/react/lifecycle.tsx
index 3b79ec0..ff18df9 100644
--- a/agent/templates/react/lifecycle.tsx
+++ b/agent/templates/react/lifecycle.tsx
@@ -9,7 +9,7 @@ function attachMarks(target: Element, destination: Element | null | undefined, e
if (enabled) {
handles.push(circle(target, { seed: 42, description: "Review this action before continuing." }));
handles.push(sticky(target, { seed: 43, text: "Read the consequences before continuing." }));
- if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here." }));
+ if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here.", labelOffsetY: -12 }));
}
return destroy;
} catch (error) { destroy(); throw error; }
diff --git a/agent/templates/react/sticky.tsx b/agent/templates/react/sticky.tsx
index c70b7ea..cf3d202 100644
--- a/agent/templates/react/sticky.tsx
+++ b/agent/templates/react/sticky.tsx
@@ -8,6 +8,6 @@ export function AnnotatedAction({ enabled = true }: { enabled?: boolean }) {
const target = useRef(null);
return <>
- {enabled && }
+ {enabled && }
>;
}
diff --git a/agent/templates/svelte/arrow.svelte b/agent/templates/svelte/arrow.svelte
index 87247d2..d38c2cb 100644
--- a/agent/templates/svelte/arrow.svelte
+++ b/agent/templates/svelte/arrow.svelte
@@ -6,7 +6,7 @@ function attachMarks(target: Element, destination: Element | null | undefined, e
const destroy = () => { for (const handle of handles.splice(0).reverse()) handle.destroy(); };
try {
if (enabled) {
- if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here." }));
+ if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here.", labelOffsetY: -12 }));
}
return destroy;
} catch (error) { destroy(); throw error; }
diff --git a/agent/templates/svelte/lifecycle.svelte b/agent/templates/svelte/lifecycle.svelte
index 323d31c..53d76bf 100644
--- a/agent/templates/svelte/lifecycle.svelte
+++ b/agent/templates/svelte/lifecycle.svelte
@@ -8,7 +8,7 @@ function attachMarks(target: Element, destination: Element | null | undefined, e
if (enabled) {
handles.push(circle(target, { seed: 42, description: "Review this action before continuing." }));
handles.push(sticky(target, { seed: 43, text: "Read the consequences before continuing." }));
- if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here." }));
+ if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here.", labelOffsetY: -12 }));
}
return destroy;
} catch (error) { destroy(); throw error; }
diff --git a/agent/templates/svelte/sticky.svelte b/agent/templates/svelte/sticky.svelte
index 3fe5a1d..cdf1ec3 100644
--- a/agent/templates/svelte/sticky.svelte
+++ b/agent/templates/svelte/sticky.svelte
@@ -4,4 +4,4 @@ import { sticky } from "@funsaized/stet/svelte";
import "@funsaized/stet/style.css";
-
+
diff --git a/agent/templates/vanilla/arrow.ts b/agent/templates/vanilla/arrow.ts
index acae8a8..5c99659 100644
--- a/agent/templates/vanilla/arrow.ts
+++ b/agent/templates/vanilla/arrow.ts
@@ -4,6 +4,6 @@ import "@funsaized/stet/style.css";
// Call after DOM mount with resolved, unique Elements. Call cleanup before removal.
export function annotate(target: Element, destination: Element) {
- const handle = arrow(target, destination, {"seed":42,"label":"Review this action."});
+ const handle = arrow(target, destination, {"seed":42,"label":"Review this action.","labelOffsetY":-12});
return { refresh: () => handle.refresh(), destroy: () => handle.destroy() };
}
diff --git a/agent/templates/vanilla/lifecycle.ts b/agent/templates/vanilla/lifecycle.ts
index 111d9be..edfe376 100644
--- a/agent/templates/vanilla/lifecycle.ts
+++ b/agent/templates/vanilla/lifecycle.ts
@@ -7,7 +7,7 @@ function attachMarks(target: Element, destination: Element | null | undefined, e
if (enabled) {
handles.push(circle(target, { seed: 42, description: "Review this action before continuing." }));
handles.push(sticky(target, { seed: 43, text: "Read the consequences before continuing." }));
- if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here." }));
+ if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here.", labelOffsetY: -12 }));
}
return destroy;
} catch (error) { destroy(); throw error; }
diff --git a/agent/templates/vanilla/sticky.ts b/agent/templates/vanilla/sticky.ts
index 4fa1c2f..50f5ce3 100644
--- a/agent/templates/vanilla/sticky.ts
+++ b/agent/templates/vanilla/sticky.ts
@@ -4,6 +4,6 @@ import "@funsaized/stet/style.css";
// Call after DOM mount with resolved, unique Elements. Call cleanup before removal.
export function annotate(target: Element) {
- const handle = sticky(target, {"seed":42,"text":"Review the consequences before continuing."});
+ const handle = sticky(target, {"seed":42,"text":"Review the consequences before continuing.","offsetY":8});
return { refresh: () => handle.refresh(), destroy: () => handle.destroy() };
}
diff --git a/agent/templates/vue/arrow.vue b/agent/templates/vue/arrow.vue
index 56fae67..174b696 100644
--- a/agent/templates/vue/arrow.vue
+++ b/agent/templates/vue/arrow.vue
@@ -7,7 +7,7 @@ function attachMarks(target: Element, destination: Element | null | undefined, e
const destroy = () => { for (const handle of handles.splice(0).reverse()) handle.destroy(); };
try {
if (enabled) {
- if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here." }));
+ if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here.", labelOffsetY: -12 }));
}
return destroy;
} catch (error) { destroy(); throw error; }
diff --git a/agent/templates/vue/lifecycle.vue b/agent/templates/vue/lifecycle.vue
index ae525c0..bd16e45 100644
--- a/agent/templates/vue/lifecycle.vue
+++ b/agent/templates/vue/lifecycle.vue
@@ -9,7 +9,7 @@ function attachMarks(target: Element, destination: Element | null | undefined, e
if (enabled) {
handles.push(circle(target, { seed: 42, description: "Review this action before continuing." }));
handles.push(sticky(target, { seed: 43, text: "Read the consequences before continuing." }));
- if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here." }));
+ if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here.", labelOffsetY: -12 }));
}
return destroy;
} catch (error) { destroy(); throw error; }
diff --git a/agent/templates/vue/sticky.vue b/agent/templates/vue/sticky.vue
index a76c3f8..a502dc5 100644
--- a/agent/templates/vue/sticky.vue
+++ b/agent/templates/vue/sticky.vue
@@ -5,5 +5,5 @@ import "@funsaized/stet/style.css";
-
+
diff --git a/agent/validate.generated.mjs b/agent/validate.generated.mjs
index c8b2dcf..2d56088 100644
--- a/agent/validate.generated.mjs
+++ b/agent/validate.generated.mjs
@@ -1,2 +1,2 @@
// Generated by Ajv (MIT); see LICENSE-ajv.txt. Do not edit.
-"use strict";export const validate = validate10;export default validate10;const schema11 = {"$schema":"http://json-schema.org/draft-07/schema#","title":"Stet Annotation Plan v1","type":"object","additionalProperties":false,"properties":{"version":{"type":"integer","const":1},"framework":{"type":"string","enum":["vanilla","react","vue","svelte","angular"]},"intent":{"type":"string","pattern":"\\S"},"annotations":{"type":"array","minItems":1,"items":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9._-]*$"},"primitive":{"const":"circle","type":"string"},"targets":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"strategy":{"enum":["ref","id","data-attribute","source","stet-attribute","css"],"type":"string"},"file":{"type":"string","pattern":"\\S"},"locator":{"type":"string","pattern":"\\S"},"description":{"type":"string","pattern":"\\S"},"rationale":{"type":"string","pattern":"\\S"}},"required":["strategy","file","locator","description"],"allOf":[{"if":{"properties":{"strategy":{"const":"css"}},"required":["strategy"]},"then":{"properties":{"rationale":{"type":"string","pattern":"\\S"}},"required":["rationale"]}}]},"minItems":1,"maxItems":1},"options":{"type":"object","additionalProperties":false,"properties":{"seed":{"type":"number"},"roughness":{"type":"number"},"boil":{"type":"number"},"stroke":{"type":"string"},"fill":{"type":"string"},"width":{"type":"number"},"resketchOnHover":{"type":"boolean"},"padding":{"type":"number"},"description":{"type":"string","pattern":"\\S"}},"required":[]}},"required":["id","primitive","targets"]},{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9._-]*$"},"primitive":{"const":"underline","type":"string"},"targets":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"strategy":{"enum":["ref","id","data-attribute","source","stet-attribute","css"],"type":"string"},"file":{"type":"string","pattern":"\\S"},"locator":{"type":"string","pattern":"\\S"},"description":{"type":"string","pattern":"\\S"},"rationale":{"type":"string","pattern":"\\S"}},"required":["strategy","file","locator","description"],"allOf":[{"if":{"properties":{"strategy":{"const":"css"}},"required":["strategy"]},"then":{"properties":{"rationale":{"type":"string","pattern":"\\S"}},"required":["rationale"]}}]},"minItems":1,"maxItems":1},"options":{"type":"object","additionalProperties":false,"properties":{"seed":{"type":"number"},"roughness":{"type":"number"},"boil":{"type":"number"},"stroke":{"type":"string"},"fill":{"type":"string"},"width":{"type":"number"},"resketchOnHover":{"type":"boolean"},"padding":{"type":"number"},"description":{"type":"string","pattern":"\\S"}},"required":[]}},"required":["id","primitive","targets"]},{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9._-]*$"},"primitive":{"const":"highlight","type":"string"},"targets":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"strategy":{"enum":["ref","id","data-attribute","source","stet-attribute","css"],"type":"string"},"file":{"type":"string","pattern":"\\S"},"locator":{"type":"string","pattern":"\\S"},"description":{"type":"string","pattern":"\\S"},"rationale":{"type":"string","pattern":"\\S"}},"required":["strategy","file","locator","description"],"allOf":[{"if":{"properties":{"strategy":{"const":"css"}},"required":["strategy"]},"then":{"properties":{"rationale":{"type":"string","pattern":"\\S"}},"required":["rationale"]}}]},"minItems":1,"maxItems":1},"options":{"type":"object","additionalProperties":false,"properties":{"seed":{"type":"number"},"roughness":{"type":"number"},"boil":{"type":"number"},"stroke":{"type":"string"},"fill":{"type":"string"},"width":{"type":"number"},"resketchOnHover":{"type":"boolean"},"padding":{"type":"number"},"description":{"type":"string","pattern":"\\S"}},"required":[]}},"required":["id","primitive","targets"]},{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9._-]*$"},"primitive":{"const":"arrow","type":"string"},"targets":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"strategy":{"enum":["ref","id","data-attribute","source","stet-attribute","css"],"type":"string"},"file":{"type":"string","pattern":"\\S"},"locator":{"type":"string","pattern":"\\S"},"description":{"type":"string","pattern":"\\S"},"rationale":{"type":"string","pattern":"\\S"}},"required":["strategy","file","locator","description"],"allOf":[{"if":{"properties":{"strategy":{"const":"css"}},"required":["strategy"]},"then":{"properties":{"rationale":{"type":"string","pattern":"\\S"}},"required":["rationale"]}}]},"minItems":2,"maxItems":2},"options":{"type":"object","additionalProperties":false,"properties":{"label":{"type":"string","pattern":"\\S"},"curvature":{"type":"number"},"seed":{"type":"number"},"roughness":{"type":"number"},"boil":{"type":"number"},"stroke":{"type":"string"},"fill":{"type":"string"},"width":{"type":"number"},"resketchOnHover":{"type":"boolean"},"padding":{"type":"number"},"description":{"type":"string","pattern":"\\S"}},"required":[]}},"required":["id","primitive","targets"]},{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9._-]*$"},"primitive":{"const":"sticky","type":"string"},"targets":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"strategy":{"enum":["ref","id","data-attribute","source","stet-attribute","css"],"type":"string"},"file":{"type":"string","pattern":"\\S"},"locator":{"type":"string","pattern":"\\S"},"description":{"type":"string","pattern":"\\S"},"rationale":{"type":"string","pattern":"\\S"}},"required":["strategy","file","locator","description"],"allOf":[{"if":{"properties":{"strategy":{"const":"css"}},"required":["strategy"]},"then":{"properties":{"rationale":{"type":"string","pattern":"\\S"}},"required":["rationale"]}}]},"minItems":1,"maxItems":1},"options":{"type":"object","additionalProperties":false,"properties":{"text":{"type":"string","pattern":"\\S"},"side":{"type":"string","enum":["auto","top","right","bottom","left"]},"seed":{"type":"number"},"roughness":{"type":"number"},"boil":{"type":"number"},"stroke":{"type":"string"},"fill":{"type":"string"},"width":{"type":"number"},"resketchOnHover":{"type":"boolean"},"padding":{"type":"number"},"description":{"type":"string","pattern":"\\S"}},"required":["text"]}},"required":["id","primitive","targets","options"]},{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9._-]*$"},"primitive":{"const":"mark","type":"string"},"targets":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"strategy":{"enum":["ref","id","data-attribute","source","stet-attribute","css"],"type":"string"},"file":{"type":"string","pattern":"\\S"},"locator":{"type":"string","pattern":"\\S"},"description":{"type":"string","pattern":"\\S"},"rationale":{"type":"string","pattern":"\\S"}},"required":["strategy","file","locator","description"],"allOf":[{"if":{"properties":{"strategy":{"const":"css"}},"required":["strategy"]},"then":{"properties":{"rationale":{"type":"string","pattern":"\\S"}},"required":["rationale"]}}]},"minItems":1,"maxItems":1},"options":{"type":"object","additionalProperties":false,"properties":{"seed":{"type":"number"},"roughness":{"type":"number"},"boil":{"type":"number"},"stroke":{"type":"string"},"fill":{"type":"string"},"width":{"type":"number"},"resketchOnHover":{"type":"boolean"},"padding":{"type":"number"},"description":{"type":"string","pattern":"\\S"}},"required":[]},"kind":{"type":"string","enum":["right","wrong"]}},"required":["id","primitive","targets","kind"]}]}}},"required":["version","framework","intent","annotations"]};const pattern0 = new RegExp("\\S", "u");const pattern1 = new RegExp("^[a-zA-Z0-9][a-zA-Z0-9._-]*$", "u");const func2 = Object.prototype.hasOwnProperty;function validate10(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(data && typeof data == "object" && !Array.isArray(data)){if(data.version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "version"},message:"must have required property '"+"version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.framework === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "framework"},message:"must have required property '"+"framework"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.intent === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "intent"},message:"must have required property '"+"intent"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.annotations === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "annotations"},message:"must have required property '"+"annotations"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}for(const key0 in data){if(!((((key0 === "version") || (key0 === "framework")) || (key0 === "intent")) || (key0 === "annotations"))){const err4 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.version !== undefined){let data0 = data.version;if(!(((typeof data0 == "number") && (!(data0 % 1) && !isNaN(data0))) && (isFinite(data0)))){const err5 = {instancePath:instancePath+"/version",schemaPath:"#/properties/version/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(1 !== data0){const err6 = {instancePath:instancePath+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.framework !== undefined){let data1 = data.framework;if(typeof data1 !== "string"){const err7 = {instancePath:instancePath+"/framework",schemaPath:"#/properties/framework/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(!(((((data1 === "vanilla") || (data1 === "react")) || (data1 === "vue")) || (data1 === "svelte")) || (data1 === "angular"))){const err8 = {instancePath:instancePath+"/framework",schemaPath:"#/properties/framework/enum",keyword:"enum",params:{allowedValues: schema11.properties.framework.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.intent !== undefined){let data2 = data.intent;if(typeof data2 === "string"){if(!pattern0.test(data2)){const err9 = {instancePath:instancePath+"/intent",schemaPath:"#/properties/intent/pattern",keyword:"pattern",params:{pattern: "\\S"},message:"must match pattern \""+"\\S"+"\""};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/intent",schemaPath:"#/properties/intent/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.annotations !== undefined){let data3 = data.annotations;if(Array.isArray(data3)){if(data3.length < 1){const err11 = {instancePath:instancePath+"/annotations",schemaPath:"#/properties/annotations/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}const len0 = data3.length;for(let i0=0; i0 1){const err20 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/0/properties/targets/maxItems",keyword:"maxItems",params:{limit: 1},message:"must NOT have more than 1 items"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(data7.length < 1){const err21 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/0/properties/targets/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}const len1 = data7.length;for(let i1=0; i1 1){const err66 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/1/properties/targets/maxItems",keyword:"maxItems",params:{limit: 1},message:"must NOT have more than 1 items"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(data28.length < 1){const err67 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/1/properties/targets/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}const len2 = data28.length;for(let i2=0; i2 1){const err112 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/2/properties/targets/maxItems",keyword:"maxItems",params:{limit: 1},message:"must NOT have more than 1 items"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}if(data49.length < 1){const err113 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/2/properties/targets/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}const len3 = data49.length;for(let i3=0; i3 2){const err158 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/3/properties/targets/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err158];}else {vErrors.push(err158);}errors++;}if(data70.length < 2){const err159 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/3/properties/targets/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err159];}else {vErrors.push(err159);}errors++;}const len4 = data70.length;for(let i4=0; i4 1){const err208 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/4/properties/targets/maxItems",keyword:"maxItems",params:{limit: 1},message:"must NOT have more than 1 items"};if(vErrors === null){vErrors = [err208];}else {vErrors.push(err208);}errors++;}if(data93.length < 1){const err209 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/4/properties/targets/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err209];}else {vErrors.push(err209);}errors++;}const len5 = data93.length;for(let i5=0; i5 1){const err260 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/5/properties/targets/maxItems",keyword:"maxItems",params:{limit: 1},message:"must NOT have more than 1 items"};if(vErrors === null){vErrors = [err260];}else {vErrors.push(err260);}errors++;}if(data116.length < 1){const err261 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/5/properties/targets/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err261];}else {vErrors.push(err261);}errors++;}const len6 = data116.length;for(let i6=0; i6 1){const err20 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/0/properties/targets/maxItems",keyword:"maxItems",params:{limit: 1},message:"must NOT have more than 1 items"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}if(data7.length < 1){const err21 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/0/properties/targets/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}const len1 = data7.length;for(let i1=0; i1 1){const err66 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/1/properties/targets/maxItems",keyword:"maxItems",params:{limit: 1},message:"must NOT have more than 1 items"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(data28.length < 1){const err67 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/1/properties/targets/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}const len2 = data28.length;for(let i2=0; i2 1){const err112 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/2/properties/targets/maxItems",keyword:"maxItems",params:{limit: 1},message:"must NOT have more than 1 items"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}if(data49.length < 1){const err113 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/2/properties/targets/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}const len3 = data49.length;for(let i3=0; i3 2){const err158 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/3/properties/targets/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err158];}else {vErrors.push(err158);}errors++;}if(data70.length < 2){const err159 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/3/properties/targets/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err159];}else {vErrors.push(err159);}errors++;}const len4 = data70.length;for(let i4=0; i4 1){const err210 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/4/properties/targets/maxItems",keyword:"maxItems",params:{limit: 1},message:"must NOT have more than 1 items"};if(vErrors === null){vErrors = [err210];}else {vErrors.push(err210);}errors++;}if(data95.length < 1){const err211 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/4/properties/targets/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err211];}else {vErrors.push(err211);}errors++;}const len5 = data95.length;for(let i5=0; i5 1){const err264 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/5/properties/targets/maxItems",keyword:"maxItems",params:{limit: 1},message:"must NOT have more than 1 items"};if(vErrors === null){vErrors = [err264];}else {vErrors.push(err264);}errors++;}if(data120.length < 1){const err265 = {instancePath:instancePath+"/annotations/" + i0+"/targets",schemaPath:"#/properties/annotations/items/oneOf/5/properties/targets/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err265];}else {vErrors.push(err265);}errors++;}const len6 = data120.length;for(let i6=0; i6` for multiple/conditional
+marks. All five integrations use the same primitive options and lifecycle model.
+
+Copy Plan into `annotation-plan.json`, then run the installed binary:
+
+```sh
+./node_modules/.bin/stet inspect --project . --json
+./node_modules/.bin/stet validate annotation-plan.json --json
+./node_modules/.bin/stet snippet circle --framework react
+```
+
+Adapt the plan's file/ref evidence to your actual source. Validate again, build
+your app, exercise native interactions with marks on/off, and inspect desktop
+and narrow layouts. JSON validation cannot prove the targets or copy are correct.
+
+## A useful agent request
+
+> Use stet-explain-ui to explain the account-security changes you implemented.
+> Annotate the meaningful changed controls, report what you actually verified,
+> and distinguish follow-up work. Keep the UI usable and preserve its layout.
+
+The order is implementation → application verification → concise annotations →
+human review of the working UI. The showcase is a prebuilt demonstration of this
+order; it does not claim a model runs or performs edits as you advance stages.
+Use review for suspected defects and showcase for a product presentation.
+
+A handoff might say:
+
+- Implemented: minimum password length, revoke control, setup entry point.
+- Checked: the exact browser interactions and build command that actually ran.
+- Follow-up: authentication provider integration, server persistence, real 2FA.
+
+The local security fixture deliberately stops before those server features. Its
+ink describes that boundary instead of claiming account security is production-ready.
+
+## Shared vocabulary across delivery
+
+| Workflow | Useful perspectives | Input | What the ink connects |
+| --- | --- | --- | --- |
+| Explain | Developer, Product, documentation | Requirement / question | Consequence to existing warning and action |
+| Review | UX, QE, Engineering | Observed defect / design intent | Finding to the actual failing element/state |
+| Showcase | Agent, Developer, Product | Demo brief | Feature value to working controls |
+| Handoff (explanation) | Agent, Engineering, reviewer | Implemented diff + evidence | Meaningful changes to live UI |
+| Verify (review) | QE, developer | Test/browser observations | Acceptance evidence to its relevant control |
+
+The viewpoint examples use distinct interfaces: workspace deletion for explanation,
+billing totals for Product, empty-search recovery for UX, account security for
+Engineering, and CSV import checks for QE. The form-review example compares a
+reproducible focus defect with a working fix annotated in green. Developer examples
+use a deployment form and an activity inbox. Stet visualizes findings; the
+application and its tests own behavior and verification.
+
+Roles and statuses live in demo metadata. No semantic project-management fields
+are added to `circle()` or the plan schema. No role-specific skills are needed.
+The four existing skill names and routing descriptions remain unchanged. A small
+handoff reference adds delivery-specific judgment to explanation; new model
+routing/application trials would be needed before claiming measured improvement.
diff --git a/examples/README.md b/examples/README.md
index 845b127..8bfcb74 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,5 +1,10 @@
# Examples
+Start with the [unified live use cases](https://stetkit.com/use-cases) or
+[run the website locally](../website/README.md). These framework demos explain
+integration and exercise API coverage; the showcase explains workflows.
+
+
Run commands from the repository root. First install dependencies with `npm ci`.
The framework demos label their opt-in boiling underlines and arrows; all motion
respects the system’s reduced-motion preference. Stop any server with Ctrl+C.
diff --git a/src/primitives.ts b/src/primitives.ts
index 627ce34..c1cfed3 100644
--- a/src/primitives.ts
+++ b/src/primitives.ts
@@ -16,12 +16,18 @@ export type { StetHandle, StetOptions } from "./mount.js";
export interface StickyOptions extends StetOptions {
text: string;
side?: "auto" | "top" | "right" | "bottom" | "left";
+ /** Pixel nudge after side selection, before viewport clamping. */
+ offsetX?: number;
+ offsetY?: number;
}
export interface ArrowOptions extends StetOptions {
label?: string;
/** Signed bend relative to arrow length. Zero makes a straight arrow. */
curvature?: number;
+ /** Pixel nudge after automatic label placement, before viewport clamping. */
+ labelOffsetX?: number;
+ labelOffsetY?: number;
}
export type MarkKind = "right" | "wrong";
@@ -210,6 +216,8 @@ export function arrow(from: Element, to: Element, options: ArrowOptions = {}): S
x = Math.min(fromRect.left, toRect.left) - halfWidth - 8;
} else y = Math.min(fromRect.top, toRect.top) - halfHeight - 8;
}
+ x += options.labelOffsetX ?? 0;
+ y += options.labelOffsetY ?? 0;
x = Math.max(halfWidth + 8, Math.min(x, innerWidth - halfWidth - 8));
y = Math.max(halfHeight + 8, Math.min(y, innerHeight - halfHeight - 8));
label.style.left = `${x - left}px`;
@@ -275,6 +283,8 @@ export function sticky(element: Element, options: StickyOptions): StetHandle {
: side === "bottom"
? rect.bottom + gap
: rect.top + (rect.height - height) / 2;
+ left += options.offsetX ?? 0;
+ top += options.offsetY ?? 0;
left = Math.max(12, Math.min(left, innerWidth - width - 12));
top = Math.max(12, Math.min(top, innerHeight - height - 12));
place(root, svg, left, top, width, height);
diff --git a/src/react.ts b/src/react.ts
index d50c2db..2aa5ee6 100644
--- a/src/react.ts
+++ b/src/react.ts
@@ -75,12 +75,12 @@ export function Highlight(props: TargetProps): null {
}
export function Sticky(props: TargetProps): null {
- const { target, text, side, ...shared } = props;
- const options = { ...optionsOf(shared), text, side };
+ const { target, text, side, offsetX, offsetY, ...shared } = props;
+ const options = { ...optionsOf(shared), text, side, offsetX, offsetY };
useTarget(target, (element) => sticky(element, options), [
...dependenciesOf(optionsOf(shared)),
text,
- side,
+ side, offsetX, offsetY,
]);
return null;
}
@@ -97,14 +97,16 @@ export function Arrow({
to,
label,
curvature,
+ labelOffsetX,
+ labelOffsetY,
...shared
}: ArrowOptions & {
from: RefObject;
to: RefObject;
}): null {
- const options = { ...optionsOf(shared), label, curvature };
+ const options = { ...optionsOf(shared), label, curvature, labelOffsetX, labelOffsetY };
useTargets([from, to], ([start, end]) => arrow(start, end, options), [
- ...dependenciesOf(optionsOf(shared)), label, curvature,
+ ...dependenciesOf(optionsOf(shared)), label, curvature, labelOffsetX, labelOffsetY,
]);
return null;
}
diff --git a/tests/agent/showcase.test.ts b/tests/agent/showcase.test.ts
new file mode 100644
index 0000000..626ed73
--- /dev/null
+++ b/tests/agent/showcase.test.ts
@@ -0,0 +1,27 @@
+import { expect, it } from 'vitest';
+import { readFileSync } from 'node:fs';
+import { allPlans } from '../../website/src/showcase/scenarios';
+import { validatePlan } from '../../agent/validate.mjs';
+
+it('validates every showcase plan and resolves its source refs', () => {
+ for (const plan of allPlans) {
+ expect(validatePlan(plan), plan.intent).toMatchObject({ ok: true, errors: [], warnings: [] });
+ for (const annotation of plan.annotations) for (const target of annotation.targets) {
+ const source = readFileSync(target.file, 'utf8');
+ expect(source).toContain(`ref={${target.locator}}`);
+ expect(source).toContain(`target={${target.locator}}`);
+ }
+ }
+});
+
+it('accepts placement recovery only on its intended primitives', () => {
+ const base = allPlans[0];
+ const target = base.annotations[0].targets[0];
+ const check = (annotation: unknown) => validatePlan({ ...base, annotations: [annotation] }).ok;
+ const sticky = { id: 'note', primitive: 'sticky', targets: [target], options: { text: 'Follow up', offsetX: 24, offsetY: -18 } };
+ const arrow = { id: 'connection', primitive: 'arrow', targets: [target, { ...target, locator: 'warning' }], options: { label: 'Changed', labelOffsetX: -12, labelOffsetY: 20 } };
+ expect(check(sticky)).toBe(true); expect(check(arrow)).toBe(true);
+ expect(check({ ...sticky, options: { ...sticky.options, offsetX: Infinity } })).toBe(false);
+ expect(check({ ...arrow, options: { ...arrow.options, labelOffsetY: '20px' } })).toBe(false);
+ expect(check({ ...base.annotations[0], options: { offsetX: 12 } })).toBe(false);
+});
diff --git a/tests/browser/visual.spec.ts b/tests/browser/visual.spec.ts
index 1bebcaa..28d1e3c 100644
--- a/tests/browser/visual.spec.ts
+++ b/tests/browser/visual.spec.ts
@@ -1,5 +1,10 @@
import { expect, test } from "@playwright/test";
+// Use a distinct browser worker for pinned-font screenshots. Firefox can retain
+// fallback-glyph choices from earlier demo pages even across browser contexts.
+// This matches the normal headless launch but separates the worker fixture pool.
+test.use({ launchOptions: { headless: true } });
+
test.beforeEach(async ({ page }) => {
await page.goto("/examples/visual/");
await page.addStyleTag({ url: '/tests/browser/fonts/fixture.css' });
diff --git a/tests/patterns/lifecycle.spec.ts-snapshots/angular-lifecycle-webkit-linux.png b/tests/patterns/lifecycle.spec.ts-snapshots/angular-lifecycle-webkit-linux.png
index 1f800f0..3387266 100644
Binary files a/tests/patterns/lifecycle.spec.ts-snapshots/angular-lifecycle-webkit-linux.png and b/tests/patterns/lifecycle.spec.ts-snapshots/angular-lifecycle-webkit-linux.png differ
diff --git a/tests/patterns/lifecycle.spec.ts-snapshots/react-lifecycle-webkit-linux.png b/tests/patterns/lifecycle.spec.ts-snapshots/react-lifecycle-webkit-linux.png
index 1f800f0..3387266 100644
Binary files a/tests/patterns/lifecycle.spec.ts-snapshots/react-lifecycle-webkit-linux.png and b/tests/patterns/lifecycle.spec.ts-snapshots/react-lifecycle-webkit-linux.png differ
diff --git a/tests/patterns/lifecycle.spec.ts-snapshots/svelte-lifecycle-webkit-linux.png b/tests/patterns/lifecycle.spec.ts-snapshots/svelte-lifecycle-webkit-linux.png
index 1f800f0..3387266 100644
Binary files a/tests/patterns/lifecycle.spec.ts-snapshots/svelte-lifecycle-webkit-linux.png and b/tests/patterns/lifecycle.spec.ts-snapshots/svelte-lifecycle-webkit-linux.png differ
diff --git a/tests/patterns/lifecycle.spec.ts-snapshots/vanilla-lifecycle-webkit-linux.png b/tests/patterns/lifecycle.spec.ts-snapshots/vanilla-lifecycle-webkit-linux.png
index 1f800f0..3387266 100644
Binary files a/tests/patterns/lifecycle.spec.ts-snapshots/vanilla-lifecycle-webkit-linux.png and b/tests/patterns/lifecycle.spec.ts-snapshots/vanilla-lifecycle-webkit-linux.png differ
diff --git a/tests/patterns/lifecycle.spec.ts-snapshots/vue-lifecycle-webkit-linux.png b/tests/patterns/lifecycle.spec.ts-snapshots/vue-lifecycle-webkit-linux.png
index 1f800f0..3387266 100644
Binary files a/tests/patterns/lifecycle.spec.ts-snapshots/vue-lifecycle-webkit-linux.png and b/tests/patterns/lifecycle.spec.ts-snapshots/vue-lifecycle-webkit-linux.png differ
diff --git a/tests/stet.test.ts b/tests/stet.test.ts
index 98370d1..d2c05a5 100644
--- a/tests/stet.test.ts
+++ b/tests/stet.test.ts
@@ -264,3 +264,32 @@ it("agent capability defaults reproduce omitted runtime defaults", () => {
explicit.destroy();
}
});
+
+it('nudges notes after side selection and clamps them to the viewport', () => {
+ const node = element(300, 300, 100, 30);
+ let handle = sticky(node, { text: 'Evidence', side: 'right', seed: 2 });
+ const read = () => { const style = document.querySelector('.stet-overlay--sticky')!.style; return [parseFloat(style.left), parseFloat(style.top)]; };
+ const baseline = read(); handle.destroy();
+ handle = sticky(node, { text: 'Evidence', side: 'right', seed: 2, offsetX: 25, offsetY: -20 });
+ expect(read()).toEqual([baseline[0] + 25, baseline[1] - 20]);
+ handle.refresh(); expect(read()).toEqual([baseline[0] + 25, baseline[1] - 20]);
+ handle.destroy();
+ handle = sticky(node, { text: 'Evidence', offsetX: -10000, offsetY: -10000 });
+ expect(read()).toEqual([12, 12]); handle.destroy();
+});
+
+it('moves an arrow label independently of its path and retains viewport clamping', () => {
+ const from = element(300, 300), to = element(600, 400);
+ const read = () => { const style = document.querySelector('.stet-label')!.style; return [parseFloat(style.left), parseFloat(style.top)]; };
+ let handle = arrow(from, to, { label: 'Review', seed: 2 });
+ const baseline = read(), path = paths(); handle.destroy();
+ handle = arrow(from, to, { label: 'Review', seed: 2, labelOffsetX: -25, labelOffsetY: 20 });
+ expect(read()).toEqual([baseline[0] - 25, baseline[1] + 20]); expect(paths()).toEqual(path);
+ handle.refresh(); expect(read()).toEqual([baseline[0] - 25, baseline[1] + 20]); handle.destroy();
+ handle = arrow(from, to, { label: 'Review', labelOffsetX: -10000, labelOffsetY: -10000 });
+ const label = document.querySelector('.stet-label')!;
+ const root = label.parentElement!;
+ expect(parseFloat(root.style.left) + parseFloat(label.style.left)).toBe(8);
+ expect(parseFloat(root.style.top) + parseFloat(label.style.top)).toBe(8);
+ handle.destroy();
+});
diff --git a/website/README.md b/website/README.md
index 2765605..899abda 100644
--- a/website/README.md
+++ b/website/README.md
@@ -1,28 +1,44 @@
# stetkit.com
The Stet product homepage, built with Vite, React, TypeScript, and TanStack Router.
-The demonstrations use the published `@funsaized/stet@0.1.0` package, including its
-React adapters. The homepage and docs introduce both agent and developer workflows,
-with project-skill setup alongside direct framework examples. This is a standalone app: it does not change the library build or
-require files outside `website/`.
+The demonstrations use **this checkout's actual runtime** through Vite aliases and
+TypeScript paths, with React deduplication. Canonical capability JSON and framework
+templates come directly from `../agent/`; the browser does not import CLI or
+validation code. This app now requires the repository checkout, rather than an
+isolated copy of `website/`. Root and website dependencies must both be installed.
+The published dependency remains pinned to 0.1.0 for release identity; aliases
+intentionally exercise upcoming runtime changes before they ship.
## Development
Use Node.js 22.12+ and npm.
```sh
-cd website
npm ci
-npm run dev
+npm --prefix website ci
+npm --prefix website run dev
```
-The homepage is `/`, the interactive pencil case is `/playground`, and the
-getting-started guide with framework examples is `/docs`. Fonts are bundled with
+Start at `/use-cases`. Direct links are:
+
+- `/use-cases/workspace-deletion`: Explain, Product, UX, Engineering and QE views.
+- `/use-cases/security-handoff`: three implemented local security controls.
+- `/use-cases/form-review`: compare buggy and fixed focus handling, with the fix annotated in green.
+- `/use-cases/feature-showcase`: feature composition over a release form.
+- `/use-cases/guided-tutorial`: a dedicated deployment form with branch and environment choices.
+- `/use-cases/live-documentation`: a working activity inbox with filters, expandable details and read state.
+- `/agent-workflow`: deterministic walkthrough of the shipped authoring pipeline.
+- `/playground`: actual primitive options, nudges, seed, reset and copy.
+- `/docs`: integration instructions and canonical framework examples.
+
+The homepage is `/`. Fonts are bundled with
the app. There are no analytics, environment variables, external font requests,
or backend services. The launch form is an interactive local demonstration.
## Quality checks
+Run these commands inside `website/` after installing both sets of dependencies.
+
```sh
npm run lint # Oxlint: correctness, React hooks, and accessibility rules
npm run lint:fix # Apply safe lint fixes
@@ -45,18 +61,23 @@ they are not a complete accessibility certification.
Import `funsaized/stet` into Vercel and configure:
-| Setting | Value |
-| ----------------- | ----------------------------- |
-| Root Directory | `website` |
-| Framework Preset | Vite |
-| Install Command | `npm ci` |
-| Build Command | `npm run build` |
-| Output Directory | `dist` |
-| Node.js Version | 22.x or a newer supported LTS |
-| Production Branch | `master` |
+| Setting | Value |
+| ----------------- | -------------------------------------------------- |
+| Root Directory | `website` |
+| Framework Preset | Vite |
+| Install Command | `npm --prefix .. ci --ignore-scripts && npm ci` |
+| Build Command | `npm --prefix .. run agent:check && npm run build` |
+| Output Directory | `dist` |
+| Node.js Version | 22.x or a newer supported LTS |
+| Production Branch | `master` |
+
+Enable **Include source files outside of the Root Directory in the Build Step**
+in Vercel's project settings, as described in the
+[Vercel monorepo documentation](https://vercel.com/docs/monorepos/monorepo-faq).
+This is needed for the checkout runtime and canonical agent assets.
`website/vercel.json` configures SPA route rewrites and immutable asset caching.
-Direct `/docs` and `/playground` requests load the application, while asset requests retain normal
+Direct `/docs`, `/playground`, `/agent-workflow` and `/use-cases/` requests load the application, while asset requests retain normal
404 behavior. No environment variables are needed.
Add `stetkit.com` in the Vercel project's domain settings, then apply the DNS
@@ -101,3 +122,35 @@ The record player is an explicitly imaginary interaction and plays no audio.
**Pause ink** stops boil and future entrance motion; reduced-motion preferences
apply automatically. **To the bottom** is a normal anchor to the collection's end.
There is no loading trigger, endless mode, or add-more control.
+
+## Showcase contracts and verification
+
+`src/showcase/scenarios.ts` owns demo-only intent, perspective and plan metadata.
+Fixtures mount explicit React adapters with shared option objects. Plans are never
+interpreted at runtime. The source panel imports those exact component files as
+text. The Frameworks panel reads generated canonical templates, not another API
+catalog. `tests/agent/showcase.test.ts` validates every bundled plan and its source
+refs; existing template checks compile examples for all five frameworks.
+
+`tests/showcase.spec.ts` covers annotation toggles, live controls, unchanged fixture
+size, distinct viewpoint surfaces and tutorial state preservation, conditional findings,
+canonical code inspection, mobile overflow, automated accessibility and fixed-seed
+viewport screenshots. Screenshots include body overlays. Run without
+`--update-snapshots` to check reviewed baselines. After an intentional visual change,
+inspect desktop/mobile images before accepting replacements.
+
+CI installs both dependency sets, checks generated drift, validates plans, compiles
+canonical templates, builds the static site and runs browser checks. It uploads
+`website/dist` as an ordinary static artifact. Existing Vercel rewrites support
+deep links without a backend. No hosting project or production deployment is
+created by these configuration files.
+
+The playground's pixel nudges are unreleased checkout features; published 0.1.0
+users should query their installed capabilities and omit these options. Use-case
+plans use existing 0.1.0 primitives/options.
+
+Website screenshot baselines use the Ubuntu 24.04 CI runner and the lockfile-pinned
+Playwright browser. Other Linux distributions can render text differently even
+with bundled fonts. When updating baselines, review the CI actual/diff images in
+`website-test-results` and verify the updated images in CI; do not loosen the pixel
+threshold to accommodate an unreviewed difference.
diff --git a/website/public/sitemap.xml b/website/public/sitemap.xml
index bb055f7..ce39eaf 100644
--- a/website/public/sitemap.xml
+++ b/website/public/sitemap.xml
@@ -3,4 +3,12 @@
https://stetkit.com/
https://stetkit.com/docs
https://stetkit.com/playground
+ https://stetkit.com/use-cases
+ https://stetkit.com/agent-workflow
+ https://stetkit.com/use-cases/workspace-deletion
+ https://stetkit.com/use-cases/security-handoff
+ https://stetkit.com/use-cases/form-review
+ https://stetkit.com/use-cases/feature-showcase
+ https://stetkit.com/use-cases/guided-tutorial
+ https://stetkit.com/use-cases/live-documentation
diff --git a/website/src/App.tsx b/website/src/App.tsx
index a8ebf03..c302e20 100644
--- a/website/src/App.tsx
+++ b/website/src/App.tsx
@@ -10,6 +10,8 @@ import { Icon } from './components/Icon';
import { Home } from './pages/Home';
import { Docs } from './pages/Docs';
import { PlaygroundPage } from './pages/PlaygroundPage';
+import { UseCases } from './pages/UseCases';
+import { AgentWorkflow } from './pages/AgentWorkflow';
import { usePageMeta } from './usePageMeta';
function NotFound() {
usePageMeta('Page not found — stet', '/404');
@@ -35,8 +37,30 @@ const playgroundRoute = createRoute({
path: '/playground',
component: PlaygroundPage,
});
+const casesRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/use-cases',
+ component: UseCases,
+});
+const caseRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/use-cases/$scenario',
+ component: UseCases,
+});
+const workflowRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/agent-workflow',
+ component: AgentWorkflow,
+});
const router = createRouter({
- routeTree: rootRoute.addChildren([indexRoute, docsRoute, playgroundRoute]),
+ routeTree: rootRoute.addChildren([
+ indexRoute,
+ docsRoute,
+ playgroundRoute,
+ casesRoute,
+ caseRoute,
+ workflowRoute,
+ ]),
scrollRestoration: true,
});
declare module '@tanstack/react-router' {
diff --git a/website/src/components/Code.tsx b/website/src/components/Code.tsx
index a97005b..6c3b113 100644
--- a/website/src/components/Code.tsx
+++ b/website/src/components/Code.tsx
@@ -1,6 +1,7 @@
+/* oxlint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/prefer-tag-over-role -- Preformatted code is a keyboard-scrollable region; preserve whitespace semantics and keyboard access. */
export function Code({ text }: { text: string }) {
return (
-
+
{text
.split(
diff --git a/website/src/components/FrameworkCode.tsx b/website/src/components/FrameworkCode.tsx
index 9a72ba9..b403fd8 100644
--- a/website/src/components/FrameworkCode.tsx
+++ b/website/src/components/FrameworkCode.tsx
@@ -2,12 +2,13 @@ import { useState } from 'react';
import { Code } from './Code';
import { CopyButton } from './CopyButton';
import { Icon } from './Icon';
+import { canonicalCode } from '../showcase/canonical';
const snippets = {
- React: `import { useRef } from "react";\nimport { Circle } from "@funsaized/stet/react";\nimport "@funsaized/stet/style.css";\n\nexport default function LittleWin() {\n const button = useRef(null);\n\n return (\n <>\n \n \n >\n );\n}`,
- JavaScript: `import { circle } from "@funsaized/stet";\nimport "@funsaized/stet/style.css";\n\nconst button = document.querySelector("#ship");\nconst sketch = circle(button, {\n stroke: "#c84935",\n});\n\n// A fresh sketch, whenever you like.\nsketch.resketch();\n\n// Clean up when the element is removed.\nsketch.destroy();`,
- Vue: `\n\n\n \n`,
- Svelte: `\n\n`,
- Angular: `import { Component } from "@angular/core";\nimport { StetCircleDirective } from "@funsaized/stet/angular";\nimport "@funsaized/stet/style.css";\n\n@Component({\n selector: "app-little-win",\n standalone: true,\n imports: [StetCircleDirective],\n template: \`\n \n \`,\n})\nexport class LittleWin {}`,
+ React: canonicalCode('react'),
+ JavaScript: canonicalCode('vanilla'),
+ Vue: canonicalCode('vue'),
+ Svelte: canonicalCode('svelte'),
+ Angular: canonicalCode('angular'),
};
type Framework = keyof typeof snippets;
export function FrameworkCode() {
@@ -27,11 +28,11 @@ export function FrameworkCode() {
{
{
- React: 'LittleWin.jsx',
- JavaScript: 'little-win.js',
- Vue: 'LittleWin.vue',
- Svelte: 'LittleWin.svelte',
- Angular: 'little-win.component.ts',
+ React: 'AnnotatedAction.tsx',
+ JavaScript: 'annotate.ts',
+ Vue: 'AnnotatedAction.vue',
+ Svelte: 'AnnotatedAction.svelte',
+ Angular: 'annotated-action.component.ts',
}[framework]
}
diff --git a/website/src/components/Layout.tsx b/website/src/components/Layout.tsx
index f7b541e..a99356a 100644
--- a/website/src/components/Layout.tsx
+++ b/website/src/components/Layout.tsx
@@ -13,6 +13,8 @@ export function Layout() {
stet•••