---
type: plan
status: active
updated: 2026-07-24
---

# BW Guides — Platform Roadmap

BW Guides is evolving from "a two-plugin docs delivery system" into a small
**documentation platform**: any project session can author guides for the
features it built, screenshot them automatically, and publish them targeted at
the right client sites — in a standard house style, without leaving that
session.

This doc is the durable plan across the multiple build sessions that takes.
The immediate reference for what exists today is `HANDOFF-NOTES.md` and
`SESSION-LOG.md`; this is the shape we're building toward.

## Decisions locked (2026-07-24, with rian)

1. **Targeting = client groups.** Each registered client site carries a client
   slug (e.g. `brentwood`). Guides target the *client*, not individual sites.
   Registering `brentwood.ca` under `brentwood` at launch auto-delivers every
   existing brentwood-targeted guide on its first sync — no re-targeting of any
   guide. This is the whole reason to prefer it: "push to dev now, both after
   launch" becomes one registration action.
2. **Authoring = CLI + skill, not a network API.** A `bw-guide` CLI does the
   mechanical work (create / update / target / screenshot / publish); a
   `write-guides` skill encodes the house standard and is invokable in any
   project session. Runs server-side through the gateway (owner-gated). No
   public write surface on the hub. MCP is a possible *later* wrapper if the
   capability is ever needed outside Claude Code — not needed now.
3. **Screenshots = headless Chrome + clean fixture, selector-cropped.** Proven
   working (see below).

## The three layers

> **SOURCE-OF-TRUTH RULE (2026-07-26):** the **hub is the single source of truth for guide
> content**. Draft files under a project's `docs/guides-draft/` are retained only as the
> *screenshot capture manifest*; their guide bodies go stale as soon as anyone edits on the
> hub, which is the natural thing to do. Rebuilding hub content from drafts is a
> data-destroying operation — verified: 13 of 20 Brentwood guides had drifted with the hub
> ahead. See HANDOFF-NOTES (2026-07-26).

> **STATUS REFRESH (overnight run, 2026-07-24):** Layer 1 **BUILT** (hub 0.2.0:
> client_slug + "Specific client" targeting + manifest resolution; brentwooddev
> registered as client:brentwood, site id 7, backlog-on-first-sync verified).
> Layer 2 **PARTIAL** (write-guides skill live at ~/.claude/skills/write-guides/;
> publishing is skill+staged-wp-cli — a dedicated `bw-guide` CLI remains unbuilt).
> Layer 3 **BUILT** (tools/bw-guide-shot.sh + tools/bw-guide-shot/: MITM-fulfill
> capture, zero gate changes, zero outbound network, works on gated client sites —
> brentwooddev Staff screen captured authenticated+styled; the id-auth capture
> identity is therefore NOT needed and the "temp-ungate" flow is retired). Hidden
> section keywords + categories ship hub→client in payload meta and power ranked
> section search (client 0.3.1). Detail below kept for design rationale; trust
> this block + SESSION-LOG for current state.

### Layer 1 — Targeting (client groups) — BUILT (see status refresh above)

Hub changes (`bw-guides-server`):
- Add a `client` slug (and optionally `environment` = dev|prod) to the sites
  registry (`{prefix}bw_guides_sites`). Bump `BW_GUIDES_SERVER_DB_VERSION`;
  `maybe_upgrade()` runs dbDelta.
- Targeting gains a client-group audience: a guide's `_bw_guides_audience` can be
  `client` with `_bw_guides_target_client = <slug>`, resolved to all active
  registered sites in that group at manifest time.
- The manifest/guides REST endpoints already filter per-authenticated-site, so
  no client-plugin protocol change is required for basic client-group delivery —
  the hub just includes a guide for a site if that site's client matches. Verify
  this holds before assuming it (a site that joins a client later must pull the
  backlog on first sync — it will, because sync is a full manifest reconcile).
- Admin UI: set a site's client on the Sites screen; choose client-group as an
  audience in the Targeting metabox.

Launch story to preserve: adding `brentwood.ca` to the `brentwood` group is the
*only* action needed at launch; existing guides flow to it automatically.

### Layer 2 — Authoring (`bw-guide` CLI + `write-guides` skill) — NOT BUILT

- **CLI `bw-guide`** (server-side, uses `srv-gw wp` against the dist host):
  `create` / `update` (title, core-block content, excerpt, tags),
  `target --client <slug>` | `--all` | `--site <host>`, `shot` (invoke the
  capture pipeline and insert the image), `publish`. Owner-gated via the gateway.
- **Skill `write-guides`**: encodes the house standard so any session produces
  consistent guides — core blocks + the design kit, one-guide-per-area /
  one-task-per-heading, question-shaped headings carrying the client's own
  vocabulary, the post-write heading search-test, and screenshots. Derives the
  target client from the project it's run in where possible.
- The "standard" is the skill's content; the CLI is the mechanism. Keep them
  separate.

### Layer 3 — Screenshots — CORE PROVEN, generalization + CLI remain

**Proven end-to-end on 2026-07-24** (against the `guide-shots` fixture):
clean fixture → headless google-chrome (already installed; puppeteer-core, no
browser download) → **on-demand WP session cookie minted via the gateway (no
stored password, 1h expiry)** → capture over the **real HTTPS origin** so all
CSS/JS load → **CSS-selector crop** (`.interface-complementary-area` = the
settings sidebar) → ImageMagick trim → auto re-gate. First real output: a clean,
styled, cropped WP 7 page-settings panel with none of `template.demoing.info`'s
BW Dev pollution.

Key technical facts learned (don't re-derive):
- The site's containers sit behind Caddy; WP decides https from a forwarded
  header. Direct container access (172.17.0.1:port) is **host-only, never
  internet-exposed**, but WP emits **https** asset URLs, so assets fail to load
  when you fake https over the plain-http container. Capturing over the **real
  HTTPS URL through Caddy** is what makes assets render.
- Auth cookie: over https WP wants the **secure** cookie
  `wordpress_sec_<md5(siteurl)>` (scheme `secure_auth`) **plus**
  `wordpress_logged_in_<hash>`, both from the **same** freshly-created session
  token (`WP_Session_Tokens::create`). Minted via `srv-gw wp -- eval`; never
  printed or stored.
- `--skip-template` is essential for the fixture: the default clone drags in all
  BW Dev plugins.

**Open items for the pipeline:**
- **Fixture capture currently temp-ungates** the site (id-auth → public →
  re-gate). Acceptable for a *no-data throwaway fixture*, and the tool re-gates
  in a `finally`. But **id-auth has no auto-re-enable timer** — a crash mid-run
  could leave it public. Harden the CLI so re-gate is guaranteed, or give the
  fixture a capture identity (below) so ungating isn't needed at all.
- **Generalizing to client sites (for feature guides) needs a real solution, not
  temp-ungate** — client sites hold real data and must never be exposed. The
  clean general answer is a **capture identity in id-auth**: a service session
  the capturer presents so it passes the gate over the real HTTPS URL (assets
  load, only the WP cookie needed). This touches id-auth (sensitive infra) — a
  deliberate, security-reviewed sub-project.
- **Feature guides screenshot the client's dev site** (where the feature lives),
  not the fixture. The fixture is for generic core-WP / plugin guides.
- **Hub images hotlink** from plugins.bowden.works to client sites. Sideload
  before screenshots ship widely (already a roadmap item).
- Selector library: maintain a small map of stable WP-admin selectors
  (settings sidebar, Status popover, block toolbar, specific metaboxes) so
  guides capture consistent regions.

**Where the capture code lives:** proof currently in the session scratchpad
(`capture/capture.js`, puppeteer-core). Promote to a real home when building the
CLI — likely `tools/` alongside the other BW tooling, or a dedicated infra dir
(node_modules may not belong in the plugin repo).

## Fixture site

- **`guide-shots`** — blank WP (`--skip-template`), port 3137,
  `guide-shots.demoing.info`, id-auth gated (default password). Admin `support`;
  password generated at creation and **not stored anywhere** (capture uses
  on-demand cookies, not the password). Standard fresh content (Sample Page id 2,
  Hello world! id 1).
- For plugin-specific guides, activate the plugin-under-test here and add known
  sample content. Theme is irrelevant for wp-admin screenshots (the editor UI is
  core); add Kadence only if a guide needs front-end shots.
- **Decision to confirm with rian:** is `guide-shots` a pure Claude-driven
  capture appliance, or should it stay comfortably browsable by rian? Today it's
  normally browsable (public https via id-auth + WP login); capture temporarily
  ungates. Fine as-is; revisit if the temp-ungate window is unwanted.

## Build order (suggested)

1. **Layer 1 (client groups)** — foundational; everything targets through it.
2. **Screenshot CLI** — wrap the proven capture as `bw-guide shot`, guarantee
   re-gate, add the selector library.
3. **Authoring CLI + `write-guides` skill** — compose 1 + 2 into the
   "write guides for our features" flow.
4. **id-auth capture identity** — unlock client-site feature-guide screenshots
   without exposure (security-reviewed).
5. **Image sideloading** — before wide rollout.

## Security posture (paranoid-mode obligations)

- No new listening service; the CLI is one-shot, invoked via the gateway
  (owner-gated). No public write API.
- No stored WP passwords anywhere; capture auth is on-demand, short-lived,
  gateway-minted cookies, wiped after use.
- Direct container access is host-only. Any public exposure (temp-ungate) is a
  reviewed, reversible, no-data-fixture-only step with guaranteed re-gate.
- Run `srv-gw security-audit` after standing up each new piece.
