ditdash U-I5a/b/c: URL-as-state, match filters, tombstones, explain, raw toggle, jump lookups #6

Merged
jsutter merged 4 commits from ditdash-u-i5 into main 2026-10-11 15:31:15 +00:00
Owner

I5a: view-state round-trips, per-noun match filters, coverage-on-empty, per-entity timelines, openapi params. I5b: permalinks, alias redirect + tombstones, explain views, raw-JSON toggle. I5c (C4 seam): jump lookups. Critic fixes: tombstones match the writer canonical-URL grammar AND the literal form (W-1, mutation-checked); class-scoped timeline fragments labeled honestly (J-1). Critic: PASS-with-findings; dispatcher-run suite + clippy green.

I5a: view-state round-trips, per-noun match filters, coverage-on-empty, per-entity timelines, openapi params. I5b: permalinks, alias redirect + tombstones, explain views, raw-JSON toggle. I5c (C4 seam): jump lookups. Critic fixes: tombstones match the writer canonical-URL grammar AND the literal form (W-1, mutation-checked); class-scoped timeline fragments labeled honestly (J-1). Critic: PASS-with-findings; dispatcher-run suite + clippy green.
The view-state module (ViewState) generalizes the /history-only filter
parse into one parse/serialize pair: match, from/to/range, sort (URL
state only — no new sort engine), view, q, and the history filter set.
Every page consumes its own key set (page_view_keys); a parameter a page
does not consume is ignored and never echoed into a link. Every page and
twin handler now passes RawQuery, and the central filters block lands on
every page's data, so page and twin carry one state.

Each noun list (hosts, tenants, apps, users, operators, clients, edges,
releases) gains the case-insensitive substring match filter applied in
the read model after the policy gate, an input plus applied-filter chips
with clear links, and a coverage-on-empty statement naming the surfaces
searched and every unreachable source (canon rule 39 / FAILURE-039).

Every entity page carries the #history timeline fragment — the records
whose entity names the page's derived key (lenient to the class prefix
until U-F2a's writer grammar is confirmed), linked into the feed with the
entity filter and the records' absolute range; a bundle without the
surface renders the named not-collected gap. The openapi document gains
the query-parameter objects on the templated twins (additive).

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
Rename records are permanent: a request naming a row alias answers 301
with the canonical slug's Location (the query string rides along), after
the policy gate — an alias under a denied scope answers the same 404 as
any unknown, so no existence leaks through redirects. An unknown slug
keeps today's honest unknown.

A detail page whose row is absent but whose entity key appears in the
collected history renders a tombstone: the deletion note over the
entity's records (the same lenient entity-key matcher the timelines
use); no records keeps the honest unknown.

Explain views land as <details> panels for the derived values that exist
today: the health rollup (the recorded checks, their states, the
envelope's confidence) and the release-lag chain (channel → resolved
release → running rev with form/offset/hold/floors) on the tenant page,
and the traffic share's exact server-built exprs + window on the app and
tenant traffic panels.

The raw toggle: view=json renders the twin's serialized envelope inline
in the page frame with the copyable API-request and CLI commands
(selectable static text, no clipboard script); the link rides every data
page's frame and /me; the back link and the raw link preserve the other
URL state, so the toggle round-trips.

The jump lookups (q=) split to U-I5c per the critic C4 split seam (this
unit banks past the ~400-line mark).

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
The q= lookup replaces a search verb by pattern class: an IP literal
routes to host rows (public and LAN fields), port:<n> to the edge port
rows plus their owning hosts, commit:<sha-prefix> to release revs and
app deployed refs, secret:<name> to the secret key/env rows
(operator-class), user@domain to user+operator emails, a bare username
to usernames, a domain/TLD suffix to tenant domains and app aliases, and
a plain slug/tag to releases, tenants, apps, users, and operators. The
index is a pure function rebuilt from the bundle on every render.

Every candidate passes the policy gate before it enters the result list:
a denied candidate renders nothing, not a dead link, and a viewer's
secret: jump names the operator-only surface in its coverage statement
without reading it. An empty jump states its coverage — every noun
searched plus every source the run could not reach — and the q round-
trips through the filters block, the input, and the raw link like every
other view-state key.

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
The production change-record writer records a canonical entity URL
(https://forge.deepnet.com/<org>/apps/<slug> — collector.yml's stable-key
table; the frozen change-record schema pins the shape), not the literal
<class>:<key> the fixtures use — against real records the tombstone
fragment matched nothing and real removals rendered as ordinary history.

The reader now parses the URL path segments alongside the literal form:
the class marker (apps, tenants, ...) maps to the class, the tenanted
families key as <org>/<id> (the pages' <tenant>/<key> grammar), the
bare-id families as the id, and msp as the org. The class must agree and
the org segment scopes the record, so a foreign tenant's same-slug
record never joins. The literal form keeps matching (fixtures + future
writers).

The class-scoped fragments (the users/operators #history feeds) label
their scope honestly: the empty coverage line names the class feed (all
users / all operators), never "this entity".

Tests: the fixture gains URL-form records (a destroyed app, a user
record, a foreign tenant's app) — the tombstone, the class feed, and the
cross-tenant negative each render; reverting the matcher fails the
tombstone test (mutation-checked).

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
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
deepnet/ditdash!6
No description provided.