Engagement Phase 12a. The site had pages for where a message goes (Notifications and
email) and what it says (Message templates), and nothing at all for what makes one get
sent -- the four Engagement screens the workstream built.
New page: Engagement rules. Rules, Audiences, the trigger catalog and the send log on
one page, sitting between the two it joins up. Templates already has its own page and
Suppressions is in Troubleshooting, so neither is repeated here.
Two things it exists to state plainly:
* Every rule ships disabled, including the ones a module brings. "Installed" is not
"on", and an upgrade whose Team mail went quiet is the same fact.
* The ceiling is a TREE, not a ladder. The tempting reading -- a staff-only event
could obviously also go to one person -- is wrong, and the example is the argument:
"one person" for cheat detection is the player it was detected on.
Troubleshooting gains the symptom that page answers ("nothing is sent for one
particular event"): the rule is off, the rule is dormant, its own cooldown held it, or
the audience is empty.
Privacy: two rows the engagement work makes necessary, and one sentence it made false.
* app-content claimed "Nothing is cached for offline use". Phase 8 shipped a DataStore
snapshot of the inbox, so it was untrue -- and that row feeds the generated Play Data
Safety answers, which is a store-review matter rather than a doc nit. The snapshot now
has its own row and its own Play mapping (Messages / Other in-app messages; not
collected by us, stored on the device), and app-content's claim is narrowed to
everything else.
* deploy-engagement, for the deployment scope: an address is now used for more than
getting into an account, there is a delivery log holding a one-way hash of it, and
there is a suppression list. Its retention line says what is true rather than what a
reader assumes -- none of these tables has a retention sweep.
PLAY_DATA_SAFETY.md regenerated from the inventory; legal.lastUpdated moved with the page
it dates.
Verified: the whole `verify` chain green -- checkSidebar (plannedSidebar moved with the
live tree), checkFacts 19/19, checkQuickstart 59, checkReference 22, checkLinks 2605,
checkA11y, checkCsp, playDataSafety --check, 42 + 7 tests. Read in a browser as well, in
the served build.
AI-assisted: written with Claude Code.
Co-Authored-By: Claude <noreply@anthropic.com>
The operator half of engagement Phase 5b, which 6.0b of ENGAGEMENT.md assigns to
this repo: what the Templates screen is for, how a shipped default is edited in
place without an upgrade taking the edit back, why variables are clicked rather
than typed, the two halves of every message, draft vs published, the preview and
its dark-mode approximation, test sends, duplicating to make a new template, and
the send log.
Written for someone running a site, not someone reading the design document: it
explains what to do and why the refusals exist, and names no version numbers - the
facts check reads authority from each repo's `main`, and none of this is there yet.
Co-Authored-By: Claude <noreply@anthropic.com>
Twenty pages completing the tree section 10 planned: Modules (8), Architecture
(5) and Reference (7). Four decisions, D38-D41, recorded in PLAN.md section 10.
D39 is the one that shaped the phase. Section 1 forbids re-specifying a
contract, and a Reference section is exactly where that rule is most tempting to
break, so the line is drawn at names: every environment variable, config key,
installer command, visibility rung and canonical document is listed with one
terse line saying what it is FOR, while shapes, semantics and every "why" stay
in the canonical document.
That is only safe because the names are checked. checkReference.mjs compares six
enumerations against the repositories that own them, over the Gitea API, as set
comparisons in BOTH directions -- and the second direction is the one that earns
its keep, because a reference page does not usually rot by describing something
that vanished, it rots by quietly not mentioning what was added since.
The check went green on its first run, which is the least trustworthy possible
outcome, so it was verified by breaking it: seven mutations, all caught. The one
worth keeping is the visibility ladder REORDERED with its membership unchanged
-- it is a security boundary, and a set comparison alone would have passed it.
D41 turns plannedSidebar from a checklist into a checked invariant, and finding
out why was the phase's first defect: it had already drifted, because phase 7
added the Content page under D37 and never updated the list. Nothing failed,
because nothing read it. checkSidebar.mjs now asserts the two trees agree on
groups, labels and order -- order because the order of Getting started IS the
installation path.
Two more things the writing found. PLAN.md's page count was wrong and had been
since section 10 was written ("roughly 38, 37 planned" for a tree of forty).
And module.json's `mounts` and the SPA's paths are different mechanisms that no
single document stated plainly -- module-uo declares admin: ["/shard",
"/uo-link"] while its screen lives at /admin/uo/link, because API routes are
deliberately NOT namespaced while SPA routes are. That is precisely the
distinction the installer got wrong in v0.1.0, and it now has a named home.
D40: the docs link to /architecture/'s drawn diagrams rather than importing
them. Those components carry marketing chrome and depend on diagram.css, which
Starlight does not load; the docs use text diagrams, which paste into an issue.
npm run verify green: 40 pages across 5 groups agree with plannedSidebar, 2390
internal links resolve, 123 repository links point at a branch, 19 facts, 59
quickstart checks, 22 reference enumerations, astro check 0 errors, 36 tests.
Co-Authored-By: Claude <noreply@anthropic.com>
Twenty documentation pages: Getting started (7) and Administration (13), the
journey no existing document owns end to end because the repositories are
organised by component and an operator is not.
Four decisions of record, taken before anything was written (D34–D37, PLAN.md
§10 "How phase 7 built the documentation journey"):
- D34 one PR for all twenty pages.
- D35 the install page is SELF-CONTAINED: it prints a complete Compose file and
a complete .env that an operator copies without visiting another repository.
That is a copy of somebody else's file, so it is checked rather than trusted —
scripts/checkQuickstart.mjs re-reads website main:docker-compose.yml and
main:.env.example over the Gitea API and fails on any disagreement, in both
directions: a value that drifts fails, and a service or variable that appears
upstream fails until it is either included or recorded as deliberately omitted
with a reason. Its first run found two stale entries.
- D36 every Administration screen was walked on a real deployment before it was
described — the rig being the quickstart itself, against the published image,
so one run proved the install page and produced the detail the admin pages
needed.
- D37 a thirteenth Administration page, Content, so that every admin nav row has
a home without organising the docs by the app's menu.
What the live deployment disproved, all three now documented:
- The documented Compose deploy does not boot. SECRET_ENC_KEY is required in
production (utils/secretBox.js throws at require time) and is missing from
website's ROOT .env.example — the file Compose reads. It is present in
server/.env.example, which is why dev never hits it. The quickstart carries it,
declared as an upstream omission so the check fails the day it is fixed.
- The installer points operators at a screen that no longer exists: it prints
<site>/admin/shard, and INSTALL.md §5 repeats it, but since the module cutover
the screen is /admin/uo/link. Both the binary and the guide are stale.
- The admin Restart button opens a window.confirm whose text is the honest
warning that a deployment with no supervisor does not come back — which is why
`restart: unless-stopped` is called out as load-bearing rather than left as
boilerplate.
And the defect only a look found, three phases running: the .env block's prose
promised that every highlighted line must be changed, while `mark` given the
variable names highlighted the names alone and left the values unmarked. Every
check passed on a page that was wrong about its own highlighting.
verify green: 890 internal links, 52 branch links, 19 facts, 59 quickstart
checks, 0 astro-check errors.
Co-Authored-By: Claude <noreply@anthropic.com>
Astro 7 with the Node adapter, Starlight mounted at /docs, the token file, both
self-hosted typefaces, the layout shell, and the two build-time checks from §12.
The palette's gold and cyan are sampled from runic-emblem.png rather than
guessed, per §11: 494,059 opaque pixels binned by hue, each value annotated with
its measured contrast against the ground, and restricted rather than brightened
where a ratio fails.
- checkTokens.mjs fails the build on any colour literal outside tokens.css,
which is what keeps §7's "recolouring is a file copy" promise true.
- checkFacts.mjs re-reads all 14 externally-sourced facts from their authorities
over the Gitea API and fails on disagreement. It also enforces D13: no email
address in the source outside brand-default/brand.json.
- Both were negative-tested; neither has ever been allowed to pass by default.
§6 asks for output:'server' with per-page prerender=true. Astro 7 expresses the
same runtime shape as output:'static' with an adapter, opting individual routes
out — so the default is static rather than accidentally server-rendered.
Co-Authored-By: Claude <noreply@anthropic.com>