The pilot's country data now places every country in a real region, so the
country type has 7 regions. gen:poster-sections-presets regenerated the list
(19 presets); check:poster-sections had failed on the stale countries-other
preset and passes now. Bundle, tree and SRI rebuilt.
Merge studio's poster type registry + pins-in-state + preview-flag pilot into
the region data fix. Built-output conflicts resolved by taking the pilot's
public/ then rebuilding the three bundles that embed country-posters.json
(country-posters, poster-design, regions-dashboard) on the merged source, so
they carry both the registry changes and the corrected regions. Tree + SRI
regenerated. check-poster-section's dropped 'other' scope and the 18 region-only
records are preserved through the merge.
gen:poster-sections-presets writes poster-sections.instances.json from each
type's scopes() (all regions, then one preset per region titled by the type's
own section title in the site's English, then configure), the same list
check:poster-sections holds the file to. Today it reproduces the 20 presets
exactly, in the data's region order; when the data's regions change, running
it is the whole fix.
scrollTo(paneId) called target.scrollIntoView(), which scrolls every scrollable
ancestor, the embedding page included. On another site, "Map" / "Show on map"
moved the host's own page so that the map's top met the window's top, and a host
shorter than the window then showed its white space under the map. The column
is the scroller (position:absolute; overflow-y:auto) and each pane's
offsetParent, so col.scrollTo({ top: target.offsetTop }) lands the pane exactly
and leaves the page alone.
Tightened before the India tab lands. A built bundle that READS globalThis.DXM_PREVIEW_UNRELEASED would ship the
withheld path itself, behind a global that any embedding page or console can set before the element loads, so
withheld content would print from production.
- A: no file under public/ names the flag at all (was: no assignment).
- B: every source line that reads OR writes it carries import.meta.env.DEV (was: writes only), so each read
compiles away in a build.
- Self-test: a built setter, a built read, an unguarded source setter and an unguarded source read must each
fail; the DEV-guarded forms and comments pass.
country-posters.json is statically imported by these three bundles (via
countryPosterType.js, poster-styles.vue and regions-list.vue), so the region
re-derive stales their built output. Rebuild the three, regenerate the tree
(manifest + leaf pages) and refresh SRI. The composite-freshness gate named
exactly these three; the other 26 bundles are unaffected. Only these three
bundle .js change; the doxa-maps-spa leaf updates its embedded integrity hash
for country-posters.
Each country's region is set to the live wagf_region its groups carry (file
label spelling); only the region field changes (stats, bbox, count, India row
untouched). The stale 'Other' bucket (18 countries) empties out and each moves
to its real region: USA and the non-Spanish Caribbean + the Guianas to North
America & Non-Spanish Caribbean; Andorra/Monaco/San Marino to Europe; Cocos,
Christmas, Niue, Norfolk, Tokelau, Wallis & Futuna to Oceania. Europe goes
27 -> 30, matching the live Regions dashboard.
check:poster-section drops 'other' from its covering scopes (the bucket is now
empty, so the /regions/other scope resolves to zero countries); the seven real
regions still cover all 175 countries exactly once.
Region-only diff (18 region lines); B25 India withheld unchanged
(check:country-sheet-one-path, poster-section, poster-browser-equiv,
poster-design-catalogue all PASS).
The presets file now has to hold, per poster type, exactly an all-regions
preset plus one per region the type's adapter returns from scopes(). A region
the data drops (a stale "other") or gains then fails the check, instead of
lingering as a preset. 'country' is measured always; 'upg100-list' once its
type file is on the tree. Self-test: a preset for a region the type does not
have must fail.
Poster Sections becomes a parameterized profile like doxa-simple-map: its
props (type, scope with 'all' = every region, locale, columns, collapsed)
choose the <dxm-poster-section> sections it renders, and
poster-sections.instances.json lists 20 presets, each built into its own page
and embed snippet in manifest.json:
- country posters: all regions + each of DX Life's /regions regions (8);
- UPG 100 posters: all regions + each of its 9 regions (they show once the
upg100-list type is registered on the page);
- configure: the panel of choices and Copy embed, for making new presets.
The element's columns attribute now comes from the library (landing
separately), so this commit drops its library edits; check:poster-sections
reads columns back through that attribute and passes once it is on the pilot.
- ONE REGISTRY PER PAGE. Every bundle inlines its own copy of posterTypes.js, and <dxm-poster-section> is
defined by whichever bundle loads first, so with a module-level Map that element saw only its own bundle's
types: type="upg100-list" rendered "none" when the country-posters bundle had defined it. The registry now
lives on the page (globalThis.__DXM_POSTER_TYPES__, created once): every copy registers into it and reads
from it; a name registered twice keeps the first (a dev warning; the built-in country is silent); every
registration is announced on window (dxm-poster-type-registered), and PosterSectionView looks again when
its type arrives after it asked.
- COLUMNS (carried from the Poster Sections plugin PR so the element's library lands once): columns="1".."5"
maps to profile-config.columns and to PosterWall's existing columns prop; absent or out of range is
today's default, and a reader's saved per-row choice still wins.
- check:poster-type-registry (gates:pre; self-test in gates:post): two independent copies of the module side
by side, as two bundles on one page; a type registered through one is found through the other; first wins;
announced. The self-test's per-bundle registry fails.
New library/packet/pins-in-state.js: a deterministic pixel-space pin
de-overlap for the whole-India poster that also keeps every pin inside
its own ADM1 state (multipolygon + holes), pulling a pushed-out pin to
the nearest point just inside. Reports displacement, remaining overlaps,
pins-outside (0), and an infeasible list for states too small to hold
their pins — never a silent overlap. Imitates the poster-render pin
relaxation (read, not called). Adds check:pins-in-state (real India data
when present, else a 60x96 fixture; hole/island + poison arms).
A new bundle, plugin-designs/poster-sections, with one profile, Poster Sections.
A panel of choices (poster type from the registry at runtime, region or All
regions, language, posters per row, start collapsed) drives the library's
<dxm-poster-section> elements shown live below it, and Copy embed gives the
bundle script plus the exact tags for those choices, with absolute URLs so they
work on another site. The bundle also registers <dxm-poster-section>, so a site
that loads only poster-sections.js can place the tags.
Library: the element takes a columns attribute (posters per row, 1-5), passed
to the wall's existing columns prop; absent keeps today's default, so existing
importers are unchanged. New catalogue area posterSections in all 11 locales.
check:poster-sections (in gates:pre) reads every panel choice back through the
element's own attribute reader; its self-test proves a builder that drops
columns, drops collapsed, or repeats a region under All regions fails.
globalThis.DXM_PREVIEW_UNRELEASED lets a page show what is withheld from the public (the whole-India country
poster). defineMapElement.js, every element's first module, now sets it behind import.meta.env.DEV: true only
under the Vite dev server (the pilot's hot-reload preview), a compile-time false in every build, so the build
drops the line. The built tree, which goes to the research landing and DX Life's /dx-maps, never carries it.
check:preview-flag-dev-only (gates:pre; self-test in gates:post): no file under public/ assigns the flag in any
spelling a minifier writes, and every source assignment is behind import.meta.env.DEV (2 planted defects).
- The post-drag click guard (400 ms) was armed by every drag, but a drag that claimed its touchstart
produces no browser click to guard against, so it only ate the reader's next real tap (a caret tap right
after a drag did not collapse the sheet). It is now armed only for a drag that did not claim its start.
- A claimed touch becomes a drag only past 6 px (a thumb tap moves 1-3 px), so a slightly moving tap on the
caret or the ✕ keeps its click.
- A comment no longer names an internal role.
The section the Country Posters tab draws (a collapsible region head + its poster cards) is now usable on its
own, for any poster set, by parameters:
- library/components/poster/posterTypes.js: a TYPE REGISTRY. A type is an adapter (items for a scope, the
section's title, the print actions). The library registers 'country' (library data + the shared sheet path,
so a withheld whole-country poster stays withheld); plugins register their own types from their own code
(UPG 100 lists, regions, continents later). No plugin code in the library.
- 'country' scopes: a region key in any spelling. DX Life's /regions slug ('latin-america-and-caribbean'),
the API's value, the wall's slug and the English name all reduce to one canonical key.
- PosterSectionView.vue: one section, drawn by a PosterWall in a new SECTION-ONLY mode (no wall head; off by
default, so every existing wall renders as before), embedded so the page scrolls it.
- <dxm-poster-section type scope collapsible collapsed locale assets-base api-base-url is-dark>: the
attributes map onto the standard element's profile-config (posterSectionConfig.js, pure), so it keeps the
per-instance i18n and store. registerPosterSectionElement() from any bundle; Country Posters ships it first.
- country-posters/poster-section: a page showing two sections (Africa open, Oceania collapsed).
- poster.sectionNone (11 locales): an unknown type or an empty scope says so, never a blank.
check:poster-section (gates:pre) + self-test: the registry, DX Life's 8 scopes covering every country once,
withheld India, the attribute mapping, and PosterSection.vue injecting every name its template uses; 3 poisons.
The known collapse-while-scrolled landing is left exactly as it is.
- MapControlMenu dispatches 'dxmaps:panel-close' (composed, bubbling), the twin of 'dxmaps:panel-open', when a
panel closes by ✕, an outside tap or Esc, or is unmounted while open. A host can then hide its own chrome
while a panel is up (DX Life's top scroll strip sat over the Share panel's ✕).
- The mobile legend's resize drag scrolled the page. Only the strip and the title rows carried
touch-action:none, so a drag that began elsewhere in the 44px grab zone let the browser start panning
before touchmove could cancel it, and its later touchmoves were not cancelable. touchstart in the grab zone
now claims the gesture (preventDefault), and a touch that never becomes a drag is clicked on release
(inputs keep their native tap). The height is written once per frame (rAF), and the last frame lands
before the snap.
A section of a poster wall (its head: the name, the count, the collapse toggle; and its cards: the preview,
"show on map", the print options) is now its own component, library/components/poster/PosterSection.vue. The
markup moved VERBATIM; PosterWall renders every group through it and PROVIDES what a section uses (its props,
its collapse state, the print actions and their progress overlay) under POSTER_WALL_CTX. The section's CSS stays
in PosterWall's one shadow-style string, which applies to this markup in the same root.
Every wall renders the same DOM: measured in the shared browser, before (the pilot's built tree) vs after, at
1440 and 390, en and ar: Country Posters, UPG 100's poster wall, Poster Design's styles and the Regions list
(with every region's posters open) are identical once the server port and the root's scope hash are normalised;
the same cards, sections, intro text and scroll widths. The first step towards a reusable poster section.
The gate compiled PosterWall.vue alone, as a client component, and runs
Vite without the Vue plugin. A child component imported by PosterWall
(a section component, which a refactor is about to add) would fail to
load, turning the gate red on correct code.
One resolveId/load hook (enforce 'pre', so Vite's resolver does not load
the raw SFC first) now client-compiles PosterWall and every .vue it
imports, each at its real path, so their own relative imports resolve as
in the build; the earlier '../../' path rewrite is gone. Arm L loads a
parent fixture that renders a child .vue by relative import
(internal/fixtures/poster-wall-host-seams/) and asserts the child's text.
It fails without the hook and without 'pre'.
A MapInfo mounted at the top of a map's own shadow tree has no parentElement, so it attached no listener and a
standalone leaf would have kept the pointer over Share. It now falls back to its root node (the shadow root),
which the panel's event reaches.
On a phone, at first load, the onboarding pointer could sit over the open Share panel and cover its ✕
(cities-with-internet). The pointer can be drawn by the host page, outside the map's shadow root, so no
z-index inside the map can lift a panel above it. Instead, MapControlMenu (Share, and every menu built on it)
announces that it opened with a composed 'dxmaps:panel-open' event, and MapInfo, hearing it on its own
container (so only that map's panels count), hides its pointer; a page-load pointer is then done for this
load, as if its popup had been opened.
The lock guards the pilot, so it is the pilot lock, and the act is a merge onto the pilot: the
README's summary row, the harness paragraph, the kitchen list, the glossary and the alt text, and the
diagram's title, the lock ring, the coordinator line and the legend. "Publish to landing" and
"landing (main)" keep the word for the landing branch. Also: about 15 seconds to see a landed change;
a "merge" glossary row instead of "rebase", with pilot and landing beside development and main; the
cloud paragraph names BRANCH=development and PRODUCTION_BRANCH=main. The PNG is re-rendered at 2x.
Step 5 merged the pilot INTO the PR branch, which made the PR branch carry everyone's unpublished
work. It now merges the PR branch into a detached copy of the pilot inside the lock, checks, pushes
that merge, and switches back; a conflict is aborted and fixed on the PR branch. Step 4 says the
per-agent preview servers run on your own machine, and the cloud paragraph says to set the container's
BRANCH to development for the pilot server (its default is main).
The pilot branch (development) is the shared working version a hot-reload server follows; the
landing branch (main) is production, which its server rebuilds from; PR branches hold one feature
each, start from the landing branch and land on the pilot after the checks. The worktree, lock,
gates and build-owner steps stay. A short paragraph on cloud development with a swarm: a self-hosted
forge, a pilot and a landing server per app, and the pilot server's PR switching named as planned
only. The diagram's labels follow (pilot at the centre behind the lock, landing below); the PNG is
re-rendered at 2x from the SVG.
- WEB MODE GETS THE TOP BAR BACK. The corner ☰ with no header was rejected on a real phone. Both shell modes
now show the one top bar on a phone, the ☰ at its start and the language at its end; web mode's bar takes no
camera inset (the shell is not at the top of the screen in a website). The corner circle and the drawer-foot
language are gone. check:shell-header renders both modes (13 planted defects).
- THE FLOOR NEVER EXCEEDS A SIZED HOST. ProfileLoader's 100svh floor is for a host sized by its content (the
scroll-page leaf). Inside a host with a definite height (DX Life's map slot, overflow hidden) it made the map
taller than the host, which clipped the map's bottom and its legend footer, and tied the map's height to the
window's. ProfileLoader now MEASURES the host (the floor at 0 and at a huge value: a sized host keeps its
height) and drops the floor inside a sized one, re-measuring when the host resizes (a ResizeObserver on the
host, not a window resize).
- LegendDesktop caps itself at the map's height (100% - 80px), not the window's (100vh - 80px).