From d9547bdc408d6a5a92216daf752b6034a7477f92 Mon Sep 17 00:00:00 2001 From: Shane Neubauer Date: Wed, 30 Sep 2026 17:47:04 +1000 Subject: [PATCH 1/4] handbook: add skeleton and draft of part one --- .claude/CLAUDE.md | 10 +- README.md | 3 + apps/handbook/AUTHORING.md | 173 ++++++++++ apps/handbook/CLAUDE.md | 19 ++ apps/handbook/README.md | 42 +++ .../content/handbook/component-showcase.mdx | 115 +++++++ apps/handbook/content/handbook/index.mdx | 22 ++ apps/handbook/content/handbook/meta.json | 5 + .../content/handbook/part-five/meta.json | 5 + .../handbook/part-five/stub-chapter.mdx | 7 + .../content/handbook/part-four/meta.json | 5 + .../handbook/part-four/stub-chapter.mdx | 7 + .../content/handbook/part-one/assumptions.mdx | 74 +++++ .../handbook/part-one/finding-problems.mdx | 72 ++++ .../content/handbook/part-one/ideas.mdx | 58 ++++ .../content/handbook/part-one/meta.json | 13 + .../handbook/part-one/right-problem.mdx | 76 +++++ .../handbook/part-one/talking-to-people.mdx | 98 ++++++ .../content/handbook/part-one/who-has-it.mdx | 78 +++++ .../content/handbook/part-one/your-idea.mdx | 77 +++++ .../content/handbook/part-three/meta.json | 5 + .../handbook/part-three/stub-chapter.mdx | 7 + .../content/handbook/part-two/meta.json | 5 + .../handbook/part-two/stub-chapter.mdx | 7 + apps/handbook/next.config.mjs | 30 ++ apps/handbook/notes/README.md | 13 + apps/handbook/notes/TEMPLATE.md | 28 ++ apps/handbook/notes/index.md | 22 ++ apps/handbook/notes/part-one/.gitkeep | 0 apps/handbook/notes/part-one/_part.md | 14 + apps/handbook/package.json | 40 +++ apps/handbook/postcss.config.mjs | 1 + .../handbook/public/logo/full-color-white.svg | 20 ++ apps/handbook/public/logo/full-color.svg | 20 ++ apps/handbook/public/logo/mark.svg | 14 + apps/handbook/source.config.ts | 55 ++++ .../src/app/(handbook)/[[...slug]]/page.tsx | 62 ++++ apps/handbook/src/app/(handbook)/layout.tsx | 20 ++ apps/handbook/src/app/global.css | 310 ++++++++++++++++++ apps/handbook/src/app/icon.svg | 14 + apps/handbook/src/app/layout.tsx | 75 +++++ apps/handbook/src/app/not-found.tsx | 13 + .../src/components/handbook/aside.tsx | 23 ++ .../src/components/handbook/callout.tsx | 63 ++++ .../src/components/handbook/chapter-meta.tsx | 50 +++ .../src/components/handbook/checklist.tsx | 140 ++++++++ .../src/components/handbook/playbook.tsx | 34 ++ apps/handbook/src/components/provider.tsx | 14 + apps/handbook/src/lib/chapter-status.ts | 5 + apps/handbook/src/lib/layout.shared.tsx | 41 +++ apps/handbook/src/lib/source.ts | 12 + apps/handbook/src/lib/url.ts | 29 ++ apps/handbook/src/mdx-components.tsx | 80 +++++ apps/handbook/tsconfig.json | 33 ++ package.json | 89 ++++- pnpm-lock.yaml | 70 ++++ turbo.json | 1 + 57 files changed, 2415 insertions(+), 3 deletions(-) create mode 100644 apps/handbook/AUTHORING.md create mode 100644 apps/handbook/CLAUDE.md create mode 100644 apps/handbook/README.md create mode 100644 apps/handbook/content/handbook/component-showcase.mdx create mode 100644 apps/handbook/content/handbook/index.mdx create mode 100644 apps/handbook/content/handbook/meta.json create mode 100644 apps/handbook/content/handbook/part-five/meta.json create mode 100644 apps/handbook/content/handbook/part-five/stub-chapter.mdx create mode 100644 apps/handbook/content/handbook/part-four/meta.json create mode 100644 apps/handbook/content/handbook/part-four/stub-chapter.mdx create mode 100644 apps/handbook/content/handbook/part-one/assumptions.mdx create mode 100644 apps/handbook/content/handbook/part-one/finding-problems.mdx create mode 100644 apps/handbook/content/handbook/part-one/ideas.mdx create mode 100644 apps/handbook/content/handbook/part-one/meta.json create mode 100644 apps/handbook/content/handbook/part-one/right-problem.mdx create mode 100644 apps/handbook/content/handbook/part-one/talking-to-people.mdx create mode 100644 apps/handbook/content/handbook/part-one/who-has-it.mdx create mode 100644 apps/handbook/content/handbook/part-one/your-idea.mdx create mode 100644 apps/handbook/content/handbook/part-three/meta.json create mode 100644 apps/handbook/content/handbook/part-three/stub-chapter.mdx create mode 100644 apps/handbook/content/handbook/part-two/meta.json create mode 100644 apps/handbook/content/handbook/part-two/stub-chapter.mdx create mode 100644 apps/handbook/next.config.mjs create mode 100644 apps/handbook/notes/README.md create mode 100644 apps/handbook/notes/TEMPLATE.md create mode 100644 apps/handbook/notes/index.md create mode 100644 apps/handbook/notes/part-one/.gitkeep create mode 100644 apps/handbook/notes/part-one/_part.md create mode 100644 apps/handbook/package.json create mode 100644 apps/handbook/postcss.config.mjs create mode 100644 apps/handbook/public/logo/full-color-white.svg create mode 100644 apps/handbook/public/logo/full-color.svg create mode 100644 apps/handbook/public/logo/mark.svg create mode 100644 apps/handbook/source.config.ts create mode 100644 apps/handbook/src/app/(handbook)/[[...slug]]/page.tsx create mode 100644 apps/handbook/src/app/(handbook)/layout.tsx create mode 100644 apps/handbook/src/app/global.css create mode 100644 apps/handbook/src/app/icon.svg create mode 100644 apps/handbook/src/app/layout.tsx create mode 100644 apps/handbook/src/app/not-found.tsx create mode 100644 apps/handbook/src/components/handbook/aside.tsx create mode 100644 apps/handbook/src/components/handbook/callout.tsx create mode 100644 apps/handbook/src/components/handbook/chapter-meta.tsx create mode 100644 apps/handbook/src/components/handbook/checklist.tsx create mode 100644 apps/handbook/src/components/handbook/playbook.tsx create mode 100644 apps/handbook/src/components/provider.tsx create mode 100644 apps/handbook/src/lib/chapter-status.ts create mode 100644 apps/handbook/src/lib/layout.shared.tsx create mode 100644 apps/handbook/src/lib/source.ts create mode 100644 apps/handbook/src/lib/url.ts create mode 100644 apps/handbook/src/mdx-components.tsx create mode 100644 apps/handbook/tsconfig.json diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index fdd8ecfc49b..551465274f9 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -11,7 +11,8 @@ This is a **Turborepo** monorepo with pnpm workspaces (`apps/*` and `packages/*` │ ├── site/ # Prisma marketing site and multi-zone host (port 3000) │ ├── docs/ # Prisma documentation site (Next.js 16 + Fumadocs, port 3001) │ ├── blog/ # Prisma blog (Next.js + Fumadocs, port 3002) -│ └── eclipse/ # Eclipse design system showcase (Next.js + Fumadocs, port 3003) +│ ├── eclipse/ # Eclipse design system showcase (Next.js + Fumadocs, port 3003) +│ └── handbook/ # Builders Handbook (Next.js + Fumadocs, port 3004) ├── packages/ │ ├── ui/ # Shared UI components (@prisma-docs/ui, no build step) │ └── eclipse/ # Eclipse design system (@prisma/eclipse, published, builds to dist/) @@ -21,7 +22,7 @@ This is a **Turborepo** monorepo with pnpm workspaces (`apps/*` and `packages/*` ``` **Apps** - each pins its own dev port in its `dev` script, so `pnpm dev` at the root starts all -four side by side. +five side by side. - **`site`** (apps/site, port 3000) - The prisma.io marketing site and the root zone of the multi-zone setup. It has no `basePath`, serves its assets from `/site-static`, and owns the @@ -36,6 +37,11 @@ four side by side. (`output: "export"`, unoptimized images) and has no `basePath`, so it is not one of the zones the site app rewrites into. +- **`handbook`** (apps/handbook, port 3004) - The Builders Handbook: MDX chapters in + `content/handbook/`, served under `basePath: "/handbook"` with assets at `/handbook-static`. + Content is dictated by Shane and structured by Claude; `apps/handbook/AUTHORING.md` is binding. + Not yet forwarded by the site app's rewrites. + **Packages:** - **`@prisma-docs/ui`** (packages/ui) - Shared shadcn-style components, hooks, footer data, and diff --git a/README.md b/README.md index e7927a9b4b7..cf595dc9f2b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This repository is a **pnpm monorepo** containing the Prisma documentation, blog |------|--------------| | `apps/docs` | Prisma documentation site (Next.js + Fumadocs) | | `apps/blog` | Prisma blog | +| `apps/handbook` | Builders Handbook (Next.js + Fumadocs), chapters written by Shane, see `apps/handbook/AUTHORING.md` | | `apps/eclipse` | Eclipse design system documentation | | `packages/eclipse` | Eclipse design system component library (`@prisma/eclipse`) | | `packages/ui` | Shared UI components and utilities (`@prisma-docs/ui`) | @@ -36,6 +37,7 @@ This starts all apps via Turbo: - **Docs** — http://localhost:3001 - **Blog** — http://localhost:3002 - **Eclipse** — http://localhost:3003 +- **Handbook** — http://localhost:3004/handbook To run a single app: @@ -43,6 +45,7 @@ To run a single app: pnpm --filter docs dev # docs only pnpm --filter blog dev # blog only pnpm --filter eclipse dev # eclipse design system docs only +pnpm --filter handbook dev # builders handbook only ``` ## Build diff --git a/apps/handbook/AUTHORING.md b/apps/handbook/AUTHORING.md new file mode 100644 index 00000000000..bf9ab73e676 --- /dev/null +++ b/apps/handbook/AUTHORING.md @@ -0,0 +1,173 @@ +# Authoring the Builders Handbook + +This file is the binding content spec. If a chapter contradicts it, the chapter is wrong. + +## The one rule + +**Shane writes the handbook. Claude structures it.** Every claim, opinion, example, number, and recommendation on a page came out of Shane's head first. Claude's job is to turn a raw brain-dump into readable chapters: order the thoughts, cut repetition, tighten sentences, apply the component vocabulary below, and flag gaps. Claude never fills a gap with invented content. A gap becomes a `TODO(shane):` comment in the MDX and a bullet in the review notes. + +Claude is allowed to have ideas. Shane asked for them. They go in the chapter's private outline comment under a "Claude's suggestions" heading, clearly separate from "From your notes". A suggestion becomes content only when Shane says so, in his words. Names, numbers, quotes, and anecdotes are never suggested: those are asked for as questions. + +The previous attempt at this book (`buildaproduct`) failed because the content was generated. Every rule below exists to stop that happening again. + +## Workflow, chapter by chapter + +1. **Dictate.** Shane brain-dumps into `notes//.md`. Voice transcript, bullet spew, half sentences, all fine. Tangents welcome; they get cut later. Nothing in `notes/` is ever published or linted. +2. **Structure.** Claude reads the note and writes `content/handbook//.mdx` with `status: structured`. Rules: + - Keep Shane's claims, order of importance, and voice. Rephrase for clarity; do not rephrase for smoothness. + - Keep his examples and numbers exactly. If a number is missing, `TODO(shane): number?`, not a plausible guess. + - Use only the components in this file. + - At the end of the MDX, add a private review-notes comment listing: what was cut, what was reordered, every TODO, and any place Claude was unsure it understood the point. + +Private comments (outlines, review notes) use MDX comment syntax, wrapped so Prettier leaves them alone. Shane's editors format on save with Prettier, which rewrites a bare multi-line `{/* */}` into invalid MDX and takes every page down with a 500. HTML comments (``) are not valid MDX at all. The blank lines around the markers are required: + +```mdx +{/* prettier-ignore-start */} + +{/* +OUTLINE (private, never published) + +FROM YOUR NOTES +- ... + +CLAUDE'S SUGGESTIONS +- ... + +QUESTIONS FOR YOU +- ... +*/} + +{/* prettier-ignore-end */} +``` + +A chapter that has been scoped but not written carries an outline comment in this shape and a one-line visible placeholder. +3. **Review.** Shane reads the diff, fixes what Claude got wrong, resolves TODOs, and flips `status: reviewed`. +4. **Publish.** Remove the `status` field. The badge disappears. + +Chapter frontmatter: + +```yaml +--- +title: "3. Name of the chapter" # number in the title; the sidebar shows titles verbatim +description: One or two sentences. The chapter's promise, plainly. +status: stub | dictated | structured | reviewed # omit once published +readingTime: 12 # minutes, hand-set +--- +``` + +Chapter numbering is manual and global across Parts. Renumber by hand when a chapter moves. + +## Voice + +- Light and easy reading. Not a textbook. `content/handbook/index.mdx` is the reference for tone: Shane wrote it himself. +- Talk to the reader as "you". "We" is fine for the handbook and the reader going through it together ("we'll step through..."). Warm is fine. An exclamation mark now and then is fine. +- Where one of Shane's own sentences breaks a rule in this file, Shane's sentence wins. The rules exist to stop Claude's defaults, not to edit Shane. +- Opinions stated as opinions. "Do X" beats "you may want to consider X." The book has a point of view because Shane does. +- Plain words. A tired non-native-English reader gets every sentence on first pass. +- Concrete over abstract. A number, a filename, a quote from a real person beats "many builders find." +- Short paragraphs. Three to five sentences. One idea each. +- Technical and non-technical readers share the text. Where they need different instructions, say so in one line and give both. Do not write two books. + +## Banned on sight + +Text that reads frictionless-smooth is a bug. Specifically banned in all prose, headings, captions, and code comments: + +1. **Negation pivots.** "Not just X, it's Y." "This isn't about X, it's about Y." State it positively. One deliberate, load-bearing contrast per chapter at most. +2. **Rule-of-three cadence.** "Clear, concise, and compelling" as the default list shape. Vary: two items, four items, one flat statement. Three is fine when three is the count. +3. **Empty intensifiers.** delve, crucial, robust, seamless, leverage (verb), unlock, elevate, landscape, journey, game-changer, empower, supercharge, transformative, harness, navigate (metaphorical), tapestry. Say what the thing does. +4. **Reflexive hedging.** "It's important to note", "it's worth considering", both-sidesing where the book holds an opinion. +5. **Perfect parallelism in prose.** Every bullet opening with a bolded label and colon; uniform paragraph lengths; symmetric section shapes. Structured components (Playbook, Checklist) are UI and exempt. Running prose must be lumpier than the components. +6. **Grand openers and closers.** Throat-clearing intros, inspirational wrap-ups, "ultimately", "at the end of the day", any closing paragraph that restates the chapter. +7. **Em dashes.** Banned everywhere, including code comments and captions. Use a period, comma, colon, or parentheses. En dashes only for numeric ranges (10–15 min). +8. **Ceremonial metaphors.** "A testament to", "cornerstone", "the data tells a story", personified abstractions. +9. **Cryptic wordplay.** Headings or sentences that tease the point instead of stating it. "The menu changed" as a heading is out; "Pricing changed after month three" is in. Vivid is fine when the meaning is fully on the page. +10. **Invented specifics.** Any name, number, quote, or anecdote that is not in Shane's notes. This is the one that matters most. + +## Chapter anatomy + +Not every chapter has every part, and the order flexes. Playbooks and checklists are optional: use one only when the chapter has real steps or a real output. The fuller shape is: + +1. **Title + description.** The description is the promise: what the reader can do after this chapter. +2. **Opening paragraphs.** Straight into the point. No "in this chapter we will." +3. **Body sections** under `##` headings. Readers skim; headings should make sense as a list on their own (that list is the right-hand TOC). +4. **One worked example**, threaded through as `:::example` callouts placed right after the concept they illustrate. Real, from Shane's experience, awkward parts included. +5. **Playbook**, if the chapter has actions. Numbered steps, one bounded action each, time estimate on every step. +6. **Checklist** at the end: what must be true before the next chapter is worth reading. Three to six items. + +Target length: 600 to 900 words of prose. Components do not count toward the budget. A chapter that wants to run longer gets split into two pages. + +## Component vocabulary + +Callouts, directive syntax (preferred in source): + +```mdx +:::note +Context the reader might want. Skipping it costs nothing. +::: + +:::tip +A shortcut. Skipping it costs time. +::: + +:::warning +Something that fails silently. Skipping it costs an afternoon. +::: + +:::example +A beat from a real build. How it actually went. +::: +``` + +Custom title: `...`. + +Code blocks: fenced, with a language. Add `title="path/to/file.ts"` for a filename tab. The copy button is automatic. + +Playbook: + +```mdx + + + Optional body. Prose, a code block, a short list. + + + +``` + +Checklist (interactive, persists per browser; `id` must be unique across the whole handbook and stable, or readers' ticks reset): + +```mdx + + Something that must be true. + Something else. + +``` + +Margin note (side commentary; floats to the gutter on wide screens, inset card elsewhere): + +```mdx + +``` + +Footnotes: GitHub-style `[^1]` with the definition at the bottom of the file. + +Tables: GitHub-style pipes. Keep them narrow; the reading column is 44rem. + +## Files + +``` +apps/handbook/ +├── AUTHORING.md # this file +├── notes/ # raw dictation, never published +│ └── /.md +├── content/handbook/ # published MDX +│ ├── meta.json # Part order +│ ├── index.mdx # landing / table of contents +│ └── / +│ ├── meta.json # Part title + chapter order +│ └── .mdx +└── src/components/handbook/ # Callout, Aside, Playbook, Checklist, ChapterMeta +``` + +Parts are folders. A Part's title lives in its `meta.json`. Chapter order within a Part is the `pages` array. Slugs are filenames; pick them once and do not rename (URLs are forever). diff --git a/apps/handbook/CLAUDE.md b/apps/handbook/CLAUDE.md new file mode 100644 index 00000000000..ea2a5082c0f --- /dev/null +++ b/apps/handbook/CLAUDE.md @@ -0,0 +1,19 @@ +# apps/handbook + +The Builders Handbook. Read `AUTHORING.md` before writing or editing anything under `content/`. + +Non-negotiable: **content comes from Shane's notes in `notes/`, never from Claude.** Claude structures, tightens, and applies the component vocabulary. A missing fact becomes `TODO(shane): ...` in the MDX, not a plausible sentence. The banned-phrasing list in `AUTHORING.md` applies to every word on every page, including code comments. + +Shell and components live in `src/`; they follow `apps/docs` and `apps/eclipse` patterns (stock Fumadocs `DocsLayout`, Eclipse tokens, `@prisma-docs/ui` helpers). Port 3004, `basePath: "/handbook"`, `assetPrefix: "/handbook-static"`. + +Commits in this repo need a type, a scope of `handbook`, and a Linear reference in the body. See the root `CONTRIBUTING.md`. + + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/apps/handbook/README.md b/apps/handbook/README.md new file mode 100644 index 00000000000..9a586f749a8 --- /dev/null +++ b/apps/handbook/README.md @@ -0,0 +1,42 @@ +# Builders Handbook + +A handbook for people who build software with AI, technical or not. Published by Prisma. Next.js + Fumadocs, one chapter per MDX file, served under `basePath: "/handbook"` as a multi-zone app alongside docs and blog. + +**Content is written by Shane, structured with AI assistance.** Read `AUTHORING.md` before touching anything under `content/`. + +## Run locally + +```bash +pnpm install +pnpm --filter handbook dev +``` + +Runs on **http://localhost:3004/handbook** (site is 3000, docs 3001, blog 3002, eclipse 3003). `@prisma/eclipse` must be built first; `pnpm dev` at the root does that via Turbo, or run `pnpm --filter @prisma/eclipse build` once. + +## Structure + +- `content/handbook/` — published chapters. Parts are folders, each with a `meta.json`. +- `notes/` — Shane's raw dictation per chapter. Never published. +- `AUTHORING.md` — voice, banned phrasing, chapter anatomy, component vocabulary, dictation workflow. +- `src/components/handbook/` — `Callout`, `Aside`, `Playbook`/`Step`, `Checklist`/`Check`, `ChapterMeta`. +- `src/app/(handbook)/` — Fumadocs `DocsLayout` (sidebar + TOC) and the `[[...slug]]` page renderer. +- `source.config.ts` — MDX pipeline: directive callouts, Shiki via `@prisma-docs/ui`, frontmatter schema (`status`, `readingTime`). + +## Wiring into prisma.io/handbook (not done yet) + +When this goes public, `apps/site/next.config.mjs` needs the same treatment docs and blog get: + +```js +const HANDBOOK_ORIGIN = process.env.NEXT_HANDBOOK_ORIGIN || "https://handbook.prisma.io"; +// ...in rewrites().beforeFiles: +{ source: "/handbook", destination: `${HANDBOOK_ORIGIN}/handbook`, missing: [{ type: "host", value: HANDBOOK_ORIGIN_HOST }] }, +{ source: "/handbook/:any*", destination: `${HANDBOOK_ORIGIN}/handbook/:any*`, missing: [...] }, +{ source: "/handbook-static/:path*", destination: `${HANDBOOK_ORIGIN}/handbook-static/:path*`, missing: [...] }, +``` + +plus a Vercel project for this app, `NEXT_HANDBOOK_ORIGIN` on the site project, and a CSP/security-headers block copied from `apps/blog/next.config.mjs`. Search, OG images, `llms.txt`, and sitemap are also deferred until there are real chapters. + +## Related + +- `ARCHITECTURE.md` at the repo root explains the multi-zone setup. +- `apps/docs` and `apps/blog` are the sibling zones this app copies its patterns from. diff --git a/apps/handbook/content/handbook/component-showcase.mdx b/apps/handbook/content/handbook/component-showcase.mdx new file mode 100644 index 00000000000..738c479b982 --- /dev/null +++ b/apps/handbook/content/handbook/component-showcase.mdx @@ -0,0 +1,115 @@ +--- +title: Component showcase +description: Every building block a chapter can use, demonstrated once with obviously fake text. Delete this chapter before the handbook ships. +status: stub +readingTime: 4 +--- + +Everything below is DUMMY TEXT. It exists to show what each component looks like in the shell. If a sentence on this page sounds like advice, it is not; it is filler. + +## Prose + +A chapter is mostly paragraphs. Lorem-flavoured filler: the quick brown fox configures a deployment and the lazy dog reviews the pull request. Inline `code` looks like this, a [link](/part-one/ideas) looks like this, and **bold** is for the one phrase per section that must not be skimmed past. + +A second paragraph, so the rhythm between paragraphs is visible. Sentences vary in length. Some are short. Others run on a little longer so that the line-height and measure can be judged against something that resembles real writing rather than a single line of placeholder. + +### A third-level heading + +Third-level headings appear in the table of contents on the right. Fourth-level headings do not, so use them sparingly. + +## Callouts + +Four moods. Use the directive syntax in MDX; it reads better in the source than a JSX tag. + +:::note +A **note** is context the reader might want. Skipping it costs nothing. +::: + +:::tip +A **tip** is a shortcut. Skipping it costs time. +::: + +:::warning +A **warning** is a thing that goes wrong silently. Skipping it costs a bad afternoon. +::: + +:::example +An **example** is a beat from a real build: this is how it actually went. Placeholder story: the founder shipped on a Tuesday, the environment variable was missing, and the fix was pasting the build log into the agent. +::: + +The JSX form takes a custom title: + + + Same component, same styling, your own label. + + +## Code + +Fenced blocks get a copy button. Add a `title` for a filename tab. + +```ts title="src/db.ts" +import { PrismaClient } from "@prisma/client"; + +export const db = new PrismaClient(); +``` + +```bash +bun install +bun run dev +``` + +Inline code stays inline: run `bun run dev` and open the port it prints. + +## Playbook + +Numbered, bounded actions. One action per step. Each step carries a time estimate so the reader can decide whether to start now or after lunch. + + + + A sentence or two of instruction. Not a wall. If the step needs a wall, it is two steps. + + + Steps can contain code: + + ```bash + echo "placeholder" + ``` + + + + +## Margin note + +The paragraph next to this note keeps its flow. On a wide screen the note sits in the gutter to the right. On a narrow screen it renders as an inset card right here. + + + +More filler paragraph so the float has something to sit beside. The reader who ignores the margin note loses nothing essential; the reader who reads it gets a little more colour on why the paragraph says what it says. + +## Footnotes + +A claim that wants a source.[^1] Another claim.[^2] + +[^1]: Footnotes render at the bottom of the chapter. Placeholder source. +[^2]: Second placeholder source. + +## Table + +| Thing | Placeholder value | Note | +| ------------ | ----------------- | ----------------------- | +| First thing | 42 | Filler | +| Second thing | 7 | Also filler | +| Third thing | 0 | You get the idea by now | + +## Checklist + +Every chapter ends with one. Ticks persist in the reader's browser. + + + You have seen every component on this page. + You have toggled dark mode using the control at the bottom of the sidebar. + You have resized the window to see the margin note move. + You understand that none of this text is real content. + diff --git a/apps/handbook/content/handbook/index.mdx b/apps/handbook/content/handbook/index.mdx new file mode 100644 index 00000000000..74437fae0e0 --- /dev/null +++ b/apps/handbook/content/handbook/index.mdx @@ -0,0 +1,22 @@ +--- +title: Builders Handbook +description: How to take a product from an idea to something live with real users, using AI, whether or not you can code. +--- + +This handbook is your guide to building a product, using AI tools, and getting real users. A product is more than just a piece of software. A product is a bit of value that solves a problem for somebody, and often they'll pay money for it. While often simple, it's not always easy to get right. This handbook helps you think through all the important pieces to create something that someone truly cares about. + +## Who this is for + +Anyone who wants to build a product from scratch. You may be technical, but you may not. This handbook is for everyone. + +## Where it takes you + +We will go from zero to having a live product with real users - maybe even some paying customers! We'll step through all the important moments like identifying problems to solve, coming up with ideas, validating them, building and deploying using AI tools, positioning, distribution, getting your first users, and getting more users. + +## Parts + +- **Part I: Coming up with an idea.** Ideation, the problem, who it's for, and validation. Everything before you build anything. +- **Part II: Building the first version.** The tools, the build, the features. +- **Part III: Going live.** Deployment, pricing, taking payments, and getting it in front of people. +- **Part IV: Getting first users.** Basic go-to-market, feedback, learning, and iteration. +- **Part V: Getting more users.** Growth mechanics, product-led growth, and scaling. diff --git a/apps/handbook/content/handbook/meta.json b/apps/handbook/content/handbook/meta.json new file mode 100644 index 00000000000..73e0fcaf381 --- /dev/null +++ b/apps/handbook/content/handbook/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Builders Handbook", + "root": true, + "pages": ["index", "part-one", "part-two", "part-three", "part-four", "part-five", "component-showcase"] +} diff --git a/apps/handbook/content/handbook/part-five/meta.json b/apps/handbook/content/handbook/part-five/meta.json new file mode 100644 index 00000000000..376f9741d8a --- /dev/null +++ b/apps/handbook/content/handbook/part-five/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Part V: Getting more users", + "defaultOpen": true, + "pages": ["stub-chapter"] +} diff --git a/apps/handbook/content/handbook/part-five/stub-chapter.mdx b/apps/handbook/content/handbook/part-five/stub-chapter.mdx new file mode 100644 index 00000000000..dc95a9012a7 --- /dev/null +++ b/apps/handbook/content/handbook/part-five/stub-chapter.mdx @@ -0,0 +1,7 @@ +--- +title: "11. Stub chapter" +description: Part V has one stub so the sidebar shows it until its chapters are dictated. +status: stub +--- + +Placeholder. Shane dictates the chapters for this Part. diff --git a/apps/handbook/content/handbook/part-four/meta.json b/apps/handbook/content/handbook/part-four/meta.json new file mode 100644 index 00000000000..4a19a9f21ce --- /dev/null +++ b/apps/handbook/content/handbook/part-four/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Part IV: Getting first users", + "defaultOpen": true, + "pages": ["stub-chapter"] +} diff --git a/apps/handbook/content/handbook/part-four/stub-chapter.mdx b/apps/handbook/content/handbook/part-four/stub-chapter.mdx new file mode 100644 index 00000000000..7b71e9da6ac --- /dev/null +++ b/apps/handbook/content/handbook/part-four/stub-chapter.mdx @@ -0,0 +1,7 @@ +--- +title: "10. Stub chapter" +description: Part IV has one stub so the sidebar shows it until its chapters are dictated. +status: stub +--- + +Placeholder. Shane dictates the chapters for this Part. diff --git a/apps/handbook/content/handbook/part-one/assumptions.mdx b/apps/handbook/content/handbook/part-one/assumptions.mdx new file mode 100644 index 00000000000..d7566333542 --- /dev/null +++ b/apps/handbook/content/handbook/part-one/assumptions.mdx @@ -0,0 +1,74 @@ +--- +title: "5. What are you assuming?" +description: Write down what needs to be true, then choose what to check first. +--- + +Some parts of your problem sentence will come from things you've seen or heard. Other parts will be guesses. Write down which is which before you go looking for evidence. + +An assumption is something you're treating as true without yet knowing enough to rely on it. You need assumptions to get started. Making them visible gives you a way to check them, and a way to notice when an idea depends on something that turns out to be wrong. + +Take the sentence you've been working on: + +> Person X can't do Y because Z. + +You might know X personally and have seen them struggle with Y. But is Z really the cause? Does the difficulty matter enough for them to do something about it? Are there other people in a similar situation? Each of those questions could change what you build, or whether you build anything. + +## Write statements you can check + +“People want an easier way” doesn't tell you much. It's hard to imagine someone arguing with it, and it gives you little direction for a conversation. + +Make the assumption specific enough that you could discover it's wrong. In our hypothetical designer example, you might write: + +> Waiting for client decisions regularly prevents this designer from finishing work when they planned to. + +That gives you something to investigate. You can ask about recent projects, what held them up, and what happened because of the delay. If the designer usually finishes on time, or the delays come from their own workload, you have a reason to revisit the assumption. + +Keep separate guesses on separate lines. “Designers have this problem and will pay for an app” contains two things to check. Evidence of the problem doesn't establish willingness to pay for your proposed solution. + +## Check the assumptions that matter most + +You don't need to investigate everything at once. Look for an assumption that would change your whole plan if it were false, especially one you currently know little about. + +For the designer, finding out whether waiting causes a real difficulty comes before deciding what features a feedback tool should have. If the designer is content with the current process, a discussion about reminders won't tell you whether there's a product worth building. + +A simple table can help. This one shows possible assumptions for our example; it contains no research findings. + +| Assumption | What would make you reconsider? | +| ----------------------------------------------------------------- | ------------------------------------------------------------------------ | +| Waiting for decisions disrupts the designer's work. | Recent projects show the wait has little effect. | +| Clients are unsure what decision they're being asked to make. | They understand the request, but are waiting on someone else's approval. | +| The designer wants to change the current process. | They've considered alternatives and prefer what they do today. | +| Other designers face a similar difficulty. | People doing similar work describe a different problem. | +| There's a useful exchange the designer would make for a solution. | The benefit doesn't justify the money or effort it would cost them. | + +Beside your own list, record what evidence you already have. “I saw it happen” and “I think this is probably true” are different starting points. You can leave an entry as “unknown.” + +## Decide what would change your mind + +It's easier to explain away an unwelcome answer when you haven't decided what you're looking for. Before a conversation, write down what would make you less confident in the assumption you're checking. + +You could learn that the problem is rare, that your explanation is wrong, or that the current solution is good enough. Be willing to record that. You can still decide to investigate further, but give the answer a place in your notes. + +Choose the most important uncertainty to take into your next conversation. Write a question that could help you learn about it. We'll work on asking those questions in [Talking to people](./talking-to-people.mdx). + + + You've written down the assumptions your idea depends on. + You've recorded what you know already and what remains unknown. + You've chosen an important uncertainty to investigate first. + You've written what could make you reconsider it. + + +{/* prettier-ignore-start */} + +{/* +REVIEW NOTES (private, never published) +- Source: notes/part-one/_part.md and the original chapter outline. First draft requested in this conversation. +- Kept: naming assumptions and gathering evidence for them. +- Reordered: start with the reader's existing sentence, separate its guesses, then choose what to investigate. +- Added for review: checking important uncertainties first and recording what would change your mind before a conversation. +- The narrow table is an illustrative working format. It is not presented as Shane's personal process or as completed research. +- Cut: numerical confidence scores and a fixed required number of assumptions. The notes don't establish either. +- TODO(shane): Does this simple list and evidence approach resemble how you want readers to work? A personal example could replace the illustrative table later. +*/} + +{/* prettier-ignore-end */} diff --git a/apps/handbook/content/handbook/part-one/finding-problems.mdx b/apps/handbook/content/handbook/part-one/finding-problems.mdx new file mode 100644 index 00000000000..4b298706f60 --- /dev/null +++ b/apps/handbook/content/handbook/part-one/finding-problems.mdx @@ -0,0 +1,72 @@ +--- +title: "2. Finding problems" +description: Where to look for problems, and how to choose which ones to investigate. +--- + +Start with the things you can see people struggling with. Your own work is a good place to look. So are the people you know and the communities you're already part of. You have somewhere to start asking questions, and a chance of understanding the answers. + +A problem might be so familiar that you barely notice it. You copy the same information into different places. Someone has to chase a reply before they can finish their work. A task is awkward enough that you keep putting it off. Look for the things people have accepted as part of getting through the day. + +You don't need to know how to code to notice any of this. Knowing how a job actually gets done gives you something useful to build from. + +## Look at what people already do + +Complaints can point you towards a problem. What happens after the complaint is useful too. + +Someone might keep a spreadsheet because their existing tools don't quite do the job. They might pay for software they find frustrating, or ask a colleague to handle something by hand. Those workarounds give you a place to look more closely. What are they trying to achieve? Which part keeps going wrong? + +In our hypothetical designer example, imagine that the designer keeps a list of projects waiting for client feedback. They check the list and send follow-up messages before starting the day's work. You can now investigate a specific activity: keeping projects moving when a client hasn't replied. + +The spreadsheet itself might work perfectly well. Seeing it doesn't automatically mean you should replace it with an app. Find out which part of the work, if any, the designer wants to change. + +And if someone has no workaround, ask why. They might have given up, lack the means to do anything about it, or simply have more pressing things to deal with. You need that context before deciding how much the problem matters. + +## Is it worth looking into? + +At this stage, you're choosing what to investigate. You can make that choice without knowing whether anyone will buy a product yet. + +For each problem you notice, ask: + +- When does it happen, and how often? +- What does it stop the person from doing? +- What happens if they leave it unsolved? +- What do they already spend, in time or money, trying to deal with it? + +A frequent annoyance might be worth solving because it takes up part of every day. Something that happens rarely could still matter a lot if the consequences are serious. Frequency helps you understand the problem; it doesn't decide its value on its own. + +Money already changing hands is useful evidence that someone values an outcome. But payment isn't the only way to recognise a problem. A person spending their evenings doing something manually is giving up something too. Later, you'll need to find out what they'd be willing to exchange for your particular solution. + +## Write down the problem you noticed + +Keep a short list. For each entry, write what someone was trying to do, what got in the way, and anything you actually saw them do about it. Leave questions beside the bits you're guessing. + +Be careful with entries like “there's no app for this.” That tells you what you might build. It leaves the person's difficulty unexplained. Try describing what they struggle to do today, without mentioning your proposed app. + +You might end up with several possibilities. That's enough to work with. In the [next chapter](./who-has-it.mdx), we'll narrow them down by finding a real person behind each one. + + + You have a short list of problems you've noticed. + + Each entry describes something a person is trying to do and what's getting + in the way. + + + You've separated what you've observed from what you're guessing. + + + +{/* prettier-ignore-start */} + +{/* +REVIEW NOTES (private, never published) +- Source: notes/part-one/_part.md and the original chapter outline. First draft requested in this conversation. +- Kept: finding problems and deciding whether they're worth solving. Reframing stays in chapter 4. +- Reordered: observation, existing behaviour, a first assessment, then a short problem list. +- Added for review: starting close to home, examining workarounds, and the practical questions about frequency and consequences. +- The designer's spreadsheet and follow-up routine are explicitly hypothetical. They are not a reported customer observation. +- Softened the original suggestion about complaints without action: lack of a workaround needs investigation and doesn't establish lack of need. +- TODO(shane): Is the proposed bar for investigating a problem right? Payment is useful evidence here, but isn't required. +- TODO(shane): A real problem you noticed, especially one you decided against, could replace the hypothetical illustration. +*/} + +{/* prettier-ignore-end */} diff --git a/apps/handbook/content/handbook/part-one/ideas.mdx b/apps/handbook/content/handbook/part-one/ideas.mdx new file mode 100644 index 00000000000..cdb947c951e --- /dev/null +++ b/apps/handbook/content/handbook/part-one/ideas.mdx @@ -0,0 +1,58 @@ +--- +title: "1. You don't need a big idea" +description: You don't need the next Google. Start with a problem you could help someone solve. +--- + +You don't need an idea for the next Google or Facebook to build a product. You need a problem that someone cares about, and an idea for how to solve it. That can be quite small. + +Maybe you already have an idea. A tool you wish existed, or something you've wanted to build for a while. Keep it. We'll use this part of the handbook to look at the problem underneath it and find out who would care if you solved it. + +If you don't have an idea yet, that's fine too. You can start by paying attention to the things people struggle with. + +## An idea is a possible answer + +Try thinking about your idea like this: + +> I have an idea how to solve **_ for _**. + +The first blank is the problem. The second is the person who has it. Those blanks give you something to investigate before you start deciding what the product should look like. + +Imagine a freelance designer who keeps getting stuck waiting for client feedback. You might have an idea for a client portal. But what would it help the designer do? Get a clear decision so they can finish the work? Find feedback that's scattered across messages? Those are different problems, even though a portal could sound like an answer to either. + +:::note +The designer is a hypothetical example we'll use throughout Part I. The situations illustrate the questions to ask; they aren't a case study or evidence that this product should exist. +::: + +## Ideas are disposable + +You can change your idea when you learn more. You can throw it away. Finding out that a different approach would help someone more is a good reason to do that. + +This matters when you're excited to start building. AI tools give you a way to turn an idea into software, but you still need to understand what would make that software useful. A working product has to do something someone cares about. + +Write your own version of the sentence above. If you get stuck on either blank, leave a question there. We'll work through those questions, starting with [where to find problems](./finding-problems.mdx). + + + + You've written down the problem behind your idea, or marked it as a + question. + + + You've written who might have that problem, even if you're still unsure. + + You're willing to change the idea as you learn more. + + +{/* prettier-ignore-start */} + +{/* +REVIEW NOTES (private, never published) +- Source: notes/part-one/_part.md and the original chapter outline. First draft requested in this conversation. +- Kept: a small product is enough, ideas are disposable, and an idea responds to someone's problem. +- Reordered: introduced the problem sentence before explaining why an idea can change. +- Added for review: the sentence exercise, checklist, and explicitly hypothetical designer example used across Part I. These are draft teaching material, not Shane's experience. +- Cut: the suggested claims about building in days and a specific number of customers. Neither is needed here. +- TODO(shane): Choose whether to keep the hypothetical example or replace it with a real product from your experience. +- Kept this opening chapter shorter than the general length target. +*/} + +{/* prettier-ignore-end */} diff --git a/apps/handbook/content/handbook/part-one/meta.json b/apps/handbook/content/handbook/part-one/meta.json new file mode 100644 index 00000000000..41ec202b9cd --- /dev/null +++ b/apps/handbook/content/handbook/part-one/meta.json @@ -0,0 +1,13 @@ +{ + "title": "Part I: Coming up with an idea", + "defaultOpen": true, + "pages": [ + "ideas", + "finding-problems", + "who-has-it", + "right-problem", + "assumptions", + "talking-to-people", + "your-idea" + ] +} diff --git a/apps/handbook/content/handbook/part-one/right-problem.mdx b/apps/handbook/content/handbook/part-one/right-problem.mdx new file mode 100644 index 00000000000..9455ea7139d --- /dev/null +++ b/apps/handbook/content/handbook/part-one/right-problem.mdx @@ -0,0 +1,76 @@ +--- +title: "4. Is it the right problem?" +description: Question the way you've described the problem before choosing a solution. +--- + +You have a person and a problem. Now look again at the explanation you've written down. Is it what you've actually learned, or the first explanation that occurred to you? + +The way you describe a problem affects the solutions you can see. If you say the designer needs a client portal, you've already decided what to build. If you say they're waiting for a clear decision, you have room to find out why that decision isn't happening. + +## Change the way you look at it + +Thomas Wedell-Wedellsborg calls this reframing in [What's Your Problem?](https://howtoreframe.wordpress.com/the-book/). His elevator example makes the idea easy to see. Imagine tenants complaining about a slow lift. You could investigate making it faster. You could also investigate making the wait less annoying, perhaps by putting mirrors beside it. The complaint gives you more than one problem you could work on.[^reframing] + +The same applies to your sentence from the last chapter: + +> Person X can't do Y because Z. + +Look closely at “because Z.” You might have observed a delay without knowing what caused it. Before you decide how to remove it, write down some other explanations that could fit what you've seen. + +In our hypothetical designer example, the client might have missed the request. They might be unsure what feedback is needed. Or they might be waiting for someone else to make the decision. Each possibility suggests different work: a reminder, a clearer request, or finding the person who can approve the draft. + +You don't have to choose the right explanation from your desk. You need to recognise that you have several to check. + +## Ask what they're trying to achieve + +One of the book's reframing strategies is to reconsider the goal. Ask what the person would gain if the problem were solved.[^reframing] + +For our designer, “get more feedback” might be a poor description of the goal. More comments could mean more conflicting requests and more work. Finishing the project requires a decision about whether the current draft is acceptable and, if it isn't, what needs to change. + +That changes the questions you'd ask the designer. Who needs to agree? What happens after a reply arrives? At what point can the designer actually move on? + +It can also change who you need to talk to. The designer sees their side of the exchange. The client might be struggling to gather a decision from colleagues. If your proposed solution depends on the client doing something differently, you'll need to understand that side too. + +## Look for times it goes well + +The book also suggests examining “bright spots”: occasions when the difficulty is absent or has already been handled successfully.[^reframing] + +Suppose the designer gets useful feedback quickly from some clients. Ask what happens on those projects. Perhaps the request goes to a single decision-maker, or they agree in advance what the client will be reviewing. These are possibilities to investigate, not conclusions about our fictional designer. + +Looking at an easier project gives you something to compare with the difficult one. It might reveal a useful practice you could help people repeat. It might also show that the problem only happens in a narrower situation than you thought. + +## Take the questions with you + +Keep your original sentence, then write an alternative version. Beside each, note what you'd need to learn to decide whether it fits. For the designer, you might need to see the last request for feedback and understand what happened after it was sent. + +Keep this exercise short. A more elegant sentence doesn't establish that you're right. The [next chapter](./assumptions.mdx) turns these uncertainties into a list you can investigate when you talk to people. + + + + You've checked that the outcome in your sentence is something the person + cares about. + + + You've written another possible explanation for what's getting in their way. + + + You have a question about when the problem is easier, or doesn't happen. + + + +[^reframing]: Thomas Wedell-Wedellsborg explains these ideas in his article [How to solve the right problems](https://dialoguereview.com/how-to-solve-the-right-problems/), adapted from What's Your Problem?. The designer application here is hypothetical. + +{/* prettier-ignore-start */} + +{/* +REVIEW NOTES (private, never published) +- Source: notes/part-one/_part.md and the original chapter outline. First draft requested in this conversation. +- Kept: checking the problem framing and drawing on What's Your Problem?. +- Reordered: a brief explanation of reframing, then its application to the sentence from chapter 3, followed by two strategies. +- Book concepts and elevator illustration were checked against the author's own article. The elevator is presented as an illustration, not a verified historical event. +- Added for review: the designer applications, questions for the client, and comparison of alternative problem sentences. +- Cut: the full five-strategy list to keep the chapter light. No personal story about Shane solving the wrong problem was invented. +- TODO(shane): Are reconsidering the goal and examining bright spots the concepts you most want to carry over from the book? +*/} + +{/* prettier-ignore-end */} diff --git a/apps/handbook/content/handbook/part-one/talking-to-people.mdx b/apps/handbook/content/handbook/part-one/talking-to-people.mdx new file mode 100644 index 00000000000..1a64f9a4236 --- /dev/null +++ b/apps/handbook/content/handbook/part-one/talking-to-people.mdx @@ -0,0 +1,98 @@ +--- +title: "6. Talking to people" +description: Ask about what actually happens, and use what you learn to check your assumptions. +--- + +Talk to the person you named earlier. You have a problem sentence and some assumptions to check. The conversation is a chance to find out where those guesses fit their experience and where they fall apart. + +It can feel easier to show someone your idea and ask whether they like it. But a kind response gives you very little to build on. Someone can like your idea without having the problem, wanting a solution, or ever using what you make. + +## Ask about their experience + +Rob Fitzpatrick's [The Mom Test](https://www.momtestbook.com/) is a useful guide to these conversations. Its advice is to focus on the other person's life, ask about things that have happened, and give them room to speak. That helps you learn even when someone wants to be encouraging. + +In our hypothetical designer example, “Would you use a tool that makes client feedback easier?” invites a prediction. Try asking about the last project they worked on: + +- How did you ask for feedback on the draft? +- What happened after you sent it? +- How did you decide what to change? +- Was anything holding up the work? +- What did you do next? + +Leave room for the answer to be that everything went well. You want to learn whether your problem description fits their experience. + +If something was difficult, follow it. Ask what made it difficult and what the consequences were. If they mention a way around it, ask how that works. A real example can take the conversation somewhere you hadn't thought to ask about. + +## Make it easy to talk + +A short invitation helps someone understand why you're asking. You could adapt this message: + +> I'm trying to understand how freelance designers get client feedback and finish projects. Could I ask you about how it worked on a recent project? Would you have 15 minutes for a chat? + +This is a template, not a message from a real customer conversation. Use your own words and describe what you're actually trying to learn. Keep the time you ask for, and be clear if you're taking notes or want to record the conversation. + +Ask about their work before showing your proposed solution. Have your important questions nearby, but follow useful details when they come up. + +Before you finish, ask whether they know someone else who has dealt with the same situation. That can help you find people beyond your immediate circle. + +## Watch what you do with the answers + +You can accidentally steer someone towards the answer you want. “How frustrating is it when clients don't reply?” assumes both a delay and frustration. “What happened after you sent it?” lets them describe either. + +You can also steer your own notes. If someone likes the idea but says their existing process works well, record both. Don't turn that into a note that says they're interested and leave out the part that challenges your plan. + +Give short answers room to develop. You don't need to fill every pause with another explanation of your idea. If you're unsure what someone means, ask them to describe a specific occasion or show how they handled it. + +## Separate evidence from encouragement + +The Mom Test also emphasises commitments as a way to understand customer interest.[^commitment] Notice what people have done and what they actually follow through on. + +A recent example tells you about the problem. A workaround shows you how they currently cope. Paying for an existing service tells you something about what they value, though it doesn't establish that they'd pay for yours. Agreeing to a trial is a useful next step; taking part in it tells you more. + +Keep each piece of evidence attached to the assumption it bears on. Someone introducing you to a colleague helps you find another conversation. It doesn't prove that your product solves their problem. + +For the designer, you might discover that clients reply promptly, but their comments conflict. That would change your explanation of the delay. Record it and return to your problem sentence before deciding what to build. + +## Update your notes, then choose the next step + +After each conversation, write down what happened in the examples they described, what surprised you, and which assumptions became more or less convincing. Keep your interpretation separate from their words. An unanswered question can stay unanswered. + +Start with the person you named, then talk to others in a similar situation. Look for repeated problems and differences that matter. A useful next step becomes clearer when you can explain who has the difficulty, what it costs them, and what you still need to learn. A conversation count alone can't tell you that. + +AI can help you prepare questions or organise your notes. Treat answers from a simulated customer as guesses to investigate. They don't tell you what a real person has experienced or will do. + +If the evidence changes your mind, change the person, outcome, or explanation in your sentence. You can also set the idea aside. If you have a problem worth pursuing, the [next chapter](./your-idea.mdx) helps you choose a small response to it. + + + + You've talked to someone with the problem about things that actually + happened. + + Your notes include answers that challenged your assumptions. + + You've updated your problem sentence and recorded what remains uncertain. + + + You've decided what to investigate or try next, even if that means choosing + another problem. + + + +[^commitment]: Rob Fitzpatrick's [teaching guide for The Mom Test](https://www.momtestbook.com/teachers) identifies recognising biased answers and using commitments to assess interest as core topics. The designer questions and invitation here are examples written for this handbook. + +{/* prettier-ignore-start */} + +{/* +REVIEW NOTES (private, never published) +- Source: notes/part-one/_part.md and the original chapter outline. First draft requested in this conversation. +- Kept: talking to people, avoiding bias, The Mom Test, and gathering evidence for assumptions. +- Reordered: experience-based questions, practical invitation, bias, interpreting evidence, and updating notes. +- Added for review: a clearly labelled invitation template, hypothetical designer questions, and using AI for preparation rather than simulated evidence. +- The 15-minute invitation is an editable example from the outline's suggestion, not a required research duration or a message Shane has sent. +- Cut: the proposed absolute evidence ranking. Different observations answer different questions, and an introduction doesn't prove demand. +- No customer quotes, outcomes, or personal experiences are presented as real. The example answers are possibilities. +- TODO(shane): Do you have a real invitation or conversation to replace the template and hypothetical example? +- TODO(shane): The draft uses an evidence-based next-step decision without a fixed interview count. Is there a practical starting number you personally recommend? +*/} + +{/* prettier-ignore-end */} diff --git a/apps/handbook/content/handbook/part-one/who-has-it.mdx b/apps/handbook/content/handbook/part-one/who-has-it.mdx new file mode 100644 index 00000000000..4504dfd65a6 --- /dev/null +++ b/apps/handbook/content/handbook/part-one/who-has-it.mdx @@ -0,0 +1,78 @@ +--- +title: "3. Who has this problem?" +description: A problem starts with a person. Find someone you can talk to about it. +--- + +If you can't name or find someone who has the problem, assume it doesn't exist. You're not ready to build a product for them yet. + +That might sound strict, but it gives you a useful next step. A real person can tell you what they're trying to do, show you where they get stuck, and explain what they've already tried. You can learn something from them that changes your idea. + +“Small business owners” can't do that. Neither can an imaginary customer you've described in a document. Find an actual person you could contact. + +## Put a person in the sentence + +Take a problem from your list and write it this way: + +> Person X can't do Y because Z. + +**X is the person.** Use their name in your own notes. Add the context that matters to the problem, such as the work they're doing or the situation they're in. + +**Y is what they're trying to get done.** Describe an outcome they care about. “Finish a project” gives you more to work with than “use a better tool.” + +**Z is what's getting in their way.** This is your current explanation, and it's often the part you know least about. Mark it as a guess if you haven't heard it from the person or seen it yourself. + +Sometimes they can do Y, but it takes more effort than they can comfortably give it. Say that. The sentence is there to help you be precise; you don't need to force every problem into “can't.” + +## Make the problem specific enough to discuss + +“Getting client feedback is hard” leaves a lot open. Whose feedback? What's hard about it? What is the person waiting to do? + +For the hypothetical designer we've been following, a more useful version would be: + +> [Name] can't finish a design project because the client hasn't given a clear decision on the latest draft. + +Replace `[Name]` with a real person when you write your own version. Our example is fictional; your next conversation needs someone who actually has the problem. + +Notice how much this sentence leaves open. Perhaps the client hasn't looked at the draft. Perhaps several people have given conflicting feedback. Or perhaps the designer and client disagree about what counts as finished. We have a person and an outcome to ask about, but we still need to check the explanation. + +You could also discover that the wait doesn't bother the designer. They have other work to do and the deadline is comfortable. Describing a difficulty in a sentence doesn't establish that someone wants it solved. + +## What if that person is you? + +You count as a starting point. You can describe the last time it happened and what you did about it. That gives you something concrete to investigate. + +If you're making a tool just for yourself, helping yourself may be enough. If you want other users, find someone else with the problem too. Your own experience can't tell you how it fits into another person's life, or whether they'd change what they do today. + +If you only know a type of person, use that description to find someone. Ask people you know for an introduction, or look in a community where those people already spend time. A group you can reach gives you a starting place for finding a name. + +Go through your list and write a sentence for each problem. Set aside any you can't connect to a real person. Choose one of the remaining problems to carry into the [next chapter](./right-problem.mdx), where we'll question the way you've described it. + + + You've chosen a problem and named someone who has it. + + You can describe what they're trying to do and what you think is getting in + the way. + + + You know how to contact them, or have a concrete way to get an introduction. + + + If you are the first person, you have a plan to find someone else before + building for others. + + + +{/* prettier-ignore-start */} + +{/* +REVIEW NOTES (private, never published) +- Source: notes/part-one/_part.md and the original chapter outline. First draft requested in this conversation. +- Kept: the opening opinion, the person-first framing, and Person X can't do Y because Z. +- Reordered: the sentence comes before advice on finding someone or using your own experience. +- Added for review: literal contactability, the distinction between a personal tool and a product for others, and allowing costly effort as well as inability in Y. +- The designer sentence is a hypothetical template with a name placeholder. No customer name or reported conversation has been invented. +- TODO(shane): Confirm that being your own first person is enough to start, with another person needed before building for other users. +- TODO(shane): Confirm the literal-person threshold. A reachable group is treated as a route to finding someone, not as a substitute. +*/} + +{/* prettier-ignore-end */} diff --git a/apps/handbook/content/handbook/part-one/your-idea.mdx b/apps/handbook/content/handbook/part-one/your-idea.mdx new file mode 100644 index 00000000000..ef0721a4246 --- /dev/null +++ b/apps/handbook/content/handbook/part-one/your-idea.mdx @@ -0,0 +1,77 @@ +--- +title: "7. From problem to idea" +description: Choose a small way to help someone with a problem you understand. +--- + +Go back to the sentence we started with: + +> I have an idea how to solve **_ for _**. + +You should now be able to fill it in with more confidence. You have someone to talk to, some evidence of what they're struggling with, and a better understanding of what's getting in the way. There will still be things you don't know. Your first version can help you learn about those. + +## Consider different ways to help + +Take the explanation in your problem sentence and write down different ways you could address it. Give yourself room to think beyond the first product idea you had. + +Suppose conversations in our hypothetical designer example suggest that clients struggle to understand what decision is needed. You could try a clearer feedback request. You could provide a page that puts the draft beside the decision the client needs to make. You could help the designer agree on who will approve the work before the project starts. + +These approaches ask different things of the designer and the client. A clearer request might fit into the email they already send. A new portal might require both people to change how they work. That effort is part of what you're asking them to exchange for the benefit. + +Choose an approach that addresses the difficulty you found and is small enough to try with the person you've been talking to. You can test a clearer request by helping write one. You can show how a page would work with a sketch. Some ideas need working software before you can learn much; others give you a useful answer before that. + +## Describe the value in a sentence + +Write what your idea would help the person do: + +> For [person], who struggles to [outcome] because [obstacle], this helps by [what it does]. + +For our example, that could become: + +> For a freelance designer waiting for a clear client decision, this puts the current draft and the decision needed in one place, so the client knows what to respond to. + +That is a proposal to check with people. It doesn't mean a page will solve the problem. A missing decision-maker or a client with no time to review the work could still leave the designer waiting. + +Keep the first version close to this sentence. Features that don't help deliver the promised outcome can wait. You'll have a clearer reason to add them when you see what people actually need. + +## Take the idea back to someone + +Now show the idea to the people you've spoken with. Explain what it does and ask them to walk through how it would fit into their work. You can use the specific project they described earlier to make that discussion concrete. + +Ask for a next step that fits what you can offer. With the designer, that might be trying a revised feedback request on a suitable project, then discussing what happened. Agree on what you'd look for: did the client understand the request, and did the reply let the designer move forward? + +If you intend to charge for the product, start finding out who would pay and what they're spending on the problem today. Pricing gets more attention in Part III, but an assumption about payment belongs on your list now. A friendly reaction to a sketch leaves that question open. + +Bring your problem sentence, notes from conversations, and one small idea into Part II. Keep the remaining uncertainties with them. Those notes will help you decide what the first version needs to do and what to learn when someone tries it. + + + + You can name someone with the problem and explain the evidence you've + gathered. + + + You've chosen a small idea and can describe the outcome it should help them + achieve. + + + You know who you can show it to and what you'd like to learn from trying it. + + + You've kept a record of the assumptions that are still untested. + + + +{/* prettier-ignore-start */} + +{/* +REVIEW NOTES (private, never published) +- Source: notes/part-one/_part.md, the introduction, and the original chapter outline. First draft requested in this conversation. +- There was no chapter-specific dictation. This chapter is a proposed handoff based on the stated relationship between a problem, an idea, and an exchange of value. +- Reordered: consider possible responses, describe the value, then show the idea and agree on a useful next step. +- Added for review: trying a small response, considering the effort of adoption, a value sentence, and checking the intended outcome with a real project. +- All designer applications remain hypothetical. No trial result or willingness to pay has been invented. +- Cut: detailed feature planning and pricing mechanics. These belong to later Parts. +- TODO(shane): Confirm this chapter belongs at the end of Part I rather than the start of Part II. +- TODO(shane): Does considering several responses before choosing a small one reflect how you want readers to approach their first idea? +*/} + +{/* prettier-ignore-end */} diff --git a/apps/handbook/content/handbook/part-three/meta.json b/apps/handbook/content/handbook/part-three/meta.json new file mode 100644 index 00000000000..05f04e5ec80 --- /dev/null +++ b/apps/handbook/content/handbook/part-three/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Part III: Going live", + "defaultOpen": true, + "pages": ["stub-chapter"] +} diff --git a/apps/handbook/content/handbook/part-three/stub-chapter.mdx b/apps/handbook/content/handbook/part-three/stub-chapter.mdx new file mode 100644 index 00000000000..0897014c910 --- /dev/null +++ b/apps/handbook/content/handbook/part-three/stub-chapter.mdx @@ -0,0 +1,7 @@ +--- +title: "9. Stub chapter" +description: Part III has one stub so the sidebar shows it until its chapters are dictated. +status: stub +--- + +Placeholder. Shane dictates the chapters for this Part. diff --git a/apps/handbook/content/handbook/part-two/meta.json b/apps/handbook/content/handbook/part-two/meta.json new file mode 100644 index 00000000000..be2af6f9848 --- /dev/null +++ b/apps/handbook/content/handbook/part-two/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Part II: Building the first version", + "defaultOpen": true, + "pages": ["stub-chapter"] +} diff --git a/apps/handbook/content/handbook/part-two/stub-chapter.mdx b/apps/handbook/content/handbook/part-two/stub-chapter.mdx new file mode 100644 index 00000000000..88428ed31e0 --- /dev/null +++ b/apps/handbook/content/handbook/part-two/stub-chapter.mdx @@ -0,0 +1,7 @@ +--- +title: "8. Stub chapter" +description: Part II has one stub so the sidebar shows it until its chapters are dictated. +status: stub +--- + +Placeholder. Shane dictates the chapters for this Part. diff --git a/apps/handbook/next.config.mjs b/apps/handbook/next.config.mjs new file mode 100644 index 00000000000..2ca3dcb265d --- /dev/null +++ b/apps/handbook/next.config.mjs @@ -0,0 +1,30 @@ +import { createMDX } from "fumadocs-mdx/next"; + +const withMDX = createMDX(); + +// The handbook is its own multi-zone app, mounted at /handbook the same way +// docs and blog are mounted at /docs and /blog. apps/site does not forward +// /handbook/* yet; see README.md for the rewrite to add when it goes public. +// No CSP/security headers yet either: copy the block from apps/blog when this +// gets a public origin. + +/** @type {import('next').NextConfig} */ +const config = { + reactStrictMode: true, + basePath: "/handbook", + assetPrefix: "/handbook-static", + images: { unoptimized: true }, + transpilePackages: ["@prisma/eclipse"], + async redirects() { + return [ + { + source: "/", + destination: "/handbook", + permanent: false, + basePath: false, + }, + ]; + }, +}; + +export default withMDX(config); diff --git a/apps/handbook/notes/README.md b/apps/handbook/notes/README.md new file mode 100644 index 00000000000..ec1897b5042 --- /dev/null +++ b/apps/handbook/notes/README.md @@ -0,0 +1,13 @@ +# notes/ + +Raw dictation. One file per chapter, mirroring `content/handbook/`: + +``` +notes/part-one/what-this-is.md -> content/handbook/part-one/what-this-is.mdx +``` + +Anything goes in here: voice transcripts, bullet spew, half-thoughts, links, "actually scrap that." This directory is never published, never linted, never spell-checked. It is the source of truth for what Shane meant; the MDX is the source of truth for what the handbook says. + +Claude reads a note, writes the chapter, and adds nothing the note does not contain. See `../AUTHORING.md`. + +Template for a new note: copy `TEMPLATE.md`. diff --git a/apps/handbook/notes/TEMPLATE.md b/apps/handbook/notes/TEMPLATE.md new file mode 100644 index 00000000000..e8e8b1a235c --- /dev/null +++ b/apps/handbook/notes/TEMPLATE.md @@ -0,0 +1,28 @@ +# + +Part: +Status: dictated + +## The point + + + +## Brain dump + + + +## The example + + + +## Actions + + + +## Before they move on + + + +## Don't + + diff --git a/apps/handbook/notes/index.md b/apps/handbook/notes/index.md new file mode 100644 index 00000000000..9b9c3c2e148 --- /dev/null +++ b/apps/handbook/notes/index.md @@ -0,0 +1,22 @@ +# Intro page (content/handbook/index.mdx) + +Status: dictated (2026-09-18, chat) + +## Brain dump + +- this is for people building PRODUCTS with AI... not just building software. That distinction is important. +- it's for anyone who wants to build a product from scratch, technical or not. +- it will guide them through from idea to live product, and getting first users. + +## Review answers (2026-09-29, chat) + +- software is a bunch of code. a product is an exchange of value, a solution to a problem, something with a value proposition and a defined set of people who care about that value +- kill "the hard parts were never the typing". also kill the reference to "both of you" +- we'll lightly cover getting first users and growth concepts +- Parts, first pass: + 1. coming up with an idea (covering ideation, problems, who it's for, validation, all the stuff before building anything) + 2. building the first version (covering the tools, the build, the features, the deployment, getting it live, in front of people) + 3. getting first users (covering basic GTM, learning, iteration, feedback, etc) + 4. getting more users (covering basic growth mechanics, PLG, scaling, etc) +- then: split part 2 in half, put the money stuff in the second half. packing more into part 2 is too much. +- agreed structure: I idea / II building the first version / III going live (deploy, pricing, payments, in front of people) / IV first users / V more users diff --git a/apps/handbook/notes/part-one/.gitkeep b/apps/handbook/notes/part-one/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/handbook/notes/part-one/_part.md b/apps/handbook/notes/part-one/_part.md new file mode 100644 index 00000000000..af9e6640ba2 --- /dev/null +++ b/apps/handbook/notes/part-one/_part.md @@ -0,0 +1,14 @@ +# Part I: Coming up with an idea (part-level notes) + +Status: dictated (2026-09-30, chat). Split into seven pages, agreed 2026-09-30: ideas, finding-problems, who-has-it, right-problem, assumptions, talking-to-people, your-idea. Each page carries a private outline comment. + +## Brain dump + +- I'd like to introduce "ideas" ... talk lightly about how you don't need the next google or facebook, ideas are disposable, and the main thing is to solve a problem for someone. an idea should be a response to a problem, ie. "I have an idea how to solve X" +- I want to talk about problems... how to find problems to solve, how to know if they are worth solving, and are they the right problems? I personally love the book "What's your problem?" and we should take concepts from it. +- A problem starts with a person... WHO has this problem? if you can't name a person who has it, then you're not ready. this is important, because naturally the next step is going to be to talk to that person. so if you can't name or find someone with the problem, you should assume it doesn't exist. you can't say "it's a problem that it's hard to do X" it should be "Person X can't do Y because Z" (which leads us to possible solutions) +- validation and research -- talking to people, avoiding biases, "the mom test", naming assumptions and gathering evidence for them + +## Don't + +- this should all be light and easy reading... not heavy textbook stuff. look at my writing style in the index page and you'll see what I like. diff --git a/apps/handbook/package.json b/apps/handbook/package.json new file mode 100644 index 00000000000..1f10ae5ad83 --- /dev/null +++ b/apps/handbook/package.json @@ -0,0 +1,40 @@ +{ + "name": "handbook", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "dev": "next dev --port 3004", + "start": "next start --port 3004", + "check": "oxfmt . --write && oxlint . --fix", + "types:check": "fumadocs-mdx && next typegen && tsc --noEmit", + "postinstall": "fumadocs-mdx" + }, + "dependencies": { + "@base-ui/react": "catalog:", + "@fumadocs/base-ui": "catalog:", + "@prisma-docs/ui": "workspace:*", + "@prisma/eclipse": "workspace:^", + "fumadocs-core": "catalog:", + "fumadocs-mdx": "catalog:", + "fumadocs-ui": "catalog:", + "lucide-react": "catalog:", + "next": "catalog:", + "next-themes": "catalog:", + "react": "catalog:", + "react-dom": "catalog:", + "remark-directive": "catalog:", + "zod": "catalog:" + }, + "devDependencies": { + "@tailwindcss/postcss": "catalog:", + "@types/mdx": "catalog:", + "@types/node": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "postcss": "catalog:", + "tailwindcss": "catalog:", + "typescript": "catalog:" + } +} diff --git a/apps/handbook/postcss.config.mjs b/apps/handbook/postcss.config.mjs new file mode 100644 index 00000000000..88fb0107489 --- /dev/null +++ b/apps/handbook/postcss.config.mjs @@ -0,0 +1 @@ +export { default } from "@prisma-docs/ui/postcss.config"; diff --git a/apps/handbook/public/logo/full-color-white.svg b/apps/handbook/public/logo/full-color-white.svg new file mode 100644 index 00000000000..19e803de053 --- /dev/null +++ b/apps/handbook/public/logo/full-color-white.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/apps/handbook/public/logo/full-color.svg b/apps/handbook/public/logo/full-color.svg new file mode 100644 index 00000000000..19c4f1c037a --- /dev/null +++ b/apps/handbook/public/logo/full-color.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/apps/handbook/public/logo/mark.svg b/apps/handbook/public/logo/mark.svg new file mode 100644 index 00000000000..c9d58efca3f --- /dev/null +++ b/apps/handbook/public/logo/mark.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/apps/handbook/source.config.ts b/apps/handbook/source.config.ts new file mode 100644 index 00000000000..281ec7365c1 --- /dev/null +++ b/apps/handbook/source.config.ts @@ -0,0 +1,55 @@ +import remarkDirective from "remark-directive"; +import { remarkDirectiveAdmonition, remarkImage, remarkMdxFiles } from "fumadocs-core/mdx-plugins"; +import { defineConfig, defineDocs, frontmatterSchema, metaSchema } from "fumadocs-mdx/config"; +import lastModified from "fumadocs-mdx/plugins/last-modified"; +import { z } from "zod"; +import { rehypeCodeOptions } from "@prisma-docs/ui/mdx/rehype-code-options"; + +import { CHAPTER_STATUS } from "./src/lib/chapter-status"; + +export const handbook = defineDocs({ + dir: "content/handbook", + docs: { + schema: frontmatterSchema.extend({ + status: z.enum(CHAPTER_STATUS).optional(), + // Minutes, hand-set. Reading-time estimators lie about prose with + // checklists and code in it. + readingTime: z.number().int().positive().optional(), + }), + postprocess: { + includeProcessedMarkdown: true, + }, + }, + meta: { + schema: metaSchema, + }, +}); + +export default defineConfig({ + plugins: [lastModified()], + mdxOptions: { + rehypeCodeOptions, + remarkPlugins: [ + remarkDirective, + [ + remarkDirectiveAdmonition, + { + // `:::note` etc. in MDX. The types on the right are what + // src/mdx-components.tsx receives in CalloutContainer. + types: { + note: "note", + tip: "tip", + warning: "warning", + warn: "warning", + example: "example", + }, + }, + ], + [remarkImage, { useImport: false }], + remarkMdxFiles, + ], + remarkCodeTabOptions: { + parseMdx: true, + }, + }, +}); diff --git a/apps/handbook/src/app/(handbook)/[[...slug]]/page.tsx b/apps/handbook/src/app/(handbook)/[[...slug]]/page.tsx new file mode 100644 index 00000000000..f6573808f76 --- /dev/null +++ b/apps/handbook/src/app/(handbook)/[[...slug]]/page.tsx @@ -0,0 +1,62 @@ +import { source } from "@/lib/source"; +import { getMDXComponents } from "@/mdx-components"; +import { withHandbookBasePath } from "@/lib/url"; +import { ChapterMeta } from "@/components/handbook/chapter-meta"; +import { notFound } from "next/navigation"; +import type { Metadata } from "next"; +import { createRelativeLink } from "fumadocs-ui/mdx"; +import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/page"; + +interface PageParams { + slug?: string[]; +} + +export default async function Page({ params }: { params: Promise }) { + const { slug } = await params; + const page = source.getPage(slug); + if (!page) notFound(); + + const MDX = page.data.body; + const { status, readingTime, lastModified } = page.data; + + return ( + + {page.data.title} + {page.data.description} + + + + + + ); +} + +export async function generateStaticParams() { + return source.generateParams(); +} + +export async function generateMetadata({ + params, +}: { + params: Promise; +}): Promise { + const { slug } = await params; + const page = source.getPage(slug); + if (!page) notFound(); + + // The root layout's template appends "| Builders Handbook"; the landing + // page IS the handbook, so it takes the bare name. + const isIndex = !slug || slug.length === 0; + + return { + title: isIndex ? { absolute: page.data.title } : page.data.title, + description: page.data.description, + alternates: { canonical: withHandbookBasePath(page.url) }, + openGraph: { + siteName: "Prisma", + title: page.data.title, + description: page.data.description, + url: withHandbookBasePath(page.url), + }, + }; +} diff --git a/apps/handbook/src/app/(handbook)/layout.tsx b/apps/handbook/src/app/(handbook)/layout.tsx new file mode 100644 index 00000000000..918e840171b --- /dev/null +++ b/apps/handbook/src/app/(handbook)/layout.tsx @@ -0,0 +1,20 @@ +import { DocsLayout } from "fumadocs-ui/layouts/docs"; +import { source } from "@/lib/source"; +import { baseOptions } from "@/lib/layout.shared"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/apps/handbook/src/app/global.css b/apps/handbook/src/app/global.css new file mode 100644 index 00000000000..e9875256708 --- /dev/null +++ b/apps/handbook/src/app/global.css @@ -0,0 +1,310 @@ +@import "tailwindcss"; +@import "@prisma/eclipse/styles/globals.css"; +@import "fumadocs-ui/css/shadcn.css"; +@import "fumadocs-ui/css/preset.css"; +@import "@prisma-docs/ui/styles"; + +/* Wire the next/font/local variables into the Eclipse token names. Declared + after the imports so this :root wins on cascade order. The bare family name + behind each variable is the @font-face from the package's fonts.css. */ +:root { + --font-sans-display: + var(--font-sora, "Sora"), "Sora", "Inter", "Roboto", "Helvetica Neue", "Arial Nova", + "Nimbus Sans", "Arial", sans-serif; + --font-sans: + var(--font-inter, "Inter"), "Inter", "Roboto", "Helvetica Neue", "Arial Nova", "Nimbus Sans", + "Arial", sans-serif; + --font-mono: + var(--font-mona-mono, "Mona Sans Mono VF"), ui-monospace, "Cascadia Code", "Source Code Pro", + "Menlo", "Consolas", "DejaVu Sans Mono", monospace; +} + +/* Brand heading rule: Sora, Medium, natural tracking. Unlayered on purpose; + fumadocs' prose typography emits heading weights into @layer utilities and + unlayered declarations outrank every layer. */ +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-sans-display); + font-weight: 500; + letter-spacing: normal; +} + +body { + font-weight: 380; +} + +/* --------------------------------------------------------------------------- + Shell retheme, copied from apps/docs: remap the shadcn/fumadocs variables + onto prism tokens. :root and .dark are both declared in full so neither + leaks into the other. Values are Eclipse tokens, never literals. +--------------------------------------------------------------------------- */ +:root { + --radius: 0.625rem; + + --background: var(--color-background-default); + --foreground: var(--color-foreground-neutral); + --card: var(--color-background-default); + --card-foreground: var(--color-foreground-neutral); + --popover: var(--color-background-default); + --popover-foreground: var(--color-foreground-neutral); + + --primary: var(--color-prism-cyan-600); + --primary-foreground: #ffffff; + + --secondary: var(--color-background-neutral-weak); + --secondary-foreground: var(--color-foreground-neutral); + --muted: var(--color-background-neutral); + --muted-foreground: var(--color-foreground-neutral-weak); + + --accent: var(--color-prism-cyan-100); + --accent-foreground: var(--color-prism-cyan-700); + + --destructive: var(--color-foreground-error); + --border: var(--color-stroke-neutral); + --input: var(--color-stroke-neutral); + --ring: var(--color-prism-cyan-600); + + --sidebar: var(--color-background-default); + --sidebar-foreground: var(--color-foreground-neutral); + --sidebar-primary: var(--color-prism-cyan-600); + --sidebar-primary-foreground: #ffffff; + --sidebar-accent: var(--color-prism-cyan-100); + --sidebar-accent-foreground: var(--color-prism-cyan-700); + --sidebar-border: var(--color-stroke-neutral); + --sidebar-ring: var(--color-prism-cyan-600); +} + +.dark { + --background: var(--color-background-default); + --foreground: var(--color-foreground-neutral); + --card: var(--color-background-neutral-weak); + --card-foreground: var(--color-foreground-neutral); + --popover: var(--color-background-neutral-weak); + --popover-foreground: var(--color-foreground-neutral); + + --primary: var(--color-prism-cyan-400); + --primary-foreground: var(--color-background-default); + + --secondary: var(--color-background-neutral); + --secondary-foreground: var(--color-foreground-neutral); + --muted: var(--color-background-neutral); + --muted-foreground: var(--color-foreground-neutral-weak); + + --accent: var(--color-prism-cyan-950); + --accent-foreground: var(--color-prism-cyan-300); + + --destructive: var(--color-foreground-error); + --border: var(--color-stroke-neutral); + --input: var(--color-stroke-neutral); + --ring: var(--color-prism-cyan-400); + + --sidebar: var(--color-background-default); + --sidebar-foreground: var(--color-foreground-neutral); + --sidebar-primary: var(--color-prism-cyan-400); + --sidebar-primary-foreground: var(--color-background-default); + --sidebar-accent: var(--color-prism-cyan-950); + --sidebar-accent-foreground: var(--color-prism-cyan-300); + --sidebar-border: var(--color-stroke-neutral); + --sidebar-ring: var(--color-prism-cyan-400); +} + +#nd-sidebar { + background-color: transparent; +} + +/* Reading measure. A handbook is read, not scanned, so body text runs a + touch larger than docs. */ +#nd-page .prose { + font-size: 1.0625rem; + line-height: 1.7; +} + +/* The brand's triple-band ray, as a reusable 4px edge. */ +:root { + --hb-ray: linear-gradient( + to bottom, + var(--color-prism-cyan-400) 0% 33%, + var(--color-prism-yellow-300) 33% 66%, + var(--color-prism-red-500) 66% 100% + ); +} + +/* --------------------------------------------------------------------------- + Handbook components +--------------------------------------------------------------------------- */ + +/* Callout: the `example` mood swaps its solid left border for the ray. */ +.hb-callout-ray { + border-left-color: transparent; + background-image: var(--hb-ray); + background-repeat: no-repeat; + background-size: 4px 100%; + background-position: left top; +} + +/* Margin note. Inset card by default; floats into the right gutter only when + the viewport has room for sidebar + article + TOC + note. */ +.hb-aside { + margin: 1.25rem 0; + padding: 0.75rem 1rem; + border-left: 2px solid var(--color-stroke-neutral-strong); + font-size: 0.9rem; + line-height: 1.55; + color: var(--color-foreground-neutral-weak); +} +.hb-aside-label { + display: block; + margin-bottom: 0.25rem; + font-family: var(--font-sans-display); + font-size: 0.7rem; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--color-foreground-neutral-weaker); +} +.hb-aside-body > :first-child { + margin-top: 0; +} +.hb-aside-body > :last-child { + margin-bottom: 0; +} +@media (min-width: 1700px) { + .hb-aside { + float: right; + clear: right; + width: 15rem; + margin: 0.25rem -17rem 1rem 1.5rem; + padding: 0 0 0 0.75rem; + border-left-width: 2px; + } +} + +/* Playbook: numbered steps with a hairline spine. */ +.hb-playbook { + list-style: none; + counter-reset: hb-step; + margin: 1.5rem 0; + padding: 0; +} +.hb-step { + counter-increment: hb-step; + position: relative; + padding: 0 0 1.5rem 3rem; + margin: 0; +} +.hb-step::before { + content: counter(hb-step); + position: absolute; + left: 0; + top: 0; + width: 2rem; + height: 2rem; + display: grid; + place-items: center; + border-radius: 999px; + background: var(--color-background-neutral-reverse); + color: var(--color-foreground-neutral-reverse); + font-family: var(--font-sans-display); + font-size: 0.85rem; + font-weight: 500; +} +.hb-step:not(:last-child)::after { + content: ""; + position: absolute; + left: calc(1rem - 1px); + top: 2.25rem; + bottom: 0.25rem; + width: 2px; + background: var(--color-stroke-neutral); +} +.hb-step-head { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.5rem 0.75rem; + min-height: 2rem; +} +.hb-step-title { + font-family: var(--font-sans-display); + font-size: 1.05rem; + font-weight: 500; + color: var(--color-foreground-neutral); +} +.hb-step-time { + font-family: var(--font-mono); + font-size: 0.75rem; + padding: 0.1rem 0.5rem; + border-radius: 999px; + background: var(--color-background-neutral); + color: var(--color-foreground-neutral-weak); + white-space: nowrap; +} +.hb-step-body { + margin-top: 0.5rem; +} +.hb-step-body > :first-child { + margin-top: 0; +} +.hb-step-body > :last-child { + margin-bottom: 0; +} + +/* Checklist. */ +.hb-checklist { + margin: 1.5rem 0; + border: 1px solid var(--color-stroke-neutral); + border-radius: var(--radius-square); + background: var(--color-background-neutral-weaker); + overflow: hidden; +} +.hb-checklist-head { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--color-stroke-neutral); + background: var(--color-background-default); +} +.hb-checklist-title { + font-family: var(--font-sans-display); + font-weight: 500; +} +.hb-checklist-count { + font-family: var(--font-mono); + font-size: 0.8rem; + color: var(--color-foreground-neutral-weak); +} +.hb-checklist--done .hb-checklist-count { + color: var(--color-prism-cyan-700); +} +.dark .hb-checklist--done .hb-checklist-count { + color: var(--color-prism-cyan-300); +} +.hb-checklist-items { + list-style: none; + margin: 0; + padding: 0.5rem 1rem; +} +.hb-check { + display: flex; + gap: 0.75rem; + align-items: flex-start; + padding: 0.35rem 0; + margin: 0; +} +.hb-check-label { + cursor: pointer; + line-height: 1.6; +} +.hb-check-label p { + margin: 0; +} +.hb-check--done .hb-check-label { + color: var(--color-foreground-neutral-weaker); + text-decoration: line-through; + text-decoration-color: var(--color-stroke-neutral-strong); +} diff --git a/apps/handbook/src/app/icon.svg b/apps/handbook/src/app/icon.svg new file mode 100644 index 00000000000..c9d58efca3f --- /dev/null +++ b/apps/handbook/src/app/icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/apps/handbook/src/app/layout.tsx b/apps/handbook/src/app/layout.tsx new file mode 100644 index 00000000000..88eb965f4e8 --- /dev/null +++ b/apps/handbook/src/app/layout.tsx @@ -0,0 +1,75 @@ +import "./global.css"; +import { Provider } from "@/components/provider"; +import { getBaseUrl } from "@/lib/url"; +import localFont from "next/font/local"; +import Script from "next/script"; +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { FontAwesomeScript as EclipseFA } from "@prisma/eclipse"; + +// Fonts are vendored in @prisma/eclipse; same three faces as the other zones. +const inter = localFont({ + src: [ + { + path: "../../../../packages/eclipse/src/static/fonts/InterVariable.woff2", + weight: "100 900", + style: "normal", + }, + { + path: "../../../../packages/eclipse/src/static/fonts/InterVariable-Italic.woff2", + weight: "100 900", + style: "italic", + }, + ], + variable: "--font-inter", + display: "swap", +}); + +// Display face. Only the latin subset is preloaded; latin-ext comes from the +// plain "Sora" @font-face in the package's fonts.css behind this variable. +const sora = localFont({ + src: "../../../../packages/eclipse/src/static/fonts/SoraVF-latin.woff2", + weight: "100 800", + style: "normal", + variable: "--font-sora", + display: "swap", +}); + +const monaSansMono = localFont({ + src: "../../../../packages/eclipse/src/static/fonts/MonaSansMonoVF[wght].woff2", + variable: "--font-mona-mono", + display: "swap", + weight: "200 900", +}); + +export const metadata: Metadata = { + metadataBase: new URL(getBaseUrl()), + title: { + default: "Builders Handbook", + template: "%s | Builders Handbook", + }, + description: "A handbook for people who build software with AI, from Prisma.", +}; + +export default function Layout({ children }: { children: ReactNode }) { + return ( + + + {/* FontAwesome: Eclipse's Alert and friends render glyphs. */} +