Files
compound-engineering-plugin…/plugins/compound-engineering/skills/ce-brainstorm/references/html-rendering.md

28 KiB
Raw Permalink Blame History

HTML Rendering

This is a format-rendering reference — it describes how to render any artifact in HTML, independent of which skill is producing it.

It is paired with a section contract (plan-sections.md, brainstorm-sections.md, etc.) that describes what the artifact contains. This reference describes how HTML specifically presents it. The same content rendered by different skills shares the same HTML principles.

The HTML artifact is the only artifact the skill produces for that run — output mode is exclusive (markdown OR HTML, never both). Downstream consumers that read HTML today (ce-work, human readers) do so directly; the agent-consumability rules below make that work. ce-doc-review is not currently an HTML consumer — its mutation mechanics are markdown-only, so the ce-plan handoff gates the 5.3.8 doc-review pass to OUTPUT_FORMAT=md runs and skips it for HTML.

Hard invariants

These hold regardless of which skill produced the artifact.

  • Single self-contained HTML5 file. No companion .css, .js, or .svg files. CSS lives in <style>. SVG lives inline. Images are base64 data URIs or inline SVG. The one permitted exception is a <link rel="stylesheet"> to a CDN webfont CSS endpoint (Google Fonts, Bunny Fonts, etc.), paired with an offline-readable fallback font stack so the doc remains readable if the CDN is unreachable.

  • All metadata appears as visible text — single source of truth. The artifact's metadata (title, type, status, date, etc. — exact fields per-skill, defined in the section contract) renders as visible HTML elements that downstream agents and humans read. No hidden machine-readable copy in any form: no <script type="application/json"> frontmatter block, no data-* attribute mirror, and no <meta name="status"> / <meta name="created"> / <meta name="origin"> in <head> duplicating the same values that appear in the visible header. One representation for each value — drift across two copies is the failure this rule prevents.

    The text-and-attribute redundancy in <time datetime="2026-05-12">2026-05-12</time> is acceptable because the attribute is a parser hint, not a hidden copy.

  • Editable status renders as <span class="status">{value}</span>. Downstream tooling (ce-work shipping flip, future HTML-aware consumers) finds and rewrites status by selector. Embedding the status value inside a header <dl> cell (<dt>Status</dt><dd>active</dd>), inside a <meta> tag, or as visible text without the class="status" hook all break the flip mechanic — the consumer either can't locate the value or can't disambiguate it from prose. The status span may sit anywhere in the doc (inside the header metadata, in a stats strip, in a hero banner); placement is a visual choice, the selector shape is the contract.

  • Stable IDs as anchor IDs AND visible text. Every ID-bearing item (R-IDs, U-IDs, A-IDs, F-IDs, AE-IDs, KTDs) gets id="r1" on its element AND appears as visible text inside the element (e.g., the text "R1." inside the table cell or heading). Downstream agents find the ID in source the same way they find it in markdown.

  • Source / composition signal. A visible footer at the bottom of the doc names the composition timestamp and the source identifier (the user prompt context, the upstream brainstorm doc when one exists, or just the composing skill name when there's no external source). Example shape: <footer class="composition-signal">Composed 2026-05-17T14:23Z by ce-plan from <code>docs/brainstorms/...-requirements.md</code></footer>. Under exclusive output mode this signal is the artifact's own provenance — there's no markdown sibling to reference. Omitting it leaves readers unable to tell how stale the rendering is.

  • ASCII identifiers. Class names, element IDs, data attribute names are ASCII-only.

Precedence stack for style preferences

Honor user style preferences in this order (highest to lowest):

  1. In-session conversation — explicit direction the user gave this run.
  2. Preferred stylesheet reference named in loaded agent-instruction context (typically AGENTS.md / CLAUDE.md, but scan loaded context; don't enumerate locations). The reference may be a file path (docs/style.css), a URL, a named library ("Tailwind"), or a style brand ("Stripe docs"). Agent-instruction files carry deliberate agent-aware preferences, so this tier sits above DESIGN.md.
  3. DESIGN.md discovered on the filesystem (see "DESIGN.md discovery" below).
  4. Fallback default — the opinionated palette / typography choices the agent makes when no preference exists.

Active-recall at compose time

Before writing the CSS, scan loaded context for any stylesheet reference the user has indicated for documents like this. If found and inlinable (short local file, fetchable URL within budget), inline it into <style>. If found but not inlinable (large framework, paywalled stylesheet, named system without a fetchable source), compose CSS in its spirit — typography, color, density cues drawn from the named system. Only fall back to the default style when no preference signal exists.

The single-file invariant is preserved either way. External <link rel="stylesheet"> is permitted only for CDN webfont CSS (with the offline fallback font stack); never link to an external stylesheet carrying layout, color, or typography rules the doc cannot read offline.

DESIGN.md discovery

When tier 3 of the precedence stack applies, look for a DESIGN.md file in these locations, first match wins:

  1. Worktree root (resolve via git rev-parse --show-toplevel).
  2. docs/DESIGN.md.
  3. .compound-engineering/DESIGN.md.

Read once at compose time. Absent → fall through to the fallback default.

Worktree-root only — do not fall through to a main checkout. Users working from a worktree who want HTML defaults can add DESIGN.md to the worktree.

DESIGN.md is a partial override, not all-or-nothing. Real DESIGN.md files vary widely: some are token tables, some are CSS variables, some are prose; most cover a subset of what HTML composition needs. Apply the tokens that fit a long-form text doc — typography roles, text colors, contrast targets, border-radius scale, elevation primitives, muted-vs-accent split. Skip the rest. Three specific failure modes to defend against:

  • Scope mismatch (product UI vs doc surface). A DESIGN.md aimed at product marketing or app UI may name page-surface colors, button states, input borders, or hero backgrounds that are tied to that surface, not to a generic doc. Page-surface colors are the canonical trap — --surface: #c0f0fb belongs on the product's marketing page, not on every plan or requirements doc the team writes. Extract the principle (the design language uses a tinted surface) rather than the literal value when the token is product-UI-scoped. Apply literal values only when the token is generic enough to transfer (text color, type scale ratio, radius scale, contrast ratio).
  • Partial coverage. When DESIGN.md defines some categories but not others (e.g., colors but no spacing scale, typography but no elevation), use DESIGN.md for what it covers and the fallback default for what it doesn't. Do not require DESIGN.md to be complete before honoring it.
  • Named font without a fetchable source. When DESIGN.md names a font (e.g., "Signifier", "Every") without a CDN URL or local @font-face source the agent can inline, treat the name as a hint about the design intent, not a literal directive. Emit a system-font stack in the same family (serif vs sans vs mono) and pick a weight that matches the intent. The single-file invariant still holds; do not link to an external stylesheet to fetch the named font.
  • Typography-scale mismatch. DESIGN.md typography tokens are often sized for product UI — marketing pages, app screens, hero sections — with body text at 18-20px and headings at 32-52px. A long-form doc surface needs body at ~14-16px and headings at ~1.2-1.6× body. When the DESIGN.md size scale looks product-scaled, use the family, weight, and OpenType feature assignments (these carry the design language) and pick the agent's own size scale for the doc surface. Apply DESIGN.md sizes literally only when the tokens are clearly doc-scaled — body tokens at 14-16px, headings under ~32px.

Format principles

These shape what "good" HTML looks like; the agent applies them per artifact based on content.

Readable measure, not full bleed

Long-form text is unreadable at full viewport width — past ~80 characters per line the eye loses the return sweep and scanning slows. As a fallback-default (precedence tier 4, overridden by in-session direction or DESIGN.md), center the document in a content container and hold prose to a comfortable measure.

  • Page container. A centered column with a max-width in the ~820-960px band (margin-inline: auto) keeps the doc off the far edges of wide monitors while leaving room for the format's richer shapes.
  • Prose measure. Hold running paragraphs to roughly 65-80 characters (max-width: ~70ch on text blocks). The named test: read a paragraph at full window width on a wide display — if the return sweep to the next line is effortful, the measure is too wide.
  • Let wide content break out. Tables, diagrams, and side-by-side columns may use the full container width (or wider) when the content needs it — the measure constraint is for prose, not for everything.

Express the constraint in ch/rem rather than a single hardcoded pixel value so it survives font-size and DESIGN.md overrides. DESIGN.md or an in-session instruction overrides these values; this is the fallback when no layout preference exists.

Markdown source is content, not design

When markdown (or markdown-shaped chat context) is part of the input, use it for semantic content — what the doc is about, what sections exist, what facts each section establishes. Do NOT treat its bullet-vs-table presentation choices as authoritative; re-choose the rendering per content shape in HTML's richer affordance space. If the markdown rendered 13 requirements as a bulleted list, that does NOT mean HTML must render them as a list — ask whether 13 items sharing ID + body shape deserve a table.

Prose is authoritative

When a visualization disagrees with the surrounding prose, the prose governs. If they diverge, the visualization is wrong.

When the doc has a Sources & References (or equivalent reference-index) section, hyperlink each entry to its canonical destination so readers can open it directly. A long bare-text list of paths and ticket IDs is the format's biggest unforced UX miss — the reader has to copy-paste every entry into a browser or IDE.

Resolve the repo's GitHub URL once at compose time:

git remote get-url origin

Apply linking to three reference shapes:

  • Repo-relative code/doc paths (services/foo.ts, docs/solutions/bar.md) → <repo-url>/blob/main/<path>.
  • Named GitHub PRs/issues (PR #636, issue #1048) → <repo-url>/pull/636 or <repo-url>/issues/1048.
  • Named external trackers (Linear ESP-1705, Jira PROJ-123) → link only when the workspace URL is established in loaded context (e.g., a linear.app/<workspace>/... URL appeared earlier in the session or in AGENTS.md); otherwise leave as text.

Do not invent URLs. If origin isn't a GitHub URL (GitLab, Bitbucket, internal host) and the equivalent main-tree URL pattern isn't obvious, leave entries as <code> text. If the external tracker workspace isn't established, leave as text. A broken or guessed link is worse than no link.

Scope: reference index only, not inline prose. Inline <code> mentions of paths or PRs inside paragraph prose stay as code or text. Linking every mention would clutter; readers expect clickable jumps where the doc presents itself as a reference index.

Text contrast is local

Every text-on-background pairing must hold up on its own. A color that works for prose on the page background does not automatically work for a small label inside a tinted container. The most common violation: applying a generic "muted" text variable (calibrated for prose-on-bg) to secondary text inside an accent-soft / warn-soft / info-soft container.

Test by reading each filled shape's labels at the rendered scale. If the subtitle or secondary text feels washed-out against the fill, the choice is wrong for that local context — pick a color from the same family as the fill (accent-text for accent-soft, etc.) or drop the muting entirely and rely on font-size and weight for hierarchy.

Body bold not colored by default

Reserve accent text color for status chips, ID chips, links, and section borders. Do NOT color <strong> in body content by default. Bold weight already carries emphasis; applying accent color to every <strong> in a long list overwhelms the eye, especially in dark mode. CSS should leave strong at color: inherit unless a specific surface (status pill, ID chip) is being styled.

No JS framework runtimes

A small inline <script> for active-section TOC tracking or anchor- permalink behavior is acceptable. React, Vue, Svelte, or any framework runtime is not. The single-file invariant doesn't permit framework bundles, and the artifact's longevity doesn't warrant a build dependency.

Section anatomy

How section types commonly render in HTML. These are patterns, not contracts — the agent picks shapes that fit the content.

  • Summary / Problem Frame — semantic <section> with prose paragraphs. Optionally precede with an eyebrow label (small-caps tag above the title) for editorial polish.
  • Requirements<table> is the default at 5+ uniform items; bullets at smaller counts. Concern-grouping takes precedence over the flat-table default: when requirements span distinct concerns, group them under bold inline headers (or per-group sections) first, then apply the 5+ table default within each group rather than flattening the whole section into one table. Each row has the R-ID as visible text in its own column. Consider adding a "covered by" column for reverse traceability when ID-anchored items have downstream references in the same doc.
  • Implementation Units — repeating <article> cards with a stable ID chip (visible "U1" text), a metadata strip (<dl> with field labels and values for Goal, Files, Dependencies), and secondary content (Approach, Test Scenarios, Verification, Patterns to Follow) inside <details> collapsibles, default-closed. At 3+ units the default-closed rule is load-bearing — rendering all units fully expanded turns the doc into one continuous scroll where the reader can't see the unit list at a glance. The metadata strip is the primary always-visible surface; subsection labels (<summary>) are clickable affordances for readers to expand on demand. A single unit with no secondary content can skip <details> entirely; the rule fires when content exists to hide. The <dl> strip is for descriptive fields (Goal, Files, Dependencies). A directive field — Execution note is the canonical case, carrying a procedural instruction the implementer must act on (e.g. "start with a failing integration test") — does not belong in the strip, where it renders as a passive pair styled like a date and gets skimmed past. Render it as an advisory callout (see Tinted callout cards) so its visual weight matches its actionability. The test: descriptive value -> metadata pair; something the reader must act on -> callout.
  • Key Technical Decisions — repeating cards with the decision ID, bold decision title (often with inline code for technical identifiers), and prose rationale. Flat cards (not collapsibles) — these are reference material readers scan, not drill into.
  • Risks — color-coded cards with status eyebrow (e.g., "RISK · MITIGATED" / "OPEN · DEFERRED FOLLOW-UP") and prose body. Color of the left-border or accent communicates status at a glance.
  • Scope Boundaries — callout cards with color-coded left borders (in-scope vs deferred vs outside) when the distinction is meaningful.

The agent picks more elaborate or simpler shapes based on what each specific artifact's content needs.

Diagrams

When the section contract calls for a diagram (architecture, sequence, flowchart, state machine, swim lane, data-flow, quantitative comparison), HTML renders it as inline SVG. The agent picks the shape that conveys the content fastest — there is no fixed catalog of "approved" diagram types. If the content is quantitative comparison across categories, a bar chart is the right shape; if it's component relationships, a topology diagram; if it's process flow across participants, a swim lane; etc.

Conceptual diagrams are not wireframes. The wireframe affordance below is scoped to brainstorm requirements docs about visual products and is excluded for non-visual systems. That exclusion is about wireframes only — a brainstorm about a data model, schema, agent workflow, or migration is still free to use a conceptual diagram (a before/after field map, a source-of-truth fan-out, a state diagram). Don't let the wireframe exclusion suppress a conceptual diagram the content warrants.

Diagrams complement prose; they never replace it. A diagram is an accelerant placed next to the prose it illustrates, not a substitute. The IDed prose stays complete and standalone — a reader who ignores every diagram still gets the full content in text, and a text-reading downstream agent (which does not parse SVG geometry) is never left with a relationship that exists only in the picture. This extends the prose-is-authoritative rule above: prose governs not only on disagreement but on completeness, so adding a diagram is not license to thin the prose it depicts.

Layout legibility for hand-authored SVG

The agent designs SVG coordinates without rendering — layouts that look fine in source can collide in practice. Before emitting, trace each labeled arrow and each text label:

  • No arrow path passes through a text label. If an arrow line or curve crosses a label's bounding box, the text reads as struck-through and the arrow reads as terminating at the wrong element. Fix by re-routing the arrow, moving the label, or applying paint-order: stroke fill with a stroke color matching the diagram background to halo the label. The halo width is a judgment call: narrow enough not to bleed into glyph strokes (a halo whose width approaches the glyph's own stroke width muddies the text color), wide enough to mask underlying arrows (at least the arrow's stroke width plus a hairline). Verify by inspecting rendered text at the target font size — if glyphs look thicker or more colored-toward-halo than the same text outside the diagram, the halo is too wide.
  • Arrow labels sit adjacent to the arrow's midpoint (typically within ~10-15px above or beside the line they describe). A label floating at the diagram's edge that readers have to trace back to an arrow is broken — readers will misread.
  • Avoid long curves that traverse the diagram to connect a component on one side to one on the other. If A and D need a labeled connection across a multi-component layout, prefer reordering boxes so A and D are adjacent, numbered step badges next to each participant that the caption ties together, or a short labeled-channel notation — rather than one curve crossing multiple unrelated elements.
  • Differentiate diagram shapes by geometry first, by fill semantics second. Geometry (diamond = decision, rect = step, oval = start/end, parallelogram = data) carries the role unambiguously. Fill semantics (accent-soft for highlighted path, warn-soft for fallthrough) carry meaning. Resist introducing additional neutral-tint tiers (a slightly-lighter grey to mark "decision shapes are different from boxes") — when geometry already differentiates, an additional luminance tier adds no information and creates fragility: small RGB deltas survive native browser rendering but can be flattened or inverted inconsistently by dark-mode extensions, accessibility plugins, or printing.

Plan architecture diagrams are not directional sketches

Do not add hedging captions or section preambles to plan SVG diagrams — phrases like "directional guidance for review, not implementation specification" do not belong on plan diagrams or on unit-card technical-design subsections. Plan diagrams render the same authoritative content as the surrounding prose; the prose-is-authoritative rule already governs disagreement. Hedging language is reserved for the wireframe affordance below, which carries a required directional caption because the wireframe is explicitly NOT a spec.

Wireframe mockups (requirements docs only)

When a brainstorm requirements document describes a user-facing visual surface (UI feature, screen layout, screen flow, component placement), the HTML rendering may include a wireframe mockup. This affordance applies ONLY to brainstorm requirements docs that describe visual products — not to plan artifacts, and not to brainstorms about non-visual systems (API design, agent workflows, infrastructure).

When a wireframe is included:

  • Fidelity ceiling: wireframe, not mockup. Gray boxes for layout regions, text labels for content placeholders, intentional placeholder copy ([Product name], [CTA label], [user avatar]). No pixel-perfect colors, no exact typography choices, no specific component-library references. The wireframe communicates spatial arrangement and structure, not visual style.
  • Static only. Inline SVG or simple HTML/CSS for layout. No JS interaction, no working form fields, no state changes, no live data.
  • Anti-padding. One wireframe per distinct visual concept.
  • Mandatory directional caption. Every wireframe carries an explicit "directional, not the spec" note adjacent to it. Required wording (or close paraphrase): "Directional only — illustrates the intended user-facing shape. Exact colors, spacing, copy, and component choices are placeholders for review, not requirements."

Without this caption the wireframe risks being read as a binding visual spec, which the affordance is explicitly designed to avoid.

Affordance idioms

Common HTML affordances the agent can reach for when content benefits. These are examples, not requirements — the agent picks what each artifact's content warrants. Other affordances not listed here are fine when the content suggests them.

  • Sticky TOC sidebar with active-section indicator — available when the agent judges navigation will materially help and the implementation is reliable: two-column layout on desktop, collapsed to top-of-page on mobile, paired with a small inline IntersectionObserver script that toggles .active on the matching nav anchor. Trade-off: a broken sticky TOC (layout collisions, active-section state drift, dark-mode CSS issues) is worse than a static top-of-doc TOC. For most long docs, default-closed <details> on repeating cards (see Implementation Units anatomy) already cuts the visible scroll length enough that a static TOC works — reach for sticky only when collapsibles alone don't solve the navigation problem.
  • Within-section sub-nav for sections containing 6+ repeating cards (Implementation Units, KTDs, Risks at large counts). A short list of card-anchor links (<ul> of <a href="#u1">U1. ...</a>) rendered at the top of the section gives readers a jump table — no JS needed. Lower-complexity alternative to the sticky TOC for the specific case of long card sections.
  • Eyebrow labels (small-caps tag above section titles) for editorial polish, especially when section titles are narrative rather than literal.
  • Stats strip at the top of the doc when the artifact has 3+ quantifiable signals worth surfacing at a glance.
  • <details> + <summary> for collapsible secondary content inside repeating cards. All collapsibles start closed — open attribute should not appear on any <details> inside repeating cards by default.
  • Side-by-side columns for parallel content (Request / Response, Before / After, Two alternatives).
  • Tinted callout cards for content that is "different in kind" (Deferred, Open Questions, advisory notes, unit-level execution notes) — color-coded left borders communicate kind at a glance.

Agent-consumability rules

Downstream agents that read HTML today (ce-work, future consumers) read the HTML file as text linearly, not via DOM extraction. ce-doc-review is not a current HTML consumer (see opening note). Compose so semantic understanding is reachable in source:

  • Use semantic HTML over <div> soup. <article> per unit card, <dl> for metadata pairs, <table> for tabular content, <details> / <summary> for collapsibles, <section> for top-level doc sections. Structure markers carry meaning to a text-reading agent.
  • Render field labels as visible text, not as attributes. Emit <dt>GOAL</dt><dd>...</dd>, not <dd data-field="goal">...</dd>. The label is the semantic anchor.
  • Keep U-IDs, R-IDs, and similar as visible text in headings and table cells, not only as id="" attributes. The agent finds "U1." in source the same way it finds "U1." in markdown.
  • Match section heading vocabulary to what the section contract defines. When the section contract says "Implementation Units," the HTML heading is "Implementation Units" — not "How we'll build it," even if the narrative version reads better. Section heading vocabulary is the contract downstream consumers grep for. (Editorial re-titles can appear as eyebrow labels, sub-headings, or visual framing — but the load-bearing section heading matches the contract name.)
  • All semantic content lives in actual HTML text. No CSS ::before { content: "..." } carrying meaning, no background images as content, no semantic info that only renders. Whatever the agent sees in source is what it knows.
  • Stable structure is the public API. Element types, the ID and label scheme, and the field-label vocabulary do not break across versions. Visual styling can change freely.

Post-compose audit

Before returning the artifact, scan it for common slips:

  • Single self-contained file. No companion .css / .js / .svg.
  • No hidden machine-readable metadata copy. No <script type="application/json"> frontmatter block, no data-* attributes mirroring visible values, no <meta name="status"> / <meta name="created"> / <meta name="origin"> etc. in <head> duplicating the visible header. Metadata lives in visible text; one source of truth per value.
  • Status renders as <span class="status">{value}</span> so downstream tooling can flip active → completed by selector.
  • All stable IDs appear as both id="" and visible text.
  • Section heading vocabulary matches the section contract names (downstream agents grep these).
  • Source / composition signal is present as a visible footer at the bottom of the doc (composition timestamp + source identifier).
  • Repeating cards with 3+ instances put secondary content inside default-closed <details>. Fully-expanded unit cards in a long Implementation Units section is a failure mode — the reader can't see the unit list at a glance. Verify by skimming the rendered units: each <article> should render as its ID + title + metadata strip with collapsibles below, not as one long block.
  • Within-section sub-nav is present for sections with 6+ repeating cards.
  • Body <strong> is not colored with accent palette.
  • <details> inside repeating cards have no open attribute.
  • Diagram labels are legible — no arrow paths crossing text, halo width appropriate for font size.
  • Diagrams complement prose, not replace it. Every relationship a diagram conveys is also present in the surrounding IDed prose; no content lives only in an SVG.
  • No JS framework runtimes included. Small inline <script> for active-section TOC tracking or anchor-permalink behavior is the only acceptable JS.
  • Each heading level is visually distinct from others and from inline bold.
  • No template placeholders ({skill}, <value>, [plan title]) leaked into output.
  • No process exhaust callouts in the artifact.