From beb5495c6b36b69e766b32bcff0b1e4feb525665 Mon Sep 17 00:00:00 2001 From: Sai Nimmagadda Date: Mon, 7 Sep 2026 07:31:46 -0400 Subject: [PATCH 1/6] Build live workflow showcase with distinct role examples --- .github/workflows/website.yml | 16 +- CHANGELOG.md | 14 + README.md | 30 +- agent/annotation-plan.d.ts | 4 + agent/capabilities.json | 18 +- agent/catalog.mjs | 4 +- agent/patterns.mjs | 2 +- agent/schemas/annotation-plan.schema.json | 12 + agent/schemas/capabilities.schema.json | 18 +- agent/skills/stet-explain-ui/SKILL.md | 5 + agent/skills/stet/SKILL.md | 1 + agent/skills/stet/references/handoff.md | 20 + .../skills/stet/references/troubleshooting.md | 6 +- agent/snippets.mjs | 4 +- agent/templates/angular/arrow.ts | 2 +- agent/templates/angular/lifecycle.ts | 2 +- agent/templates/angular/sticky.ts | 2 +- agent/templates/react/arrow.tsx | 2 +- agent/templates/react/lifecycle.tsx | 2 +- agent/templates/react/sticky.tsx | 2 +- agent/templates/svelte/arrow.svelte | 2 +- agent/templates/svelte/lifecycle.svelte | 2 +- agent/templates/svelte/sticky.svelte | 2 +- agent/templates/vanilla/arrow.ts | 2 +- agent/templates/vanilla/lifecycle.ts | 2 +- agent/templates/vanilla/sticky.ts | 2 +- agent/templates/vue/arrow.vue | 2 +- agent/templates/vue/lifecycle.vue | 2 +- agent/templates/vue/sticky.vue | 2 +- agent/validate.generated.mjs | 2 +- docs/agent-usage.md | 7 + docs/reference.md | 17 + docs/showcase-verification.md | 37 ++ docs/visual-handoff.md | 91 +++ examples/README.md | 5 + src/primitives.ts | 10 + src/react.ts | 12 +- tests/agent/showcase.test.ts | 27 + tests/browser/visual.spec.ts | 5 + tests/stet.test.ts | 29 + website/README.md | 83 ++- website/public/sitemap.xml | 8 + website/src/App.tsx | 26 +- website/src/components/Code.tsx | 3 +- website/src/components/FrameworkCode.tsx | 21 +- website/src/components/Layout.tsx | 2 + website/src/components/Playground.tsx | 187 +++++- website/src/constants.ts | 5 +- website/src/pages/AgentWorkflow.tsx | 142 +++++ website/src/pages/Home.tsx | 31 +- website/src/pages/UseCases.tsx | 204 +++++++ website/src/showcase/Activity.tsx | 52 ++ website/src/showcase/Artifacts.tsx | 131 +++++ website/src/showcase/Deployment.tsx | 68 +++ website/src/showcase/Release.tsx | 111 ++++ website/src/showcase/Security.tsx | 84 +++ website/src/showcase/TeamExamples.tsx | 113 ++++ website/src/showcase/Workspace.tsx | 116 ++++ website/src/showcase/canonical.ts | 13 + website/src/showcase/options.ts | 84 +++ website/src/showcase/scenarios.ts | 335 +++++++++++ website/src/showcase/showcase.css | 539 ++++++++++++++++++ website/tests/homepage.spec.ts | 4 +- website/tests/showcase.spec.ts | 271 +++++++++ .../form-review-desktop-linux.png | Bin 0 -> 649615 bytes .../form-review-mobile-linux.png | Bin 0 -> 108717 bytes .../security-handoff-desktop-linux.png | Bin 0 -> 540369 bytes .../security-handoff-mobile-linux.png | Bin 0 -> 58287 bytes .../workspace-desktop-linux.png | Bin 0 -> 684493 bytes .../workspace-mobile-linux.png | Bin 0 -> 74136 bytes website/tsconfig.json | 6 +- website/vercel.json | 29 +- website/vite.config.ts | 22 +- 73 files changed, 3020 insertions(+), 96 deletions(-) create mode 100644 agent/skills/stet/references/handoff.md create mode 100644 docs/showcase-verification.md create mode 100644 docs/visual-handoff.md create mode 100644 tests/agent/showcase.test.ts create mode 100644 website/src/pages/AgentWorkflow.tsx create mode 100644 website/src/pages/UseCases.tsx create mode 100644 website/src/showcase/Activity.tsx create mode 100644 website/src/showcase/Artifacts.tsx create mode 100644 website/src/showcase/Deployment.tsx create mode 100644 website/src/showcase/Release.tsx create mode 100644 website/src/showcase/Security.tsx create mode 100644 website/src/showcase/TeamExamples.tsx create mode 100644 website/src/showcase/Workspace.tsx create mode 100644 website/src/showcase/canonical.ts create mode 100644 website/src/showcase/options.ts create mode 100644 website/src/showcase/scenarios.ts create mode 100644 website/src/showcase/showcase.css create mode 100644 website/tests/showcase.spec.ts create mode 100644 website/tests/showcase.spec.ts-snapshots/form-review-desktop-linux.png create mode 100644 website/tests/showcase.spec.ts-snapshots/form-review-mobile-linux.png create mode 100644 website/tests/showcase.spec.ts-snapshots/security-handoff-desktop-linux.png create mode 100644 website/tests/showcase.spec.ts-snapshots/security-handoff-mobile-linux.png create mode 100644 website/tests/showcase.spec.ts-snapshots/workspace-desktop-linux.png create mode 100644 website/tests/showcase.spec.ts-snapshots/workspace-mobile-linux.png diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 2317586..fd79051 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: @@ -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` }; 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/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..1516c91 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,29 @@ 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. 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`,
-  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•••