diff --git a/apps/docs/src/App.tsx b/apps/docs/src/App.tsx index 96e8d5c..c28a490 100644 --- a/apps/docs/src/App.tsx +++ b/apps/docs/src/App.tsx @@ -1,7 +1,6 @@ import { ThemeProvider } from "ghost-ui"; import { Navigate, Route, Routes, useParams } from "react-router"; import DocsIndex from "@/app/docs/page"; -import WorkflowPage from "@/app/docs/workflow/page"; import HomePage from "@/app/page"; import GhostDriftLanding from "@/app/tools/drift/page"; import GhostExpressionLanding from "@/app/tools/expression/page"; @@ -41,7 +40,6 @@ export function App() { } /> } /> } /> - } /> } /> } /> @@ -73,7 +71,11 @@ export function App() { /> } + element={} + /> + } /> {/* Redirects from legacy root /foundations and /components URLs */} diff --git a/apps/docs/src/app/docs/page.tsx b/apps/docs/src/app/docs/page.tsx index 7c68ae2..975d415 100644 --- a/apps/docs/src/app/docs/page.tsx +++ b/apps/docs/src/app/docs/page.tsx @@ -28,8 +28,8 @@ const sections: { icon: , }, { - name: "Drift Workflow", - href: "/tools/drift/workflow", + name: "The Workflow", + href: "/tools", description: "The five moves: profile, compare, review, evolve, and zoom out to the org expression — with examples for each.", icon: , diff --git a/apps/docs/src/app/tools/page.tsx b/apps/docs/src/app/tools/page.tsx index b97610a..4992d45 100644 --- a/apps/docs/src/app/tools/page.tsx +++ b/apps/docs/src/app/tools/page.tsx @@ -5,91 +5,92 @@ import { Compass, FileText, Network, Orbit, Palette } from "lucide-react"; import type { ReactNode } from "react"; import { Link } from "react-router"; import { AnimatedPageHeader } from "@/components/docs/animated-page-header"; +import { WorkflowContent } from "@/components/docs/workflow-content"; import { SectionWrapper } from "@/components/docs/wrappers"; const tools: { name: string; href: string; - description: string; + blurb: string; icon: ReactNode; }[] = [ { name: "ghost-map", href: "/tools/map", - description: - "Topology. Generates map.md — the navigation card every other Ghost tool reads to learn where the design implementation lives.", - icon: , + blurb: "Topology — map.md", + icon: , }, { name: "ghost-expression", href: "/tools/expression", - description: - "Authoring. Owns expression.md — the canonical design-language artifact. Lint, describe, diff, and emit grounding bundles for any generator.", - icon: , + blurb: "Authoring — expression.md", + icon: , }, { name: "ghost-drift", href: "/tools/drift", - description: - "Detection. Compares expressions, tracks stance (ack / track / diverge), and ships the review / verify / remediate recipes.", - icon: , + blurb: "Detection — compare, ack, track", + icon: , }, { name: "ghost-fleet", href: "/tools/fleet", - description: - "Elevation. Reads many (map.md, expression.md) members and emits fleet.md — pairwise distances, group-by tables, tracks-graph.", - icon: , + blurb: "Elevation — fleet.md", + icon: , }, { name: "ghost-ui", href: "/tools/ui", - description: - "Reference UI library. 97 shadcn-distributed components + an MCP server. The system Ghost dogfoods its expression against.", - icon: , + blurb: "Reference UI library", + icon: , }, ]; -export default function ToolsIndex() { - const ref = useStaggerReveal(".tool-card", { - stagger: 0.06, - y: 30, - duration: 0.7, +function ToolStrip() { + const ref = useStaggerReveal(".tool-chip", { + stagger: 0.05, + y: 16, + duration: 0.5, }); + return ( +
+ {tools.map((tool) => ( + +
+ {tool.icon} + + {tool.name} + +
+

+ {tool.blurb} +

+ + ))} +
+ ); +} + +export default function ToolsIndex() { return ( -
- {tools.map((tool) => ( - -
- {tool.icon} -
- - - {tool.name} - - - -

- {tool.description} -

- - ))} -
+ + +
); } diff --git a/apps/docs/src/components/docs/dock.tsx b/apps/docs/src/components/docs/dock.tsx index 689cab8..cc2ecd6 100644 --- a/apps/docs/src/components/docs/dock.tsx +++ b/apps/docs/src/components/docs/dock.tsx @@ -245,12 +245,12 @@ export function Dock() { { - navigate("/tools/drift/workflow"); + navigate("/tools"); setSearchOpen(false); }} > - Drift Workflow + Workflow diff --git a/apps/docs/src/app/docs/workflow/page.tsx b/apps/docs/src/components/docs/workflow-content.tsx similarity index 76% rename from apps/docs/src/app/docs/workflow/page.tsx rename to apps/docs/src/components/docs/workflow-content.tsx index 4da33d5..44fc236 100644 --- a/apps/docs/src/app/docs/workflow/page.tsx +++ b/apps/docs/src/components/docs/workflow-content.tsx @@ -4,8 +4,6 @@ import { cn } from "ghost-ui"; import gsap from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import { useEffect, useRef, useState } from "react"; -import { AnimatedPageHeader } from "@/components/docs/animated-page-header"; -import { SectionWrapper } from "@/components/docs/wrappers"; gsap.registerPlugin(ScrollTrigger); @@ -93,7 +91,89 @@ function StepLead({ children }: { children: React.ReactNode }) { ); } -/* ─────────────────────── 1. Profile — expression.md excerpt ─────── */ +/* ─────────────────────── 1. Map — map.md excerpt ────────────────── */ + +function MapExcerpt() { + return ( +
+
+ map.md + + excerpt + +
+
+        
+          {"---\n"}
+          schema
+          : ghost.map/v1{"\n"}
+          id
+          : ghost{"\n"}
+          repo
+          : block/ghost{"\n"}
+          composition
+          :{"\n"}
+          {"  frameworks:\n"}
+          {"    - "}
+          
+            {'{ name: react, version: "19" }'}
+            {"\n"}
+          
+          {"  styling:\n"}
+          {"    - "}
+          tailwind{"\n"}
+          {"    - "}
+          css-vars{"\n"}
+          registry
+          :{"\n"}
+          {"  path: "}
+          
+            packages/ghost-ui/registry.json{"\n"}
+          
+          {"  components: "}
+          97{"\n"}
+          design_system
+          :{"\n"}
+          {"  paths:\n"}
+          {"    - "}
+          
+            packages/ghost-ui/src/components{"\n"}
+          
+          {"    - "}
+          
+            packages/ghost-ui/src/styles{"\n"}
+          
+          {"  entry_files:\n"}
+          {"    - "}
+          
+            packages/ghost-ui/src/styles/tokens.css{"\n"}
+          
+          {"    - "}
+          
+            packages/ghost-ui/expression.md{"\n"}
+          
+          ui_surface
+          :{"\n"}
+          {"  include:\n"}
+          {"    - "}
+          
+            packages/ghost-ui/src/components/**{"\n"}
+          
+          {"  exclude:\n"}
+          {"    - "}
+          "**/dist/**"{"\n"}
+          {"    - "}
+          
+            "**/node_modules/**"{"\n"}
+          
+          {"---\n"}
+        
+      
+
+ ); +} + +/* ─────────────────────── 2. Profile — expression.md excerpt ─────── */ function ExpressionExcerpt() { return ( @@ -611,8 +691,18 @@ function StanceBubble({ ); } +type Stance = "aligned" | "drifting" | "accepted" | "diverging"; +type ActiveStance = "aligned" | "accepted" | "diverging"; + +const STANCE_FILL: Record = { + aligned: "fill-foreground/40", + drifting: "fill-foreground/70", + accepted: "fill-foreground", + diverging: "fill-foreground", +}; + function HistoryRibbon() { - const points = [ + const points: { t: string; v: number; stance: Stance }[] = [ { t: "Jan", v: 0.08, stance: "aligned" }, { t: "Feb", v: 0.12, stance: "aligned" }, { t: "Mar", v: 0.18, stance: "drifting" }, @@ -621,6 +711,9 @@ function HistoryRibbon() { { t: "Jun", v: 0.31, stance: "diverging" }, { t: "Jul", v: 0.36, stance: "diverging" }, ]; + const [playhead, setPlayhead] = useState(null); + const [hypothesis, setHypothesis] = useState("aligned"); + const max = 0.5; const w = 420; const h = 80; @@ -633,6 +726,34 @@ function HistoryRibbon() { }) .join(" "); + const effectiveStance = (i: number): Stance => + playhead !== null && i >= playhead ? hypothesis : points[i].stance; + + const setPlayheadFromPointer = (e: React.PointerEvent) => { + const svg = e.currentTarget; + const pt = svg.createSVGPoint(); + pt.x = e.clientX; + pt.y = e.clientY; + const ctm = svg.getScreenCTM(); + if (!ctm) return; + const local = pt.matrixTransform(ctm.inverse()); + const ratio = Math.max(0, Math.min(1, local.x / w)); + const idx = Math.round(ratio * (points.length - 1)); + setPlayhead(idx); + }; + + const handlePointerDown = (e: React.PointerEvent) => { + e.currentTarget.setPointerCapture(e.pointerId); + setPlayheadFromPointer(e); + }; + const handlePointerMove = (e: React.PointerEvent) => { + if (!(e.buttons & 1)) return; + setPlayheadFromPointer(e); + }; + + const playheadX = playhead !== null ? playhead * stepX : null; + const monthsAffected = playhead !== null ? points.length - playhead : 0; + return (
@@ -643,9 +764,44 @@ function HistoryRibbon() { .ghost/history.jsonl
+ +
+ if stance from playhead = + {(["aligned", "accepted", "diverging"] as const).map((s) => ( + + ))} + {playhead !== null && ( + + )} +
+ + {playheadX !== null && ( + + + + + )} {points.map((p, i) => { const x = i * stepX; const y = h - (p.v / max) * h; + const stance = effectiveStance(i); return ( {p.t} @@ -699,11 +879,32 @@ function HistoryRibbon() { })}

- Crossing the threshold in March could have been noise. The{" "} - accepted stance in April said “yes, on purpose.” - The diverging stance in June said “we own this - now.” The curve hasn't changed shape — but the meaning of - every point after it has. + {playhead === null ? ( + <> + Crossing the threshold in March could have been noise. The{" "} + accepted stance in April said “yes, on + purpose.” The diverging stance in June said “we + own this now.” The curve hasn't changed shape — but the + meaning of every point after it has.{" "} + + Drag the chart to scrub a decision moment. + + + ) : ( + <> + From{" "} + {points[playhead].t}{" "} + forward — {monthsAffected}{" "} + {monthsAffected === 1 ? "month" : "months"} re-classified as{" "} + {hypothesis}.{" "} + {hypothesis === "aligned" && + "A bug to fix on every point past the playhead."} + {hypothesis === "accepted" && + "Reviewed and OK — known, intentional drift."} + {hypothesis === "diverging" && + "This dimension is ours now; the parent no longer measures it."} + + )}

); @@ -825,29 +1026,78 @@ function OrgExpression() { ); } -/* ═══════════════════════════ Main page ════════════════════════════════ */ +/* ═══════════════════════════ Public component ═════════════════════════ */ -export default function WorkflowPage() { +export function WorkflowContent() { return ( - - + <> + {/* ── Step 1: Map ─────────────────────────────────────────────── */} + + Step 01 · Map + Map the project + + Before profiling can happen, the host agent needs to know{" "} + where the design system actually lives in your repo.{" "} + ghost-map walks the project — manifests, language + histogram, component registry, styling system — and writes a{" "} + map.md at the repo root: a navigation card every other + Ghost tool reads. + + +
+ {[ + { + layer: "Topology", + name: "Where", + desc: "design_system.paths and entry_files — the folders and files that own the look.", + }, + { + layer: "Composition", + name: "How", + desc: "Frameworks, styling system, build manifest. The shape of the codebase, not the look.", + }, + { + layer: "Surface", + name: "What counts", + desc: "ui_surface globs — what's user-facing UI vs. tooling, tests, dist artifacts.", + }, + ].map((l) => ( +
+
+ {l.layer} +
+
+ {l.name} +
+

+ {l.desc} +

+
+ ))} +
+

+ The map is short on purpose — pointers, not contents. It tells{" "} + ghost-expression which folders to read when profiling, + and tells ghost-fleet which surfaces to count when + aggregating. The success gate is ghost-map lint, which + validates against ghost.map/v1. +

+
- {/* ── Step 1: Profile ─────────────────────────────────────────── */} + {/* ── Step 2: Profile ─────────────────────────────────────────── */} - Step 01 · Profile + Step 02 · Profile Write an expression.md - Open your project in a host agent with the{" "} - ghost-expression skill installed and ask it to{" "} - profile this design language. The recipe walks the agent - through your theme CSS, tailwind config, and component primitives, - resolves variable chains, and writes a single{" "} - expression.md at the repo root — YAML frontmatter for - machines, Markdown body for humans. + With map.md in place, open the project in a host agent + with the ghost-expression skill installed and ask it to{" "} + profile this design language. The recipe follows the map to + your theme CSS, tailwind config, and component primitives, resolves + variable chains, and writes a single expression.md at the + repo root — YAML frontmatter for machines, Markdown body for humans.
@@ -893,9 +1143,9 @@ export default function WorkflowPage() {

- {/* ── Step 2: Compare ────────────────────────────────────────── */} + {/* ── Step 3: Compare ────────────────────────────────────────── */} - Step 02 · Compare + Step 03 · Compare Measure the distance Two expressions in, one answer out: an overall distance, a @@ -941,9 +1191,9 @@ export default function WorkflowPage() {

- {/* ── Step 3: Review ──────────────────────────────────────────── */} + {/* ── Step 4: Review ──────────────────────────────────────────── */} - Step 03 · Review + Step 04 · Review Catch drift in a PR Review is a skill recipe your host agent runs, not a CLI @@ -969,9 +1219,9 @@ export default function WorkflowPage() {

- {/* ── Step 4: Evolve ─────────────────────────────────────────── */} + {/* ── Step 5: Evolve ─────────────────────────────────────────── */} - Step 04 · Evolve + Step 05 · Evolve Turn drift into signal Not every drift is a bug. Sometimes you changed that radius on @@ -1018,9 +1268,9 @@ export default function WorkflowPage() {

- {/* ── Step 5: Org ────────────────────────────────────────────── */} + {/* ── Step 6: Org ────────────────────────────────────────────── */} - Step 05 · Org + Step 06 · Org Zoom out to the org expression Most orgs don't have one design language — they have several @@ -1145,11 +1395,15 @@ export default function WorkflowPage() { Artifacts What Ghost leaves behind - Five moves, four files. Everything Ghost knows is checked into your + Six moves, five files. Everything Ghost knows is checked into your repo; nothing lives in a service you have to log into. -
+
{[ + { + file: "map.md", + desc: "Where the design system lives — folders, registry, surface.", + }, { file: "expression.md", desc: "What the system looks like, in three layers.", @@ -1184,6 +1438,6 @@ export default function WorkflowPage() {
- + ); }