Read-only zones prototype against knot-rest #1

Merged
arne merged 26 commits from spec/zones-readonly-prototype into main 2026-05-01 21:32:28 +02:00
Owner

First vertical slice of the Nordaaker DNS console: lists zones and shows
each zone's status + records, served by Go from knot-rest, swapped in the
browser by htmx 4.

What's in here

  • Typed knot-rest client at `internal/knot/`: types, `bearerTransport`
    (live), `fixtureTransport` (offline), three `GET` methods (`ListZones`,
    `ZoneStatus`, `ZoneRecords`), and JSON fixtures for three zones.
  • Per-page templates at `web/templates/`: shared `layout.html`,
    `zones.html`, `zone.html`, three fragments (`zones-table`,
    `zone-records`, `zones-error`), `/design` retrofitted onto the layout.
  • Handlers at `internal/server/zones.go`: `/zones`, `/zones/{zone}`,
    plus matching `/refresh` fragment endpoints. Error fragment shared
    between full-page and refresh paths via htmx 4's swap-on-error.
  • Console shell CSS: extends existing tokens; no new tokens.
  • htmx 4.0.0-beta2 vendored under `web/static/js/`.
  • Env-driven main: `KNOT_REST_URL`/`TOKEN`/`FIXTURES`/`NORDAAKER_ADDR`.

Spec: `docs/superpowers/specs/2026-05-01-knot-rest-prototype-design.md`
Plan: `docs/superpowers/plans/2026-05-01-knot-rest-prototype.md`

Out of scope (deferred to later slices)

Mutations, transaction subtree, signature page, DNSSEC, server status,
config, pagination, real-time, multi-tenant, auth UI.

Test plan

  • `go test ./...` — 19 tests, all passing
  • `gofmt -l ./...` — clean
  • Offline: `KNOT_REST_FIXTURES=internal/knot/fixtures go run .` →
    `/zones` lists three zones; click into one for status + records;
    Refresh updates only the table; `/zones/nope.test.` shows error
    fragment with Try again, console shell intact.
  • Live: `KNOT_REST_URL=… KNOT_REST_TOKEN=… go run .` against a
    real knot-rest, confirm zones list and record data render.
  • Missing env: `go run .` exits with
    `knot: live mode requires both KNOT_REST_URL and KNOT_REST_TOKEN`.
First vertical slice of the Nordaaker DNS console: lists zones and shows each zone's status + records, served by Go from knot-rest, swapped in the browser by htmx 4. ## What's in here - **Typed knot-rest client** at \`internal/knot/\`: types, \`bearerTransport\` (live), \`fixtureTransport\` (offline), three \`GET\` methods (\`ListZones\`, \`ZoneStatus\`, \`ZoneRecords\`), and JSON fixtures for three zones. - **Per-page templates** at \`web/templates/\`: shared \`layout.html\`, \`zones.html\`, \`zone.html\`, three fragments (\`zones-table\`, \`zone-records\`, \`zones-error\`), \`/design\` retrofitted onto the layout. - **Handlers** at \`internal/server/zones.go\`: \`/zones\`, \`/zones/{zone}\`, plus matching \`/refresh\` fragment endpoints. Error fragment shared between full-page and refresh paths via htmx 4's swap-on-error. - **Console shell CSS**: extends existing tokens; no new tokens. - **htmx 4.0.0-beta2** vendored under \`web/static/js/\`. - **Env-driven main**: \`KNOT_REST_URL\`/\`TOKEN\`/\`FIXTURES\`/\`NORDAAKER_ADDR\`. Spec: \`docs/superpowers/specs/2026-05-01-knot-rest-prototype-design.md\` Plan: \`docs/superpowers/plans/2026-05-01-knot-rest-prototype.md\` ## Out of scope (deferred to later slices) Mutations, transaction subtree, signature page, DNSSEC, server status, config, pagination, real-time, multi-tenant, auth UI. ## Test plan - [ ] \`go test ./...\` — 19 tests, all passing - [ ] \`gofmt -l ./...\` — clean - [ ] Offline: \`KNOT_REST_FIXTURES=internal/knot/fixtures go run .\` → \`/zones\` lists three zones; click into one for status + records; Refresh updates only the table; \`/zones/nope.test.\` shows error fragment with Try again, console shell intact. - [ ] Live: \`KNOT_REST_URL=… KNOT_REST_TOKEN=… go run .\` against a real knot-rest, confirm zones list and record data render. - [ ] Missing env: \`go run .\` exits with \`knot: live mode requires both KNOT_REST_URL and KNOT_REST_TOKEN\`.
arne added 24 commits 2026-05-01 17:10:48 +02:00
Vertical slice through Go + htmx + plain-CSS against knot-rest:
list zones, view zone status + records. Establishes typed knot
client (live + fixture transports), htmx 4 wiring, error model,
and a first pass at the console shell.

Also bumps the README link to the new knot-rest location at
code.bas.es/nordaaker/knot-rest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
20 TDD-shaped tasks: knot client (types, transports, methods,
fixtures), htmx 4 vendoring, console shell CSS + layout, zones
list/detail pages with refresh fragments, env-driven wiring,
README updates, and a manual smoke test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Non-minified build from https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-beta2/dist/htmx.js
Version pinned in web/static/js/HTMX_VERSION.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement the final read-only method on the knot client (ZoneRecords)
following the same pattern as ZoneStatus (URL-escape zone name, pass to
c.get). Add TDD test that decodes fixture JSON. Create fixture files for
example.com (records only), example.org (status + records), and
nordaaker.test (status + records) so the dev server has a complete
picture of three zones.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit landed it at templates/ (repo root) by mistake,
but the embed.FS root is web/ — see main.go:embed all:web/templates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Relax Assets.{Templates,Static} from embed.FS to fs.FS so tests can
supply os.DirFS without the embed path restriction. Implements the two
zone-list handlers with real knot.ListZones calls and error-path
rendering; updates zones.html to branch on .Error. Tests use os.DirFS
rooted at the repo root (../../ from internal/server/) to match the
production embed layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The error from knot.NewClient already starts with "knot: " — the
extra log.Fatalf prefix produced "knot: knot: live mode requires…".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- redact upstream body from knot error logs (spec required no body)
- handleZoneRefresh: drop unused ZoneStatus call (saves an upstream RTT)
- zone.html: split error branch so Status survives a Records failure
- components.css: add page-header / page-section / kv / button / data-table__toolbar rules so the templates render with proper spacing
- design.html: rename inner <main> to <section> to satisfy HTML5
- gofmt cleanup
Reworks the prototype's chrome and content surfaces to use the shared
design-system vocabulary, and updates /design itself with the missing
pieces this surfaced.

Layout & prototype templates:
- Replace top-bar shell with a left rail (logo lockup, vertical nav,
  theme toggle pinned to the bottom) — pattern from
  bundle/console/index.html.
- Drop placeholder .button / .data-table / .data-error / .page-section
  for design-system equivalents: .btn, .np-table, .alert, .np-card,
  with a new .np-card--flush variant for table-bearing cards (header
  padded, body flush so the table sits border-to-border).
- Page headers use .ds-page-head (eyebrow + serif h1 + lede). Records
  table uses .np-table records-table with .chip on type cells and
  .mono on data cells.
- Clickable-row pattern: .np-table.is-clickable + data-row-href on
  each <tr>; click delegation in helpers.js follows the href, keeping
  the inner <a> for keyboard / right-click / middle-click. Applied to
  the zones list; records table stays non-clickable (it's data to copy).
- Move click delegation + theme toggle from inline <script> in
  layout.html to web/static/js/helpers.js.

/design updates:
- Drop internal ds-rail; replace section anchor list with a sticky
  <select> dropdown so the page can use the full content width now
  that the global rail provides primary nav.
- Add section "12 / Sidebar — Console shell" with a static specimen.
- Add section "13 / Spacing — Step rhythm" with a visual bar per
  --space-N token.
- Tables section blurb describes the new is-clickable variant.
- Hoist .ds-page-head / .eyebrow / .lede / .theme-toggle /
  .records-table column widths from design.html's page-private <style>
  into components.css so they're real reusable components.
- Move {{define "mark"}} from design.html to layout.html so every page
  can render the SVG mark.
- Fix swatch specificity: .swatch .chip had background: transparent,
  which beat .sw-glacier-NN on specificity and rendered every swatch
  hollow. Drop the transparent override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- knot.fixtureTransport: reject paths that resolve outside fixtures root
  via filepath.Rel, with a regression test.
- knot.Client.get: surface io.ReadAll errors as APIError instead of
  swallowing them into a misleading decode failure.
- zone.html lede: drop the "role · " prefix when role is empty so the
  serial doesn't end up with a leading separator.
- design.html: .ds-specimen-stage.tight gains overflow:hidden so a
  flush child's edge background (e.g. table <th>) clips to the stage
  radius — fixes section 9's broken top corners.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
arne merged commit bcee550086 into main 2026-05-01 21:32:28 +02:00
arne deleted branch spec/zones-readonly-prototype 2026-05-01 21:32:28 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nordaaker/web!1
No description provided.