The sorted report finally named it: `table.api-tokens__table` at 833px
wide, with its `.visually-hidden` heading reaching right=737 — exactly
the document's scrollWidth. Same mechanism as the sessions table, a
second table I had not wrapped.
Locally the API-tokens table was empty and therefore narrow, which is why
this only ever appeared in CI. With a token present it reproduces:
without the wrapper 345px of page overflow, with it none.
The feed-token table gets the same treatment — it is built the same way
and would fail as soon as someone holds a feed token with a long name.
The "[in fitting scroller]" marker in the report is misleading for these:
`main.main` is a scroller, but it is `position: static`, so it never
clipped the absolutely positioned heading. Only a positioned ancestor
does — which is what `.table-scroll` now is.
Verified locally against a real stack, with a wide token table present:
reflow guard green, whole a11y pack green in both colour schemes.
Der Field-Baustein verdrahtet Hinweis/Fehler jetzt per aria-describedby
und aria-invalid mit dem Eingabefeld (cloneElement auf das einzelne
Kind; Fragmente bleiben unangetastet) — Screenreader nennen den Fehler
damit auch beim Feld-Fokus. Quota-Typ-Select mit Namen; die leeren
Aktions-/Erledigt-Spaltenköpfe in API-Tokens, Feed-Tokens, Sitzungen
und der Aufgabenübersicht (NodeView UND Server-Renderpfad) tragen
visually-hidden-Beschriftungen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
Token-authenticated machine access at /api/public/v1 — the foundation for
the built-in MCP endpoint (#105).
Personal access tokens:
- api_tokens table (SHA-256 hash, scope read|write, optional pond
restriction, expiry, revocation, throttled last-used) + migration;
secrets are dt_pat_<random>, shown exactly once
- lifecycle endpoints under /users/me/api-tokens (session-only — a leaked
token can never mint more tokens) with audit entries
api.token_created/api.token_revoked
- settings UI section (create with scope/expiry/pond restriction,
one-time reveal with copy, list with status + revoke), de+en
Activation (404 semantics per #60 on both levels):
- instance setting api.enabled (default off, admin settings switch)
- pond setting apiEnabled (default off, pond settings toggle; the
PondsService settings-merge learned the key — the #92 lesson)
Surface (/api/public/v1, excluded from the SPA's global prefix):
- me, ponds, pages (list/read as Markdown+HTML, create from Markdown via
the shared pipeline, PATCH title/content, DELETE to trash), search
(permission-filtered + narrowed to exposed ponds, highlights as **…**),
markdown ZIP export, labels (tree, create/rename/recolour/move/delete,
assign/unassign), comments (threads, create, resolve/reopen)
- content replacement travels the collab-owned document path: the new
state lands as a MANUAL version "API update", then the established
restore NOTIFY applies it — open editors converge, history stays
append-only, no second lineage (VersionsService.replaceContent)
- hand-maintained OpenAPI 3.1 document at /openapi.json, pinned to the
controller by a route-coverage test in both directions
Enforcement:
- PublicApiGuard: instance switch → bearer PAT auth (request.user is the
token's user) → per-token rate limit (429 + Retry-After) → scope
(403 scope_required) → pond opt-in + token restriction
- the shared PermissionGuard then applies the unchanged permission model;
PageParamSource gained pondSlugParam for the slug+slug routes
- no cookies anywhere → no CSRF surface (pinned by a hostile-Origin test)
- every write audit-logged as api.write with the token attributed
Tests/verification:
- 12-test e2e pack: lifecycle, switches, permission matrix
(reader/editor/outsider × scopes), restriction, page roundtrip incl.
restore-NOTIFY assertion, labels, comments incl. policy, search
narrowing, ZIP export, rate limit; full api suite 60/60 green
(quota fixture via per-user override — never the instance default)
- new collab-pack test proves an open editor converges onto an API
content replacement (green against a local seeded stack)
- UI smoke against the built SPA: token create/reveal/revoke, pond
opt-in persists, admin switch persists (10/10)
- docs/self-hosting/public-api.md + README link
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1