diff --git a/packages/web/DESIGN.md b/packages/web/DESIGN.md index c8f9920..4bb61b6 100644 --- a/packages/web/DESIGN.md +++ b/packages/web/DESIGN.md @@ -27,6 +27,7 @@ LazyCodex feels like a serious command surface for complex codebases: near-black | Brand/mid | `--brand-mid` | `#16a34a` | Green gradient middle | | Brand/outer | `--brand-outer` | `#15803d` | Selection and gradient edge | | Accent/primary | `--accent-primary` | `#4ade80` | CTAs, focus, active docs links | +| Accent/on-primary | `--accent-on-primary` | `#052e16` | Text on accent-primary fills | | Accent/soft | `--accent-primary-soft` | `rgba(74,222,128,0.1)` | Soft green fills | | Accent/border | `--accent-primary-border` | `rgba(74,222,128,0.24)` | Soft green outlines | | Accent/mint | `--accent-mint`, `--accent-glow` | `#86efac` | Highlights, glow text | @@ -41,6 +42,26 @@ LazyCodex feels like a serious command surface for complex codebases: near-black | Status/warning | `--status-warning` | `#f59e0b` | Warnings | | Status/error | `--status-error` | `#ef4444` | Errors | +### Codex window adapter tokens (ulw-demo / team-mode mocks only) + +The interactive Ultrawork demo and the Team Mode thread mock reproduce the light Codex Desktop +surface inside the dark canvas. That light surface is an isolated adapter palette — it never leaks +into ordinary landing/docs UI, and ordinary tokens never restyle the window interior. + +| Role | Token | Value | Usage | +| --- | --- | --- | --- | +| Window/canvas | `--codex-window-bg` | `#ffffff` | Codex window body | +| Window/chrome | `--codex-window-chrome` | `#f6f7f6` | Title bar, sidebar, composer field | +| Window/border | `--codex-window-border` | `rgba(10,12,11,0.12)` | Window ring, pane dividers | +| Window/text | `--codex-window-text` | `#17211b` | Primary transcript text | +| Window/text-soft | `--codex-window-text-soft` | `#5b675f` | Tool rows, metadata, timestamps | +| Window/chip | `--codex-window-chip` | `rgba(10,12,11,0.06)` | Inline code chips, path chips | +| Window/active | `--codex-window-active` | `rgba(34,197,94,0.12)` | Active step, active roster row | +| Window/active-border | `--codex-window-active-border` | `rgba(22,101,52,0.28)` | Active step/proof outlines | +| Window/accent | `--codex-window-accent` | `#166534` | Active-state text on light surface (AA on white) | +| Window/glyph-text | `--codex-window-glyph-text` | `#ffffff` | Letters inside roster glyph squares | +| Window/traffic | `--codex-window-traffic-red/-amber/-green` | `#f87171` / `#fbbf24` / `#34d399` | macOS traffic-light ornaments | + ### Rules - New UI uses `--accent-primary` and `--accent-mint`; `--accent-cyan` and `--accent-teal` remain green aliases only for compatibility. @@ -126,6 +147,39 @@ All spacing resolves to a 4px rhythm. Existing Tailwind values map to the same r - **Variants**: primary filled text button, secondary outlined button. - **States**: hover scale or tonal shift, visible focus ring, no layout-property animation. +### CodexWindow (ulw-demo) + +- **Source**: `components/site/ulw-demo/codex-window.tsx` (client leaf), scene data in `lib/ulw-demo-scenes.ts`. +- **Structure**: light Codex Desktop window (adapter tokens above): title bar with traffic lights and + `ULTRAWORK MODE ENABLED!` badge, transcript pane (command chip → status line → scene headline → + scene body → 8 numbered workflow steps), right rail (Environment card, Subagents roster, + narrative card, `goals.json / ledger.jsonl` card), composer bar, scene tab strip with play/pause. +- **Variants**: 8 scenes (`research → plan → todo → assign → red → green → qa-retry → checkpoint`), + each atomically updating command, status, headline, body, active step, roster lanes, proof chips, + ledger, and JSON card. +- **States**: `data-scene` index; `data-playing` for autoplay; per-step `data-active`; per-lane + `data-live`. Scene tabs expose `role=tab` + `aria-selected`; play/pause exposes `aria-pressed`; + scene status announces via `aria-live="polite"`. +- **Accessibility**: fully keyboard operable; content remains readable with JS disabled (scene 0 + server-rendered); every scene reachable without autoplay. +- **Integrity**: live DOM only — no raster screenshot, ``, or `background-image` may stand in + for window content. +- **Roster glyph colors**: the subagent glyph squares use per-agent identity hues (blue explorer, + amber librarian, violet planner, …) faithful to the Codex Desktop reference. They are scoped to + the window adapter and are identity badges, not brand accents — the green-only brand rule applies + outside the window. + +### TeamModeSection / UlwResearchSection + +- **Source**: `components/site/team-mode-section.tsx`, `components/site/ulw-research-section.tsx`; + copy constants in `lib/site-config.ts`. +- **Structure**: TeamMode shows a leader thread plus member thread cards (light chrome via the same + adapter tokens) with a `Sent by Codex from another thread` note bubble; UlwResearch is a compact + feature band composed from existing surface primitives. +- **Copy rule**: every visible string traces to `plugins/omo/skills/teammode/SKILL.md`, + `plugins/omo/skills/ulw-research/SKILL.md`, or `content/docs/*.md` via the copy ledger — no + invented claims, metrics, customers, or dates. + ### DocsHero - **Source**: `components/design-system/docs-hero.tsx`. @@ -148,6 +202,20 @@ All spacing resolves to a 4px rhythm. Existing Tailwind values map to the same r - Respect `prefers-reduced-motion`; `splash-reveal` disables itself. - Focus states are visible through global `:focus-visible` and component-level rings. - Docs interactions must keep working: mobile menu, sidebar search, Cmd/Ctrl-K focus, hash navigation, scroll-spy, and prev/next cards. +- **Hover is an affordance, not decoration.** Hover feedback may exist only on elements with a real + action (links, buttons, tabs, inputs). A hover effect on a non-actionable element — cards, list + rows, headings, chips, roster rows — is a defect and must be removed. + +### ulw-demo timeline + +- Scene transitions animate `opacity`/`transform` only (150-300ms); step and lane activation use the + same budget. No layout-property animation anywhere in the window. +- Autoplay starts when the demo scrolls into view (IntersectionObserver), advances scenes every + ~7s, pauses on any user interaction with the tabs or the play/pause control, and never traps focus. +- `prefers-reduced-motion: reduce` disables autoplay entirely and makes scene switches instant; + tabs remain fully operable. +- The typing-caret effect on the command chip is CSS-only (`opacity` blink) and disabled under + reduced motion. ## 7. Depth & Surface diff --git a/packages/web/app/globals.css b/packages/web/app/globals.css index 6eb9f03..3d51181 100644 --- a/packages/web/app/globals.css +++ b/packages/web/app/globals.css @@ -1,3 +1,4 @@ @import "tailwindcss"; @import "./styles/design-system.css"; @import "./styles/landing.css"; +@import "./styles/ulw-demo.css"; diff --git a/packages/web/app/page.tsx b/packages/web/app/page.tsx index ba9c8d1..b7ec5cb 100644 --- a/packages/web/app/page.tsx +++ b/packages/web/app/page.tsx @@ -13,7 +13,9 @@ import { Hero } from "../components/site/hero" import { InstallBlock } from "../components/site/install-block" import { SiteFooter } from "../components/site/site-footer" import { SiteHeader } from "../components/site/site-header" -import { UltraworkSection } from "../components/site/ultrawork-section" +import { TeamModeSection } from "../components/site/team-mode-section" +import { UlwDemoSection } from "../components/site/ulw-demo/ulw-demo-section" +import { UlwResearchSection } from "../components/site/ulw-research-section" export default function LandingPage(): JSX.Element { return ( @@ -27,10 +29,12 @@ export default function LandingPage(): JSX.Element { + + + - diff --git a/packages/web/app/styles/design-system.css b/packages/web/app/styles/design-system.css index 7b554c9..0932eaf 100644 --- a/packages/web/app/styles/design-system.css +++ b/packages/web/app/styles/design-system.css @@ -36,6 +36,7 @@ /* Accent — the live-wire green (green-400/300, unambiguously green). */ --accent-primary: #4ade80; + --accent-on-primary: #052e16; --accent-primary-soft: rgba(74, 222, 128, 0.1); --accent-primary-border: rgba(74, 222, 128, 0.24); --accent-cyan: #4ade80; @@ -51,6 +52,22 @@ --status-success: #22c55e; --status-warning: #f59e0b; --status-error: #ef4444; + + /* Codex window adapter — light Codex Desktop surface used ONLY by the + ulw-demo and team-mode mocks (see DESIGN.md § Codex window adapter). */ + --codex-window-bg: #ffffff; + --codex-window-chrome: #f6f7f6; + --codex-window-border: rgba(10, 12, 11, 0.12); + --codex-window-text: #17211b; + --codex-window-text-soft: #5b675f; + --codex-window-chip: rgba(10, 12, 11, 0.06); + --codex-window-active: rgba(34, 197, 94, 0.12); + --codex-window-active-border: rgba(22, 101, 52, 0.28); + --codex-window-accent: #166534; + --codex-window-glyph-text: #ffffff; + --codex-window-traffic-red: #f87171; + --codex-window-traffic-amber: #fbbf24; + --codex-window-traffic-green: #34d399; } html { diff --git a/packages/web/app/styles/ulw-demo.css b/packages/web/app/styles/ulw-demo.css new file mode 100644 index 0000000..fde57ba --- /dev/null +++ b/packages/web/app/styles/ulw-demo.css @@ -0,0 +1,468 @@ +/* Interactive Ultrawork demo — light Codex Desktop window on the dark canvas. + Tokens: the --codex-window-* adapter palette (DESIGN.md § Codex window adapter). + Motion: transform/opacity/color only; autoplay + caret disabled under + prefers-reduced-motion (DESIGN.md § ulw-demo timeline). */ + +@layer components { + .ulw-window { + width: 100%; + max-width: 1060px; + border-radius: 14px; + border: 1px solid var(--codex-window-border); + background: var(--codex-window-bg); + color: var(--codex-window-text); + box-shadow: 0 30px 90px rgba(0, 0, 0, 0.55); + overflow: hidden; + text-align: left; + } + + .ulw-menubar, + .ulw-titlebar, + .ulw-composer { + background: var(--codex-window-chrome); + border-bottom: 1px solid var(--codex-window-border); + } + + .ulw-menubar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + padding: 6px 14px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--codex-window-text-soft); + } + + .ulw-menubar strong { + color: var(--codex-window-text); + } + + .ulw-menubar-left { + display: flex; + gap: 12px; + min-width: 0; + } + + .ulw-mode-flag { + color: var(--codex-window-accent); + font-weight: 600; + letter-spacing: 0.04em; + white-space: nowrap; + } + + .ulw-titlebar { + display: flex; + align-items: center; + gap: 14px; + padding: 8px 14px; + } + + .ulw-traffic { + display: inline-flex; + gap: 6px; + } + + .ulw-traffic span { + width: 10px; + height: 10px; + border-radius: 999px; + background: var(--codex-window-chip); + } + + .ulw-traffic span:nth-child(1) { background: var(--codex-window-traffic-red); } + .ulw-traffic span:nth-child(2) { background: var(--codex-window-traffic-amber); } + .ulw-traffic span:nth-child(3) { background: var(--codex-window-traffic-green); } + + .ulw-window-tabs { + display: flex; + gap: 8px; + font-family: var(--font-mono); + font-size: 11.5px; + color: var(--codex-window-text-soft); + min-width: 0; + overflow: hidden; + } + + .ulw-window-tab { + padding: 4px 10px; + border-radius: 7px; + white-space: nowrap; + } + + .ulw-window-tab[data-current="true"] { + background: var(--codex-window-bg); + border: 1px solid var(--codex-window-border); + color: var(--codex-window-text); + } + + .ulw-content { + display: grid; + grid-template-columns: minmax(0, 1fr) 272px; + } + + .ulw-transcript { + padding: 22px 24px 18px; + display: flex; + flex-direction: column; + gap: 14px; + min-width: 0; + } + + .ulw-command { + display: flex; + align-items: baseline; + gap: 8px; + padding: 8px 12px; + border-radius: 8px; + background: var(--codex-window-chip); + font-family: var(--font-mono); + font-size: 12px; + overflow-wrap: anywhere; + } + + .ulw-command > span { + color: var(--codex-window-accent); + } + + .ulw-caret { + display: inline-block; + width: 7px; + height: 13px; + margin-left: 2px; + background: var(--codex-window-accent); + vertical-align: text-bottom; + animation: ulw-caret-blink 1.1s steps(2, start) infinite; + } + + @keyframes ulw-caret-blink { + to { opacity: 0; } + } + + .ulw-scene-copy { + display: flex; + flex-direction: column; + gap: 6px; + min-height: 112px; + } + + .ulw-scene-status { + font-family: var(--font-mono); + font-size: 11px; + letter-spacing: 0.02em; + color: var(--codex-window-text-soft); + } + + .ulw-scene-copy h3 { + font-size: clamp(20px, 2.4vw, 28px); + font-weight: 650; + letter-spacing: -0.02em; + line-height: 1.15; + text-wrap: balance; + } + + .ulw-scene-copy p { + font-size: 13.5px; + line-height: 1.55; + color: var(--codex-window-text-soft); + max-width: 62ch; + } + + .ulw-scene-swap { + animation: ulw-scene-swap 260ms cubic-bezier(0.16, 1, 0.3, 1); + } + + @keyframes ulw-scene-swap { + from { + opacity: 0; + transform: translate3d(0, 6px, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } + } + + .ulw-steps { + display: flex; + flex-direction: column; + gap: 6px; + } + + .ulw-step { + display: flex; + gap: 10px; + align-items: baseline; + border: 1px solid transparent; + border-radius: 9px; + padding: 7px 10px; + transition: background-color 200ms ease, border-color 200ms ease; + } + + .ulw-step[data-active="true"] { + background: var(--codex-window-active); + border-color: var(--codex-window-active-border); + } + + .ulw-step[data-active="true"] strong { + color: var(--codex-window-text); + } + + .ulw-step-number { + font-family: var(--font-mono); + font-size: 10.5px; + color: var(--codex-window-accent); + padding-top: 2px; + } + + .ulw-step strong { + font-size: 12.5px; + font-weight: 600; + color: var(--codex-window-text-soft); + transition: color 200ms ease; + } + + .ulw-step p { + font-size: 11.5px; + line-height: 1.45; + color: var(--codex-window-text-soft); + } + + .ulw-proofs { + display: flex; + flex-wrap: wrap; + gap: 6px; + } + + .ulw-proof { + font-family: var(--font-mono); + font-size: 10.5px; + padding: 4px 9px; + border-radius: 7px; + border: 1px solid var(--codex-window-border); + color: var(--codex-window-text-soft); + transition: color 200ms ease, background-color 200ms ease, border-color 200ms ease; + } + + .ulw-proof[data-active="true"] { + background: var(--codex-window-active); + border-color: var(--codex-window-active-border); + color: var(--codex-window-accent); + font-weight: 600; + } + + .ulw-composer { + display: flex; + align-items: center; + gap: 10px; + padding: 9px 14px; + border-top: 1px solid var(--codex-window-border); + border-bottom: none; + font-size: 12px; + color: var(--codex-window-text-soft); + min-width: 0; + } + + .ulw-composer-text { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .ulw-composer-meta { + font-family: var(--font-mono); + font-size: 10.5px; + white-space: nowrap; + } + + .ulw-composer-send { + width: 22px; + height: 22px; + border-radius: 999px; + background: var(--codex-window-text); + flex-shrink: 0; + } + + .ulw-side { + border-left: 1px solid var(--codex-window-border); + background: color-mix(in srgb, var(--codex-window-chrome) 55%, var(--codex-window-bg)); + padding: 16px 14px; + display: flex; + flex-direction: column; + gap: 12px; + min-width: 0; + } + + .ulw-side-card { + border: 1px solid var(--codex-window-border); + border-radius: 10px; + background: var(--codex-window-bg); + padding: 10px 12px; + display: flex; + flex-direction: column; + gap: 6px; + } + + .ulw-side-heading { + font-family: var(--font-mono); + font-size: 10.5px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--codex-window-text-soft); + } + + .ulw-side-row { + display: flex; + justify-content: space-between; + gap: 8px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--codex-window-text); + } + + .ulw-side-row span:last-child { + color: var(--codex-window-text-soft); + } + + .ulw-workers { + display: flex; + flex-direction: column; + gap: 3px; + } + + .ulw-worker { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 8px; + border-radius: 7px; + font-size: 11.5px; + color: var(--codex-window-text-soft); + transition: color 200ms ease, background-color 200ms ease; + } + + .ulw-worker[data-live="true"] { + background: var(--codex-window-active); + color: var(--codex-window-text); + font-weight: 500; + } + + .ulw-worker-name { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .ulw-worker small { + font-family: var(--font-mono); + font-size: 9.5px; + color: var(--codex-window-text-soft); + white-space: nowrap; + } + + .ulw-worker-glyph { + width: 16px; + height: 16px; + border-radius: 4px; + display: inline-flex; + align-items: center; + justify-content: center; + font-family: var(--font-mono); + font-size: 9px; + font-weight: 700; + color: var(--codex-window-glyph-text); + flex-shrink: 0; + } + + .ulw-worker-glyph[data-lane="root"] { background: #115e59; } + .ulw-worker-glyph[data-lane="explore"] { background: #1d4ed8; } + .ulw-worker-glyph[data-lane="library"] { background: #92400e; } + .ulw-worker-glyph[data-lane="plan"] { background: #6d28d9; } + .ulw-worker-glyph[data-lane="todo"] { background: #334155; } + .ulw-worker-glyph[data-lane="execute"] { background: #166534; } + .ulw-worker-glyph[data-lane="test"] { background: #b91c1c; } + .ulw-worker-glyph[data-lane="qa"] { background: #be185d; } + .ulw-worker-glyph[data-lane="review"] { background: #4338ca; } + .ulw-worker-glyph[data-lane="continuation"] { background: #475569; } + + .ulw-ledger pre { + font-family: var(--font-mono); + font-size: 10px; + line-height: 1.5; + color: var(--codex-window-text-soft); + white-space: pre-wrap; + overflow-wrap: anywhere; + } + + .ulw-controls { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px; + margin-top: 18px; + } + + .ulw-control { + font-family: var(--font-mono); + font-size: 12px; + padding: 8px 14px; + border-radius: 9px; + border: 1px solid var(--border-default); + background: var(--surface-2); + color: var(--text-secondary); + transition: color 150ms ease, background-color 150ms ease, border-color 150ms ease; + } + + .ulw-control:hover { + color: var(--text-primary); + background: var(--surface-3); + } + + .ulw-control[aria-selected="true"] { + background: var(--accent-primary); + border-color: var(--accent-primary); + color: var(--accent-on-primary); + font-weight: 600; + } + + .ulw-control[aria-pressed="true"] { + color: var(--accent-primary); + border-color: var(--accent-primary-border); + } + + @media (max-width: 860px) { + .ulw-content { + grid-template-columns: 1fr; + } + + .ulw-side { + border-left: none; + border-top: 1px solid var(--codex-window-border); + } + + .ulw-menubar-left span { + display: none; + } + } + + @media (prefers-reduced-motion: reduce) { + .ulw-scene-swap { + animation: none; + } + + .ulw-caret { + animation: none; + } + + .ulw-step, + .ulw-proof, + .ulw-worker, + .ulw-control { + transition: none; + } + } +} diff --git a/packages/web/components/design-system/layout.tsx b/packages/web/components/design-system/layout.tsx index 86e4d77..d8e5fc2 100644 --- a/packages/web/components/design-system/layout.tsx +++ b/packages/web/components/design-system/layout.tsx @@ -9,6 +9,10 @@ interface ClassNameProps extends ChildrenProps { readonly className?: string } +interface SectionProps extends ClassNameProps { + readonly id?: string +} + interface SkipLinkProps { readonly children?: ReactNode readonly className?: string @@ -53,9 +57,10 @@ export function MarketingContainer({ export function MarketingSection({ children, className, -}: ClassNameProps): JSX.Element { + id, +}: SectionProps): JSX.Element { return ( -
+
{children}
) diff --git a/packages/web/components/site/brand-image.tsx b/packages/web/components/site/brand-image.tsx deleted file mode 100644 index 0cd49a3..0000000 --- a/packages/web/components/site/brand-image.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type { JSX } from "react" - -interface BrandImageProps { - readonly src: string - readonly alt: string - readonly width: number - readonly height: number - readonly className?: string - readonly priority?: boolean -} - -export function BrandImage({ - src, - alt, - width, - height, - className = "", - priority = false, -}: BrandImageProps): JSX.Element { - // Strip extension to get base path - const basePath = src.replace(/\.[^/.]+$/, "") - - return ( - - - - {alt} - - ) -} diff --git a/packages/web/components/site/command-card.tsx b/packages/web/components/site/command-card.tsx index 773b1ec..9cf4d49 100644 --- a/packages/web/components/site/command-card.tsx +++ b/packages/web/components/site/command-card.tsx @@ -73,8 +73,9 @@ function GlyphIcon({ type }: { readonly type: LazyCommand["glyph"] }): JSX.Eleme } export function CommandCard({ command }: CommandCardProps): JSX.Element { + // No hover state: the card itself carries no action (DESIGN.md §6). return ( -
+
@@ -85,7 +86,7 @@ export function CommandCard({ command }: CommandCardProps): JSX.Element {
- + {command.syntax} diff --git a/packages/web/components/site/team-mode-section.tsx b/packages/web/components/site/team-mode-section.tsx new file mode 100644 index 0000000..91b3fe6 --- /dev/null +++ b/packages/web/components/site/team-mode-section.tsx @@ -0,0 +1,96 @@ +import type { JSX } from "react" +import { MarketingRuleGrid, MarketingSection } from "../design-system/layout" +import { SurfaceCard } from "../design-system/surfaces" +import { BodyText, Kicker, SectionHeading } from "../design-system/typography" +import { SITE_CONFIG } from "../../lib/site-config" + +/** + * Team Mode — copy grounded in plugins/omo/skills/teammode/SKILL.md and the + * recorded Codex Desktop team session (see .omo/evidence/copy-ledger.md). + * The thread mock reuses the Codex window adapter tokens; rows are not + * interactive, so they carry no hover states. + */ +export function TeamModeSection(): JSX.Element { + const { teamMode } = SITE_CONFIG + + return ( + + +
+ {teamMode.kicker} + + {teamMode.title} + + {teamMode.body} +

+ {teamMode.compositionRule} +

+

+ {teamMode.stateNote} +

+
+ +
+
+
+
+
+ {teamMode.memberThreads.map((member) => ( +
+ + {member.name} + + + {member.status} + +
+ ))} +
+

+ {teamMode.threadNote} +

+

+ Member A COMPLETE verification note: report exists, pinned-link + check passed, and no GitHub mutations/repo edits. +

+
+
+
+ + +

+ {teamMode.whenTitle} +

+
    + {teamMode.whenPoints.map((point) => ( +
  • +
  • + ))} +
+
+
+
+
+ ) +} diff --git a/packages/web/components/site/ultrawork-section.tsx b/packages/web/components/site/ultrawork-section.tsx deleted file mode 100644 index 97475a8..0000000 --- a/packages/web/components/site/ultrawork-section.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { JSX } from "react" -import { MarketingSection } from "../design-system/layout" -import { ShowcaseSurface } from "../design-system/surfaces" -import { GradientTitle, InlineCode } from "../design-system/typography" -import { SITE_CONFIG } from "../../lib/site-config" -import { BrandImage } from "./brand-image" - -export function UltraworkSection(): JSX.Element { - return ( - -

- {SITE_CONFIG.ultraworkTagline} -

- -
- - {SITE_CONFIG.ultraworkExample} - -
- - - - Ultrawork - - - - -
- ) -} diff --git a/packages/web/components/site/ulw-demo/codex-window.tsx b/packages/web/components/site/ulw-demo/codex-window.tsx new file mode 100644 index 0000000..9b9a092 --- /dev/null +++ b/packages/web/components/site/ulw-demo/codex-window.tsx @@ -0,0 +1,118 @@ +"use client" + +import { useEffect, useRef, useState, type JSX, type KeyboardEvent } from "react" +import { + ULW_DEMO_AUTOPLAY_MS, + ULW_DEMO_SCENES, +} from "../../../lib/ulw-demo-scenes" +import { ComposerBar, SideRail, TranscriptPane, WindowChrome } from "./window-panes" + +/** + * Scene state machine for the Codex-desktop window (DESIGN.md § CodexWindow). + * Autoplay arms on scroll-into-view, pauses on any scene interaction, and + * never starts under prefers-reduced-motion. Scene 0 is server-rendered. + */ +export function CodexWindow(): JSX.Element { + const [sceneIndex, setSceneIndex] = useState(0) + const [playing, setPlaying] = useState(false) + const rootRef = useRef(null) + const tabRefs = useRef<(HTMLButtonElement | null)[]>([]) + const scene = ULW_DEMO_SCENES[sceneIndex % ULW_DEMO_SCENES.length] ?? ULW_DEMO_SCENES[0] + + useEffect(() => { + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return + const node = rootRef.current + if (!node) return + const observer = new IntersectionObserver( + (entries) => { + if (entries.some((entry) => entry.isIntersecting)) { + setPlaying(true) + observer.disconnect() + } + }, + { threshold: 0.2 }, + ) + observer.observe(node) + return () => observer.disconnect() + }, []) + + useEffect(() => { + if (!playing) return + const timer = window.setInterval( + () => setSceneIndex((index) => (index + 1) % ULW_DEMO_SCENES.length), + ULW_DEMO_AUTOPLAY_MS, + ) + return () => window.clearInterval(timer) + }, [playing]) + + function selectScene(index: number): void { + setSceneIndex(index) + setPlaying(false) + } + + function onTabKeyDown(event: KeyboardEvent, index: number): void { + const delta = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0 + if (delta === 0) return + event.preventDefault() + const next = (index + delta + ULW_DEMO_SCENES.length) % ULW_DEMO_SCENES.length + selectScene(next) + tabRefs.current[next]?.focus() + } + + return ( +
+
+ +
+ + +
+ +
+ +
+ + {/* display:contents keeps the shared flex-wrap layout while giving the + tablist only tab children (aria-required-children). */} +
+ {ULW_DEMO_SCENES.map((entry, index) => ( + + ))} +
+
+
+ ) +} diff --git a/packages/web/components/site/ulw-demo/ulw-demo-section.tsx b/packages/web/components/site/ulw-demo/ulw-demo-section.tsx new file mode 100644 index 0000000..f81bdd4 --- /dev/null +++ b/packages/web/components/site/ulw-demo/ulw-demo-section.tsx @@ -0,0 +1,41 @@ +import type { JSX } from "react" +import { MarketingSection } from "../../design-system/layout" +import { InlineCode, Kicker } from "../../design-system/typography" +import { SITE_CONFIG } from "../../../lib/site-config" +import { CodexWindow } from "./codex-window" + +/** + * Interactive Ultrawork demo — the landing's product anchor. Intro copy is + * grounded in content/docs/ultrawork.md (see .omo/evidence/copy-ledger.md); + * the window itself replays a real ulw run as a live-DOM scene machine. + */ +export function UlwDemoSection(): JSX.Element { + return ( + + {SITE_CONFIG.ulwDemo.kicker} +

+ {SITE_CONFIG.ulwDemo.title} +

+

+ {SITE_CONFIG.ulwDemo.intro} +

+ +
+ + {SITE_CONFIG.ultraworkExample} + +
+ +
+ “{SITE_CONFIG.ulwDemo.quote}” +
+ +
+ +
+
+ ) +} diff --git a/packages/web/components/site/ulw-demo/window-panes.tsx b/packages/web/components/site/ulw-demo/window-panes.tsx new file mode 100644 index 0000000..d4e376c --- /dev/null +++ b/packages/web/components/site/ulw-demo/window-panes.tsx @@ -0,0 +1,157 @@ +import type { JSX } from "react" +import { + ULW_DEMO_ENVIRONMENT, + ULW_DEMO_PROOFS, + ULW_DEMO_STEPS, + ULW_DEMO_WORKERS, + type UlwScene, +} from "../../../lib/ulw-demo-scenes" + +/** + * Presentational panes for the Codex window. Pure functions of the active + * scene — every visible string comes from `lib/ulw-demo-scenes.ts` + * (source-grounded, see .omo/evidence/copy-ledger.md). + */ + +export function WindowChrome(): JSX.Element { + return ( + <> + +
+
+ + ) +} + +export function TranscriptPane({ + scene, + sceneIndex, +}: { + readonly scene: UlwScene + readonly sceneIndex: number +}): JSX.Element { + return ( +
+
+ + + {scene.command} + +
+ + {/* The live region stays OUTSIDE the keyed swap subtree: React must + mutate its text in place for screen readers to announce scenes. */} + + {scene.status} + +
+

{scene.title}

+

{scene.body}

+
+ +
+ {ULW_DEMO_STEPS.map((step, index) => ( +
+ {String(index + 1).padStart(2, "0")} +
+ {step.heading} +

{step.detail}

+
+
+ ))} +
+ +
+ {ULW_DEMO_PROOFS.map((proof, index) => ( + + {proof} + + ))} +
+
+ ) +} + +export function ComposerBar({ scene }: { readonly scene: UlwScene }): JSX.Element { + return ( +
+ + {scene.composer} + Full access + 5.5 High +
+ ) +} + +export function SideRail({ scene }: { readonly scene: UlwScene }): JSX.Element { + return ( + + ) +} diff --git a/packages/web/components/site/ulw-research-section.tsx b/packages/web/components/site/ulw-research-section.tsx new file mode 100644 index 0000000..f8b1819 --- /dev/null +++ b/packages/web/components/site/ulw-research-section.tsx @@ -0,0 +1,44 @@ +import type { JSX } from "react" +import { MarketingSection } from "../design-system/layout" +import { AccentSurface } from "../design-system/surfaces" +import { BodyText, Kicker, SectionHeading } from "../design-system/typography" +import { SITE_CONFIG } from "../../lib/site-config" + +/** + * ulw-research — copy grounded in plugins/omo/skills/ulw-research/SKILL.md + * (see .omo/evidence/copy-ledger.md). Lane chips are informational, not + * interactive, so they carry no hover states. + */ +export function UlwResearchSection(): JSX.Element { + const { ulwResearch } = SITE_CONFIG + + return ( + + +
+ {ulwResearch.kicker} + + {ulwResearch.title} + + {ulwResearch.body} +
+ +
+
    + {ulwResearch.lanes.map((lane) => ( +
  • + {lane} +
  • + ))} +
+

+ {ulwResearch.activation} +

+
+
+
+ ) +} diff --git a/packages/web/e2e/landing-sections.spec.ts b/packages/web/e2e/landing-sections.spec.ts new file mode 100644 index 0000000..04bedc9 --- /dev/null +++ b/packages/web/e2e/landing-sections.spec.ts @@ -0,0 +1,85 @@ +import { expect, test } from "@playwright/test" +import { SITE_CONFIG } from "../lib/site-config" + +/** + * New landing sections contract (TDD target state). + * + * Team Mode and ulw-research sections render grounded copy only, and the + * information architecture keeps: install → demo → commands → workflows → + * team mode → ulw-research → Hephaestus. + */ + +async function topOf(page: import("@playwright/test").Page, text: string): Promise { + return page + .getByText(text, { exact: false }) + .first() + .evaluate((node) => node.getBoundingClientRect().top + window.scrollY) +} + +test.describe("team mode section", () => { + test("renders the grounded team mode copy", async ({ page }) => { + await page.goto("/") + await expect( + page.getByRole("heading", { name: SITE_CONFIG.teamMode.title }), + ).toBeVisible() + await expect( + page.getByText(SITE_CONFIG.teamMode.compositionRule, { exact: false }).first(), + ).toBeVisible() + await expect( + page.getByText(SITE_CONFIG.teamMode.whenTitle, { exact: false }).first(), + ).toBeVisible() + await expect( + page.getByText(SITE_CONFIG.teamMode.threadNote, { exact: false }).first(), + ).toBeVisible() + for (const member of SITE_CONFIG.teamMode.memberThreads) { + await expect(page.getByText(member.name, { exact: false }).first()).toBeVisible() + } + }) +}) + +test.describe("ulw-research section", () => { + test("renders the grounded ulw-research copy", async ({ page }) => { + await page.goto("/") + await expect( + page.getByRole("heading", { name: SITE_CONFIG.ulwResearch.title }), + ).toBeVisible() + await expect( + page.getByText(SITE_CONFIG.ulwResearch.body, { exact: false }).first(), + ).toBeVisible() + await expect( + page.getByText("Activates only on an explicit demand", { exact: false }).first(), + ).toBeVisible() + }) +}) + +test.describe("information architecture", () => { + test("keeps the planned section order", async ({ page }) => { + await page.goto("/") + + const install = await topOf(page, SITE_CONFIG.installCommand) + const demo = await page + .locator("#ulw-demo") + .evaluate((node) => node.getBoundingClientRect().top + window.scrollY) + const commands = await topOf(page, "$ulw-loop") + const workflows = await topOf(page, SITE_CONFIG.featureWorkflows.title) + const teamMode = await topOf(page, SITE_CONFIG.teamMode.title) + const research = await topOf(page, SITE_CONFIG.ulwResearch.title) + // The hero eyebrow contains the omoIntro title case-insensitively, so + // anchor on the section heading role instead of raw text. + const hephaestus = await page + .getByRole("heading", { name: SITE_CONFIG.omoIntro.title }) + .evaluate((node) => node.getBoundingClientRect().top + window.scrollY) + + expect(install).toBeLessThan(demo) + expect(demo).toBeLessThan(commands) + expect(commands).toBeLessThan(workflows) + expect(workflows).toBeLessThan(teamMode) + expect(teamMode).toBeLessThan(research) + expect(research).toBeLessThan(hephaestus) + + await page.screenshot({ + path: "../../.omo/evidence/g3-c1/landing-1280-full.png", + fullPage: true, + }) + }) +}) diff --git a/packages/web/e2e/ulw-demo.spec.ts b/packages/web/e2e/ulw-demo.spec.ts new file mode 100644 index 0000000..f5ea6aa --- /dev/null +++ b/packages/web/e2e/ulw-demo.spec.ts @@ -0,0 +1,91 @@ +import { expect, test } from "@playwright/test" +import { ULW_DEMO_SCENES } from "../lib/ulw-demo-scenes" + +/** + * Interactive Ultrawork demo contract (TDD target state). + * + * The demo is a live-DOM Codex-desktop window driven by a typed scene machine: + * autoplay on scroll-into-view, scene tabs (role=tab), play/pause (aria-pressed), + * reduced-motion disables autoplay, and no horizontal overflow at 390px. + */ + +const RESEARCH = ULW_DEMO_SCENES[0] +const RED = ULW_DEMO_SCENES[4] +const CHECKPOINT = ULW_DEMO_SCENES[7] + +test.describe("ulw demo — happy path @happy", () => { + test("renders scene 0, autoplays forward, and tabs jump to checkpoint", async ({ page }) => { + await page.goto("/") + const demo = page.locator("#ulw-demo") + await demo.scrollIntoViewIfNeeded() + + // Scene 0 is the server-rendered initial state. + await expect(page.getByText(RESEARCH.title, { exact: true })).toBeVisible() + await expect(page.getByText("ULTRAWORK MODE ENABLED!", { exact: true })).toBeVisible() + + // Autoplay must advance beyond scene 0 once in view (interval ~7s). + await expect(page.getByRole("tab", { name: ULW_DEMO_SCENES[1].tab })).toHaveAttribute( + "aria-selected", + "true", + { timeout: 12_000 }, + ) + + // Direct scene selection: checkpoint updates every pane atomically. + await page.getByRole("tab", { name: CHECKPOINT.tab }).click() + await expect(page.getByText(CHECKPOINT.title, { exact: true })).toBeVisible() + await expect(demo.getByText("checkpoint --status complete", { exact: false }).first()).toBeVisible() + await expect(demo.getByText(CHECKPOINT.sideTitle, { exact: true })).toBeVisible() + + // Play/pause is a real control with observable state. + const playToggle = page.getByRole("button", { name: /pause|play/i }).first() + const before = await playToggle.getAttribute("aria-pressed") + await playToggle.click() + await expect(playToggle).not.toHaveAttribute("aria-pressed", String(before)) + + await page.screenshot({ + path: "../../.omo/evidence/g2-c1-demo-checkpoint.png", + fullPage: false, + }) + }) +}) + +test.describe("ulw demo — reduced motion + mobile @edge", () => { + test("reduced motion disables autoplay but tabs still switch scenes", async ({ page }) => { + await page.emulateMedia({ reducedMotion: "reduce" }) + await page.goto("/") + const demo = page.locator("#ulw-demo") + await demo.scrollIntoViewIfNeeded() + + await expect(page.getByText(RESEARCH.title, { exact: true })).toBeVisible() + + // No autoplay: after > one interval the first tab is still selected. + await page.waitForTimeout(9_000) + await expect(page.getByRole("tab", { name: RESEARCH.tab })).toHaveAttribute( + "aria-selected", + "true", + ) + + await page.getByRole("tab", { name: RED.tab }).click() + await expect(page.getByText(RED.title, { exact: true })).toBeVisible() + }) + + test("no horizontal overflow at 390x844 with the last scene open", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }) + await page.goto("/") + const demo = page.locator("#ulw-demo") + await demo.scrollIntoViewIfNeeded() + + await page.getByRole("tab", { name: CHECKPOINT.tab }).click() + await expect(page.getByText(CHECKPOINT.title, { exact: true })).toBeVisible() + + const overflow = await page.evaluate( + () => document.documentElement.scrollWidth - document.documentElement.clientWidth, + ) + expect(overflow).toBeLessThanOrEqual(0) + + await page.screenshot({ + path: "../../.omo/evidence/g2-c2-demo-mobile.png", + fullPage: false, + }) + }) +}) diff --git a/packages/web/lib/site-config.ts b/packages/web/lib/site-config.ts index 449ea68..816974a 100644 --- a/packages/web/lib/site-config.ts +++ b/packages/web/lib/site-config.ts @@ -19,7 +19,6 @@ export const SITE_CONFIG = { period: ".", }, harnessPillars: ["goals not recipes", "parallel exploration", "verified completion"], - ultraworkTagline: "ultrawork turns the harness into a verified run.", ultraworkExample: "ulw add authentication", omoIntro: { kicker: "Where it comes from", @@ -79,6 +78,45 @@ export const SITE_CONFIG = { "Skills auto-activate when a task matches their domain, so you do not need to study every one first. Add a skill name to your prompt when you want to call it explicitly; ulw-research is the maximum-saturation mode for deep codebase, web, official-docs, and OSS-repo research.", skills: ["ulw-research", "review-work", "remove-ai-slops", "frontend", "programming", "visual-qa", "LSP", "AST-grep"], }, + // Copy grounded in content/docs/ultrawork.md and ulw-loop.md — see .omo/evidence/copy-ledger.md. + ulwDemo: { + kicker: "Ultrawork, live", + title: "Watch an ultrawork run close the loop", + intro: + "Include ultrawork (or the short alias ulw) anywhere in your prompt and the harness switches to maximum-precision, outcome-first, evidence-driven orchestration. An agent saying it is done does not mean the work is done — the work is done when observable evidence verifies it.", + quote: "Plan, execute, verify, and keep the evidence attached.", + }, + // Copy grounded in plugins/omo/skills/teammode/SKILL.md — see .omo/evidence/copy-ledger.md. + teamMode: { + kicker: "Team Mode", + title: "Run a named team of cooperating Codex threads", + body: "One leader, durable state on disk. The main session is always the team leader: it splits the work and assigns each slice, holds live situational awareness of every member, verifies and QAs what they deliver, relays findings between members, and synthesizes the result.", + compositionRule: + "Members are defined by a concrete part, ownership area, or perspective — never a vague job role.", + whenTitle: "When a team beats plain subagents", + whenPoints: [ + "The work does not split into perfectly isolated pieces, but doing it in parallel is clearly more convenient — members need to see and react to each other's findings.", + "One task still needs exploration, yet its goal is already clear — parallel investigation under a fixed objective.", + ], + stateNote: + "A bundled cross-platform script writes the .omo/teams state plus an auto-generated member field manual.", + threadNote: "Sent by Codex from another thread", + memberThreads: [ + { name: "Triage feature and question issues", status: "running" }, + { name: "Review PR readiness", status: "running" }, + { name: "Triage LazyCodex issues", status: "reported" }, + { name: "Triage runtime bug reports", status: "reported" }, + ], + }, + // Copy grounded in plugins/omo/skills/ulw-research/SKILL.md — see .omo/evidence/copy-ledger.md. + ulwResearch: { + kicker: "$ulw-research", + title: "Maximum-saturation research orchestration", + body: "Parallel explore and librarian swarms across the codebase, web, official docs, and OSS repos; a recursive expand loop driven by the leads workers return; empirical verification by running code; cited synthesis and optional reports.", + activation: + "Activates only on an explicit demand for research — say ulw-research or any ulw research wording in your prompt. While active, exhaustive coverage is the goal.", + lanes: ["codebase", "web", "official docs", "OSS repos"], + }, } as const; export type SiteConfig = typeof SITE_CONFIG; diff --git a/packages/web/lib/ulw-demo-scenes.ts b/packages/web/lib/ulw-demo-scenes.ts new file mode 100644 index 0000000..367f31e --- /dev/null +++ b/packages/web/lib/ulw-demo-scenes.ts @@ -0,0 +1,214 @@ +/** + * Source-grounded scene data for the interactive Ultrawork demo. + * + * Every visible string traces to OMO/LazyCodex source truth via + * `.omo/reference/source-ledger.md` (workflow beats → omo source lines) and + * `content/docs/{ultrawork,ulw-loop,manual-qa}.md`. Do not invent beats, + * metrics, or command flags here — extend the ledger first. + */ + +export type UlwLane = + | "root" + | "explore" + | "library" + | "plan" + | "todo" + | "execute" + | "test" + | "qa" + | "review" + | "continuation"; + +export type UlwScene = { + readonly key: string; + readonly tab: string; + readonly command: string; + readonly status: string; + readonly title: string; + readonly body: string; + readonly composer: string; + readonly sideTitle: string; + readonly sideBody: string; + readonly ledger: string; + readonly json: string; + readonly lanes: readonly UlwLane[]; + readonly proof: number; +}; + +export type UlwStep = { readonly heading: string; readonly detail: string }; + +export type UlwWorker = { + readonly name: string; + readonly role: string; + readonly lane: UlwLane; + readonly glyph: string; +}; + +export const ULW_DEMO_STEPS = [ + { heading: "Explorer + Librarian swarm", detail: "Call parallel subagents to find repo truth, docs, commands, and inaccurate claims to avoid." }, + { heading: "Plan agent / Prometheus", detail: "Combine research into a decision-complete plan before implementation begins." }, + { heading: "TodoWrite + task system", detail: "Register atomic tasks and blockedBy edges; `.omo/ulw-loop/goals.json` stores criteria." }, + { heading: "$start-work assigns lanes", detail: "Executor, QA Executor, reviewer, and gate workers receive bounded deliverables." }, + { heading: "TDD red", detail: "Capture the failing-first proof before production code changes." }, + { heading: "GREEN + record-evidence", detail: "Make the smallest fix, pass the test, and record criterion-scoped evidence." }, + { heading: "QA fail -> goal_retried", detail: "Manual QA can fail the run; ULW records the failure and retries the same criterion." }, + { heading: "Quality gate + checkpoint", detail: "Only code review, manualQa, gateReview, iteration, and coverage evidence close the story." }, +] as const satisfies readonly UlwStep[]; + +export const ULW_DEMO_PROOFS = [ + "research facts", + "plan artifact", + "TodoWrite", + "worker lanes", + "TDD red", + "record-evidence", + "QA fail retry", + "checkpoint complete", +] as const satisfies readonly string[]; + +export const ULW_DEMO_WORKERS = [ + { name: "Root Orchestrator", role: "holding goal", lane: "root", glyph: "R" }, + { name: "Explorer the 53rd", role: "repo scan", lane: "explore", glyph: "X" }, + { name: "Explorer the 54th", role: "tests", lane: "explore", glyph: "X" }, + { name: "Librarian the 24th", role: "docs", lane: "library", glyph: "L" }, + { name: "Librarian the 25th", role: "contracts", lane: "library", glyph: "L" }, + { name: "Plan agent / Prometheus", role: "waiting", lane: "plan", glyph: "P" }, + { name: "TodoWrite adapter", role: "tasks", lane: "todo", glyph: "T" }, + { name: "Executor the 23rd", role: "implementation", lane: "execute", glyph: "E" }, + { name: "TDD Executor the 12th", role: "red/green", lane: "test", glyph: "T" }, + { name: "QA Executor the 23rd", role: "Manual QA", lane: "qa", glyph: "Q" }, + { name: "lazycodex-code-reviewer", role: "codeReview", lane: "review", glyph: "C" }, + { name: "lazycodex-gate-reviewer", role: "gateReview", lane: "review", glyph: "G" }, + { name: "Stop/SubagentStop hook", role: "continue", lane: "continuation", glyph: "S" }, +] as const satisfies readonly UlwWorker[]; + +export const ULW_DEMO_SCENES = [ + { + key: "research", + tab: "01 Research", + command: 'task(subagent_type="explorer") + task(subagent_type="librarian")', + status: "Research wave · Explorer and Librarian gather source truth", + title: "Ultrawork starts by fanning out research.", + body: "Root does not guess. Explorer reads the local implementation, Librarian checks docs and contracts, and their findings become the input to the Plan agent.", + composer: "Root is waiting for Explorer and Librarian findings before the Plan agent writes anything.", + sideTitle: "Research lanes are live.", + sideBody: "Explorer reads the local implementation while Librarian checks docs and skill contracts. Root waits for both before planning.", + ledger: "plan_created pending research facts", + json: '{ "activeGoalId": null, "criteria": "pending" }', + lanes: ["root", "explore", "library"], + proof: 0, + }, + { + key: "plan", + tab: "02 Plan", + command: "$ulw-plan .omo/plans/ultrawork-demo.md", + status: "Planning · Plan agent / Prometheus synthesizes the lanes", + title: "Plan first, then execute with evidence.", + body: "The Plan agent merges Explorer and Librarian findings into a decision-complete plan: references, acceptance criteria, QA channel, evidence path, and workers.", + composer: "Plan agent is writing the handoff surface; no product code is touched here.", + sideTitle: "Plan agent is planner-only.", + sideBody: "The safe claim is planning, not execution. Implementation begins only after start-work or ulw-loop picks up the plan.", + ledger: "plan_created .omo/plans/ultrawork-demo.md\nsteering_accepted research findings", + json: '{ "briefPath": ".omo/ulw-loop/brief.md", "goalsPath": ".omo/ulw-loop/goals.json" }', + lanes: ["root", "explore", "library", "plan"], + proof: 1, + }, + { + key: "todo", + tab: "03 Todo", + command: "TodoWrite -> task_create + omo ulw-loop create-goals", + status: "Todo registration · file-backed tasks and success criteria", + title: "The todo list becomes durable state.", + body: "TodoWrite commits atomic work before generation. In OMO, ulw-loop persists goals, criteria, and the append-only ledger under `.omo/ulw-loop/`.", + composer: "Registering G001 with C001 happy, C002 edge, and C003 regression criteria.", + sideTitle: "TodoWrite is not decoration.", + sideBody: "Tasks are marked complete only after their matching artifact lands; multi-agent work uses dependencies instead of loose memory.", + ledger: "plan_created 1 goal(s) created\nG001 status=pending\nC001/C002/C003 status=pending", + json: '{ "activeGoalId": null, "goals": [{ "id": "G001-ultrawork-demo", "status": "pending" }] }', + lanes: ["root", "plan", "todo"], + proof: 2, + }, + { + key: "assign", + tab: "04 Assign", + command: "$start-work .omo/plans/ultrawork-demo.md", + status: "Assignment · subagents receive bounded tasks", + title: "Root assigns work instead of doing it all directly.", + body: "Executor owns implementation, TDD Executor owns the failing-first proof, QA Executor owns the real browser scenario, and reviewers own the final gate.", + composer: "Executor, TDD, QA, and review lanes are active; root watches dependencies and drift.", + sideTitle: "Subagents are visible work lanes.", + sideBody: "The roster makes delegation observable: each worker owns a narrow deliverable and returns artifact-backed evidence.", + ledger: "goal_started G001-ultrawork-demo Attempt 1\nactiveGoalId=G001-ultrawork-demo", + json: '{ "activeGoalId": "G001-ultrawork-demo", "attempt": 1, "status": "in_progress" }', + lanes: ["root", "todo", "execute", "test", "qa", "review", "continuation"], + proof: 3, + }, + { + key: "red", + tab: "05 TDD RED", + command: "bun test ultrawork-demo.test.ts # TDD red", + status: "TDD red · failing-first proof captured", + title: "The first proof is allowed to fail.", + body: "A behavior change gets a RED proof before the fix. ULW treats a hollow test as non-evidence, so the failure has to match the user-facing contract.", + composer: "TDD Executor captured RED for the right reason; Executor can now make the smallest GREEN change.", + sideTitle: "TDD red is a gate, not theater.", + sideBody: "The run records the failing proof before production changes, then routes the fix to the right worker.", + ledger: "criterion_failed G001 C001\nmessage=TDD red captured before fix", + json: '{ "C001": { "status": "fail", "capturedEvidence": "TDD red" } }', + lanes: ["root", "execute", "test"], + proof: 4, + }, + { + key: "green", + tab: "06 GREEN", + command: 'omo ulw-loop record-evidence --goal-id G001 --criterion-id C001 --status pass --evidence "GREEN unit proof + cleanup receipt"', + status: "GREEN · criterion-scoped evidence is recorded", + title: "GREEN lands only with recorded evidence.", + body: "After the smallest fix, ULW records non-empty evidence against the exact criterion. The ledger entry is `evidence_captured`, not a vague done message.", + composer: "Executor returned GREEN; root is recording C001 evidence before moving to real-surface QA.", + sideTitle: "record-evidence is append-only proof.", + sideBody: "The criterion now has status pass, capturedEvidence, capturedAt, and a ledger entry.", + ledger: "evidence_captured G001 C001 status=pass\ncapturedEvidence=GREEN unit proof", + json: '{ "C001": { "status": "pass", "capturedEvidence": "GREEN unit proof" } }', + lanes: ["root", "execute", "test", "todo"], + proof: 5, + }, + { + key: "qa-retry", + tab: "07 QA retry", + command: "browser QA -> QA fail -> omo ulw-loop complete-goals --retry-failed", + status: "QA fail · retry the same criterion until the surface passes", + title: "Manual QA can send the work back.", + body: "Tests prove the unit contract; browser/manual QA proves the real surface. If QA fails, ULW records failure evidence, retries the goal, and keeps the loop moving.", + composer: "QA Executor found a real-surface mismatch; root records goal_failed and resumes with --retry-failed.", + sideTitle: "QA fail is part of the loop.", + sideBody: "The demo should show retry, not pretend the first pass is final.", + ledger: "goal_failed G001 evidence=QA fail\ncriterion_failed C002 real surface mismatch\ngoal_retried G001 Attempt 2", + json: '{ "status": "in_progress", "attempt": 2, "failureReason": "QA fail" }', + lanes: ["root", "execute", "test", "qa", "continuation"], + proof: 6, + }, + { + key: "checkpoint", + tab: "08 Checkpoint", + command: 'omo ulw-loop checkpoint --goal-id G001 --status complete --evidence "manual QA + review + criteria evidence" --codex-goal-json .omo/evidence/get-goal-complete.json --quality-gate-json .omo/evidence/quality-gate.json', + status: "Checkpoint · quality gate closes the story", + title: "Done means the quality gate passes.", + body: "The final story needs codeReview, manualQa, gateReview, iteration, criteriaCoverage, and artifact-backed evidence before checkpointing complete.", + composer: "Root has code review, Manual QA screenshots, gate review, iteration proof, and criteria coverage.", + sideTitle: "checkpoint --status complete is the close.", + sideBody: "Only after the quality gate is clean does the run checkpoint the story and write aggregate completion evidence.", + ledger: "evidence_captured C002/C003 status=pass\naggregate_completed G001\ncheckpoint --status complete", + json: '{ "aggregateCompletion": { "status": "complete" }, "qualityGate": "clean" }', + lanes: ["root", "qa", "review", "todo"], + proof: 7, + }, +] as const satisfies readonly UlwScene[]; + +export const ULW_DEMO_ENVIRONMENT: readonly (readonly [string, string])[] = [ + ["Changes", "scoped"], + [".omo/ulw-loop", "ledger"], + ["Mode", "ulw ulw ulw"], +] as const; + +export const ULW_DEMO_AUTOPLAY_MS = 7000; diff --git a/packages/web/public/img/badge-ultrawork.avif b/packages/web/public/img/badge-ultrawork.avif deleted file mode 100644 index 2c8fa56..0000000 Binary files a/packages/web/public/img/badge-ultrawork.avif and /dev/null differ diff --git a/packages/web/public/img/badge-ultrawork.png b/packages/web/public/img/badge-ultrawork.png deleted file mode 100644 index 50cb940..0000000 Binary files a/packages/web/public/img/badge-ultrawork.png and /dev/null differ diff --git a/packages/web/public/img/badge-ultrawork.webp b/packages/web/public/img/badge-ultrawork.webp deleted file mode 100644 index d518df4..0000000 Binary files a/packages/web/public/img/badge-ultrawork.webp and /dev/null differ