`list_qualifying_questions` reduced the visitor's `?chain=` to a
bool, so one followup path unlocked every followup on the site -
redoal's `/watching` (an email-link page whose header says "never in
the nav") showed up for anyone who had just subscribed.
The nav now resolves the chain hash to the one question it verifiably
answered (the same `lookup_node` a `requires_chain` gate uses) and
offers a followup only when it is on that path or further down from
the current page over `action` edges. `content::outcomes_reachable_from`
walks those edges through followup pages only, so a "back to the
start" action ends the walk at the front door instead of fanning out.
Templated actions (`/shape/{curve.key}`) resolve to their dynamic
page's pattern. No schema change: the `followup` flag keeps meaning
"not a front door", and the edges were already in the content.
Also: `main.loading`/`main.not-found` gets the same `svh` ladder as
`.hero-piece`, the last bare `vh` in the stylesheet, so wrapper
browsers that resize the webview as their toolbar animates don't
reflow the placeholder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
A question may carry event: {starts, duration, place}. While the
window is open the page is announced in a strip at the top of every
header (name, when, 'in 3 days'), soonest first, and kept out of the
footer nav; when it closes the page becomes a followup - only a
visitor carrying an answer chain still sees it.
announce.rs keeps one record per event page in the runtime-owned
portal_events bucket (built-in state graph: announced ->
awaiting_summary -> summarized, content may override) and, on a
one-minute idempotent sweep, moves ended windows to awaiting_summary,
publishing the transition on portal.answers.submitted as 'Summary
due' - the post-what-happened task a review desk picks up. Lint
warns when event pages exist but nothing reads portal_events.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
site.yaml's hero is now plain | module, where module names a
JavaScript file the content repo ships (mount(container) -> handle
with stop()). Portal serves content assets same-origin at
/site/<path> (Gitea raw sends no CORS headers), starts the module at
HTML parse time, adopts it on hydration, mounts fresh via the inline
script's __mountHero on client-side navigation, and stops it on
leave. The YES canvas (yes.js) and the gesture hero mode leave the
engine - uhhm/questions ships YES as its hero.js, redoal/questions
ships a sine-swings band. Gesture form canvas no longer balloons after
a stroke: the wrap's aspect-ratio reservation is scoped to :empty
(pre-mount) so it can't turn echo-strip height into width inside the
flex field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>