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
16 changes: 16 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ jobs:
go-version-file: "go.mod"
cache: false
- run: go test ./...
styles:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version-file: ".tool-versions"
- run: npm ci
# public/app.css is committed so the binary and container need no Node.
# A rebuild that changes it means a template or script gained a class the
# committed sheet does not carry, and the running app would be missing it.
- name: Rebuild stylesheet and check it matches the committed one
run: |
npm run css
git diff --exit-code --stat public/app.css \
|| { echo "::error::public/app.css is stale. Run 'npm run css' and commit the result"; exit 1; }
e2e:
runs-on: ubuntu-latest
defaults:
Expand Down
115 changes: 115 additions & 0 deletions .impeccable/critique/2026-08-08T10-17-31Z__src-views-index-html.md

Large diffs are not rendered by default.

31 changes: 18 additions & 13 deletions .impeccable/design.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@
},
"mono": {
"displayName": "Evidence Mono",
"purpose": "Every captured value headers, paths, IPs, query strings, bodies, endpoint URL, request UUID."
"purpose": "Every captured value: headers, paths, IPs, query strings, bodies, endpoint URL, request UUID."
}
},
"shadows": [
Expand All @@ -237,7 +237,7 @@
{
"name": "hover-lift",
"value": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
"purpose": "Applied only to the six home-page use-case cards, and only on hover. Nothing above the resting whisper exists at rest."
"purpose": "Defined but currently unused. Elevation change is reserved for surfaces that respond to the cursor; every card in the product is static content."
}
],
"motion": [
Expand All @@ -260,6 +260,11 @@
"name": "copy-confirm",
"value": "1500ms label swap",
"purpose": "Copy controls swap their label to Copied! and revert after 1.5s. Send status clears after 2s."
},
{
"name": "reduced-motion",
"value": "@media (prefers-reduced-motion: reduce)",
"purpose": "Transitions and the waiting ellipsis collapse to a resting state. All motion here is decorative, so removing it substitutes cleanly."
}
],
"breakpoints": [
Expand Down Expand Up @@ -298,7 +303,7 @@
"name": "Text Action",
"kind": "button",
"refersTo": "button-secondary",
"description": "No fill, no border. Label ink resolving to signal indigo on hover or alert rose when the action deletes.",
"description": "No fill, no border. Label ink resolving to signal indigo on hover: or alert rose when the action deletes.",
"html": "<div style=\"display:flex;gap:1rem;align-items:center\"><button class=\"ds-action\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><rect x=\"9\" y=\"2\" width=\"6\" height=\"4\" rx=\"1\"/><path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\"/></svg>Copy request</button><button class=\"ds-action ds-action-danger\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>Delete</button></div>",
"css": ".ds-action { display: inline-flex; align-items: center; gap: 0.25rem; background: none; border: none; padding: 0; color: oklch(55.4% 0.046 257.417); font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; line-height: 1.429; cursor: pointer; transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1); } .ds-action:hover { color: oklch(51.1% 0.262 276.966); } .ds-action-danger:hover { color: oklch(58.6% 0.253 17.585); } .ds-action:focus-visible { outline: none; box-shadow: 0 0 0 2px oklch(58.5% 0.233 277.117); border-radius: 0.25rem; }"
},
Expand Down Expand Up @@ -330,7 +335,7 @@
"name": "Request Panel",
"kind": "card",
"refersTo": "panel",
"description": "One captured request: a rule-separated header carrying the badge and UUID, then labelled regions separated by hairline-faint rules rather than nested boxes. Key/value rows are flex a 10rem label column above 40rem, label stacked above value below it.",
"description": "One captured request: a rule-separated header carrying the badge and UUID, then labelled regions separated by hairline-faint rules rather than nested boxes. Key/value rows are flex: a 10rem label column above 40rem, label stacked above value below it.",
"html": "<article class=\"ds-panel\"><header class=\"ds-panel-head\"><span class=\"ds-badge ds-badge-post\">POST</span><a class=\"ds-uuid\" href=\"#\">3f9a1c72-8d40-4e11-b6a2-9c7f0e5d2a18</a></header><div class=\"ds-panel-body\"><div class=\"ds-label\">Details</div><dl class=\"ds-rows\"><div class=\"ds-row\"><dt>Time</dt><dd>2 minutes ago</dd></div><div class=\"ds-row\"><dt>Client IP</dt><dd class=\"ds-mono\">203.0.113.42</dd></div><div class=\"ds-row\"><dt>Path</dt><dd class=\"ds-mono\">/to/quiet-sunset-4821</dd></div></dl><div class=\"ds-label\" style=\"margin-top:1rem\">Body</div><pre class=\"ds-well\">{\n \"event\": \"payment.succeeded\",\n \"amount\": 4200\n}</pre></div></article>",
"css": ".ds-panel { background: #fff; border: 1px solid oklch(92.9% 0.013 255.508); border-radius: 0.5rem; box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); overflow: hidden; font-family: ui-sans-serif, system-ui, sans-serif; } .ds-panel-head { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem 1.25rem; border-bottom: 1px solid oklch(92.9% 0.013 255.508); } .ds-uuid { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.75rem; color: oklch(55.4% 0.046 257.417); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ds-uuid:hover { color: oklch(51.1% 0.262 276.966); } .ds-panel-body { padding: 1.25rem; } .ds-label { font-size: 0.75rem; font-weight: 500; line-height: 1.333; letter-spacing: 0.025em; text-transform: uppercase; color: oklch(55.4% 0.046 257.417); margin-bottom: 0.5rem; } .ds-rows { margin: 0; font-size: 0.875rem; color: oklch(27.9% 0.041 260.031); } .ds-row { display: flex; flex-direction: column; padding: 0.375rem 0; border-bottom: 1px solid oklch(96.8% 0.007 247.896); } .ds-row:last-child { border-bottom: none; } .ds-row dt { color: oklch(55.4% 0.046 257.417); } .ds-row dd { margin: 0; min-width: 0; } @media (min-width: 40rem) { .ds-row { flex-direction: row; gap: 0.75rem; } .ds-row dt { width: 10rem; flex-shrink: 0; } } .ds-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.75rem; } .ds-well { margin: 0; background: oklch(98.4% 0.003 247.858); border: 1px solid oklch(92.9% 0.013 255.508); border-radius: 0.25rem; padding: 0.75rem; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.75rem; line-height: 1.333; color: oklch(27.9% 0.041 260.031); overflow: auto; }"
},
Expand Down Expand Up @@ -361,24 +366,24 @@
],
"narrative": {
"northStar": "The Arrivals Board",
"overview": "httphq is a board you watch, not a console you operate. Requests land at the top of the stream, get colour-coded by class, and expire. The whole visual system is built so a developer working in another window can glance over and read the board — status first, detail on approach — then go back to what they were doing. Everything that isn't the arriving traffic is chassis: a slate field, white panels, hairline seams, and exactly one indigo control per view.\n\nThe register is precise, quiet and fast. Restraint here is not minimalism as a style choice; it is what makes the board readable. The interface never claims more than it can prove, never decorates a value it captured, and never puts a second thing in colour next to a method badge. Type is system-native and loads instantly — there is no webfont anywhere, and that is a feature of a tool whose entire promise is being usable within seconds of arrival.\n\nFour looks are rejected outright, and all four are confirmed prohibitions rather than taste: marketing-SaaS gloss (gradient meshes, floating mockups, testimonial carousels), the dense enterprise console (dark chrome, packed toolbars, panels nested in panels), terminal cosplay (green-on-black, ASCII framing, faux-CRT effects), and playful dev-tool mascotry (cartoon characters, blob illustrations, jokey empty states). Monospace appears throughout, but as evidence handling — never as costume.",
"overview": "httphq is a board you watch, not a console you operate. Requests land at the top of the stream, get colour-coded by class, and expire. The whole visual system is built so a developer working in another window can glance over and read the board, status first, detail on approach, then go back to what they were doing. Everything that isn't the arriving traffic is chassis: a slate field, white panels, hairline seams, and exactly one indigo control per view.\n\nThe register is precise, quiet and fast. Restraint here is not minimalism as a style choice; it is what makes the board readable. The interface never claims more than it can prove, never decorates a value it captured, and never puts a second thing in colour next to a method badge. Type is system-native and loads instantly, there is no webfont anywhere, and that is a feature of a tool whose entire promise is being usable within seconds of arrival.\n\nFour looks are rejected outright, and all four are confirmed prohibitions rather than taste: marketing-SaaS gloss (gradient meshes, floating mockups, testimonial carousels), the dense enterprise console (dark chrome, packed toolbars, panels nested in panels), terminal cosplay (green-on-black, ASCII framing, faux-CRT effects), and playful dev-tool mascotry (cartoon characters, blob illustrations, jokey empty states). Monospace appears throughout, but as evidence handling, never as costume.",
"keyCharacteristics": [
"A slate-50 field with white panels; hairline seams, not shadows, make the edges",
"One indigo action per view; every other control is a slate outline or bare text",
"Full-spectrum colour is spent entirely on the seven HTTP-method badges",
"Everything the client sent is monospace; everything httphq says is sans",
"A single breakpoint (40rem) governs the entire responsive system",
"Liveness is signalled in the browser chrometab title count, favicon dot not by animating the page"
"Liveness is signalled in the browser chrome, tab title count, favicon dot, not by animating the page"
],
"rules": [
{
"name": "The Borrowed Palette Rule",
"body": "The four syntax colours are a vendored theme, not system tokens. Never restyle individual hljs-* classes to bring them closer to the slate-and-indigo world either swap the whole theme or leave it alone. A half-retinted syntax palette reads as a bug in the highlighter.",
"body": "The four syntax colours are a vendored theme, not system tokens. Never restyle individual hljs-* classes to bring them closer to the slate-and-indigo world: either swap the whole theme or leave it alone. A half-retinted syntax palette reads as a bug in the highlighter.",
"section": "colors"
},
{
"name": "The Board Rule",
"body": "Full-spectrum colour belongs to the method badge and nothing else. If a new element wants blue, emerald, amber, violet or sky, the answer is no that vocabulary means \"this is the class of traffic that arrived\", and every additional user dilutes the only colour-coding on the page.",
"body": "Full-spectrum colour belongs to the method badge and nothing else. If a new element wants blue, emerald, amber, violet or sky, the answer is no: that vocabulary means \"this is the class of traffic that arrived\", and every additional user dilutes the only colour-coding on the page.",
"section": "colors"
},
{
Expand All @@ -393,7 +398,7 @@
},
{
"name": "The Rose Is Removal Rule",
"body": "Rose means something is about to be destroyed the Delete all button, the per-request delete hover, the DELETE badge. Its one sanctioned exception is the unread dot painted onto the favicon, where it means \"traffic landed while you were away\". Rose is never used for form validation or generic error text.",
"body": "Rose means something is about to be destroyed: the Delete all button, the per-request delete hover, the DELETE badge. Its one sanctioned exception is the unread dot painted onto the favicon, where it means \"traffic landed while you were away\". Rose is never used for form validation or generic error text.",
"section": "colors"
},
{
Expand All @@ -403,7 +408,7 @@
},
{
"name": "The Two Uppercase Rule",
"body": "Uppercase exists in exactly two places: field labels and method badges. Both are 0.75rem with +0.025em tracking. Nothing else in the system is uppercased not buttons, not navigation, not headings.",
"body": "Uppercase exists in exactly two places: field labels and method badges. Both are 0.75rem with +0.025em tracking. Nothing else in the system is uppercased: not buttons, not navigation, not headings.",
"section": "typography"
},
{
Expand All @@ -423,15 +428,15 @@
},
{
"name": "The Dashed-Means-Waiting Rule",
"body": "A dashed border means a container that is correctly empty and expecting content. It is used oncethe waiting state and must never be borrowed for a disabled, errored, or drop-target surface.",
"body": "A dashed border means a container that is correctly empty and expecting content. It is used once, the waiting state, and must never be borrowed for a disabled, errored, or drop-target surface.",
"section": "shapes"
}
],
"dos": [
"Do put every new surface on the board field with a panel-white fill, a 1px hairline seam, and the resting whisper shadow. That trio is the system's default surface.",
"Do set anything the client sent in monospace at 0.75rem, and anything httphq says in sans.",
"Do give a new region an uppercase 0.75rem/500/+0.025em label ink heading, and separate its rows with hairline-faint rules instead of nesting another bordered box.",
"Do pair outline: none with a visible focus-visible ring every single time 2px signal-indigo-lit plus a white offset ring on buttons, 1px focus-indigo plus a border shift on fields.",
"Do pair outline: none with a visible focus-visible ring every single time: 2px signal-indigo-lit plus a white offset ring on buttons, 1px focus-indigo plus a border shift on fields.",
"Do take the radius from the category: 0.25rem for data, 0.375rem for controls, 0.5rem for panels.",
"Do design to the 40rem breakpoint alone, stacking below it and going horizontal above it.",
"Do keep icons at 24×24 viewBox, stroke-width 2, fill none, aria-hidden, beside a text label.",
Expand All @@ -441,7 +446,7 @@
"Don't spend blue, emerald, amber, violet or sky on anything but a method badge. That spectrum is the traffic classification and nothing else.",
"Don't add a second indigo-filled button to a view. One primary action per screen, every other control outlined or bare.",
"Don't introduce a saturated fill with white text; coloured chips are a ~97% wash with a ~50% ink of the same hue.",
"Don't use rose for validation errors or generic failure text rose means removal, plus the unread favicon dot.",
"Don't use rose for validation errors or generic failure text: rose means removal, plus the unread favicon dot.",
"Don't reach past the hover lift for elevation. There is no shadow-lg in this system, and a borderless card with a bigger shadow is wrong here even when it looks fine alone.",
"Don't add a webfont. The system stack is a performance commitment on a tool whose promise is a working URL in seconds.",
"Don't add a second breakpoint. If a layout can't resolve with sm alone, simplify the layout.",
Expand Down
12 changes: 12 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Generated by `npm run css`. Prettier would reformat the minified output and
# the next build would revert it, so the CI freshness check could never pass.
public/app.css

# Build output and dependencies
bin
node_modules
e2e/test-results
e2e/playwright-report

# Impeccable critique archives are generated snapshots, not source.
.impeccable/critique
Loading
Loading