Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Operational knowledge lives in `.okf/` (`build/`, `content/`, `design/`, `workfl
## 🚨 Behavioral Constraints

- **Feature/idea intake**: `/deliver <idea>` (from the `jt-delivery` plugin) — triage → GOAL → author≠verifier orchestration per the `jt-delivery:contract` skill §1a; repo-specific bindings (gates, canon, reviewer routing): `docs/workflows/autonomous-delivery-prompt.md`.
- **Manager triage (Paul 2026-08-01)**: Paul adds requests mid-flight; triage each against the active plan — now / sequenced / backlog / groom-first — and report the verdict in one line so he can override. Deferring is a decision; interrupting a gated pipeline for a non-critical request is the failure. **Autonomy grant**: handle sprints end-to-end; for big/critical calls run a 2-4 agent voting panel and make the call (split + irreversible → hold for Paul). Record every autonomous call with its evidence. Paul's explicit words override any panel.
- **Manager triage (Paul 2026-08-01)**: Paul adds requests mid-flight; triage each against the active plan — now / sequenced / backlog / groom-first — and report the verdict in one line so he can override. Deferring is a decision; interrupting a gated pipeline for a non-critical request is the failure. **Autonomy grant**: handle sprints end-to-end; for big/critical calls AND any highly OPINIONATED question (design taste, naming, strategy trade-offs — anywhere reasonable people disagree) run a voting panel of subagents with DISTINCT PERSONAS/lenses and organize an explicit vote (Paul 2026-08-21); make the call (split + irreversible → hold for Paul). Record every autonomous call with its evidence. Paul's explicit words override any panel.
- **Agent 4-eyes on EVERY stage, author ≠ verifier (Paul 2026-08-20, widened 2026-08-21)**: every change is written by one agent and verified by a different one; review lands BEFORE the artifact leaves the workshop (plan before Paul sees it, diff before commit, finding before reported, measurement before quoted). Brief reviewers with goal + artifact, never conclusions; ask for measurements, not verdicts. Internal sub-agents per stage — **model routing (Paul 2026-08-21): critical decisions, editing/outlining AND design critique on fable; coding, code review AND content writing on opus; mechanical/simple tasks on sonnet; spawns never inherit the session model implicitly — pass `model` on every Agent call**; `/codex:review` once at the merge gate for user-facing changes. Full protocol: `jt-delivery:contract` skill §5; repo routing: `docs/workflows/autonomous-delivery-prompt.md`. (Evidence: 2026-08-20/21 — reviewers caught a CSS-breaking defect, a fabricated quote, and 11 findings on the delivery prompt itself, all after author self-review.)
- **Async-first (MANDATORY)**: written, discoverable artifacts for every decision, finding, and handoff; sync is the exception, written back same-day. A task is not done until a cold session can read its state with zero questions. Canonical surfaces: `.agents/skills/async-first-communication/SKILL.md`; same SOP in `AGENTS.md`.
- **Company facts have a canon**: check `.okf/content/claims-canon.md` before publishing any number (founding 2008-09-01; tenure from `site.Params.foundingYear`; "4.8/5 on Clutch" linked, NO review count; founder 22+ vs company 18+). A number with no in-repo source is a defect. Correcting one means sweeping the instruction layer too — `PRODUCT.md`, `docs/business/`, outreach kits have re-introduced bad canon before (4 of 8 published figures were wrong on 2026-08-14).
Expand Down
7 changes: 6 additions & 1 deletion bin/qtest
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ PAGE_TESTS = {
"free-consultation" => "free_consultation",
"vibe-code-rescue" => "vibe_code_rescue",
"friday-report" => "friday_report",
# v2 clean-slate rail (ADR-0006): layouts/next/* + pages/next-pilot.css
"next-pilot" => "next_pilot",
"simple-page" => "privacy_policy",
# critical/privacy-policy-critical.css maps by basename; alias it to the
# simple-page tests so a touch there doesn't abort as an unknown key
Expand Down Expand Up @@ -73,7 +75,10 @@ def pages_for(file)
when %r{^themes/beaver/assets/css/pages/([^/]+)\.css$} then [Regexp.last_match(1)]
when %r{^themes/beaver/assets/css/critical/([^/]+)-critical\.css$} then [Regexp.last_match(1)]
when %r{^themes/beaver/layouts/blog/}, %r{^content/blog/} then %w[blog-list blog-single]
when %r{^themes/beaver/assets/css/}, %r{^themes/beaver/layouts/} then :all
when %r{^layouts/next/}, %r{^content/next/} then PAGE_TESTS.keys.grep(/^next-/) # every v2 key; shared v2 templates affect them all
# Terminal: root layouts/ had NO case at all, so a root-layouts-only diff
# mapped to nil and qtest exited 0 having tested nothing (false green).
when %r{^themes/beaver/assets/css/}, %r{^themes/beaver/layouts/}, %r{^layouts/} then :all
end
end

Expand Down
10 changes: 10 additions & 0 deletions content/next/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: v2 preview
# Clean-slate v2 rail (ADR-0006). Everything under /next/ is staging on the
# production host: never indexed, never listed, never in the sitemap.
# noindex -> layouts/partials/seo/enhanced-meta-tags.html robots meta
# private -> layouts/sitemap.xml skips the page
cascade:
noindex: true
private: true
---
6 changes: 6 additions & 0 deletions content/next/services/fractional-cto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Fractional CTO (v2 preview)
source_page: /services/fractional-cto
layout: single
type: next
---
139 changes: 139 additions & 0 deletions docs/adr/0006-clean-slate-dual-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Architecture Decision Record: Clean-slate v2, dual-run with a /next/ staging rail

**ADR-0006**
**Date:** 2026-08-21
**Status:** Accepted (approved by Paul in plan mode, 2026-08-21)
**Supersedes in part:** ADR-0003's *propagate-into-legacy* rollout mechanism,
for pages not yet shipped. ADR-0003's brand decisions remain in force on the
live site until the design competition names a winner.

## Title

Stop converting the legacy cascade page by page. Build v2 on a separate rail,
run both, and cut over one page at a time through a non-indexed `/next/` path.

## Context

ADR-0003 chose a strangler rollout: convert each page bundle in place, one PR
at a time, propagating the new tokens into the existing FL-derived cascade.
Five months of evidence say the two halves of that plan performed very
differently:

| Approach | Outcome |
|---|---|
| Born-clean page (blog rebuild, blog-single, course, vibe-code-rescue, friday-report) | shipped; 0–11 `fl-` lines; no reverts |
| Propagate into legacy (1a.4 footer, the 41-rule eyebrow sweep, #540's first attempt) | reverted, or created the seam it was removing |
| Buttons (780 call sites) and the 39 remaining blacks | never started |

The pattern is not about skill. A born-clean page owns its whole cascade, so a
change to it is local and its blast radius is one screenshot key. A
propagation change lands in a cascade recovered from an obfuscated FL-Builder
export, where `legacy-theme-skin.css` and a `:not()`-chain anchor rule outrank
whatever the page declares — so each fix needs a specificity workaround, and
the workaround is the next defect.

Paul's direction is that the site must read as a top-tier service with a
top-tier UI. The recovered cascade is not the foundation for that, and the
cost of making it one is now measurable in reverts.

## Decision

1. **Legacy is frozen.** `themes/beaver/layouts/**` and the existing bundles
take defect fixes only. Two ADR-0003 work items are **dropped** as
work-done-twice: the 780-site button unification and the 39 remaining
hardcoded blacks. They will be resolved by pages moving to v2, not by
editing the pages twice.
2. **v2 gets its own rail**, forked from the theme rather than grown out of
it: `layouts/next/baseof.html` + `layouts/next/single.html` +
`themes/beaver/assets/css/pages/next-pilot.css`. The rail keeps every
FUNCTIONAL part of the theme baseof verbatim (SEO meta, the five schema
partials, favicons, analytics — conversion events are attribute-driven, so
the partial keeps them firing — service worker, skip link,
`<main id="main-content">`) and reuses the chrome partials as they are. It
drops the 7 hardcoded `fl-*` body classes, the `.fl-page` wrapper and the
legacy slice members (`style.css`, `586.css`, `vendors/base-4.min.css`,
`legacy-theme-skin.css`).
3. **`/next/` is staging on the production host**, not a separate deploy. It
is `noindex` (frontmatter cascade → `enhanced-meta-tags.html`) and excluded
from the sitemap (`private: true` → `layouts/sitemap.xml`). **No robots.txt
disallow** — that would hide the `noindex` meta from the crawlers that need
to read it. Staging is **sunset after two clean flips**.
4. **A staging stub owns only the URL.** Its `source_page` resolves the real
page; the v2 layout renders from that single source so copy cannot drift
between rails. The canonical points at the source page, emitted from the
shared meta partial so exactly one canonical ships.
**PR-1's pilot is a RAIL SMOKE PAGE, not a service-page mirror** — measured:
47 of the real page's 536 words render, because service copy lives in
frontmatter (`overview`, `faqs`) the smoke template does not read. Porting
those frontmatter sections is PR-2's FIRST task, before any styling. Do not
judge the design against the smoke page.
5. **Flipping a page keeps the permalink** so no redirects are needed, and the
revert is a frontmatter change. But the flip is only safe ONCE the v2
template renders everything the page's frontmatter carries (see 4) — setting
`type: next` on the real page before that guts it to the smoke-page subset.
The flip checklist: v2 template reads every frontmatter section the page
uses → baselines for the page key → then `type: next`.
6. **Homepage carve-out.** A v2 homepage may be built at `/next/` at any time,
but the PUBLIC flip honours the ≈2026-09-17 engagement-read gate from
ADR-0004 — the homepage is the only surface with enough traffic for the
read to mean anything, and flipping it early destroys the baseline.

## Consequences

**The rail is not zero-legacy — navigation.css leaks a body rule.** The v2
bundle reuses `navigation.css` and `footer.css` for chrome, and
`navigation.css:20` sets `body { font-family: system-ui; font-size: 18px;
line-height: 1.6; color: #121212 }` — so v2 pages inherit legacy body sizing
and a hardcoded colour literal unless the page bundle overrides them. Known,
accepted for PR-1; PR-2's design pass owns the override (measured: the pilot's
loose h1 leading is this rule).

### Positive

- Every v2 page is born clean; no page is edited twice.
- The rail is testable on the production host, on real devices, before any
visitor-facing change.
- Rollback is a frontmatter revert rather than a CSS bisect.
- Closing a related false-green: root `layouts/` had no `bin/qtest` mapping at
all, so a root-layouts-only diff exited 0 having tested nothing. The rail
forced the terminal `^layouts/` → escalate-to-all case that fixes it.

### Negative / accepted risk

- **Two rails exist at once.** A chrome partial edited during the dual-run
period must be checked on both. Mitigated by the freeze: legacy takes defect
fixes only.
- **If the competition winner deviates from ruby/LIGHT**, this ADR supersedes
ADR-0003's brand decisions too, and the already-shipped blog and course
surfaces join a re-skin queue. Accepted cost, taken knowingly.
- **`/next/` is publicly reachable.** Anyone with the URL sees unfinished work.
Accepted: `noindex` + sitemap exclusion is the right trade against the cost
of a separate deploy target, and the pages carry no claims of their own.
- The rail duplicates ~70 lines of baseof. Deliberate: a shared baseof with
conditionals is exactly the coupling this ADR exists to remove.

### Neutral

- No new dependency, no build-pipeline change. The v2 bundle goes through the
same `assets/css-processor.html` as every other bundle.

## Alternatives considered

**Keep propagating (ADR-0003 as written).** Rejected on its own evidence:
three of three propagation attempts reverted, and the two largest items never
started.

**A second Hugo site / separate deploy target.** Cleanest isolation, but it
cannot be reviewed on the real host with the real chrome, and the flip becomes
a migration rather than two frontmatter lines.

**Redesign in place behind a feature flag.** Same cascade, same specificity
fights, plus a flag to maintain.

## References

- `docs/projects/2608-site-design-system/20-29-strategy/20.06-clean-slate-v2-plan.md` — the approved plan this ADR records
- ADR-0003 — the design system whose rollout mechanism this supersedes in part
- ADR-0004 — why the homepage flip waits on an engagement read
- `docs/workflows/new-page.md` §v2 rail — which fork a new page belongs on
- `docs/projects/2509-css-migration/css-bundle-ownership-map.md` — the `next-pilot` bundle row
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
- `0003-site-design-system.md` — one design system for site chrome ("Rescue Room")
- `0004-static-site-experimentation.md` — what gets A/B tested, what cannot be, what replaces it
- `0005-reusable-ai-instruction-layer.md` — extract generic skills/agents/contract as a Claude Code plugin; repos keep thin bindings
- `0006-clean-slate-dual-run.md` — freeze legacy, build v2 on its own rail, stage at `/next/`; supersedes ADR-0003's propagate-into-legacy mechanism
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ zero FL-Builder export files remain in any slice.
| not_found | `404.html` | — (404.css, dynamic-404.css) | 16.2K → 16.2K | no-FL |
| friday-report | `page/friday-report.html` | pages/friday-report.css | new 2026-08-20 | born semantic (no FL modules; one `#fl-main-content` specificity override against legacy-theme-skin) |
| pagination | `list.html` (second bundle) | — (pagination.css only) | 0.2K → 0.2K | no-FL |
| next-pilot | `layouts/next/single.html` (repo root, NOT in theme) | pages/next-pilot.css | new 2026-08-21 | v2 clean-slate rail (ADR-0006): css-variables + navigation.css + footer.css only — no style.css/586/base-4/legacy-theme-skin. Serves `/next/*` staging pages |

¹ careers was a byte-identical verbatim move (3086-layout2 had zero dead
nodes); the small growth vs 07-12 is upstream content drift (course v2 merge
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,19 @@ action, not six.

## Judging (after both directions exist)

- Critic: `/impeccable` critique **on fable** (Paul: design critic = fable),
plus ICP cold-eyes panel — lenses: first-5-seconds trust, purchase-intent
CTA clarity, mobile.
- Critic: `/impeccable` critique **on fable** (Paul: design critic = fable).
- **Persona voting panel (Paul 2026-08-21: highly opinionated questions get
multiple subagents with different personas and an organized vote).** Distinct
personas, each voting with its strongest objection + evidence:
1. *Alex the burned founder* (the ICP, first-5-seconds trust) — sonnet
2. *The CFO skeptic* (purchase-intent: would I authorize $15K/mo off this
page?) — sonnet
3. *The mobile commuter* (390px, thumb-reach, scan-only) — sonnet
4. *The brand strategist* (what does this say about us vs every other
agency site?) — sonnet
Votes are organized into one tally; independent agreement is valid,
manufactured dissent is not; each names the strongest finding AGAINST its
own preferred direction.
- Paul sees ONE side-by-side board per direction: homepage + single-service
screens, 3-line verdict per lens, one "what this says about us" sentence.
- **Paul picks.** Rejection feedback, if both fail, seeds direction 3.
35 changes: 31 additions & 4 deletions docs/workflows/new-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@ The checklist for building a new page that reuses the shared components and
stays consistent with the design system. Every trap listed here has bitten a
real sprint; do not skip steps.

## 0. Pick your fork first — LEGACY or v2 rail

Since ADR-0006 there are **two** paved paths, and §1–8 below describe the
**LEGACY** one. Legacy is frozen: it takes defect fixes and pages already on
it, not new design work.

| | LEGACY (§1–8 below) | **v2 rail** |
|---|---|---|
| Baseof | `themes/beaver/layouts/baseof.html` (7 `fl-*` body classes, `.fl-page` wrapper) | `layouts/next/baseof.html` (neither) |
| Shell | `fl-row` / `fl-col-group` / `fl-col` nesting is load-bearing | plain semantic HTML; **no `fl-*` anywhere** |
| Bundle | `critical/base.css` + `586.css` + `vendors/base-4.min.css` + `style.css` + `legacy-theme-skin.css` + … | `foundations/css-variables.css` + `navigation.css` + `footer.css` + your page CSS. Nothing else. |
| Where it renders | its own permalink | `/next/<path>/` — `noindex`, out of the sitemap |

**A NEW page, or a page being redesigned, goes on the v2 rail.** Copy
`layouts/next/single.html` and `themes/beaver/assets/css/pages/next-pilot.css`
as the starting shape, add a stub under `content/next/` whose `source_page`
points at the real page (the stub owns only the URL — copy keeps one source of
truth), and flip the real page with `type: next` ONLY once the v2 template renders every frontmatter section the page uses (ADR-0006 §5 checklist) — flipping before that guts the live page to whatever subset the template reads.

Steps 2 (bundle order), 5 (purge prefix) and 6 (qtest map, screenshot tests,
ownership map) apply to BOTH forks — a v2 page still needs its `PAGE_TESTS`
key and its ownership-map row, and `test/unit/paved_path_guard_test.rb` fails
without them. Steps 1, 3 and 4's `fl-*` guidance apply to legacy only.

Read `docs/adr/0006-clean-slate-dual-run.md` before starting either.

## 1. Template

Create `themes/beaver/layouts/page/<name>.html`. Start from
Expand All @@ -17,10 +43,11 @@ structural shell:
... fl-row / fl-col-group / fl-col / fl-col-content nesting ...
```

The `fl-*` framework classes are load-bearing (layout grid from
`critical/fl-layout-grid.css`) — keep the nesting; name YOUR elements with a
page prefix (`<name>-hero`, `<name>-grid`, ...). Never introduce hash-like
class names.
The `fl-*` framework classes are load-bearing **on this fork** (layout grid
from `critical/fl-layout-grid.css`) — keep the nesting; name YOUR elements
with a page prefix (`<name>-hero`, `<name>-grid`, ...). Never introduce
hash-like class names. On the **v2 rail** there is no grid framework and no
`fl-*` at all — see §0.

## 2. CSS bundle slice

Expand Down
Loading
Loading