Commit graph

6 commits

Author SHA1 Message Date
Doxa Maps
00f7d349eb Shareable pages P2b: nested shells at any depth have their tab in the address (?subtab= path)
When a shell sits inside another plugin that owns the address (Plugin Studio → the Research Gallery → …), its own
tab had no key. P1 never wrote it from a nested shell, and P2's ownAddress dropped a nested `tab`, so every tab
inside Studio had the same URL.

`subtab` is a PATH, one segment per nested shell below the owner: ?subtab=regions-dashboard at depth 1, and
?subtab=posterverse.upg-100 when a shell sits inside a shell. There are at most 6 segments, each a plain id.
- A nested shell claims its tab with claimView({ tab }, { depth }), where depth comes from its profile-config
  addressDepth (depthOf; its parent passes it down).
- The owner records the claim as that segment: the segments above are kept, the deeper ones and pane/place dropped.
- The same tab again is a no-op (a shell's first-paint claim on a deep link keeps everything below it).
- A segment missing above the claimed depth (a hole) is refused.
- The owner's own tab change drops the whole path.
- subtabAt(search, depth) gives a shell its segment.
Standalone pages and DX Life never see subtab: there the shell is the owner and writes `tab`.

check:view-address H covers depth 1, 2 and 3: first-paint replace, pick push, a composite's pane/place beside
subtab, the same tab claimed again, Back, and the owner's tab change. It has 9 new poisons (52).
2026-10-03 07:50:25 +00:00
Doxa Maps
dacb9a0762 useViewAddress: unchanged means the same view-key VALUES, not the same string
Measured in a browser on a fresh load: ?owner=x&pane=map came back as ?pane=map&owner=x through one replaceState.
viewSearch puts the view keys first, and writeViewKeys compared strings. The link meant the same thing and no Back
step was added, but it was a needless write, and on a host it would be a needless router.replace. writeViewKeys now
compares the parsed tab/pane/place. check:view-address B covers it, with a poison (44); the real-file revert fails.
2026-10-03 06:45:39 +00:00
Doxa Maps
c963a2a9fa P2: studio's 5B notes. A nested element cannot switch the owner's tab, a kept-alive pane or place is claimed back when its tab is shown again, and scrollTo takes null
- ownAddress records only pane and place. tab is the owner's own key, so a nested announcement cannot switch it.
- A shell keeps Regions (and UPG100) alive when another tab is open. Switching back dropped ?pane= and ?place=
  (the tab change), while the screen still showed them. pane-wrapper and region-dashboards now watch for being
  shown again (a ResizeObserver: no height, then height) and claim their pane / place back with a replace. An
  address that names one wins, because Back to an entry of this tab is applied while the column has no layout.
- scrollTo(id, null) behaves as scrollTo(id).

check:view-address covers each of these (34 poisons, each caught). The slow pane-wrapper arm runs only for
pane-wrapper poisons, so the self-test takes 16 s.
2026-10-03 06:26:53 +00:00
Doxa Maps
003af1caa8 Shareable pages P2: a composite's open pane and Regions' subject are in the page address
The address gets its second and third keys:
- ?pane= is the open view of a composite;
- ?place= is Regions' subject.
Both keep the ONE-writer rule:
- On its own leaf the composite owns the address and writes.
- Nested in a shell it announces, and the shell writes.
- On a partner's page nothing is written; a link is still read.

useViewAddress:
- claimView() is the one call for a reader's choice: the owner writes it, any other element announces it.
- ownAddress() lets an owner record its nested elements' announcements, and stops them there.
- An announcement carries only the view keys, one event per action.
- pane and place are siblings: a tab change drops both, but a pane change keeps the place, because Regions keeps
  its region while the reader is on the list.

pane-wrapper (buildPaneColumn) takes an opt-in `address`:
- a link opens its pane without writing;
- a click is a push, and a selection made by the same click rides in the same push (one Back step);
- where the reader's scroll settles is a replace;
- Back/Forward scroll and never write;
- the first pane is the default and is never written.

Regions:
- regions-mfe opts its column in and records its panes' announcements on its own leaf. A list click opens the
  dashboard with its place in one write.
- region-dashboards claims ?place= instead of writing its #slug with its own pushState (replace at first paint,
  push on a choice, re-applied on Back). An old #slug link still opens, and on its own leaf it is rewritten to
  ?place=.
- urlSync:false now guards only the tab title.

check:view-address adds:
- E: nested claims and the owner;
- F: pane-wrapper on a simulated column;
- G: the Regions wiring.
The census drops region-dashboards (5 writers now). Self-test: 29 poisons, each caught. Real-source mutations are
caught too.
2026-10-03 06:09:51 +00:00
Doxa Maps
00782796a9 useViewAddress: say which writers share commitUrl, and why a push copies the router's state
The header claimed that every write to the address bar goes through commitUrl. Only the view keys and the filters
do. The tree's other history writers are pinned by check:view-address's census. The header also says why a push
copies the previous state: a Vue Router page is a 'host' owner, so its router does its own pushes.

Comment only.
2026-10-03 05:23:18 +00:00
Doxa Maps
30dc9a1457 Shareable pages P1: one address helper, and the filters write through it
library/composables/useViewAddress.js is the page's address for the view a reader is on: ?tab= &pane= &place=, with
a level rule, an id whitelist, and ONE history write (commitUrl). It keeps history.state and sets its `current` to
the new URL, so a link survives the next in-app navigation on a Vue Router host. The OUTERMOST owner writes,
named by profile-config urlOwner:
- 'app' writes history itself;
- 'host' hands its router one dxm-address;
- absent, nothing is written and links are still read.
Nested elements announce dxm-view. Back/Forward is read and never written.

useShareableView's filter write used to call replaceState(null, ...) itself, which wiped the router's state. It
now goes through commitUrl (writeFilterView). A filter write keeps tab/pane/place, and a tab/pane/place write keeps
the filters.

Level 2 is `pane`, not `view`: ?view= is already published by four of our pages.

check:view-address (gates:pre) runs on a simulated browser history. It covers:
- the whitelist and the level rule;
- one push per tab change, with state + current kept;
- no write when unchanged, on a partner page, or by a host owner;
- the filters and the view keys keeping each other through writes, Back and Forward;
- a census of every history writer in library/ and plugin-designs/ (6 today). A new writer fails until it is listed.

The self-test has 14 poisons, each caught. Real-source mutations are caught too: drop current, drop the unchanged
guard, drop the level rule, an uncomposed event, no length cap, an aliased pushState in a plugin.
2026-10-03 05:20:43 +00:00