drogna

A demonstration harness. Every number in it is invented.

ADR-0032: addressability goes below the panel

Status: Accepted Date: 29 August 2026 Feature: 111 (the Background tab) Amends: ADR-0028, which stated that the panel id is the unit of URL addressability.

Written as ADR-0031 and renumbered to 0032 when main moved: the site-tooling record took 0031 first. A record's number is a fixed address for the life of the project (README.md), so the collision is resolved by moving the record nobody has referred to yet, not by reusing a number.

Context

SRD-v2 FR-15 makes views URL-addressable, and ADR-0028 recorded how: #/view/<id> activates the panel of that id, and activation writes the hash back, so the address bar always names what is shown. app/src/shell/views.ts matched ^#/view/([a-z][a-z0-9_-]*)$ — one segment, exactly — and ADR-0028 said so outright: "the panel id is the unit of URL addressability (FR-15)."

Feature 111 needs more than that. Background is a course of eleven explainers with 69 steps between them, and FR-003 requires each step to be addressable so that a PR comment or a blog post links to one step rather than to the tab. #/view/background/mqtt/3 cannot be expressed by a scheme with one segment.

This is hard to reverse. Once a link is shared, its meaning is somebody else's; changing what an address selects afterwards breaks a page nobody controls. That is the whole point of FR-15, and it is why this earns a record rather than a commit message.

Decision

An address is a view id and an opaque remainder. #/view/<id> is unchanged. Anything after the view id is handed to that panel and is never parsed by the shell.

And the writeback must stop erasing what it does not recognise. Shell.tsx rewrote the address to hashForView(panel.id) whenever the hash did not equal a bare view address. Left alone, the first activation — including the one dockview fires while restoring a layout — would have replaced #/view/background/mqtt/3 with #/view/background. That is worse than a broken link: it is a link that works, then quietly forgets where it pointed, and only on a second visit.

The decision is hashOnActivation(hash, panelId), which returns nothing when the address already names the activated panel, whatever else it carries.

Consequences

Alternatives rejected