From 85f1fbac89c1d46fec66b58a9a9baa4523446b59 Mon Sep 17 00:00:00 2001 From: Karn Date: Sun, 23 Aug 2026 15:04:28 +0530 Subject: [PATCH 01/10] docs: design spec for the site and README overhaul Co-Authored-By: Claude Fable 5 --- .../2026-08-23-site-readme-overhaul-design.md | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-23-site-readme-overhaul-design.md diff --git a/docs/superpowers/specs/2026-08-23-site-readme-overhaul-design.md b/docs/superpowers/specs/2026-08-23-site-readme-overhaul-design.md new file mode 100644 index 0000000..acf7db4 --- /dev/null +++ b/docs/superpowers/specs/2026-08-23-site-readme-overhaul-design.md @@ -0,0 +1,244 @@ +# flue.sh and README overhaul: a README in a browser + +Date: 2026-08-23. Status: approved in conversation, implementing. + +## Goal + +flue.sh reads like a paid product. It is an MIT side project. The site +becomes a plain document: one monospace column, headings, bullets, tables, +commands. The reference is https://code.storage/. The README gets the same +copy, so the two say one thing. + +The positioning also changes. The site sold "walk and read the answer on +your phone". The thing people use is organisation: every Claude Code, Codex +and terminal session across every machine, in one list, with the agent +history beside it. Remote access is one feature in that list, not the moat. + +## Non-goals + +- No new features in the app. Every claim on the page is verified in + `web/src` and `internal/` before it is written. +- No MDX pipeline. The page is React composed from a small set of + markdown-looking primitives. +- `og.png`, `docs/hero-*.png` and `docs/architecture.png` stay as they are. + A new OG image is a follow-up. +- The app (`web/`) is untouched. + +## Look + +One font, Geist Mono, already loaded from Google Fonts. The Inter import and +the `rsms.me` preconnect go. + +- Body 14px, line height 1.75. Headings are the same size, bold, uppercase, + prefixed with a dim `#` or `##`. No display sizes anywhere. +- One column, left aligned, `max-w-[52rem]`, page padding `px-6`. Prose is + capped at `68ch`. Mocks span the full column. +- Colours stay zinc and teal, the tokens already in `site/src/styles.css`. + Teal is used for the `$` prompt, the heading markers, the copy + confirmation, and nothing else. Links are foreground and underlined. +- Radius 0 on everything the site owns. The mocks keep their radii because + they draw the app, and the app has radii. +- Light and dark both stay, with the existing toggle and pre-paint script. +- Removed: `backdrop-scan`, `Rails`, `SectionRule`, the glow blur, the + eyebrow pill, lucide icons in prose, the `Button` component, the + `shadow-2xl` on the mocks (a hairline ring stays), the dashed card grid on + docs pages, and the sticky header. + +## Primitives: `site/src/components/md.tsx` + +One file owns the look. Every page composes these and nothing else. + +| Export | Renders | +| ------- | ------------------------------------------------------------- | +| `H1` | `# ` marker in muted, then bold uppercase text | +| `H2` | `## ` marker in muted, then bold uppercase text | +| `P` | paragraph, `max-w-[68ch]` | +| `Ul` | `*` bulleted list, marker in muted, hanging indent | +| `Ol` | `1.` numbered list, tabular numbers, hanging indent | +| `A` | underlined link, foreground, external gets `target=_blank` | +| `Code` | inline code, hairline background | +| `Kbd` | inline keyboard chord, same look as `Code` | +| `Pre` | block of preformatted text, hairline border, `overflow-x-auto` | +| `Shell` | `Pre` with `$` lines coloured as prompts | +| `Table` | two-column key/value table, hairlines, mono | +| `Rule` | `---` in muted | +| `Meta` | the top-of-page block: three short lines in muted | + +`Table` is a real `` so it reflows and reads to a screen reader. It +is drawn with hairlines, not ASCII pipes, which do not reflow. + +The docs helpers in `doc-page.tsx` (`Section`, `P`, `Lead`, `Code`, `Link`, +`Shell`, `Note`, `Steps`, `Step`) keep their names and props and are +re-implemented on top of `md.tsx`, so the four `docs.*.tsx` routes change +little. `Note` becomes a block quote with a hairline left rule. `Steps` +becomes `Ol`. + +## Homepage + +Top to bottom. Text is final unless the build turns up a claim that is not +true, in which case the claim goes, not the code. + +``` +karnstack +flue +2026 · MIT · v0.5.1 + +# Your Claude Code sessions and terminals, in one place + +flue is a daemon that keeps terminal sessions alive, and a web app that +lists every one of them across your machines. Claude Code, Codex, Pi, +builds, SSH. Name them, tag them, pin them, find them, open them from +any screen you own. + +$ curl -fsSL https://flue.sh/install.sh | sh [copy] + or brew install karnstack/tap/flue + +[ FleetWindow + PhoneFrame, full column width, no glow ] + +## What it does + +* Sessions outlive the tab. Close it, the agent keeps working. Reopen it, + the scrollback replays. +* Sessions outlive flue. Each one runs in its own holder process. Update + or crash the daemon, nothing stops. After a reboot, each comes back with + its history and the command that resumes the conversation. +* One list, every machine. Group by machine or by tag. Name, tag, pin, + search. Drag a row onto a group to retag it. Save a view. +* One keystroke to any of them. ⌘K or Ctrl+Shift+K lists every session on + every machine, and the highlighted row shows its last 14 lines. + Ctrl+Shift+1 to 9 jumps to a pinned one. +* Splits and tabs. ⌘D splits right, ⇧⌘D splits down, ⌥⌘T opens a tab. +* Agent history. Every Claude Code, Codex and Pi conversation from every + machine. Search it, see tokens and cost per day, read the transcript, + resume it in a terminal. +* Files. Select a path in a terminal, peek at the file, open it in a tab. +* Phone and tablet. Pair once with a QR code. A key bar with Esc, Tab, + arrows and Ctrl. Two devices can mirror one session. + +[ SwitcherWindow, wired up, full column width ] + +## Remote access + +The daemon listens on loopback and nothing else. One command deploys a +relay Worker and this web app into your own Cloudflare account, on the +free plan. Every machine you own shares that relay. + + your machine ──── your Cloudflare account ──── your phone + daemon, holds relay, holds no key browser, pins the + the key daemon's key + 127.0.0.1:7717 flue-relay.you.workers.dev paired once, by QR + +Everything crossing it is end-to-end encrypted with Noise IK. The middle +box forwards bytes it cannot read. There is no flue account, no flue +server and no billing. flue.sh serves docs and downloads and is never part +of the data path. + +$ flue relay setup + (the existing RELAY_LINES transcript, as a Shell block) + +→ What it deploys, and what it costs (/docs/relay) + +## Facts + +| License | MIT | +| Binary | One static Go binary | +| Platforms | macOS, Linux, WSL | +| Hosted service | None | +| Account | None | +| Remote access | Your Cloudflare account, free plan | +| Encryption | Noise IK, end to end | +| Version | 0.x, commands can still change | + +## Install + +$ curl -fsSL https://flue.sh/install.sh | sh +$ flue enable + +flue enable installs a login service, starts the daemon and opens the UI. +Everything after that happens in the browser. More than one machine: read +the setup guide. There is a recording of the whole setup (8:51) on that +page. + +→ Setup guide (/docs/setup) +→ Source (github) + +--- +Setup | How it works | Remote access | FAQ | GitHub | X +© 2026 karnstack. MIT. +``` + +The data-path diagram is a `Pre` so it reads as text, and is given an +`aria-label` that says the same thing in one sentence. Below `sm` it is +too wide for the column; it scrolls inside its own box, which is what +`Pre` does. + +The chords are Mac-first. Where Linux and Windows differ the chord is +listed after "or". The Linux chords for splits and tabs are +`Ctrl+Shift+D`, `Ctrl+Alt+Shift+D`, `Ctrl+Alt+T`; the page says "on Mac" +once and points at `⌘/` for the rest. + +The version in the meta block comes from `site/src/lib/site.ts` as a +constant, `VERSION`, updated by hand with each release. It is not read from +git at build time because the site deploys independently of a tag. + +## Header and footer + +- Header, not sticky: wordmark left; `[ setup ]` `[ how it works ]` + `[ remote ]` `[ faq ]` as bracketed links; theme toggle and GitHub mark + right. Below `md` the links wrap onto a second line. +- Footer: `Rule`, then the links pipe-separated on one line, then the + copyright line. + +## Docs pages + +`DocPage` keeps its props (`slug`, `title`, `blurb`, `children`). It renders: +`← flue.sh`, `H1`, lede as `P` in foreground, children, then `Rule` and +"Other pages" as a `Ul` of links. Same column as the homepage. + +Content of the four routes is kept. Only the `how-it-works` lede changes, +to match the new positioning, and any sentence that restates the +walking story goes. + +## README + +Same skeleton as the homepage, in this order: + +1. Title, tagline "Your Claude Code sessions and terminals, in one place.", + badges, links, hero picture. Unchanged except the tagline. +2. The hero paragraph, then the eight bullets, verbatim from the homepage. + The "Sessions outlive flue" section is now a bullet and is removed. +3. One line: "One static Go binary. macOS, Linux, WSL." +4. Install: unchanged. +5. Recommended setup: the four steps and the one warning about running + `flue relay setup` once. The rest of the prose goes, the link to + `/docs/setup` stays. +6. The CLI: unchanged. +7. Remote access: the two commands, one paragraph, the two links, the + architecture picture. +8. Status: four lines. Released, v0.5.1, 0.x caveat, link to FOLLOW-UPS. +9. Building and developing, License: unchanged. + +Target is around 120 lines from 172. `check-prose.mjs` does not scan the +README, so the no-dashes rule is checked by hand before the PR. + +## Also touched + +- `site/src/routes/__root.tsx`: `TITLE` and `DESCRIPTION` match the new + headline and paragraph. +- `site/public/llms.txt`: first lines match the new headline and paragraph. +- `site/src/lib/docs.tsx`: `how-it-works` blurb. +- `site/src/components/mock/fleet.tsx`, `switcher.tsx`: drop `shadow-2xl`, + keep the ring. +- `site/src/components/walkthrough.tsx`: stays, used on `/docs/setup` only. +- Deleted if unused after the rewrite: `site/src/components/ui/button.tsx`, + `site/src/components/mock/terminal.tsx`, `site/src/components/mock/diagrams.tsx`. + +## Testing + +- `cd site && pnpm run build` (vite, `check-pages`, `sitemap`, `check-prose`). +- `cd site && pnpm run lint`. +- Screenshots with `reins` at 390px and 1280px, light and dark, of `/`, + `/docs/setup`, `/docs/how-it-works`, `/docs/relay`, `/docs/faq`. Checked + for: nothing scrolls the page horizontally, the switcher still opens on + the chord, the copy button copies, the theme toggle works. +- The PR carries a before and after screenshot of `/`. From 10cb8aaa6e2db519535d05c6bd143ea869aa593c Mon Sep 17 00:00:00 2001 From: Karn Date: Sun, 23 Aug 2026 15:06:28 +0530 Subject: [PATCH 02/10] docs: implementation plan for the site and README overhaul Co-Authored-By: Claude Fable 5 --- .../plans/2026-08-23-site-readme-overhaul.md | 641 ++++++++++++++++++ 1 file changed, 641 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-23-site-readme-overhaul.md diff --git a/docs/superpowers/plans/2026-08-23-site-readme-overhaul.md b/docs/superpowers/plans/2026-08-23-site-readme-overhaul.md new file mode 100644 index 0000000..d05e3b5 --- /dev/null +++ b/docs/superpowers/plans/2026-08-23-site-readme-overhaul.md @@ -0,0 +1,641 @@ +# Site and README overhaul Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Rebuild flue.sh as one monospace document column, reposition the copy around session organisation, and make the README say the same thing. + +**Architecture:** A single primitives file `site/src/components/md.tsx` owns the markdown-in-a-browser look. The homepage, header, footer and `DocPage` compose those primitives. The three drawn mocks stay as they are, minus their shadows. The README is rewritten by hand to the spec's outline. + +**Tech Stack:** TanStack Start + React 19, Tailwind v4 (`@theme`, `@utility`), Geist Mono from Google Fonts, pnpm, wrangler. No test runner in `site/`; the checks are `pnpm run build` (vite, `check-pages`, `sitemap`, `check-prose`) and `pnpm run lint` (tsc), plus `reins` screenshots. + +**Spec:** `docs/superpowers/specs/2026-08-23-site-readme-overhaul-design.md` + +## Global Constraints + +- pnpm only. Never npm, npx or yarn. (`CLAUDE.md`) +- Work on branch `worktree-site-readme-overhaul`; land via PR. (`CLAUDE.md`) +- Prose: simple global English, no em-dashes or en-dashes anywhere, ranges use "to". `check-prose.mjs` enforces it on `site/`; check the README by hand with `grep -n '[—–]' README.md`. +- Every feature claim must be verified in `web/src` or `internal/` before it is written. The spec's copy was verified on 2026-08-23; if the build shows a claim is false, drop the claim. +- One font, Geist Mono. Colours stay zinc and teal. Radius 0 on site-owned elements. +- `og.png`, `docs/hero-*.png`, `docs/architecture.png`, `web/` untouched. +- Commit after every task with a plain-English message; commits and PR body are normal prose, not caveman. + +Run all commands from `site/` unless stated. Check cycle for every task: + +```bash +pnpm run lint && pnpm run build +``` + +Expected tail of a green build: + +``` +check-pages: 6 routes, all prerendered +sitemap: 6 pages -> https://flue.sh/sitemap.xml +check-prose: N files, no em-dashes or en-dashes +``` + +Screenshots: `pnpm run dev` on port 3100 in the background, then +`reins open http://localhost:3100/` and +`reins screenshot --browser --tab --full --out `. Run +`reins tabs` for the ids. Light and dark: press `d` on the page to toggle. + +--- + +### Task 1: Tokens and font + +**Files:** +- Modify: `site/src/styles.css` +- Modify: `site/src/routes/__root.tsx` (links, title, description) + +**Produces:** `--font-sans` and `--font-mono` both resolve to Geist Mono. `--radius: 0`. Utilities `backdrop-scan` removed. `term-cursor` kept (the switcher mock uses it). + +- [ ] **Step 1: Replace the font and radius in `styles.css`** + +Remove the `rsms.me` import. Make both font families Geist Mono. Set `--radius: 0`. Delete the `@utility backdrop-scan { ... }` block and its comment. Keep the theme tokens, the `@custom-variant dark`, the border-color base layer, and `term-cursor`. + +```css +@import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400..700&display=swap'); +@import 'tailwindcss'; +@import 'tw-animate-css'; + +@custom-variant dark (&:where(.dark, .dark *)); + +@theme { + /* One face. The site reads as a document, and a document is set in one + font. Sans is mono on purpose so that no component can drift back. */ + --font-sans: 'Geist Mono', ui-monospace, 'SF Mono', SFMono-Regular, Menlo, monospace; + --font-mono: 'Geist Mono', ui-monospace, 'SF Mono', SFMono-Regular, Menlo, monospace; +} +``` + +and in `:root`: `--radius: 0;`. + +- [ ] **Step 2: Update `__root.tsx`** + +Remove the `rsms.me` preconnect link. Set: + +```ts +const TITLE = 'flue: your Claude Code sessions and terminals, in one place' +const DESCRIPTION = + 'flue is a daemon that keeps terminal sessions alive, and a web app that lists every one of them across your machines. Claude Code, Codex, Pi, builds, SSH. Name them, tag them, pin them, find them, open them from any screen you own. One static Go binary, no hosted service.' +``` + +Keep `og:image:alt` as it is (it describes the picture, which is unchanged). + +- [ ] **Step 3: Check** + +`pnpm run lint && pnpm run build`. Green. The page will look wrong (Inter gone, blur utilities undefined are fine because Tailwind only emits what is used). + +- [ ] **Step 4: Commit** + +```bash +git add src/styles.css src/routes/__root.tsx +git commit -m "site: one font, no radius, new title" +``` + +--- + +### Task 2: The primitives, `md.tsx` + +**Files:** +- Create: `site/src/components/md.tsx` + +**Produces:** the exports below. Every later task imports from here. + +```ts +export function Meta({ lines }: { lines: string[] }): JSX.Element +export function H1({ children }: { children: ReactNode }): JSX.Element +export function H2({ id, children }: { id?: string; children: ReactNode }): JSX.Element +export function P({ children, muted }: { children: ReactNode; muted?: boolean }): JSX.Element +export function Ul({ children }: { children: ReactNode }): JSX.Element // children are
  • +export function Ol({ children }: { children: ReactNode }): JSX.Element // children are
  • +export function A({ href, children }: { href: string; children: ReactNode }): JSX.Element +export function Code({ children }: { children: ReactNode }): JSX.Element +export function Kbd({ children }: { children: ReactNode }): JSX.Element +export function Pre({ children, label, className }: { children: ReactNode; label?: string; className?: string }): JSX.Element +export function Shell({ lines }: { lines: string[] }): JSX.Element // lines starting with '$' are prompts +export function Table({ rows }: { rows: [string, ReactNode][] }): JSX.Element +export function Rule(): JSX.Element +export function Column({ children, className }: { children: ReactNode; className?: string }): JSX.Element +``` + +- [ ] **Step 1: Write the file** + +```tsx +import type { ReactNode } from 'react' + +import { cn } from '@/lib/utils' + +/** + * The page is a document, and this file is its whole vocabulary. + * + * Everything flue.sh draws is one of these: a heading with its `#` marker + * showing, a paragraph capped at a measure, a `*` list, a `$` transcript, + * a two-column table, a rule. The mocks are the only thing on the site + * that is not in here, and they are pictures. + */ + +/** The one column every page sits in. */ +export function Column({ children, className }: { children: ReactNode; className?: string }) { + return
    {children}
    +} + +/** The block at the top of a page: who, what, when. */ +export function Meta({ lines }: { lines: string[] }) { + return ( +
    + {lines.map((line) => ( +

    {line}

    + ))} +
    + ) +} + +function Marker({ children }: { children: string }) { + return ( + + ) +} + +export function H1({ children }: { children: ReactNode }) { + return ( +

    + # + {children} +

    + ) +} + +export function H2({ id, children }: { id?: string; children: ReactNode }) { + return ( +

    + ## + {children} +

    + ) +} + +export function P({ children, muted }: { children: ReactNode; muted?: boolean }) { + return ( +

    + {children} +

    + ) +} + +/** `*` list. Children are `
  • `. The marker hangs in the gutter. */ +export function Ul({ children }: { children: ReactNode }) { + return ( +
      + {children} +
    + ) +} + +/** `1.` list. Children are `
  • `. */ +export function Ol({ children }: { children: ReactNode }) { + return ( +
      + {children} +
    + ) +} + +export function A({ href, children }: { href: string; children: ReactNode }) { + const external = href.startsWith('http') + return ( + + {children} + + ) +} + +export function Code({ children }: { children: ReactNode }) { + return {children} +} + +export function Kbd({ children }: { children: ReactNode }) { + return {children} +} + +/** + * A block of text as typed. `label` is read to a screen reader in place of + * the block, for the diagram drawn in box characters. + */ +export function Pre({ + children, + label, + className, +}: { + children: ReactNode + label?: string + className?: string +}) { + return ( +
    +      {children}
    +    
    + ) +} + +/** A transcript. Lines beginning with `$` are prompts; the rest is output. */ +export function Shell({ lines }: { lines: string[] }) { + return ( +
    +      {lines.map((line, i) => (
    +        
    +          {line.startsWith('$') ? (
    +            <>
    +              $
    +              {line.slice(1)}
    +            
    +          ) : (
    +            {line}
    +          )}
    +        
    +      ))}
    +    
    + ) +} + +/** Two columns, key and value. A real table so it reflows and reads aloud. */ +export function Table({ rows }: { rows: [string, ReactNode][] }) { + return ( +
  • + + {rows.map(([k, v]) => ( + + + + + ))} + +
    + {k} + {v}
    + ) +} + +export function Rule() { + return ( + + ) +} +``` + +- [ ] **Step 2: Check** + +`pnpm run lint`. Green (the file is unused yet; tsc still type-checks it). + +- [ ] **Step 3: Commit** + +```bash +git add src/components/md.tsx +git commit -m "site: the markdown primitives every page is built from" +``` + +--- + +### Task 3: Header, footer, copy button, wordmark + +**Files:** +- Modify: `site/src/components/site-header.tsx` +- Modify: `site/src/components/site-footer.tsx` +- Modify: `site/src/components/copy-command.tsx` +- Modify: `site/src/components/theme-toggle.tsx` (remove `Button` import only; keep behaviour) +- Delete: `site/src/components/ui/button.tsx` + +**Consumes:** `Column`, `Rule`, `A` from Task 2. + +- [ ] **Step 1: Read `theme-toggle.tsx`** and note where it uses `Button`. Replace with a plain `