From 73e1fbcfa58307f0320b43adecaaed594a824943 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 22:56:42 +0000 Subject: [PATCH 01/12] Add per-variant audience docs, AGENTS.md, and project constitution - Split audience.md into an index + per-variant profiles (gdg, ingage, client) so each talk can be refined independently off main - Add GDG Cincinnati profile as the new primary target (40 min, all-dev community, live demo centerpiece, conceptual slides) - Preserve the delivered Ingage lightning talk profile verbatim - Add AGENTS.md for agent-agnostic context (Copilot/Cursor/Gemini) - Add docs/constitution.md encoding house rules as Spec Kit principles, to be mirrored into .specify/memory/constitution.md on init https://claude.ai/code/session_01468czqpkhiBJY7v812KmgB --- AGENTS.md | 62 +++++++++++++++++++++++++ docs/audience-client.md | 43 +++++++++++++++++ docs/audience-gdg-cincinnati.md | 76 +++++++++++++++++++++++++++++++ docs/audience-ingage-lightning.md | 57 +++++++++++++++++++++++ docs/audience.md | 64 ++++++++++++-------------- docs/constitution.md | 75 ++++++++++++++++++++++++++++++ 6 files changed, 341 insertions(+), 36 deletions(-) create mode 100644 AGENTS.md create mode 100644 docs/audience-client.md create mode 100644 docs/audience-gdg-cincinnati.md create mode 100644 docs/audience-ingage-lightning.md create mode 100644 docs/constitution.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1f8a146 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,62 @@ +# AGENTS.md + +Agent-agnostic context for this repository. Any coding agent (Claude, Copilot, Cursor, +Gemini, and others) should read this first. Claude Code users: [`CLAUDE.md`](CLAUDE.md) is the +fuller, canonical guide and this file defers to it; the two must not contradict. + +## What this is + +A single-page app that is **both a conference talk deck and an interactive diagram about Spec +Kit** (Spec-Driven Development). It is given in multiple rooms and lengths from one codebase: +a ~40 min GDG Cincinnati community talk (primary), a 7–8 min Ingage lightning talk, and a +client template. See [`docs/audience.md`](docs/audience.md) for the variant index. + +The talk's own thesis: Spec Kit is not a new methodology, it is the development lifecycle you +already run (requirements, design, tickets) with each artifact turned into an executable input +for an AI agent. + +## Stack and commands + +- React 19 + Vite 8, plain JSX (no TypeScript). `@xyflow/react` v12 for the node graph. ESM throughout. No router, no CSS framework, no state library. Full detail: [`docs/tech-stack.md`](docs/tech-stack.md). + +``` +npm install # install deps +npm run dev # vite dev server (http://localhost:5173) +npm run build # static bundle to dist/ +npm run preview # serve the built bundle +npm run lint # eslint . +npm test # vitest (slide/flow unit tests) +``` + +## Where things live + +- `src/App.jsx` – mode switch (slides vs. flow) + keyboard/dot nav; interleaves the deck and the flow. +- `src/slides/SlideShow.jsx` – the slide deck (one component per slide). +- `src/flow/` – the React Flow canvas (`SpecKitFlow.jsx`), nodes, edges, detail panel. +- `src/data/steps.js` – **single source of truth** for flow content (`STEPS` + `TIER_META`). +- `src/index.css` – **all** styling (slides + flow), one global stylesheet, `vmin`-based responsive sizing. +- `docs/` – audience profiles, brand, copy style, accessibility, speaker notes, Spec Kit command reference. + +## Conventions (hard rules) + +These are enforced as the project constitution ([`docs/constitution.md`](docs/constitution.md)). +Violating them is a defect, not a style preference. + +1. **No inline `style` props.** All styling lives in CSS, keyed by class. If a value must vary per render, pass it as a CSS custom property (`style={{ "--tier-color": ... }}`) and consume it in the stylesheet. +2. **Single source of truth.** Flow content goes in `src/data/steps.js`, not in node components. Styling goes in `src/index.css`. Slide ordering/numbering/slugs come from the variant manifest, not hardcoded per slide. +3. **No em dashes, ever** (brand voice). Use commas, periods, or en dashes `–`. Applies to slide copy, `steps.js`, and docs. +4. **Curly apostrophes and quotes in rendered copy.** Use `’` and `“ ”` in anything the audience sees on screen (`src/slides/*`, `src/data/steps.js`, visible strings in `src/App.jsx`), never straight marks. Does not apply to Markdown docs or code. Audit: `grep -rnE "[A-Za-z]'[A-Za-z]" src` should be empty. +5. **Readability for a projected screen.** 16:10, `vmin`-scaled type (≥ 27px body / ≥ 54px headings at 1080p), contrast ≥ 4.5:1 for anything the audience must read. See [`docs/accessibility.md`](docs/accessibility.md). +6. **Spec-Driven Development** is a proper noun, always hyphenated and title-cased (abbrev. **SDD**). Never "spec-driven development", "Spec Driven Development". + +## Voice + +Approachable, plain, declarative, brief. Contractions throughout. Banned words: robust, +comprehensive, leverage, synergize, productionalized, holistic, scalable solutions, +best-in-class. Details: [`docs/ingage-brand.md`](docs/ingage-brand.md) and [`docs/copy-style.md`](docs/copy-style.md). + +## Testing and verification + +- Run `npm run lint` and `npm test` before considering a change done. +- For visual or content changes, build (`npm run build`) and sanity-check the affected slide/flow. +- Keep `docs/` in sync with behavior changes; this repo treats docs as part of the deliverable. diff --git a/docs/audience-client.md b/docs/audience-client.md new file mode 100644 index 0000000..53797db --- /dev/null +++ b/docs/audience-client.md @@ -0,0 +1,43 @@ +# Audience – Client Talk (template) + +**Variant slug:** `client` · **Format:** TBD · **Status:** template (fill in per engagement) + +A reusable starting point for giving this talk to a client audience. Copy the values in below +per engagement, or fork this file to `audience-client-.md` for a specific client. Index: +[`audience.md`](audience.md). Closest existing reference: [`audience-gdg-cincinnati.md`](audience-gdg-cincinnati.md). + +## Who is in the room + +- **Client / company:** _TBD_ +- **Mix:** _developers only? mixed with delivery/product/leadership? (this decides whether the "two doors" non-dev accommodation comes back)_ +- **Experience level:** _TBD_ +- **Existing Spec Kit / AI-coding exposure:** _none / some / heavy_ +- **Relationship:** _prospect, active engagement, existing partner? (sets tone and how much Ingage framing is appropriate)_ + +## Format + +- **Length:** _TBD (drives how much of the GDG superset to include)_ +- **Live demo:** _yes / no, and on what (this repo meta demo, the client's own repo, or a throwaway)_ +- **Setting:** _projected room / conference room / remote_ +- **Leave-behind:** _share the repo link? client-specific deck?_ + +## Goal + +_What does a win look like for this specific client? e.g. evaluate adopting SDD, upskill an +existing team, build confidence in agent-assisted delivery._ + +## Success criteria + +- _TBD per engagement._ + +## What this implies for the content + +- **Tailor the recognition beat to their lifecycle.** Map the eight commands to the client's actual process and tooling where known. +- **Branding:** stronger Ingage presence is appropriate for a client engagement than for a community meetup. Decide per relationship. +- **Close / next step:** an engagement-relevant ask (a pilot, a follow-up working session), not a community Slack or meetup. +- **Demo target:** prefer a demo that resonates with their stack; the meta demo on this repo still works as a safe, self-contained fallback. + +## Open questions to resolve per engagement + +- _Confidentiality: can the demo touch their code, or stay on this repo?_ +- _Which sections of the GDG superset to keep vs. cut for the available time?_ diff --git a/docs/audience-gdg-cincinnati.md b/docs/audience-gdg-cincinnati.md new file mode 100644 index 0000000..c68c437 --- /dev/null +++ b/docs/audience-gdg-cincinnati.md @@ -0,0 +1,76 @@ +# Audience – GDG Cincinnati (primary) + +**Variant slug:** `gdg` · **Format:** ~40 min talk + Q&A, with 1–2 live demos · **Status:** in development (primary target) + +This is the current primary target. Every new content decision should trace back here. The +delivered Ingage lightning talk lives in [`audience-ingage-lightning.md`](audience-ingage-lightning.md); +the reusable client template is [`audience-client.md`](audience-client.md). Index: [`audience.md`](audience.md). + +## Who is in the room + +- A **GDG Cincinnati** (Google Developer Group) community meetup audience. +- **All developers.** No non-developer accommodation needed. This removes the "two doors" constraint that shaped the Ingage talk: pitch the next step at developers directly. +- **Curious about learning or growing their Spec Kit experience.** Some have never run it; some use it already and want to get better. Assume goodwill and genuine interest, not skepticism to overcome. +- **Full spectrum of experience**, junior through senior. The "you already run this lifecycle" recognition beat still earns its place for the less experienced; it just does not need to carry the whole talk the way it did for the mixed Ingage room. +- **Community, not internal team.** No shared internal context, no internal Slack, no assumed Ingage tooling. The speaker represents Ingage but the room does not. + +## Format + +- **~40 minutes + Q&A.** Roughly 5x the lightning talk. Pace can slow down; the lightning talk was tight with fast talking, this one breathes. +- **1–2 live demos**, with the demo as the centerpiece (see [`demo.md`](demo.md) once written). The Ingage "no live demo, deliberately" rule is **reversed** here. +- New structural slides this variant adds: an **intro / who-am-I** slide and a **"time for the demo"** transition slide (and a way back out of the demo). +- Target **16:10** (1920×1200), same projected-screen design discipline. +- Shared afterward as a GitHub repo link; the deck doubles as a self-guided, interactive leave-behind, same as Ingage. + +## Goal + +Create **curiosity and momentum**, and help people **grow their Spec Kit experience**: leave able and motivated to try it (or try it better). More hands-on in spirit than the Ingage talk, but the hands-on lives in the **live demo, not the slides** – slides stay conceptual and scannable. + +## Success criteria + +Any one of these is a win: + +- People are curious to keep exploring Spec Kit and adjacent AI techniques. +- People who have never tried it take a first step. +- People who already use it pick up at least one new idea (a command, a loop, a habit). +- People can place Spec Kit in the wider ecosystem (Spec Kit / OpenSpec / Kiro). + +## What this implies for the content + +- **Slides stay conceptual; the demo carries the hands-on.** Do not bloat slides with `spec.md` / `plan.md` / `tasks.md` dumps or step-by-step how-to. Keep the cadence bet from the lightning talk (one idea per slide, large type, near-zero reading load). Show the real artifacts live, in the demo. +- **Lead with recognition, but lighter.** "This is the SDLC with executable artifacts" still opens well, but for an all-dev, already-curious room it can compress; you have earned attention without it. +- **The flow can breathe.** With ~40 minutes, the eight-command flow walk is no longer time-starved. Spend real time on the loops (where iteration and quality live), since that is the part practitioners under-use. +- **Add a practitioner / "what I've learned" beat.** A community dev room values honest, in-the-trenches detail more than a polished pitch. The "What Am I Still Figuring Out?" beat expands here. +- **Place it in the ecosystem.** Same explicit goal: name the landscape so it does not get relegated to Q&A. +- **End on curiosity + a community-appropriate next step**, not the internal Slack channel. + +## Demo implications + +- **Meta demo on this repository, safety-netted.** The demo specs a real feature in this very repo (the deck app), which is self-referential ("I specced a talk about specs, now watch me spec a feature in the talk") and clone-able afterward. +- **The constitution is a teaching moment.** This repo's constitution encodes its real house rules (no inline styles, no em dashes, curly quotes, `vmin` type). The room watches the constitution enforce house style on agent-written code, which makes the most abstract command concrete. +- **Build the real feature ahead of time; re-derive a slice live.** The variant system that powers this very talk is built for real (so all decks run); the live demo re-derives a self-contained slice against a pre-built safety branch, so the talk does not ride on a cold agent run. +- **Two paths, both supported:** a clean run from a repo state with no pre-generated artifacts, and a safety branch holding the full artifacts as a fallback. + +## Leave-behind design implications + +Same as the lightning talk: per-node flow `pts` must stand alone without narration, and the repo link does double duty (learn more / get the deck). The spoken "the deck is interactive, clone it" offer is made at the close. + +## What carries over vs. what changes from the Ingage talk + +| Aspect | Ingage (lightning) | GDG Cincinnati | +| --------------------------- | ---------------------------------- | -------------------------------------------- | +| Room | Internal, mixed dev + delivery/org | Community, all developers | +| Length | 7–8 min | ~40 min + Q&A | +| Live demo | None, deliberately | 1–2, the centerpiece | +| "Two doors" (dev + non-dev) | Required | Dropped (all devs) | +| How-to depth | Avoided | In the demo, not the slides | +| Intro / who-am-I | None | Added | +| Close | Internal `#ai-practitioners` Slack | Community-appropriate (repo + keep-in-touch) | +| Branding | Ingage house deck | Ingage-represented, community-facing | + +## Open questions to resolve as content develops + +- **Branding for a community room:** keep the full Ingage light theme + logo, or soften it for a vendor-neutral GDG audience? (Leaning: keep it; the speaker is from Ingage and it is a clean brand.) +- **Close / next step:** what replaces `#ai-practitioners`? Candidates: the spec-kit repo, the GDG community, the speaker's own contact/handle, an "open questions" rallying point. +- **One demo or two?** A single meta demo on this repo, or a quick fresh-`init` happy path first, then the meta demo. (Current lean: one safety-netted meta demo, expandable to two if rehearsal time allows.) +- **Who-am-I content:** placeholder for now, to be filled in with role, Spec Kit experience, and why the speaker cares. diff --git a/docs/audience-ingage-lightning.md b/docs/audience-ingage-lightning.md new file mode 100644 index 0000000..132c2de --- /dev/null +++ b/docs/audience-ingage-lightning.md @@ -0,0 +1,57 @@ +# Audience – Ingage Lightning Talk (delivered) + +**Variant slug:** `ingage` · **Format:** 7–8 min lightning talk · **Status:** delivered (archived reference) + +This is the original audience profile, for the 7–8 minute lightning talk given to an +internal Ingage audience. It is kept verbatim as the canonical record of that talk and as +the source for the `ingage` deck variant. New work targets [`audience-gdg-cincinnati.md`](audience-gdg-cincinnati.md); +see [`audience.md`](audience.md) for the index. + +## Who is in the room + +- ~40 people at Ingage. +- **Mostly mature, experienced software development consultants.** Assume deep SDLC fluency. Do not explain basics, and do not use framing that reads as junior (no "vibe coding," no demo-gone-wrong tropes). +- **Plus org admins through project-delivery staff.** A meaningful slice are non-developers. They should still be able to follow the whole workflow, because it maps onto a project lifecycle they already run. + +This mixed makeup is the central design constraint: the talk has to land for someone who writes the code _and_ someone who scopes and delivers the work. The "you already do this" recognition beat is what serves both at once. + +## Format + +- Lightning talk: **7–8 minutes of content + 2–3 minutes Q&A.** +- Brisk delivery is fine and expected. Favor smaller, punchier slides the room reads in a glance over dense slides talked over. +- **No live demo, deliberately.** The people who would benefit from one are already comfortable with `uvx`; the install one-liner lives on the closing "What's Next?" slide as a pointer, not a walkthrough. +- Target **16:10** (1920×1200). Design for humans scanning a projected screen, not a developer at a desk. +- The deck is also shared afterward (pushed to GitHub), so it doubles as a self-guided, interactive leave-behind. + +## Goal + +Create **curiosity, awareness, and familiarity**, so people feel comfortable taking a next step and can place Spec Kit in the wider ecosystem. **This is not a how-to.** It is a "you already understand this, here is where it fits" talk. + +## Success criteria + +Any one of these is a win: + +- People feel curious to keep the conversation going about this and other AI techniques. +- People take a first step on their own. +- People understand the value tools like this bring to their work. + +## What this implies for the content + +- **Lead with recognition.** The "this is just the SDLC, with executable artifacts" moment is the hook for an SDLC-literate, mildly skeptical crowd. It earns the right to make any payoff claim. +- **Two doors for the next step.** Developers: run the init. Non-developers: read a real `spec.md` and see it is just a PRD with acceptance criteria. Do not pitch the next step at developers only. +- **Place it in the ecosystem.** "Understand where it fits" is an explicit goal, so the close names the landscape (Spec Kit / OpenSpec / Kiro) rather than leaving it to Q&A. +- **End on curiosity.** Open questions invite the conversation to continue better than a summary or a "Questions?" slide. + +## Leave-behind design implications + +The deck is shared after the talk as a GitHub repo link (one link that serves both "where to learn more" and "get the deck"). This makes the interactive flow a self-guided artifact, not just a stage prop. Two constraints follow: + +- **Per-node `pts` must be self-explanatory without narration.** Someone clicking through solo has no speaker. Write talking points so they stand alone as complete thoughts, not as prompts for a speaker to riff from. +- **The "want the deck, it is interactive" offer is made verbally on stage** at the close. The on-slide note was dropped to keep the closing "What's Next?" slide uncluttered; the repo link there still does double duty (learn more / get the deck), and the spoken offer remains the curiosity hook. + +The implication for live delivery: detail you cut from the spoken talk is not lost; it lives in the leave-behind. You can narrate lightly and let the copy carry the depth. + +## Variant-specific notes + +- **Close is internal:** the "What's Next?" slide points to the internal `#ai-practitioners` Slack channel. This is Ingage-only and does **not** carry to community variants. +- **Branding:** Ingage light theme, logo on every slide. This is the house deck. diff --git a/docs/audience.md b/docs/audience.md index 5374009..bd489cf 100644 --- a/docs/audience.md +++ b/docs/audience.md @@ -1,47 +1,39 @@ -# Audience, Format, and Goals +# Audience, Format, and Goals – Index -The canonical reference for who this talk is for and what it needs to achieve. Read this before doing any slide or script work; every content decision should trace back to it. +This talk is delivered to more than one room, in more than one length. Each room has its own +audience profile, and each maps to a **deck variant** (architecture C: one source, many +variants, selected by `?variant=` / config). **Read the profile for the variant you are +working on before any slide or script work**; every content decision should trace back to it. -## Who is in the room +## Variants -- ~40 people at Ingage. -- **Mostly mature, experienced software development consultants.** Assume deep SDLC fluency. Do not explain basics, and do not use framing that reads as junior (no "vibe coding," no demo-gone-wrong tropes). -- **Plus org admins through project-delivery staff.** A meaningful slice are non-developers. They should still be able to follow the whole workflow, because it maps onto a project lifecycle they already run. +| Variant slug | Audience profile | Room | Length | Live demo | Status | +| ------------ | -------------------------------------------------------------- | ------------------------------------ | ------------- | ----------------- | ---------------------------- | +| `gdg` | [`audience-gdg-cincinnati.md`](audience-gdg-cincinnati.md) | GDG Cincinnati (community, all devs) | ~40 min + Q&A | 1–2 (centerpiece) | **primary / in development** | +| `ingage` | [`audience-ingage-lightning.md`](audience-ingage-lightning.md) | Ingage (internal, mixed) | 7–8 min | none | delivered (archived) | +| `client` | [`audience-client.md`](audience-client.md) | per engagement | TBD | TBD | template | -This mixed makeup is the central design constraint: the talk has to land for someone who writes the code _and_ someone who scopes and delivers the work. The "you already do this" recognition beat is what serves both at once. +## Which to read -## Format +- **Default / current work:** [`audience-gdg-cincinnati.md`](audience-gdg-cincinnati.md). The GDG talk is the primary target and the superset the others draw from. +- **Maintaining the original lightning talk:** [`audience-ingage-lightning.md`](audience-ingage-lightning.md). +- **Prepping a new client talk:** copy [`audience-client.md`](audience-client.md). -- Lightning talk: **7–8 minutes of content + 2–3 minutes Q&A.** -- Brisk delivery is fine and expected. Favor smaller, punchier slides the room reads in a glance over dense slides talked over. -- **No live demo, deliberately.** The people who would benefit from one are already comfortable with `uvx`; the install one-liner lives on the closing "What's Next?" slide as a pointer, not a walkthrough. -- Target **16:10** (1920×1200). Design for humans scanning a projected screen, not a developer at a desk. -- The deck is also shared afterward (pushed to GitHub), so it doubles as a self-guided, interactive leave-behind. +## Why this is split -## Goal +The Ingage and GDG rooms differ on the constraints that actually drive content: room makeup +(mixed vs. all-dev), length (8 min vs. 40), and whether there is a live demo. Keeping one +`audience.md` forced those into a single profile. Splitting per variant lets each talk be +refined independently while all variants stay on the main line of development (no per-talk +branches), which is the same motivation behind the config-driven deck variants. -Create **curiosity, awareness, and familiarity**, so people feel comfortable taking a next step and can place Spec Kit in the wider ecosystem. **This is not a how-to.** It is a "you already understand this, here is where it fits" talk. +## Shared, cross-variant principles -## Success criteria +These hold regardless of room (details and rationale live in the per-variant files and in +[`../CLAUDE.md`](../CLAUDE.md)): -Any one of these is a win: - -- People feel curious to keep the conversation going about this and other AI techniques. -- People take a first step on their own. -- People understand the value tools like this bring to their work. - -## What this implies for the content - -- **Lead with recognition.** The "this is just the SDLC, with executable artifacts" moment is the hook for an SDLC-literate, mildly skeptical crowd. It earns the right to make any payoff claim. -- **Two doors for the next step.** Developers: run the init. Non-developers: read a real `spec.md` and see it is just a PRD with acceptance criteria. Do not pitch the next step at developers only. -- **Place it in the ecosystem.** "Understand where it fits" is an explicit goal, so the close names the landscape (Spec Kit / OpenSpec / Kiro) rather than leaving it to Q&A. +- **Lead with recognition.** "This is the SDLC you already run, with executable artifacts" is the hook that earns the right to any payoff claim. Its weight varies by room. +- **Cadence.** Smaller, punchier slides that read in a glance beat dense slides talked over. One idea per slide, large type, near-zero reading load. +- **Place it in the ecosystem.** Name the landscape (Spec Kit / OpenSpec / Kiro); do not leave "where it fits" to Q&A. - **End on curiosity.** Open questions invite the conversation to continue better than a summary or a "Questions?" slide. - -## Leave-behind design implications - -The deck is shared after the talk as a GitHub repo link (one link that serves both "where to learn more" and "get the deck"). This makes the interactive flow a self-guided artifact, not just a stage prop. Two constraints follow: - -- **Per-node `pts` must be self-explanatory without narration.** Someone clicking through solo has no speaker. Write talking points so they stand alone as complete thoughts, not as prompts for a speaker to riff from. -- **The "want the deck, it is interactive" offer is made verbally on stage** at the close. The on-slide note was dropped to keep the closing "What's Next?" slide uncluttered; the repo link there still does double duty (learn more / get the deck), and the spoken offer remains the curiosity hook. - -The implication for live delivery: detail you cut from the spoken talk is not lost; it lives in the leave-behind. You can narrate lightly and let the copy carry the depth. +- **Leave-behind discipline.** The deck is shared as a GitHub repo link afterward, so flow `pts` must stand alone without narration and the repo link does double duty (learn more / get the deck). diff --git a/docs/constitution.md b/docs/constitution.md new file mode 100644 index 0000000..912732b --- /dev/null +++ b/docs/constitution.md @@ -0,0 +1,75 @@ +# Project Constitution + +The governing principles every spec, plan, and task in this repository must satisfy. This is +the canonical source; when Spec Kit is initialized it is mirrored into +`.specify/memory/constitution.md` (gated at `/plan` via the plan template's Constitution Check +and re-checked across artifacts by `/analyze`). Keep the two in sync: edit here, then sync. + +These are not style preferences. A change that violates a principle is a defect. + +**Project:** A talk deck + interactive Spec Kit diagram, delivered as multiple variants from +one codebase. See [`AGENTS.md`](../AGENTS.md) and [`CLAUDE.md`](../CLAUDE.md). + +## Principles + +### 1. Styling lives in CSS, never inline + +No `style={{ ... }}` props on JSX with literal values. All styling lives in `src/index.css`, +keyed by class. If a value must vary per render, pass it as a CSS custom property +(`style={{ "--tier-color": value }}`) and consume it in the stylesheet. + +_Rationale:_ one source of truth for size, family, and color. Inline literals silently +override the cascade and cause drift (a hardcoded `fontSize` beating a relative `0.9em`). + +### 2. One source of truth per concern + +Flow content lives in `src/data/steps.js` (`STEPS` + `TIER_META`), not in node components. +Styling lives in `src/index.css`. Slide ordering, section numbering, and URL slugs come from +the per-variant manifest, not hardcoded into individual slides. + +_Rationale:_ the deck ships as multiple variants. Anything duplicated across variants or +across the content/render boundary will drift between them. + +### 3. No em dashes, ever + +Use commas, periods, or en dashes `–`. Applies to slide copy, `src/data/steps.js`, and docs. + +_Rationale:_ brand voice rule (see [`ingage-brand.md`](ingage-brand.md)). + +### 4. Typographic quotes in rendered copy + +Anything the audience sees on screen uses curly `’` and `“ ”`, never straight `'` / `"`. +Applies to `src/slides/*`, `src/data/steps.js`, and visible strings in `src/App.jsx`. Does not +apply to Markdown docs or code. Audit: `grep -rnE "[A-Za-z]'[A-Za-z]" src` must be empty. + +_Rationale:_ straight marks read as code on a projected screen. + +### 5. Designed for a projected screen + +16:10 aspect, `vmin`-scaled type (≥ 27px body / ≥ 54px headings at 1080p), contrast ≥ 4.5:1 +for anything the audience must read. One idea per slide, large type, near-zero reading load. + +_Rationale:_ the artifact is read across a room, not at a desk. See [`accessibility.md`](accessibility.md). + +### 6. Bold and accent have fixed, distinct meanings + +Bold (``) is structural only: a bridge line foreshadowing the next slide, or the single +most important item in a list (at most one per slide). Inline emphasis on a phrase uses the +orange `.sl-em` (``) treatment. Bullet markers use the body text color, not the accent. + +_Rationale:_ if bold and orange both mean "emphasis," neither signals anything. + +### 7. Vocabulary is exact + +**Spec-Driven Development** / **SDD**: always hyphenated, title-cased. Keep the distinctions in +[`CLAUDE.md`](../CLAUDE.md) accurate (constitution vs. AGENTS.md is intentionally cut from the +talk; checklist = within-one-spec quality, analyze = cross-artifact consistency). + +_Rationale:_ a talk about precise specs cannot be sloppy with its own terms. + +## Governance + +- **Gate:** every plan must pass a Constitution Check before research, re-checked after design (the Spec Kit `/plan` default). `/analyze` re-checks these principles across spec + plan + tasks. +- **Verification:** `npm run lint` and `npm test` must pass; the `grep` audit in Principle 4 must be empty before a change is considered done. +- **Amendment:** change this file first, then sync `.specify/memory/constitution.md`, `AGENTS.md`, and `CLAUDE.md` so all three agree. +- **Precedence:** if guidance conflicts, this constitution wins, then `CLAUDE.md`, then `AGENTS.md`. From e1c622df24313273d6297915a367d5aaae939922 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 23:11:44 +0000 Subject: [PATCH 02/12] Refactor deck to per-variant manifest (architecture C) Behavior-preserving: the ingage variant renders the same 9-slide arc as before. - src/data/variants.js: per-variant deck manifest (ordered entries: slides with id/slug/section/props, plus the flow as an entry). Single source of truth for slide order, section numbers, and slugs. resolveVariant + DEFAULT_VARIANT. - src/slides/SlideShow.jsx: slides become a SLIDE_REGISTRY keyed by id; section numbers come from a prop (vary per variant), and per-variant copy (title byline, close) is prop-driven. Removed the index-based default export. - src/App.jsx: reads ?variant= at load, renders the active variant's manifest; the flow is now just an entry, removing FLOW_SLIDE_INDEX special-casing. Nav, hash deep-linking, and dots all iterate entries generically. - Tests updated to render every slide across all variants from the manifest (73 passing). Lint and build green for changed files. - Synced CLAUDE.md, docs/tech-stack.md, docs/speaker-notes.md to the new model. Both ingage and gdg currently share the base arc; gdg diverges in the content pass. A variant picker (no default) is reserved as the live-demo feature. https://claude.ai/code/session_01468czqpkhiBJY7v812KmgB --- CLAUDE.md | 4 +- docs/speaker-notes.md | 2 +- docs/tech-stack.md | 10 +- src/App.jsx | 209 ++++++++++++++++------------------ src/data/variants.js | 66 +++++++++++ src/slides/SlideShow.jsx | 133 +++++++++++----------- src/slides/SlideShow.test.jsx | 29 +++-- 7 files changed, 263 insertions(+), 190 deletions(-) create mode 100644 src/data/variants.js diff --git a/CLAUDE.md b/CLAUDE.md index 5f530e5..37182c7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,11 +4,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project context -This is a **7-8 minute lightning talk** about Spec Kit (Spec-Driven Development) for an Ingage audience. **Audience, format, goals, and success criteria live in [`docs/audience.md`](docs/audience.md) – read it before any slide or script work.** In short: ~40 people, mostly mature/experienced developers plus delivery/org staff; the goal is curiosity, awareness, and familiarity, not a how-to. +This is a talk about Spec Kit (Spec-Driven Development), delivered as **multiple variants from one codebase** (architecture C): a ~40 min **GDG Cincinnati** community talk (primary, in development), the delivered 7-8 min **Ingage** lightning talk, and a client template. **Audience, format, goals, and success criteria are per variant: [`docs/audience.md`](docs/audience.md) is the index, read the profile for the variant you are working on before any slide or script work.** The talk has two deliverables, presented as one combined slideshow (`src/App.jsx` interleaves them): -1. **Slides** – the deck lives in `src/slides/SlideShow.jsx` (`SLIDE_COUNT = 9`). Current arc (the interactive flow is inserted after slide 5, at `FLOW_SLIDE_INDEX = 4`): +1. **Slides** – slide components live in `src/slides/SlideShow.jsx` (exported via `SLIDE_REGISTRY`), but **which slides appear, in what order, with what section number and URL slug is driven per variant by the manifest in [`src/data/variants.js`](src/data/variants.js)**, not hardcoded. The active variant is chosen by the `?variant=` query param (default `ingage`); the interactive flow is a manifest entry (`type: "flow"`), not a numbered slide. Section numbers (`section` prop) and per-variant copy (props) come from the manifest. Current `ingage` arc: 1. **Title** – hero headline "Understanding Spec Kit", byline carries the story in three beats: "Structured requirements an agent can act on. Results that land closer to done. Fits how you already work." (reframe → payoff → recognition). No kicker. The recognition beat ("the lifecycle you already run") lives on the creed/SDD slides rather than crowding the cover. Replaces the old anxiety framing ("Shipping code you didn't write"), which duplicated the Hook and excluded the non-dev half of the room. 2. **Requirements creed** – unnumbered epigraph / cold open ("Don't start coding until we understand the requirements." – every senior dev, eventually). Anchors the talk in a discipline the room already knows, before the Hook reframes it for the AI era. No `Label`: the section counter starts at the Hook. 3. **Hook** – the problem, framed as drift/rework: "Speed isn't the problem. Drift is." An agent builds fast but can drift from intent; the distance between what you intended and what got built is where surprises and rework hide. Echoes the Why Should I Care? payoff ("Less rework. Fewer surprises.") on purpose, and ends on the bridge bold "Spec-Driven Development closes that gap." (The earlier trust/traceability framing – "You didn't write it. Now you have to trust it." – was cut: it set up a "trust" payoff the talk never resolves and re-centered the code-reader on a slide the mixed room needs to stay with.) No "vibe coding" framing. diff --git a/docs/speaker-notes.md b/docs/speaker-notes.md index c177389..4c805bc 100644 --- a/docs/speaker-notes.md +++ b/docs/speaker-notes.md @@ -6,7 +6,7 @@ The deck carries the words; you carry the personality. Each slide below lists 2 ## Per-slide timings (target: 7–8 min total) -Delivery order. The flow is its own timed block, not a numbered slide. Code-level indices live in `CLAUDE.md` (`SLIDE_COUNT = 9`, `FLOW_SLIDE_INDEX = 4`). +Delivery order for the `ingage` variant. The flow is its own timed block, not a numbered slide. Slide order and section numbers are defined per variant in `src/data/variants.js` (see `CLAUDE.md`). | Slide | Section | Target | | ------- | ------------------------------------------ | ------ | diff --git a/docs/tech-stack.md b/docs/tech-stack.md index fb2f241..ef4c079 100644 --- a/docs/tech-stack.md +++ b/docs/tech-stack.md @@ -30,11 +30,11 @@ npm run lint # eslint . ``` src/ main.jsx # React entry; mounts , imports index.css - App.jsx # mode switch (slides vs flow) + keyboard/dot nav; interleaves the deck and the flow + App.jsx # reads ?variant=, renders the active variant's manifest; keyboard/dot nav; flow is an entry index.css # ALL styling (slides + flow), single global stylesheet App.css # vestigial Vite-default styles, unused by the app slides/ - SlideShow.jsx # the slide deck (one component per slide) + SlideShow.jsx # slide components + SLIDE_REGISTRY (id -> component) flow/ SpecKitFlow.jsx # ReactFlow canvas, step state, layout StepNode.jsx # custom node (command + subtitle + tier) @@ -42,7 +42,8 @@ src/ DetailPanel.jsx # per-node detail panel LabelNode.jsx # label/annotation nodes data/ - steps.js # single source of truth: STEPS content + TIER_META + steps.js # single source of truth: STEPS content + TIER_META (the flow) + variants.js # single source of truth: per-variant deck manifest (slide order/section/slug) ``` ## Conventions @@ -50,5 +51,6 @@ src/ - **Single source of truth for flow content** is `src/data/steps.js` (`STEPS` array + `TIER_META`). Edit content there, not in the node components. - **All styling lives in `src/index.css`** as plain CSS with `vmin`-based responsive sizing. No CSS modules, no Tailwind. - **Fonts** load via a Google Fonts `@import` at the top of `index.css`: Sora, Heebo, IBM Plex Mono, and Caveat (the handwritten annotation face). -- **Nav math** in `App.jsx` is driven by the exported `SLIDE_COUNT` and `FLOW_SLIDE_INDEX` from `SlideShow.jsx`; the flow is inserted at `FLOW_SLIDE_INDEX`. Update those two constants when adding/reordering slides. +- **Per-variant deck manifest** is `src/data/variants.js` (`VARIANTS`, `DEFAULT_VARIANT`, `resolveVariant`). Each variant is an ordered list of entries (`type: "slide"` with `id`/`slug`/`section`/`props`, or `type: "flow"`). This is the single source of truth for which slides appear, in what order, with what section counter and slug. Slide components are registered by `id` in `SLIDE_REGISTRY` (`src/slides/SlideShow.jsx`). Add or reorder slides here, not by editing nav math. +- **Variant selection.** The active variant is read from the `?variant=` query param at load time in `App.jsx` (falling back to `DEFAULT_VARIANT`). The in-deck location lives in the URL hash, so variant + spot is shareable (e.g. `/?variant=ingage#whats-sdd`). Nav (`App.jsx`) iterates the active variant's `entries`; the flow is just an entry, so there is no special flow-index math. - **Deep-linkable URL hash.** `App.jsx` mirrors the current location into the URL hash so any spot is shareable: no hash is the title, `#whats-the-problem` / `#whats-sdd` / etc. are slides by slug, `#spec-kit-flow` is the flow overview, and `#spec-kit-flow/` (e.g. `#spec-kit-flow/analyze`) opens the flow focused on a node. Slugs come from the exported `SLIDE_SLUGS` array in `SlideShow.jsx`, which must stay in the same order as the slides array. Node ids come from `STEP_IDS` in `steps.js`. The hash is written with `replaceState` (no history spam) and a `hashchange` listener syncs state for opened or hand-edited links. diff --git a/src/App.jsx b/src/App.jsx index f09336c..4f79aa6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,151 +1,131 @@ import { useState, useEffect } from "react"; import SpecKitFlow from "./flow/SpecKitFlow.jsx"; -import SlideShow, { - SLIDE_COUNT, - FLOW_SLIDE_INDEX, - SLIDE_SLUGS, -} from "./slides/SlideShow.jsx"; +import { SLIDE_REGISTRY } from "./slides/SlideShow.jsx"; +import { VARIANTS, resolveVariant } from "./data/variants.js"; import { STEP_IDS } from "./data/steps.js"; -const FLOW_POSITION = FLOW_SLIDE_INDEX + 1; // dot index 5 in the combined nav -const TOTAL_POSITIONS = SLIDE_COUNT + 1; // 10 dots total (9 slides + flow) +// The active talk variant is chosen by the ?variant= query param at load time +// and stays fixed for the session (switching it is a reload). The in-deck +// location lives in the URL hash, so a variant + a spot is fully shareable +// (e.g. /?variant=ingage#whats-sdd). See src/data/variants.js. +const VARIANT_KEY = resolveVariant( + new URLSearchParams(window.location.search).get("variant"), +); +const ENTRIES = VARIANTS[VARIANT_KEY].entries; +const FLOW_INDEX = ENTRIES.findIndex((e) => e.type === "flow"); -function slideToPosition(i) { - return i <= FLOW_SLIDE_INDEX ? i : i + 1; +const FLOW_SLUG = "spec-kit-flow"; + +function entryIndexBySlug(slug) { + return ENTRIES.findIndex((e) => e.slug === slug); } -// ── URL hash <-> presentation location (shareable / deep-linkable) ─────────── -// (no hash) -> title slide -// #whats-the-problem, #whats-sdd… -> that slide, by slug -// #spec-kit-flow -> interactive flow, overview -// #spec-kit-flow/analyze -> interactive flow, focused on a step node +// ── URL hash <-> in-deck location (shareable / deep-linkable) ──────────────── +// (no hash) -> first entry (title) +// #whats-the-problem, #whats-sdd… -> that slide, by slug +// #spec-kit-flow -> interactive flow, overview +// #spec-kit-flow/analyze -> interactive flow, focused on a step node function parseHash() { const raw = window.location.hash.replace(/^#\/?/, ""); - if (!raw) return { mode: "slides", slideIndex: 0, activeId: null }; - if (raw === "spec-kit-flow") - return { mode: "flow", slideIndex: FLOW_SLIDE_INDEX, activeId: null }; - if (raw.startsWith("spec-kit-flow/")) { - const id = raw.slice(14); + if (!raw) return { index: 0, activeId: null }; + if (raw === FLOW_SLUG) return { index: FLOW_INDEX, activeId: null }; + if (raw.startsWith(`${FLOW_SLUG}/`)) { + const id = raw.slice(FLOW_SLUG.length + 1); return { - mode: "flow", - slideIndex: FLOW_SLIDE_INDEX, + index: FLOW_INDEX, activeId: STEP_IDS.includes(id) ? id : null, }; } - const idx = SLIDE_SLUGS.indexOf(raw); + const idx = entryIndexBySlug(raw); return idx === -1 - ? { mode: "slides", slideIndex: 0, activeId: null } - : { mode: "slides", slideIndex: idx, activeId: null }; + ? { index: 0, activeId: null } + : { index: idx, activeId: null }; } -function locationToHash(mode, slideIndex, activeId) { - if (mode === "flow") - return activeId ? `#spec-kit-flow/${activeId}` : "#spec-kit-flow"; - return `#${SLIDE_SLUGS[slideIndex] ?? ""}`; +function locationToHash(index, activeId) { + const entry = ENTRIES[index]; + if (!entry) return ""; + if (entry.type === "flow") + return activeId ? `#${FLOW_SLUG}/${activeId}` : `#${FLOW_SLUG}`; + return `#${entry.slug}`; } export default function App() { - const [mode, setMode] = useState(() => parseHash().mode); - const [slideIndex, setSlideIndex] = useState(() => parseHash().slideIndex); - const [activeId, setActiveId] = useState(() => parseHash().activeId); - - const position = - mode === "flow" ? FLOW_POSITION : slideToPosition(slideIndex); + const initial = parseHash(); + const [index, setIndex] = useState(initial.index); + const [activeId, setActiveId] = useState(initial.activeId); - function enterFlow() { - setActiveId(null); - setMode("flow"); - } + const entry = ENTRIES[index]; + const inFlow = entry?.type === "flow"; - function exitFlow() { + function navigateTo(target) { setActiveId(null); - setMode("slides"); - setSlideIndex(FLOW_SLIDE_INDEX + 1); - } - - function navigateTo(pos) { - if (pos === FLOW_POSITION) { - enterFlow(); - } else if (pos < FLOW_POSITION) { - setActiveId(null); - setMode("slides"); - setSlideIndex(pos); - } else { - setActiveId(null); - setMode("slides"); - setSlideIndex(pos - 1); - } + setIndex(Math.max(0, Math.min(target, ENTRIES.length - 1))); } useEffect(() => { function onKey(e) { - if (mode === "slides") { - if (e.key === "ArrowRight" || e.key === "ArrowDown" || e.key === " ") { - e.preventDefault(); - setSlideIndex((i) => { - if (i === FLOW_SLIDE_INDEX) { - enterFlow(); - return i; - } - return Math.min(i + 1, SLIDE_COUNT - 1); - }); - } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { - e.preventDefault(); - if (slideIndex === FLOW_SLIDE_INDEX + 1) { - enterFlow(); - } else { - setSlideIndex((i) => Math.max(i - 1, 0)); - } - } - } else { - const idx = activeId ? STEP_IDS.indexOf(activeId) : -1; + const forward = + e.key === "ArrowRight" || e.key === "ArrowDown" || e.key === " "; + const back = e.key === "ArrowLeft" || e.key === "ArrowUp"; + + if (inFlow) { if (e.key === "Escape" || e.key === "Home") { + // First Esc/Home returns the flow to overview; a second exits forward. if (activeId) setActiveId(null); - else exitFlow(); - } else if ( - e.key === "ArrowRight" || - e.key === "ArrowDown" || - e.key === " " - ) { + else setIndex((i) => Math.min(i + 1, ENTRIES.length - 1)); + return; + } + const idx = activeId ? STEP_IDS.indexOf(activeId) : -1; + if (forward) { e.preventDefault(); - if (idx >= STEP_IDS.length - 1) exitFlow(); - else setActiveId(STEP_IDS[idx + 1]); - } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { + if (idx >= STEP_IDS.length - 1) { + // Past the last node: leave the flow forward. + setActiveId(null); + setIndex((i) => Math.min(i + 1, ENTRIES.length - 1)); + } else { + setActiveId(STEP_IDS[idx + 1]); + } + } else if (back) { e.preventDefault(); if (activeId !== null) { if (idx <= 0) setActiveId(null); else setActiveId(STEP_IDS[idx - 1]); } else { - setMode("slides"); - setSlideIndex(FLOW_SLIDE_INDEX); + // Overview, going back: leave the flow backward. + setIndex((i) => Math.max(i - 1, 0)); } } + } else { + if (forward) { + e.preventDefault(); + setActiveId(null); + setIndex((i) => Math.min(i + 1, ENTRIES.length - 1)); + } else if (back) { + e.preventDefault(); + setActiveId(null); + setIndex((i) => Math.max(i - 1, 0)); + } } } window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); - }, [mode, activeId, slideIndex]); + }, [inFlow, activeId]); // Reflect the current location in the URL hash. replaceState keeps the URL - // shareable without pushing a history entry for every step. + // shareable without pushing a history entry for every step, and preserves the + // ?variant= query param. useEffect(() => { - const desired = locationToHash(mode, slideIndex, activeId); - const current = window.location.hash; - if (desired === current) return; - if (desired === "" && (current === "" || current === "#")) return; - const url = - desired === "" - ? window.location.pathname + window.location.search - : desired; - window.history.replaceState(null, "", url); - }, [mode, slideIndex, activeId]); + const desired = locationToHash(index, activeId); + if (desired === window.location.hash) return; + window.history.replaceState(null, "", desired || window.location.pathname); + }, [index, activeId]); // Sync state when the hash changes externally (opened link, manual edit, back/forward). useEffect(() => { function onHashChange() { const loc = parseHash(); - setMode(loc.mode); - setSlideIndex(loc.slideIndex); + setIndex(loc.index); setActiveId(loc.activeId); } window.addEventListener("hashchange", onHashChange); @@ -156,7 +136,7 @@ export default function App() {
{/* Title slide already shows the hero logo up top; reserve the corner mark for the rest */} - {!(mode === "slides" && slideIndex === 0) && ( + {entry?.id !== "title" && ( )} - {mode === "slides" ? ( -
- -
- ) : ( + {inFlow ? ( <>

- 04 + + {String(entry.section).padStart(2, "0")} + · - What’s The Process? + {entry.label}

+ ) : ( +
+ {(() => { + const Slide = SLIDE_REGISTRY[entry.id]; + return Slide ? ( + + ) : null; + })()} +
)}
+ ); +} diff --git a/src/picker/VariantPicker.test.jsx b/src/picker/VariantPicker.test.jsx new file mode 100644 index 0000000..f954a47 --- /dev/null +++ b/src/picker/VariantPicker.test.jsx @@ -0,0 +1,63 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, fireEvent } from "@testing-library/react"; +import { axe } from "jest-axe"; +import VariantPicker from "./VariantPicker.jsx"; +import { VARIANTS } from "../data/variants.js"; + +// jsdom cannot compute vmin/contrast; rendered as a fragment, so region is N/A. +const AXE_OPTS = { + rules: { + "color-contrast": { enabled: false }, + region: { enabled: false }, + }, +}; + +const variantKeys = Object.keys(VARIANTS); + +describe("VariantPicker", () => { + it("renders one selectable control per variant in the manifest", () => { + const { container } = render( {}} />); + const cards = container.querySelectorAll("button.picker-card"); + expect(cards).toHaveLength(variantKeys.length); + // Data-driven: every manifest variant's label and meta appear + for (const key of variantKeys) { + const { label, meta } = VARIANTS[key]; + expect(container.textContent).toContain(label); + expect(container.textContent).toContain(meta.room); + expect(container.textContent).toContain(meta.length); + } + }); + + it("shows a live-demo indicator only for demo variants", () => { + const { container } = render( {}} />); + const demoBadges = container.querySelectorAll(".picker-card-demo"); + const demoCount = variantKeys.filter((k) => VARIANTS[k].meta.demo).length; + expect(demoBadges).toHaveLength(demoCount); + }); + + it("calls onSelect with the variant key when a card is activated", () => { + const onSelect = vi.fn(); + const { container } = render(); + const firstCard = container.querySelector("button.picker-card"); + fireEvent.click(firstCard); + expect(onSelect).toHaveBeenCalledWith(variantKeys[0]); + }); + + it("uses native buttons (keyboard reachable)", () => { + const { container } = render( {}} />); + container.querySelectorAll(".picker-card").forEach((el) => { + expect(el.tagName).toBe("BUTTON"); + }); + }); + + it("has no axe violations", async () => { + const { container } = render( {}} />); + expect(await axe(container, AXE_OPTS)).toHaveNoViolations(); + }); + + it("contains no em dashes and no straight quotes in copy", () => { + const { container } = render( {}} />); + expect(container.textContent).not.toContain("—"); + expect(container.textContent).not.toMatch(/[A-Za-z]'[A-Za-z]/); + }); +}); From dff0632d8fda1f5e1ba04e05a6a500763f628c0a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 23:59:19 +0000 Subject: [PATCH 08/12] Add GDG Cincinnati content pass (gdg variant diverges) The gdg arc becomes a ~40-min superset of the lightning arc: - New slides (src/slides/SlideShow.jsx): WhoAmISlide (unnumbered intro, placeholder copy), DemoSlide ('Time for a Demo' transition, the live meta-demo centerpiece), LessonsSlide ('What I've Learned' practitioner beat) - src/data/variants.js: split into ingageArc (unchanged, sections 1-8) and a new gdgArc (sections 1-10) with the three new slides, a community close, and the 'you' title byline. Per-variant copy passed as plain-string props (taglineLines, inviteLines) so no JSX lives in the data module - TitleSlide/WhatsNextSlide take plain-data props for per-variant copy - Docs: CLAUDE.md gdg arc note; speaker-notes.md GDG delivery addendum (40-min budget + per-slide guidance) ingage variant untouched. Lint clean (changed files), 85 unit tests pass (new gdg slides covered by the manifest sweep), build green. https://claude.ai/code/session_01468czqpkhiBJY7v812KmgB --- CLAUDE.md | 4 ++ docs/speaker-notes.md | 26 +++++++++ src/data/variants.js | 64 +++++++++++++++++++-- src/slides/SlideShow.jsx | 116 +++++++++++++++++++++++++++++++++------ 4 files changed, 188 insertions(+), 22 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5e08005..0b698cd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,6 +19,8 @@ The talk has two deliverables, presented as one combined slideshow (`src/App.jsx 8. **What Am I Still Figuring Out?** – honest close: nothing here is new, what's new is trusting an agent with it; still learning how it holds up over time. Ends on a list of genuinely open questions, the last one bolded as the standout (multiple-perspective spec review). (Interrogative label; single-column, no two-stack.) 9. **Where to start?** – the "go do it" half of the former combined close: heading "Your turn." (pays off the "workflow you already do" thesis), repo link (`https://github.com/github/spec-kit`), and the install one-liner chip. Developer-facing; the ecosystem nod lives on the SDD slide, not here. 10. **What's Next?** – the close, on its own slide: the explicit ask. Heading "Let's keep talking."; picks up the open questions from the previous slide and turns them into the invite to keep the conversation going in the team `#ai-practitioners` Slack channel. This is the load-bearing closing beat (see `docs/speaker-notes.md` → closing guardrails). Deliberately holds just the ask, with no competing repo/install block. + **`gdg` arc** (~40 min community talk, `?variant=gdg`) is a superset of the above: it adds an **unnumbered who-am-I intro** after the title (placeholder copy, props let you fill it in), a **"Time for a Demo"** transition (section 5, the live meta-demo centerpiece, after the flow), and a **"What I've Learned"** practitioner beat (section 7, lessons that stuck, distinct from the open-questions slide). It re-points the close to a **community invite** (plain-string `inviteLines` prop, no internal Slack) and the title byline to "Fits how you already work" (`taglineLines` prop). Sections renumber to 1-10. New components (`whoami`, `demo`, `lessons`) live in `SlideShow.jsx`; per-variant copy is plain-data props from the manifest (no JSX in `variants.js`). + 2. **Interactive flow visual** – a React Flow node graph of the Spec Kit workflow, shown on stage and self-guided in the shared copy. The core thesis: _Spec Kit isn't a new methodology. It's the development lifecycle you already know, except requirements, design, and tickets become executable inputs for an AI agent instead of documents that rot in a wiki._ @@ -94,6 +96,8 @@ specify init ``` + For additional context about technologies to be used, project structure, shell commands, and other important information, read the current plan + diff --git a/docs/speaker-notes.md b/docs/speaker-notes.md index 4c805bc..da52d2d 100644 --- a/docs/speaker-notes.md +++ b/docs/speaker-notes.md @@ -115,3 +115,29 @@ The closing run is three beats: the open questions (**What Am I Still Figuring O **What's Next? (the close):** - Its own slide now, holding just the ask. End on the explicit invite to keep the conversation going in `#ai-practitioners`: that is the goal, turning a passive room into people who keep talking. The heading ("Let's keep talking.") answers the prior open-questions slide – frame it as "those questions? let's chew on them together," not a sign-off. +- **Community variants do not use `#ai-practitioners`.** The `gdg` close swaps in a community invite (see below). + +## GDG Cincinnati variant (~40 min, `?variant=gdg`) + +Same spine as the lightning talk, with room to breathe and a live demo. The timings above are the lightning budget; for GDG, slow the front section, let the flow breathe, and let the demo be the centerpiece. Profile: [`audience-gdg-cincinnati.md`](audience-gdg-cincinnati.md). + +Rough 40-min budget (adjust to the clock): + +| Beat | Target | +| ------------------------------------------------- | ---------- | +| Title + Who am I (intro) | ~2 min | +| Creed + Hook | ~2 min | +| SDD + Spec Kit | ~3 min | +| Flow (8 commands, loops) | ~7 min | +| Time for a Demo (the meta demo) | ~12-15 min | +| Why Should I Care? | ~2 min | +| What I've Learned (lessons) | ~3 min | +| Still Figuring Out + Where to start + What's Next | ~4 min | +| Q&A | remainder | + +Notes on the GDG-only slides: + +- **Who am I (intro, unnumbered):** placeholder until filled in. Keep it short, credibility not life story: role, how long building software, how you got into SDD, why you care. It buys trust for the honest beats later. +- **Time for a Demo (section 5):** the centerpiece. After the flow, say "you've seen the eight commands, now watch them run on this very deck." Then switch to the terminal and run the live `/speckit` flow against this repo (see the demo runbook once written). Safety net: a finished branch you can `git checkout` if a live run wanders. The point the room should leave with: the constitution catching house-style violations on agent-written code. +- **What I've Learned (section 7):** practitioner lessons that stuck, distinct from the open-questions slide. This is what a community dev room values most, honest in-the-trenches detail. One bolded standout. +- **Close (What's Next?, section 10):** a community invite, not the internal Slack. The deck's `inviteLines` prop carries it; fill in your real handle/contact. Still end on "let's keep talking," still curiosity over summary. diff --git a/src/data/variants.js b/src/data/variants.js index 542baa2..12779ca 100644 --- a/src/data/variants.js +++ b/src/data/variants.js @@ -19,9 +19,9 @@ export const DEFAULT_VARIANT = "ingage"; -// The current shared arc. Both variants reference it today; `gdg` diverges in a -// later content pass (intro / who-am-I, "time for the demo", expanded close). -const baseArc = [ +// The ingage lightning arc (7-8 min, delivered). Section counter starts at the +// Hook; title and the requirements creed are unnumbered. +const ingageArc = [ { type: "slide", id: "title", slug: "title" }, { type: "slide", id: "requirements", slug: "quote-requirements" }, { type: "slide", id: "hook", slug: "whats-the-problem", section: 1 }, @@ -44,15 +44,67 @@ const baseArc = [ { type: "slide", id: "whatsNext", slug: "whats-next", section: 8 }, ]; +// The GDG Cincinnati arc (~40 min community talk, all-dev). Superset of the +// lightning arc: adds an intro/who-am-I, a "time for the demo" transition (the +// centerpiece), and a practitioner "what I've learned" beat; re-points the close +// to a community invite (no internal Slack); sections renumber accordingly. +// Per-variant copy is plain-data props (no JSX in this data module). +const gdgArc = [ + { + type: "slide", + id: "title", + slug: "title", + props: { + taglineLines: [ + "Structured requirements an agent can act on.", + "Results that land closer to done.", + "Fits how you already work.", + ], + }, + }, + { type: "slide", id: "whoami", slug: "who-am-i" }, + { type: "slide", id: "requirements", slug: "quote-requirements" }, + { type: "slide", id: "hook", slug: "whats-the-problem", section: 1 }, + { type: "slide", id: "sdd", slug: "whats-sdd", section: 2 }, + { type: "slide", id: "specKit", slug: "whats-spec-kit", section: 3 }, + { + type: "flow", + slug: "spec-kit-flow", + section: 4, + label: "What’s The Process?", + }, + { type: "slide", id: "demo", slug: "demo", section: 5 }, + { type: "slide", id: "why", slug: "why-should-i-care", section: 6 }, + { type: "slide", id: "lessons", slug: "what-ive-learned", section: 7 }, + { + type: "slide", + id: "honestClose", + slug: "what-am-i-still-figuring-out", + section: 8, + }, + { type: "slide", id: "whereToStart", slug: "where-to-start", section: 9 }, + { + type: "slide", + id: "whatsNext", + slug: "whats-next", + section: 10, + props: { + inviteLines: [ + "If you’re building with any of this, I want to hear how it goes.", + "Find me after, or at [your handle / contact].", + ], + }, + }, +]; + export const VARIANTS = { ingage: { label: "Ingage Lightning Talk", - entries: baseArc, + entries: ingageArc, }, gdg: { label: "GDG Cincinnati", - // Transitional: mirrors the lightning arc until the GDG content pass. - entries: baseArc, + entries: gdgArc, }, }; diff --git a/src/slides/SlideShow.jsx b/src/slides/SlideShow.jsx index a598e93..69f55ff 100644 --- a/src/slides/SlideShow.jsx +++ b/src/slides/SlideShow.jsx @@ -3,6 +3,7 @@ // "only export components" rule (as the old SLIDE_SLUGS export did); fast-refresh // of a single slide is not worth splitting the deck across files. /* eslint-disable react-refresh/only-export-components */ +import { Fragment } from "react"; // ── Primitives ────────────────────────────────────────────────────────────── @@ -24,16 +25,14 @@ function Label({ n, title }) { // slides receive it as the `section` prop and the manifest supplies the value. // Per-variant copy differences are passed as props too (see byline / close). +// taglineLines is plain data (array of strings) so the manifest can vary the +// byline per variant without JSX in the data file. Lines render stacked. function TitleSlide({ - tagline = ( - <> - Structured requirements an agent can act on. -
- Results that land closer to done. -
- Fits how we already work. - - ), + taglineLines = [ + "Structured requirements an agent can act on.", + "Results that land closer to done.", + "Fits how we already work.", + ], }) { return (
@@ -43,7 +42,14 @@ function TitleSlide({ className="sl-logo-hero" />

Understanding Spec Kit

-

{tagline}

+

+ {taglineLines.map((line, i) => ( + + {i > 0 &&
} + {line} +
+ ))} +

); } @@ -252,9 +258,13 @@ function WhereToStartSlide({ section }) { // previous slide and turns them into an invitation to keep talking. This is the // load-bearing beat (see docs/speaker-notes.md → closing guardrails). The // invite line varies per variant (internal Slack vs. community), so it is a prop. -function WhatsNextSlide({ - section, - invite = ( +// The default (ingage) invite keeps its inline emphasis. A variant can override +// with plain-string `inviteLines` from the manifest (e.g. the community close, +// which must not point at the internal Slack channel). +function WhatsNextSlide({ section, inviteLines }) { + const body = inviteLines ? ( + inviteLines.map((line, i) =>

{line}

) + ) : ( <>

If you’re poking at any of this, I want to compare notes. @@ -263,13 +273,84 @@ function WhatsNextSlide({ Let’s pick it up in our #ai-practitioners Slack channel.

- ), -}) { + ); return (
+ ); +} + +// ── GDG-only slides (community 40-min variant) ─────────────────────────────── +// Added for the gdg arc; not used by the ingage lightning talk. See +// docs/audience-gdg-cincinnati.md and CLAUDE.md (gdg arc). + +// Intro / who-am-I. Placeholder copy for now (props let the manifest or a later +// pass fill in real details). Unnumbered: it sits before the section counter. +function WhoAmISlide({ + name = "[Your name]", + role = "[Your role], Ingage", + points = [ + "[How long you’ve been building software]", + "[How you started using Spec-Driven Development]", + "[Why this matters to you, and what you’re hoping to share]", + ], +}) { + return ( +
+

Hi, I’m

+

{name}.

+

{role}

+
    + {points.map((p, i) => ( +
  • {p}
  • + ))} +
+
+ ); +} + +// "Time for the demo" transition. The screen switches to a terminal after this; +// keep it a single, punchy beat. +function DemoSlide({ section }) { + return ( +
+
+ ); +} + +// Practitioner "what I've learned" beat: honest lessons that stuck. Distinct +// from the open questions on the next slide. One bolded standout. +function LessonsSlide({ section }) { + return ( +
+
); } @@ -282,11 +363,14 @@ function WhatsNextSlide({ export const SLIDE_REGISTRY = { title: TitleSlide, + whoami: WhoAmISlide, requirements: RequirementsSlide, hook: HookSlide, sdd: SddSlide, specKit: SpecKitSlide, + demo: DemoSlide, why: PredictabilitySlide, + lessons: LessonsSlide, honestClose: HonestCloseSlide, whereToStart: WhereToStartSlide, whatsNext: WhatsNextSlide, From 78075f037d5abfdd61f6e38356ca5dd34681bdf0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 00:06:27 +0000 Subject: [PATCH 09/12] Add live demo runbook (docs/demo.md) Stage choreography for the GDG meta-demo: build the variant picker live on this repo, safety-netted by the claude/demo-variant-picker branch. - Two-path model (run clean vs git switch to the finished branch) - Pre-flight checklist, live-vs-pre-built risk split, timed ~12-15 min script - Constitution Check / analyze as the load-bearing teaching beat - Escape hatches and a reset-to-clean procedure - Link demo.md from the GDG audience profile https://claude.ai/code/session_01468czqpkhiBJY7v812KmgB --- docs/audience-gdg-cincinnati.md | 2 +- docs/demo.md | 182 ++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 docs/demo.md diff --git a/docs/audience-gdg-cincinnati.md b/docs/audience-gdg-cincinnati.md index c68c437..fc4f552 100644 --- a/docs/audience-gdg-cincinnati.md +++ b/docs/audience-gdg-cincinnati.md @@ -17,7 +17,7 @@ the reusable client template is [`audience-client.md`](audience-client.md). Inde ## Format - **~40 minutes + Q&A.** Roughly 5x the lightning talk. Pace can slow down; the lightning talk was tight with fast talking, this one breathes. -- **1–2 live demos**, with the demo as the centerpiece (see [`demo.md`](demo.md) once written). The Ingage "no live demo, deliberately" rule is **reversed** here. +- **1–2 live demos**, with the demo as the centerpiece (runbook: [`demo.md`](demo.md)). The Ingage "no live demo, deliberately" rule is **reversed** here. - New structural slides this variant adds: an **intro / who-am-I** slide and a **"time for the demo"** transition slide (and a way back out of the demo). - Target **16:10** (1920×1200), same projected-screen design discipline. - Shared afterward as a GitHub repo link; the deck doubles as a self-guided, interactive leave-behind, same as Ingage. diff --git a/docs/demo.md b/docs/demo.md new file mode 100644 index 0000000..a2fd9e0 --- /dev/null +++ b/docs/demo.md @@ -0,0 +1,182 @@ +# Demo Runbook: the live meta-demo + +The stage choreography for the GDG Cincinnati live demo. Goal: in ~12-15 minutes, run Spec Kit +on **this very repo** so the room sees the workflow produce a real feature, with the project +**constitution** keeping the agent in line. Safety-netted so a cold or wandering agent run never +sinks the talk. + +Audience profile: [`audience-gdg-cincinnati.md`](audience-gdg-cincinnati.md). The slide that hands +off to this is the `gdg` deck's **"Time for a Demo"** (section 5), right after the flow. + +## The one thing they should leave with + +Spec Kit is the lifecycle they already run, except the artifacts are executable and a written-down +constitution catches drift **before** code. Everything below serves that line. If you only get one +beat, get the **Constitution Check / analyze** beat. + +## Demo subject: the variant picker + +You build the **variant picker** (the "pick a talk" landing screen) live. It is the perfect +subject because: + +- It is **self-referential**: "I specced a talk about specs; now watch me spec a feature in the talk app you are looking at." +- The working branch genuinely **does not have it yet**, so building it live is honest, not theater. +- It is **small and visual**: a screen that lists the variants, plus a resolution rule. +- It is **fully de-risked**: a finished version lives on the `claude/demo-variant-picker` branch. + +## Two paths (both supported) + +- **Run it clean (live):** start on the working branch, which has no picker, and drive the `/speckit` flow on stage. +- **Safety net (fallback):** the `claude/demo-variant-picker` branch holds the complete artifacts (`specs/001-variant-picker/`) and the working implementation. Switch to it any time to show a finished result or to recover. + +You are never more than one `git checkout` away from a working demo. + +## Pre-flight checklist (do this before you present) + +Environment: + +- [ ] `specify` CLI installed and on PATH (`specify version`), or at least the `.specify/` + `.claude/skills/speckit-*` already committed (they are). +- [ ] Claude Code (or your agent) open in the repo, `/speckit` skills available. +- [ ] `npm install` done; `npm run dev` works. +- [ ] Terminal font **large** (18-22pt), high-contrast theme, window wide enough that command output does not wrap awkwardly. Hide noisy shell prompts/notifications. +- [ ] Browser tab open to the dev server, zoom set so the projected deck/app is readable. + +Repo state: + +- [ ] On the working branch, **clean tree** (`git status` empty), and **no `specs/001-variant-picker/`** present (that is the point of "clean"). +- [ ] The safety branch exists locally: `git fetch && git branch --list claude/demo-variant-picker` (or `git switch claude/demo-variant-picker` once to materialize it, then switch back). +- [ ] Decide your stretch level (see "Live vs pre-built" below) and rehearse it once end to end against a clock. + +Rehearsal: + +- [ ] Run the whole thing once the day before. Time it. Note where the agent pauses so you can narrate over the dead air. +- [ ] After rehearsing, **reset** (see "Reset to clean" at the end) so the live run starts clean. + +## Live vs pre-built (choose your risk) + +A full live `/speckit.implement` is the slowest, least predictable part. Recommended split: + +| Step | Live or pre-built | Why | +| ------------------------------ | --------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `/speckit.specify` | **Live** | Fast, and the most teachable: watch intent become a structured spec. | +| `/speckit.plan` | Pre-built (show the file) | The teaching beat is the **Constitution Check** table, which reads better than watching it generate. | +| `/speckit.analyze` | **Live** | Read-only, fast, safe. Produces a constitution + cross-artifact report. The money shot. | +| `/speckit.implement` | Pre-built (reveal via branch) | Slow and least predictable live. Reveal the finished feature instead. | +| Stretch: one `/implement` task | Live, only if confident and time allows | Show the constitution catching a house-style slip on agent-written code. | + +## The script (~12-15 min) + +Numbers are rough; adjust to the clock. + +### 0. Hand-off from the slide (~20s) + +On the **"Time for a Demo"** slide: "You have seen the eight commands. Let us run them, for real, on +this exact deck. I am going to build the screen that lets me pick which version of this talk to give." +Switch to the terminal. + +### 1. `/speckit.specify` live (~3-4 min) + +Run: + +``` +/speckit.specify add a variant picker landing screen: when the deck opens without a chosen +variant, show a screen listing every talk variant (name, room, length, whether it has a live +demo) and let me pick one; a valid variant in the URL still opens directly +``` + +While it runs, narrate: "Notice it is asking WHAT and WHY, not how. It is writing acceptance +criteria I can actually check." When it finishes, open `specs/001-variant-picker/spec.md` and scroll +the **User Scenarios** and **Success Criteria**. Land: "This is just a PRD. The difference is an +agent is about to act on it." + +### 2. Show the plan and the Constitution Check (~2-3 min) + +You pre-ran `/speckit.plan`. Open `specs/001-variant-picker/plan.md` and scroll to **Constitution +Check**. This is the beat: + +"Here is the part I care about. Every one of these is a house rule for this repo: no inline styles, +curly quotes, type that scales for a projector. The plan has to pass these gates before any code. +My standards are not a wiki page nobody reads anymore. They are a checklist the workflow enforces." + +Optionally open `.specify/memory/constitution.md` for a sentence: "This is where those rules live." + +### 3. `/speckit.analyze` live (~2-3 min) + +Run: + +``` +/speckit.analyze +``` + +It is read-only, so it is safe to run live. When the report table appears: "This is cross-checking +the spec, the plan, the tasks, and the constitution against each other, and flagging anything +inconsistent. This is the review that used to happen in a PR, happening before a line of code." + +### 4. Reveal the finished feature (~2-3 min) + +"I ran tasks and implement ahead of time, because watching an agent type for ten minutes is nobody's +idea of fun. Here is the result." + +``` +git stash # park the live-generated spec if needed +git switch claude/demo-variant-picker +npm run dev # if not already running +``` + +Open the app at `/` (no variant): the **picker** appears. Click a variant: the talk opens. Then: +"And because it is data-driven, adding a new talk later just shows up here. The agent built the +component, the CSS, and the tests, and it stayed inside every house rule because the constitution +was a gate, not a suggestion." + +### 5. (Stretch, optional) one live `/implement` task + +Only if confident and you have time. Pick a tiny task from `tasks.md` and run `/speckit.implement`, +or hand-edit to violate a rule (add an inline `style={{...}}` or a straight quote) and show +`npm run lint` / the test sweep catching it. High payoff, higher risk. Skip if the clock is tight. + +### 6. Back to the deck (~15s) + +`git switch` back to the talk branch if your deck is served from it, return to the slides, and move +into **Why Should I Care?**. "That is the whole loop. Now, why it is worth your time." + +## If something goes wrong (escape hatches) + +- **Agent stalls or wanders during `/specify`:** let it finish or stop it, then `git switch claude/demo-variant-picker` and open the pre-built `spec.md`. "Here is one I prepared earlier." Nobody will know. +- **`/analyze` errors or is slow:** skip it; the plan's Constitution Check already made the point. Go straight to the reveal. +- **Dev server / build breaks on the live branch:** the safety branch is known-green (lint clean on changed files, tests pass, build succeeds). Switch to it. +- **Projector/screen-share dies mid-command:** narrate from the slides; the deck's flow already shows the eight commands. Resume when back. +- **You are out of time:** cut straight from step 1 (spec generated) to step 4 (reveal). The spec + the working result alone tell the story. + +General rule: when in doubt, `git switch claude/demo-variant-picker`. It always works. + +## Reset to clean (after rehearsing, before the talk) + +Put the working branch back to its no-picker state so the live run starts clean: + +``` +git switch claude/spec-kit-presentation-planning-ljgmfj +git stash drop # if you stashed during rehearsal +rm -rf specs/001-variant-picker # remove a live-generated spec, if any +git checkout -- . # discard any other working changes +git status # confirm clean, and no specs/001-variant-picker/ +``` + +(The finished artifacts still live safely on `claude/demo-variant-picker`.) + +## Appendix: exact commands, in order + +``` +# live +/speckit.specify +# show (pre-built) +specs/001-variant-picker/plan.md (Constitution Check section) +.specify/memory/constitution.md +# live (read-only) +/speckit.analyze +# reveal +git switch claude/demo-variant-picker +npm run dev +# return / reset +git switch claude/spec-kit-presentation-planning-ljgmfj +rm -rf specs/001-variant-picker && git checkout -- . +``` From d1f50074bfcfea1ff0e8c5f615995b1a6affd297 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 00:17:17 +0000 Subject: [PATCH 10/12] Fill GDG who-am-I with Patrick's bio + headshot, finish community close - src/data/variants.js: real who-am-I copy (name, role, three GDG-relevant bullets) + photo; community close (GDG Cincinnati / Ohio DevFest), replacing the placeholder handle - src/slides/SlideShow.jsx: WhoAmISlide gains optional photo/photoAlt (circular headshot beside the intro text) - public/img/patrick-hammond.jpg: headshot cropped to a 600x600 square - src/index.css: .whoami row layout + circular .whoami-photo Lint clean, 85 tests pass (photo alt-text covered), build green (image in dist/). https://claude.ai/code/session_01468czqpkhiBJY7v812KmgB --- public/img/patrick-hammond.jpg | Bin 0 -> 87780 bytes src/data/variants.js | 19 +++++++++++++++++-- src/index.css | 26 ++++++++++++++++++++++++++ src/slides/SlideShow.jsx | 25 ++++++++++++++++--------- 4 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 public/img/patrick-hammond.jpg diff --git a/public/img/patrick-hammond.jpg b/public/img/patrick-hammond.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5bff80bb55dc83298eb888f591dd4aa990055375 GIT binary patch literal 87780 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

|TdUtyyO)%FmHTJj> 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

cMuS$#twgKQbg2f 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 DaN-?4 literal 0 HcmV?d00001 diff --git a/src/data/variants.js b/src/data/variants.js index 12779ca..ddbe5af 100644 --- a/src/data/variants.js +++ b/src/data/variants.js @@ -62,7 +62,22 @@ const gdgArc = [ ], }, }, - { type: "slide", id: "whoami", slug: "who-am-i" }, + { + type: "slide", + id: "whoami", + slug: "who-am-i", + props: { + name: "Patrick Hammond", + role: "Director at Ingage Partners. Co-founder and CTO at Atomic Robot.", + points: [ + "Co-organizer of GDG Cincinnati and Ohio DevFest.", + "Led the first Android mobile team at Kroger.", + "20+ years in, and having more fun building software than ever.", + ], + photo: "/img/patrick-hammond.jpg", + photoAlt: "Patrick Hammond", + }, + }, { type: "slide", id: "requirements", slug: "quote-requirements" }, { type: "slide", id: "hook", slug: "whats-the-problem", section: 1 }, { type: "slide", id: "sdd", slug: "whats-sdd", section: 2 }, @@ -91,7 +106,7 @@ const gdgArc = [ props: { inviteLines: [ "If you’re building with any of this, I want to hear how it goes.", - "Find me after, or at [your handle / contact].", + "Come find me, I help organize GDG Cincinnati and Ohio DevFest.", ], }, }, diff --git a/src/index.css b/src/index.css index a7662df..c5f1e39 100644 --- a/src/index.css +++ b/src/index.css @@ -1126,3 +1126,29 @@ kbd { color: #404850; line-height: 1.45; } + +/* ── Who am I (gdg intro) ─────────────────────────────────────────────────── */ +/* Circular headshot beside the intro text. Row layout overriding .slide. */ +.whoami { + flex-direction: row; + align-items: center; + gap: clamp(24px, 5vmin, 64px); +} +.whoami-photo { + width: clamp(150px, 32vmin, 360px); + height: clamp(150px, 32vmin, 360px); + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; + border: 4px solid #ffffff; + box-shadow: 0 6px 28px rgba(0, 0, 0, 0.12); +} +.whoami-text { + display: flex; + flex-direction: column; + gap: 0.35em; + min-width: 0; +} +.whoami-text .sl-tagline { + margin-bottom: -0.1em; +} diff --git a/src/slides/SlideShow.jsx b/src/slides/SlideShow.jsx index 69f55ff..28a9968 100644 --- a/src/slides/SlideShow.jsx +++ b/src/slides/SlideShow.jsx @@ -297,17 +297,24 @@ function WhoAmISlide({ "[How you started using Spec-Driven Development]", "[Why this matters to you, and what you’re hoping to share]", ], + photo, + photoAlt, }) { return ( -

-

Hi, I’m

-

{name}.

-

{role}

-
    - {points.map((p, i) => ( -
  • {p}
  • - ))} -
+
+ {photo && ( + {photoAlt + )} +
+

Hi, I’m

+

{name}.

+

{role}

+
    + {points.map((p, i) => ( +
  • {p}
  • + ))} +
+
); } From f656ad3a7805012a0bcd3ead0626714de9389025 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 23:06:25 +0000 Subject: [PATCH 11/12] Fix code-review findings + two pre-existing nav issues Review items: 1. e2e (tests/a11y.spec.js): pin ?variant=ingage on every nav (no variant now shows the picker) and update stale slugs + flow hash (#spec-kit-flow). 2. Hide the 'Pick a talk' button on the flow, where it collided with the top-left section label. 3. selectVariant navigates to path+query (no fragment) so a stale picker hash is dropped and the deck opens at the title. 4. WhatsNext: empty inviteLines now falls back to the default invite (was a truthy [] rendering a blank close). Added a regression test. 5. parseHash guards flowIndex === -1 for a future flow-less variant. Pre-existing: - Flow Esc/Home now returns to the overview only (per CLAUDE.md) and calls preventDefault; it no longer advances the deck, so Esc-to-exit-fullscreen doesn't also jump slides. - Dot aria-labels match the printed section ('Section 6') instead of array index ('Slide 8'). Lint clean, 97 unit tests pass, build green. https://claude.ai/code/session_01468czqpkhiBJY7v812KmgB --- src/App.jsx | 58 ++++++++++++++++++++++++----------- src/slides/SlideShow.jsx | 2 +- src/slides/SlideShow.test.jsx | 10 ++++++ tests/a11y.spec.js | 30 ++++++++++-------- 4 files changed, 68 insertions(+), 32 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 561e217..357371b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -12,10 +12,12 @@ function readVariantParam() { } // Choosing a variant reflects it in the URL; the reload enters deck mode at the -// first slide (no hash). The variant is fixed for the session, so switching is a -// reload, consistent with how variant resolution already works. +// first slide. We navigate to the bare path + query (no fragment) so any stale +// in-deck hash on the picker URL is dropped and the deck opens at the title. The +// variant is fixed for the session, so switching is a reload, consistent with +// how variant resolution already works. function selectVariant(key) { - window.location.search = `?variant=${encodeURIComponent(key)}`; + window.location.href = `${window.location.pathname}?variant=${encodeURIComponent(key)}`; } // Returning to the picker drops the variant (and any in-deck hash) so resolution @@ -49,13 +51,17 @@ function Deck({ variantKey }) { const parseHash = useCallback(() => { const raw = window.location.hash.replace(/^#\/?/, ""); if (!raw) return { index: 0, activeId: null }; - if (raw === FLOW_SLUG) return { index: flowIndex, activeId: null }; - if (raw.startsWith(`${FLOW_SLUG}/`)) { - const id = raw.slice(FLOW_SLUG.length + 1); - return { - index: flowIndex, - activeId: STEP_IDS.includes(id) ? id : null, - }; + // Flow hashes only resolve if this variant actually has a flow entry; + // otherwise fall through to the slug lookup (which lands on the title). + if (flowIndex !== -1) { + if (raw === FLOW_SLUG) return { index: flowIndex, activeId: null }; + if (raw.startsWith(`${FLOW_SLUG}/`)) { + const id = raw.slice(FLOW_SLUG.length + 1); + return { + index: flowIndex, + activeId: STEP_IDS.includes(id) ? id : null, + }; + } } const idx = entries.findIndex((e) => e.slug === raw); return idx === -1 @@ -94,9 +100,11 @@ function Deck({ variantKey }) { if (inFlow) { if (e.key === "Escape" || e.key === "Home") { - // First Esc/Home returns the flow to overview; a second exits forward. - if (activeId) setActiveId(null); - else setIndex((i) => Math.min(i + 1, entries.length - 1)); + // Esc/Home returns the flow to its overview (per CLAUDE.md interaction + // model). It never advances the deck, so pressing Esc to leave + // fullscreen does not also jump slides. To exit the flow, arrow back. + e.preventDefault(); + setActiveId(null); return; } const idx = activeId ? STEP_IDS.indexOf(activeId) : -1; @@ -171,10 +179,18 @@ function Deck({ variantKey }) { aria-hidden="true" /> )} - {/* Return to the variant picker to switch talks (keyboard reachable). */} - + {/* Return to the variant picker to switch talks (keyboard reachable). + Hidden on the flow, where the section label occupies the same + top-left corner; arrow back out of the flow to reach it. */} + {!inFlow && ( + + )} {inFlow ? ( <>

@@ -207,7 +223,13 @@ function Deck({ variantKey }) { data-active={i === index || undefined} onClick={() => navigateTo(i)} aria-label={ - e.type === "flow" ? "Interactive Flow" : `Slide ${i + 1}` + e.type === "flow" + ? "Interactive flow" + : e.section + ? `Section ${e.section}` + : e.slug === "title" + ? "Title" + : e.slug.replace(/-/g, " ") } /> ))} diff --git a/src/slides/SlideShow.jsx b/src/slides/SlideShow.jsx index 28a9968..a03f889 100644 --- a/src/slides/SlideShow.jsx +++ b/src/slides/SlideShow.jsx @@ -262,7 +262,7 @@ function WhereToStartSlide({ section }) { // with plain-string `inviteLines` from the manifest (e.g. the community close, // which must not point at the internal Slack channel). function WhatsNextSlide({ section, inviteLines }) { - const body = inviteLines ? ( + const body = inviteLines?.length ? ( inviteLines.map((line, i) =>

{line}

) ) : ( <> diff --git a/src/slides/SlideShow.test.jsx b/src/slides/SlideShow.test.jsx index 79f1613..93c4e63 100644 --- a/src/slides/SlideShow.test.jsx +++ b/src/slides/SlideShow.test.jsx @@ -72,4 +72,14 @@ describe("SlideShow – WCAG & copy-rule checks", () => { expect(indexCss).toContain("prefers-reduced-motion"); expect(indexCss).toContain("reduce"); }); + + // Regression: an empty inviteLines array must fall back to the default invite, + // not render the load-bearing closing slide with a blank body. + it("WhatsNext falls back to the default invite for empty inviteLines", () => { + const WhatsNext = SLIDE_REGISTRY.whatsNext; + const { container } = render(); + expect(container.querySelector(".sl-invite").textContent.trim()).not.toBe( + "", + ); + }); }); diff --git a/tests/a11y.spec.js b/tests/a11y.spec.js index c271374..c54afca 100644 --- a/tests/a11y.spec.js +++ b/tests/a11y.spec.js @@ -1,20 +1,24 @@ import { test, expect } from "@playwright/test"; import AxeBuilder from "@axe-core/playwright"; -// Mirror SLIDE_SLUGS from SlideShow.jsx — keep in sync when slides change. +// Mirror the ingage variant slide slugs from src/data/variants.js (ingageArc). +// Keep in sync when that arc changes. The deck now requires a ?variant=; with +// none it shows the variant picker, so every nav pins ?variant=ingage. const SLIDE_SLUGS = [ "title", - "requirements", - "hook", - "sdd", - "spec-kit", - "benefits", - "honest-close", + "quote-requirements", + "whats-the-problem", + "whats-sdd", + "whats-spec-kit", + "why-should-i-care", + "what-am-i-still-figuring-out", + "where-to-start", "whats-next", - "learn-more", - "qa-backup", ]; +// Build a deck URL for the ingage variant, optionally at an in-deck hash. +const deckUrl = (hash) => `/?variant=ingage${hash ? `#${hash}` : ""}`; + // At 1920×1080 (viewport set in playwright.config.js), vmin = 10.8px. // Thresholds come from docs/accessibility.md. const FONT = { @@ -32,7 +36,7 @@ async function fontSize(page, selector) { // ── Slides ────────────────────────────────────────────────────────────────── for (const slug of SLIDE_SLUGS) { - const url = slug === "title" ? "/" : `/#${slug}`; + const url = slug === "title" ? deckUrl() : deckUrl(slug); test(`slide:${slug} — no axe violations`, async ({ page }) => { await page.goto(url); @@ -82,7 +86,7 @@ for (const slug of SLIDE_SLUGS) { // ── Interactive flow ───────────────────────────────────────────────────────── test("flow — no axe violations (overview)", async ({ page }) => { - await page.goto("/#flow"); + await page.goto(deckUrl("spec-kit-flow")); await page.waitForSelector(".react-flow"); const results = await new AxeBuilder({ page }) @@ -97,7 +101,7 @@ test("flow — no axe violations (overview)", async ({ page }) => { }); test("flow — no axe violations (detail panel open)", async ({ page }) => { - await page.goto("/#flow/specify"); + await page.goto(deckUrl("spec-kit-flow/specify")); await page.waitForSelector(".detail-panel"); const results = await new AxeBuilder({ page }) @@ -108,7 +112,7 @@ test("flow — no axe violations (detail panel open)", async ({ page }) => { }); test("flow — detail panel font sizes meet minimums", async ({ page }) => { - await page.goto("/#flow/specify"); + await page.goto(deckUrl("spec-kit-flow/specify")); await page.waitForSelector(".detail-panel"); const cmd = await fontSize(page, ".detail-cmd"); From 7420a9ddaa803b27f1c90396a048ae92f0f07c1a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 23:16:39 +0000 Subject: [PATCH 12/12] Cleanup: de-duplicate variant arcs + reload-free variant switching #6 variants.js: shared entries (creed/hook/sdd/specKit/flow/why/honestClose/ whereToStart) are defined once and composed per arc; section counters are derived from position by withSections (mark cold-opens numbered:false) instead of hand-written literals. Auto-numbering reproduces the prior sections exactly (ingage 1-8, gdg 1-10); withSections clones so shared consts aren't mutated. #7 App.jsx: variant is now React state. Selecting/leaving the picker pushState's a shareable URL and updates state instead of forcing a full page reload (no bundle re-download or flow re-init mid-talk); a popstate listener handles back/forward; the Deck is keyed on the variant so it remounts to the title on switch. Deck takes an onExit prop for the 'Pick a talk' control. docs/tech-stack.md updated. Lint clean, 97 tests pass, build green. https://claude.ai/code/session_01468czqpkhiBJY7v812KmgB --- docs/tech-stack.md | 4 +- src/App.jsx | 74 +++++++++++++---------- src/data/variants.js | 139 ++++++++++++++++++++++++++----------------- 3 files changed, 129 insertions(+), 88 deletions(-) diff --git a/docs/tech-stack.md b/docs/tech-stack.md index 284379b..46ae390 100644 --- a/docs/tech-stack.md +++ b/docs/tech-stack.md @@ -53,6 +53,6 @@ src/ - **Single source of truth for flow content** is `src/data/steps.js` (`STEPS` array + `TIER_META`). Edit content there, not in the node components. - **All styling lives in `src/index.css`** as plain CSS with `vmin`-based responsive sizing. No CSS modules, no Tailwind. - **Fonts** load via a Google Fonts `@import` at the top of `index.css`: Sora, Heebo, IBM Plex Mono, and Caveat (the handwritten annotation face). -- **Per-variant deck manifest** is `src/data/variants.js` (`VARIANTS`, `DEFAULT_VARIANT`, `isKnownVariant`, `resolveVariant`). Each variant has a `label`, a `meta` object (`room`/`length`/`demo`, read by the picker), and `entries`: an ordered list (`type: "slide"` with `id`/`slug`/`section`/`props`, or `type: "flow"`). This is the single source of truth for which slides appear, in what order, with what section counter and slug, plus how the picker describes each variant. Slide components are registered by `id` in `SLIDE_REGISTRY` (`src/slides/SlideShow.jsx`). Add or reorder slides here, not by editing nav math; add a variant here and it appears in the picker automatically. -- **Variant selection + picker.** `App.jsx` reads `?variant=` at load: a known key opens that deck directly; no variant or an unknown one shows the variant picker (`src/picker/VariantPicker.jsx`) instead of silently defaulting (see `specs/001-variant-picker/`). `DEFAULT_VARIANT`/`resolveVariant` remain for callers that want a guaranteed variant, but the load path uses `isKnownVariant`. The in-deck location lives in the URL hash, so variant + spot is shareable (e.g. `/?variant=ingage#whats-sdd`). Selecting in the picker sets `?variant=`; a "Pick a talk" control returns to it. Deck nav iterates the active variant's `entries`; the flow is just an entry, so there is no special flow-index math. +- **Per-variant deck manifest** is `src/data/variants.js` (`VARIANTS`, `DEFAULT_VARIANT`, `isKnownVariant`, `resolveVariant`). Each variant has a `label`, a `meta` object (`room`/`length`/`demo`, read by the picker), and `entries`: an ordered list (`type: "slide"` with `id`/`slug`/`props`, or `type: "flow"`). Entries shared across variants are defined once as constants and composed per arc; **section counters are derived from position by `withSections`** (mark cold-open slides `numbered: false`), not written by hand, so inserting or reordering renumbers the rest automatically. This is the single source of truth for which slides appear, in what order, with what slug, plus how the picker describes each variant. Slide components are registered by `id` in `SLIDE_REGISTRY` (`src/slides/SlideShow.jsx`). Add or reorder slides here, not by editing nav math; add a variant here and it appears in the picker automatically. +- **Variant selection + picker.** The active variant is React state in `App.jsx`, initialized from `?variant=` (a known key opens that deck; no variant or an unknown one shows the variant picker `src/picker/VariantPicker.jsx` instead of silently defaulting, see `specs/001-variant-picker/`). `DEFAULT_VARIANT`/`resolveVariant` remain for callers that want a guaranteed variant, but resolution uses `isKnownVariant`. **Switching is in-app, not a full reload**: selecting/leaving `pushState`s a shareable URL (`/?variant=`, no fragment so the deck opens at the title) and updates state; a `popstate` listener re-resolves on back/forward. The `Deck` is keyed on the variant so it remounts (and resets to the title) on switch. The in-deck location still lives in the URL hash, so variant + spot is shareable (e.g. `/?variant=ingage#whats-sdd`). A "Pick a talk" control (hidden on the flow) returns to the picker. Deck nav iterates the active variant's `entries`; the flow is just an entry, so there is no special flow-index math. - **Deep-linkable URL hash.** The `Deck` in `App.jsx` mirrors the current location into the URL hash so any spot is shareable: no hash is the title, `#whats-the-problem` / `#whats-sdd` / etc. are slides by slug, `#spec-kit-flow` is the flow overview, and `#spec-kit-flow/` (e.g. `#spec-kit-flow/analyze`) opens the flow focused on a node. Slugs come from the active variant's `entries` in `variants.js`. Node ids come from `STEP_IDS` in `steps.js`. The hash is written with `replaceState` (no history spam) and a `hashchange` listener syncs state for opened or hand-edited links. diff --git a/src/App.jsx b/src/App.jsx index 357371b..015c38c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,37 +7,55 @@ import VariantPicker from "./picker/VariantPicker.jsx"; const FLOW_SLUG = "spec-kit-flow"; -function readVariantParam() { - return new URLSearchParams(window.location.search).get("variant"); -} - -// Choosing a variant reflects it in the URL; the reload enters deck mode at the -// first slide. We navigate to the bare path + query (no fragment) so any stale -// in-deck hash on the picker URL is dropped and the deck opens at the title. The -// variant is fixed for the session, so switching is a reload, consistent with -// how variant resolution already works. -function selectVariant(key) { - window.location.href = `${window.location.pathname}?variant=${encodeURIComponent(key)}`; -} - -// Returning to the picker drops the variant (and any in-deck hash) so resolution -// falls back to picker mode. -function backToPicker() { - window.location.assign(window.location.pathname); +// The active variant resolved from the URL: a known ?variant= key, else null +// (which means "show the picker"). An unknown key resolves to null too, so a bad +// link is recoverable rather than silently defaulting. +function variantFromUrl() { + const key = new URLSearchParams(window.location.search).get("variant"); + return isKnownVariant(key) ? key : null; } export default function App() { - const variantKey = readVariantParam(); - // No variant, or an unknown one: show the picker rather than silently - // defaulting. A known variant goes straight into its deck, preserving the - // existing deep-link behavior (including the in-deck hash). - if (!isKnownVariant(variantKey)) { - return ; + // Variant lives in component state, so switching is an in-app transition (no + // full reload): instant, and it never re-downloads the bundle or re-inits the + // flow graph mid-talk. The URL is kept in sync for shareability + back/forward. + const [variantKey, setVariantKey] = useState(variantFromUrl); + + // Switch into a deck: push a shareable URL (path + query, no fragment so the + // deck opens at the title) and update state. + function selectVariant(key) { + window.history.pushState( + null, + "", + `${window.location.pathname}?variant=${encodeURIComponent(key)}`, + ); + setVariantKey(key); } - return ; + + // Return to the picker: drop the variant (and any in-deck hash) from the URL. + function backToPicker() { + window.history.pushState(null, "", window.location.pathname); + setVariantKey(null); + } + + // Browser back/forward across picker <-> deck transitions: re-resolve from URL. + useEffect(() => { + function onPop() { + setVariantKey(variantFromUrl()); + } + window.addEventListener("popstate", onPop); + return () => window.removeEventListener("popstate", onPop); + }, []); + + if (!variantKey) return ; + // Keying the deck on the variant remounts it on switch, so its in-deck + // location resets to the title (the pushed URL carries no hash). + return ( + + ); } -function Deck({ variantKey }) { +function Deck({ variantKey, onExit }) { const entries = VARIANTS[variantKey].entries; const flowIndex = entries.findIndex((e) => e.type === "flow"); @@ -183,11 +201,7 @@ function Deck({ variantKey }) { Hidden on the flow, where the section label occupies the same top-left corner; arrow back out of the flow to reach it. */} {!inFlow && ( - )} diff --git a/src/data/variants.js b/src/data/variants.js index ad1237c..a1131d8 100644 --- a/src/data/variants.js +++ b/src/data/variants.js @@ -4,13 +4,16 @@ // variant live in docs/audience-*.md; the index is docs/audience.md. // // Entry shapes: -// { type: "slide", id, slug, section?, props? } +// { type: "slide", id, slug, numbered?, props? } // - `id` maps to a component in SLIDE_REGISTRY (src/slides/SlideShow.jsx) -// - `section` is the printed counter ("01" …); omit for unnumbered slides -// (title, epigraph) +// - `numbered: false` marks an unnumbered cold-open slide (title, creed, +// who-am-I); every other entry is numbered in order by withSections // - `props` are passed to the slide component, for per-variant copy -// { type: "flow", slug, section, label } -// - the interactive React Flow block; has no slide component +// { type: "flow", slug, label } +// - the interactive React Flow block; has no slide component, but IS numbered +// +// Section counters are derived from position (withSections), not written by hand, +// so inserting or reordering an entry renumbers the rest automatically. // // Selected at load time by the `?variant=` query param, validated against the // keys below via isKnownVariant. When no (or an unknown) variant is given, the @@ -19,41 +22,75 @@ export const DEFAULT_VARIANT = "ingage"; -// The ingage lightning arc (7-8 min, delivered). Section counter starts at the -// Hook; title and the requirements creed are unnumbered. -const ingageArc = [ - { type: "slide", id: "title", slug: "title" }, - { type: "slide", id: "requirements", slug: "quote-requirements" }, - { type: "slide", id: "hook", slug: "whats-the-problem", section: 1 }, - { type: "slide", id: "sdd", slug: "whats-sdd", section: 2 }, - { type: "slide", id: "specKit", slug: "whats-spec-kit", section: 3 }, - { - type: "flow", - slug: "spec-kit-flow", - section: 4, - label: "What’s The Process?", - }, - { type: "slide", id: "why", slug: "why-should-i-care", section: 5 }, - { - type: "slide", - id: "honestClose", - slug: "what-am-i-still-figuring-out", - section: 6, - }, - { type: "slide", id: "whereToStart", slug: "where-to-start", section: 7 }, - { type: "slide", id: "whatsNext", slug: "whats-next", section: 8 }, -]; +// Assign sequential section numbers ("01", "02" …) to numbered entries by +// position, leaving `numbered: false` entries without a section. Returns fresh +// objects so shared entry constants below are never mutated (and the `numbered` +// marker is stripped from the result the app sees). +function withSections(entries) { + let n = 0; + return entries.map(({ numbered, ...rest }) => { + if (numbered === false) return rest; + n += 1; + return { ...rest, section: n }; + }); +} + +// Entries shared verbatim across variants (defined once). Title and What's Next +// differ by per-variant props, so they are written inline in each arc; who-am-I, +// the demo transition, and the lessons beat are gdg-only. +const creed = { + type: "slide", + id: "requirements", + slug: "quote-requirements", + numbered: false, +}; +const hook = { type: "slide", id: "hook", slug: "whats-the-problem" }; +const sdd = { type: "slide", id: "sdd", slug: "whats-sdd" }; +const specKit = { type: "slide", id: "specKit", slug: "whats-spec-kit" }; +const flow = { + type: "flow", + slug: "spec-kit-flow", + label: "What’s The Process?", +}; +const why = { type: "slide", id: "why", slug: "why-should-i-care" }; +const honestClose = { + type: "slide", + id: "honestClose", + slug: "what-am-i-still-figuring-out", +}; +const whereToStart = { + type: "slide", + id: "whereToStart", + slug: "where-to-start", +}; + +// The ingage lightning arc (7-8 min, delivered). Title and creed are unnumbered; +// the section counter starts at the Hook (sections 1-8). What's Next uses the +// component default invite (internal Slack). +const ingageArc = withSections([ + { type: "slide", id: "title", slug: "title", numbered: false }, + creed, + hook, + sdd, + specKit, + flow, + why, + honestClose, + whereToStart, + { type: "slide", id: "whatsNext", slug: "whats-next" }, +]); // The GDG Cincinnati arc (~40 min community talk, all-dev). Superset of the -// lightning arc: adds an intro/who-am-I, a "time for the demo" transition (the -// centerpiece), and a practitioner "what I've learned" beat; re-points the close -// to a community invite (no internal Slack); sections renumber accordingly. -// Per-variant copy is plain-data props (no JSX in this data module). -const gdgArc = [ +// lightning arc: adds an unnumbered who-am-I, a "time for the demo" transition +// (the centerpiece), and a practitioner "what I've learned" beat; re-points the +// close to a community invite (no internal Slack). Sections renumber to 1-10 +// automatically. Per-variant copy is plain-data props (no JSX in this module). +const gdgArc = withSections([ { type: "slide", id: "title", slug: "title", + numbered: false, props: { taglineLines: [ "Structured requirements an agent can act on.", @@ -66,6 +103,7 @@ const gdgArc = [ type: "slide", id: "whoami", slug: "who-am-i", + numbered: false, props: { name: "Patrick Hammond", role: "Director at Ingage Partners. Co-founder and CTO at Atomic Robot.", @@ -78,31 +116,20 @@ const gdgArc = [ photoAlt: "Patrick Hammond", }, }, - { type: "slide", id: "requirements", slug: "quote-requirements" }, - { type: "slide", id: "hook", slug: "whats-the-problem", section: 1 }, - { type: "slide", id: "sdd", slug: "whats-sdd", section: 2 }, - { type: "slide", id: "specKit", slug: "whats-spec-kit", section: 3 }, - { - type: "flow", - slug: "spec-kit-flow", - section: 4, - label: "What’s The Process?", - }, - { type: "slide", id: "demo", slug: "demo", section: 5 }, - { type: "slide", id: "why", slug: "why-should-i-care", section: 6 }, - { type: "slide", id: "lessons", slug: "what-ive-learned", section: 7 }, - { - type: "slide", - id: "honestClose", - slug: "what-am-i-still-figuring-out", - section: 8, - }, - { type: "slide", id: "whereToStart", slug: "where-to-start", section: 9 }, + creed, + hook, + sdd, + specKit, + flow, + { type: "slide", id: "demo", slug: "demo" }, + why, + { type: "slide", id: "lessons", slug: "what-ive-learned" }, + honestClose, + whereToStart, { type: "slide", id: "whatsNext", slug: "whats-next", - section: 10, props: { inviteLines: [ "If you’re building with any of this, I want to hear how it goes.", @@ -110,7 +137,7 @@ const gdgArc = [ ], }, }, -]; +]); // Each variant also carries `meta` (room, length, demo) so the variant picker // can list and describe variants straight from this manifest. Source of truth