v10.2.1
Patch Changes
-
3ece586: feat(idn-regions):
idn_admin_regions.region.readshipped with the module (ADR-0046) and, until now, had no caller anywhere — notawcms-astro, not even the lookup API's own route files, which described their own consumers as including "the admin browser" when none existed. 91,599 regions were reachable only bycurl./admin/idn-regionsgains a region browser panel — drill-down by tier (province → regency/city → district → village) vialevel/parentCode, name search, keyset paging on the API's ownnextCursor(never a bare limit), a resolved ancestor path on selection,dataset=<code>to browse a superseded version, and a distinct empty state forreason: "no_active_dataset"versus an ordinary empty result. The panel is gated onregion.readalone — an ordinary TENANT permission, unlike the platform-scopeddataset.configure/.restore— so a viewer holding onlyregion.readin a non-platform tenant sees it and nothing else; the page's entryauthorizebecomes an any-of array (dataset.read OR region.read), the same shape/admin/domain-eventsalready uses for independently-readable panels. Browsing is entirely server-renderedGETforms and links, so it ships zero client JavaScript — the measured app bundle stays at 213,807 B, unchanged. No create/update/delete of individual regions: this is a read-only browser over a vendored third-party dataset, deliberately. Removes the now-staleDELIBERATELY_UNSCREENEDentry foridn_admin_regions.region.readinadmin-screen-coverage-check.ts. -
c3bb19e: fix(e2e):
grep -rn "viewport" tests/e2e/returned nothing — the whole Playwright suite has always run atdevices["Desktop Chrome"](1280×720), so "the admin surface works on mobile" was an unverified claim with no test behind it. A new read-wave specresponsive-360.e2e.tsoverrides the viewport to 360×640 (the narrowest width in real use — anything that fits 360 fits 375/390) and sweeps every STATIC discovered admin route, assertingdocument.documentElement.scrollWidthnever exceeds the viewport by more than a pixel of rounding slack; on overflow it also names the offending elements (tag, id/class, right edge) instead of leaving a bare number. Unlikeadmin-screens-render.e2e.ts, the two platform-scoped screens are NOT exempted here — a refusal page must not scroll sideways either, and horizontal overflow is well-defined for both outcomes, unlike contents-vs-refusal.discoverAdminRoutes/AdminRoute/the pages-root resolution moved out ofadmin-screens-render.e2e.tsinto a newsupport/admin-routes.tsso this spec could reuse the same discovery without importing one.e2e.tsfile into another (which would have registered the render sweep's own tests into this file's suite, under a viewport it was never written for) —admin-screens-render.e2e.ts's behaviour is unchanged. Run against a real server and Postgres: it found two genuine overflows,/admin/account(document.documentElement.scrollWidth613px, from#account-sessions-table) and/admin/seo(396px, from the SEO rules table and a<select>) — left unfixed and unexempted here, as real findings a gate exists to surface, not to launder. -
1530f59: feat(idn-regions):
rejectedwas a dataset status the schema's CHECK admitted,DatasetStatusdeclared, and/admin/idn-regions'sstatusVariant()rendered asdanger— and that no code path ever wrote. Becauseidn-regions:importvalidates the vendored dump whole and refuses to write a partial hierarchy, a failed import wrote no dataset row either, so on the console a dataset that failed to import and a dataset never attempted were indistinguishable; the failure existed only in the shell or CI log of whoever ran it. A failed validation now records its ownrejectedrow carrying the attempted dump's provenance and the reason it failed, and still writes zero region rows. The row is written AFTER the failed attempt's transaction has rolled back — recording it inside that transaction would have rolled the evidence back along with it, silently recreating the defect. Re-running the same bad dump refreshes that row in place viaON CONFLICT … DO UPDATErather than erroring on the deterministic dataset code or accumulating duplicates. Addssql/149(therejection_reasoncolumn, with a CHECK tying it to the status by construction: arejectedrow must carry a reason and no other status may). Theimportjob's descriptor also gains theenvironmentNotesit never had, naming theawcms-jobsimage that can actually execute it. -
09245a1: feat(idn-regions): activating a region dataset changes the address hierarchy every tenant is served, immediately and platform-wide, and until now the operator confirming it could see a version code, a row count and a source commit — nothing about what would actually change. A well-formed dump of the wrong year passes every check the importer makes, and rollback is a recovery that only runs after tenants have already been served the wrong data.
/admin/idn-regionsnow compares two dataset versions before the decision: per-tier counts on both sides with the delta, plus the codes added, removed, and renamed, each keyset-paged rather than rendered whole. Both versions already live side by side inawcms_idn_admin_regions— the property that makes rollback a status flip — so the comparison is a query rather than a re-parse. Gated on the tenant-scopeddataset.read, so a non-platform tenant can audit what it was served last week against what it is served now.sql/150adds the two supporting indexes, including a covering index on(dataset_id, code), and the integration tests assert the query plan does not fall back to a sequential scan of either dataset. -
150e7e3: chore(bun): the pinned Bun version moves 1.3.14 → 1.4.2 across the six places that pin it —
package.json'spackageManager, the three CI workflows that set up the current-pinbun-version(ci.yml'squalityjob,changesets.yml,release.yml),Dockerfile.production's base image, anddeploy/cron/awcms.crontab's job runner image — plusawcms-family-compatibility.yaml, the manifestfamily:conformance:checkreads to assert those six agree with each other and with itself.bun install --frozen-lockfileunder 1.4.2 leavesbun.lockbyte-identical, so nothing in the dependency graph moved. Theengines.bunfloor and theci.ymlminimum-supportedjob deliberately stay pinned at 1.3.0: since the lockfile didn't change, the set of code actually exercised at the floor didn't change either, so there was nothing there to re-verify. The one artifact that does depend on which Bun built it — the committedpublic/js/blog-preview-overlay.jsbundle thatbuild:preview-overlay:checkcompares byte-for-byte against a fresh build — was regenerated under 1.4.2 now thatpackageManagerand local Bun agree again; the bytes differ only in the bundler's internal minified variable names, not in behavior.docs/awcms/family-compatibility.md(+ its Indonesian mirror), the two implementation-blueprint docs,docs/PROJECT_STATE.md(+ mirror, made version-neutral so the next pin move doesn't require a second edit), and ADR-0032 (+ mirror) were updated to match, since a stale doc naming the old pin is exactly the kind of driftfamily:conformance:checkandtests/family-compatibility-doc-parity.test.tsexist to catch. -
c3bb19e: fix(ui): two admin screens dragged the whole page sideways on a phone. On
/admin/account, the "Where you are signed in" table was already inside the standard.table-scrollwrapper — the wrapper itself was working, but the sr-only "Actions" label in its header row (position: absolute, no offsets) had no positioned ancestor closer than the page shell, so it kept its full, unclipped table-width position instead of the wrapper's, and draggeddocument.documentElement's width out to 613px at a 360px viewport. On/admin/seo, the site-wide noindex<select>did the same thing for a different reason: as an unstyled flex item next to the other create-form fields, its automatic minimum width locked to its longest<option>'s un-truncated text ("noindex (hide the whole site from crawlers)") and refused to shrink, pushingscrollWidthto 396px. A visitor on a real phone could drag either screen horizontally to see content that should have just wrapped or scrolled in place.Fixed both at the shared rule rather than per-page:
.admin-table-wrap/.table-scrollnow setsposition: relative, so an absolutely-positioned descendant is contained by the scroll wrapper instead of escaping to whatever ancestor happens to be positioned;.admin-create-form selectnow gets the samemin-width: 0+max-width: 100%treatmenttextareaalready had, so it shrinks to its allotted space like every other field in the row. Verified against a live server and Postgres at a 360×640 viewport: both screens'document.documentElement.scrollWidthare now 360px, and the fullresponsive-360andadmin-screens-renderE2E sweeps pass. -
72b49b9: fix(docs,idn-regions): six passages still described region-dataset activation/rollback as removed from HTTP with revoked permissions (ADR-0052), a state ADR-0053 reversed the next day by restoring both as PLATFORM-scoped permissions (sql/085) with live endpoints. Corrects
idn-admin-regions/module.ts's own description/comments/job metadata (including thejobs[].environmentNotesmis-attribution to a reversed ADR-0052 holding),docs/ARCHITECTURE.md+ its Indonesian mirror,docs/PROJECT_STATE.md+ its mirror, and the module's own README + its mirror — states, once, plainly, that the HTTP path writes an audit row and the job path deliberately does not. No behavior change; no schema, API, or permission change. -
9306807: fix(ui,idn-regions,push): three operator-facing admin instructions named a
bun runjob command without naming where it can run.Dockerfile.production'sruntimestage (what actually serves/admin/idn-regionsand/admin/push-notifications) carries onlydist/, notscripts/— an operator who reads either screen and runs the printed command in that container getsScript not found. Only the separately publishedjobsimage (ghcr.io/ahliweb/awcms-jobs, built byrelease.yml) carriesscripts/and can run it./admin/idn-regions's empty state and its "What this data is, and is not" caveat, and/admin/push-notifications's VAPID-unconfigured notice, now name theawcms-jobsimage explicitly, sharing one translated sentence across all three instead of inventing the wording three times.idn-admin-regions/module.ts'sjobs[]environmentNotesforidn-regions:activate/:rollbackgained the same fact. Copy and metadata only — no behavior, schema, or API change.