check:dev-leaf-escape and its self-test join gates:pre. The comment on the
dev pages' owner rule named the wrong reason for leaving a framed page
unowned: the frame is a partner page that iframes a leaf (the iframe
embed). Plugin Studio mounts its rows directly.
Four lines outside the swarm section still used 'land' for a change joining development: the agents' first bullet, the Plugin Studio line, the cloud dev server and the laptop-follows paragraph. They now say join / add to the pilot, so 'land' only ever means the landing branch.
The precondition's scope against the pilot (its own step-4 rule): every bundle
that imports ShareButton/MapControlMenu or reads the share catalogue area, plus
the P1 shells. Built one bundle at a time, then bun run tree + fix:sri.
gates:pre 141/0/1, gates:post 99/0/16.
manifest.json marks 8 maps production (the gallery's 7 plus the country map); Country Posters and Poster Design are in the gallery but still marked experimental, and the README now says so. Publishing tags the outgoing release with its own version (v0.2.0 in the example), which is the rollback point; going back means deploying the previous release's tag.
PR branches start from the pilot and take in the latest pilot by merging, never rebasing; a second
agent, usually the repository's agent, reads the change (and looks at it where it shows). In the lock
the pilot is merged once more, gates:pre and gates:post run with the landing checks
(internal/landing-preconditions.mjs), and the branch is pushed only if nothing fails. There is no
single build owner: each change rebuilds the bundles it reaches, then the tree and SRI. Publishing
tags the outgoing release as a rollback point, runs both gate sets on the exact commit, and builds the
landing demo from the release tag. The counts follow the code: the Research Gallery composes nine maps
(plus Country Posters and Poster Design, both in Prayer Mobilization, so they leave the Experimental
list), ten maps are in production with the country map, and Posterverse brings six poster plugins
together. The glossary's "merge" row matches.
The agent model names exactly three roles: one agent per repository (handles its branches, watches the work in it), one agent per plugin, and one shepherd agent that all the other agents talk to, which keeps order and puts the merges onto the pilot in order. The harness paragraph, the kitchen list, the alt text and the diagram's circle, caption and legend say shepherd. The PNG is re-rendered at 2x.
With the roles as they are (one coordinator, one repo agent per repository, one agent per plugin), the step that read every change before it joins the pilot is the repo agent's, not an unnamed second reader: the summary row, the PR-branch bullet, the diagram's banner and its step 2 card say so. The PNG is re-rendered at 2x.
The swarm section and the delegation diagram named a gate-keeper that checks each merge; that role
does not exist. The roles are one coordinator agent that puts the merges onto the pilot in order, one
repo agent per repository that handles its branches and watches the work being done in it, and one
agent per plugin. The harness paragraph, the kitchen list, the alt text, the diagram's second circle,
its caption and its legend say so, and the Experimental shelf's "roaming agent" becomes an agent per
plugin. The PNG is re-rendered at 2x.
The deploy5-desktop card read "Proven, not yet live", which the desktop section now contradicts: the desktop app works and Rudder is its proof. The README's only use of it goes, and so do the card and its SVG source, which nothing else uses. The sentence that the map app runs as a desktop app the same way stays.
pageLink() drops ?tk= from the page's address: a leaf page reads a token from
there and leaves it in place, so pressing Share would have sent it. The URL is
rewritten only when tk is present; every other link is the address as it stands.
The comments name the page, not who measured it, and no longer cite a request.
check:map-menu-anchor strips a same-line comment before testing each return, so
a fallback named only in a comment no longer passes.
The dev leaf route wrote the URL's parts into the page's <title> and the
element's profile-config attribute, refusing only .. and /. A quote or a
tag in a crafted link broke out of the attribute on a publicly reachable
dev server. Each part must now have the shape every bundle, profile and
instance id has, before it reaches either; the title is escaped too.
check:dev-leaf-escape drives the plugin's own middleware: every leaf the
dev manifest advertises is still served in both URL forms, hostile parts
are refused in every position, and the title escapes on its own.
ShareButton: the first tab is "Share this page", the link to exactly what the
reader is looking at. Where the page owns its address (urlOwner 'app' or 'host',
on the map's own profile-config or any element above it across shadow roots)
that is the page's own URL, read at open and at copy; on a partner's page it is
the map's own page, as before. The second tab, "Embed", says in plain words what
embedding is and why it helps, keeps the buttons.shareSteps list, then the paste
hint, the sizes and the code. Copy/sizes labels come from the catalogue.
MapControlMenu: a standalone anchor (a Share button outside any map toolbar)
resolves where its menu lives when it opens, and places it below the button,
toward the side with room, inside the viewport, at the readable width (340 or the
viewport minus its edges), converting into the menu's real containing block. The
render-time area tag that sent header menus far left and narrow is gone. Map
toolbars keep their placement unchanged.
English only for now (the new share.json keys are recorded as translation debt in
check:i18n-parity). check:map-menu-anchor reads a resolution that lives in a named
function, every named-area return must keep its structural fallback.
The dev pages passed no urlOwner, so on the dev server a shell read ?tab=
but never wrote it: a clicked tab was lost on reload and a copied link
opened the first tab. The root page, every /__leaf/ page and the Studio
staging page now own the address, as the built leaves do. A framed leaf
(Studio iframes its map rows) stays unowned, so it never adds entries
to the top page's Back stack.
The dev server serves plugin-designs/doxa-maps-spa/index.html as-is, with no
profile-config, so the shell read ?tab= there but never wrote it. The page is
ours: it now passes urlOwner 'app'. A partner's page still leaves it out.
The staging page the dev server serves sets profile-config urlOwner 'app', so on
the dev server the shell writes the open tab to ?tab=: a reload keeps it and a
copied link opens it. Inside the Plugin Studio, Posterverse is nested and still
writes nothing (the Studio owns the address there).
The merged posterDesign blocks brought biblePosters.posterDesign.label and
upgMap.posterDesignTerritory, catalogue areas these two bundles ship, so
their committed bytes were stale against the merged source. Rebuilt only;
no source edit.
The first-paint write is skipped when the address names a tab the shell
doesn't know yet (a row that only resolves once the manifest is in). Writing
tab 0 there would replace the link before applyManifest could honour it;
applyManifest's own first-paint replace writes the honoured tab.
Plugin Studio read its tab from #<bundle/profile> or from localStorage, and
never wrote it, so a reload worked for one reader but a sent link did not. It
now writes the view address through useViewAddress: ?tab=<bundle>__<profile>
(a row key's "/" is not a plain id; the double underscore keeps a sent link
meaning one row even if another bundle later adopts the profile's name). A
reader's tab click is a push, the first paint a replace, and Back/Forward
re-open the named tab without writing. route() reads ?tab= first (or a bare
profile name while it names exactly one row), then the old #bundle/profile
(rewritten to ?tab= on first paint, its hash dropped), then the remembered
tab, then the first row. The listener is removed before the hot-update return.
generate-tree bakes urlOwner 'app' into our own leaf pages only (never into an
embed snippet), and a framed leaf drops it at runtime, so a page embedded in a
partner's iframe reads its address but never adds entries to the partner's
Back button.
When the shell owns the address, a reload or a shared ?tab= now wins over a
profile-config tab, so the first-paint replace can never overwrite a sent link
with the default. Elsewhere the order is unchanged (attribute, config, URL).
A reader's pick in the rail writes ?tab= through useViewAddress as a push,
so a reload keeps the tab, a sent link opens it, and Back returns to the
previous tab. Only the address owner writes: urlOwner 'app' writes history,
'host' gets a dxm-address event for its router, and a partner's page is
never written. Programmatic switches (the manifest's re-select, the device
fallback, a host's tab attribute, Back/Forward) write nothing, and there is
no first-paint write. Back/Forward re-open the tab the address names on a
page that owns its address. ?tab= is read through readViewKeys.
The shell reads ?tab= through the shared view-address helper and writes the
open tab back through it: a reader's choice is a push, first paint a replace,
and Back / Forward re-apply the tab without writing. Only the address owner
writes (urlOwner 'app' on our own pages, 'host' hands the change to a host's
router, absent on a partner's page = read only), so a reload keeps the tab
and a sent link opens it.
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.
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.
Every verse now comes from one translation, the WEB British Edition (public domain), which reads "the LORD"
(the WEB reads "Yahweh"). Every text was re-checked verbatim against it (bible.helloao.org eng_webpb), 31 of
31 matching; the check found three mid-sentence names written with the sentence-start placeholder ("says the
LORD of Armies", "Now the LORD said", "as the LORD had told him") and the British spellings (towards, honour,
offence, amongst, marvellous). The credit line names the one translation.
Output 3 opens with three sentences (browser to desktop with the same code, Rudder as the working Linux proof, its private-by-design goal) and one screenshot. How it works, Rudder's goal in full, and what isn't run yet move into a details fold-out. The two Studio bullets go back to one-line gists.
Under the desktop output, Rudder's goal from the start: offline, with no telemetry and no cloud. Your
own code and your own self-hosted speech model (NeMo-Speech.cpp and a ~742 MB model) on a modest
amount of CPU; after a one-time download of the runtime and model nothing needs the network (the
runtime was traced: zero sockets, zero DNS). Working out of the box is the aim; the first-run download
is still manual. Because a voice-to-text tool hears everything, changes to rudder-voice and
rudder-engine should come only from trusted people, each read by a second person. Planned: an egress
monitor that shows Rudder sends no data out.
The desktop output now leads with what it shows: what you run on the development server in your
browser ships as a desktop app from the same code, wrapped by Tauri in a small Rust program. Rudder is
the proof: the offline voice-to-text plugin runs in the Plugin Studio and as its own desktop app, and
the Linux app works and is used to dictate the work on many of these maps. The summary table, the
"speak instead of typing" and "one plugin, several outputs" bullets and the desktop section say so;
the details keep what is not run yet (macOS, Windows, the automatic production build).
The desktop HOWTO's proven table records the launched Linux app.
UPG100's zone-poster code lived only inside its plugin, so Poster Design
(which draws from the library) could list UPG100's posters but never show
them. It is now COPIED, unchanged, into library/packet/upg100Zones/
(zone-sheet-print, zone-sheet, legend-tree, selector-evaluator,
zone-geometry, zones-for-list, clusters.json; only the library import paths
are shorter). UPG100's own files are untouched.
Poster Design adds a UPG100 zone posters family drawn from that copy with a
real zone (middle-east-core) and all the records, as UPG100's own wall
does: the letter, 3 x 3 and 3 x 4 zone posters under UPG100's own design
names (so the drawn card replaces the no-preview plugin card), and the
prayer packet's page 1 (the full letter zone poster). Keys in 11 locales.
check:upg100-zone-copy (in gates:pre) keeps the copy equal to UPG100's
originals beyond the import paths; once UPG100 switches to the library copy
and deletes its originals it becomes a one-path gate. Its self-test proves
a one-character edit, a changed clusters.json, a missing file, a half
switch and a re-definition each fail. check:poster-design-catalogue allows
a template to carry a plugin's design name only when it declares that
plugin (poisoned). Source only: poster-design is rebuilt when the heavy
slot reopens.
The four Wall sources both sides changed (blessPoster.js, wall.css, wallCanvas.js, check-wall-no-bg-photos.mjs)
merged without conflict: the pilot's side is the comment-only privacy rewording of those files (c1a2f3698); both
sides are kept (no agent names; every branch change present). The generated files (the wall bundle, its leaf page
and the two manifests) were taken from the pilot and rebuilt from the merged source, then fix:sri and the tree.
Styles now lists the production designs by family, and puts every
experimental design last, in one Experimental (in progress) group that
starts collapsed (a click opens it). Experimental: the side-lane country
sheets (66x66, 36x60, and 60x96, still being designed), the map print (no
preview: it prints the live map), and every other plugin's design (made by
its own plugin, so not drawn here). templates.js marks them experimental;
posterDesign.family.experimental is new in all 11 locales.
The Bless poster follows the UPG 100 List posters' structure:
- the header and the prayer-themes chart close on dividers that run the page's width (to a quarter of the
border from the sheet's edge), while the text stays in the content column;
- at the foot: the call to action in a band ruled across the page ("Jesus Christ is the cornerstone, and we
are living stones. ... bless them in his name, and join the global family in prayer.", moved down from the
header), then the journey's table, a page-wide rule, and the footer: the whole priestly blessing,
Numbers 6:24-26 (World English Bible British Edition, public domain, verbatim), in two lines that fill
the width, with its reference and the credit line.
Measured on the print at 3 x 4, 2 x 3 and 5 x 8 ft, en and ar: the blessing fills 99.2% of the text width
(49 pt at 3 x 4, 74 pt at 5 x 8); the call to action is one line, 97.6% of its band in English; no text
overflows (the only overflow is the dividers' intended reach past the column). Both em constants come from
those measurements. Cards: 3 x 4 0.49 in, 2 x 3 0.35 in, 5 x 8 0.92 in (one verse per row).