- Hero: gate RasterizedYES construction on a NodeRef resolving, not
just "the Effect ran". Root cause of the reported "loading..." stall
+ "RefCell already borrowed" panic - navigating back to / client-side
could run the Effect before the new <canvas> was actually in the
DOM, yes.js did an unchecked getElementById(...).getContext() on
null and threw mid-reactive-update, corrupting wasm_bindgen_futures'
executor badly enough to panic on the next tick. Same NodeRef-gating
pattern the prosekit editor's own mount already used.
- style/main.css: .prosekit-wrap/.prosekit-toolbar/.prosekit-editor to
match the existing input/textarea look (border, background, focus
ring).
- prosekit-editor.js: a real toolbar (Bold, Italic, H1, H2, Link, and
a dedicated Gitea-repo-embed button reusing the paste rule's own
insert logic) with active-state highlighting via the standard
ProseMirror markActive/nodeActive idiom.
Sign in link now carries ?redirect=<question_id>; /auth/login stashes
it in the session (validated same-site-path-only to avoid an open
redirect via a crafted query param), /auth/callback reads it back for
the final redirect instead of a hardcoded "/".
- type: prosekit requirement kind, backed by public/prosekit-editor.js
(ProseMirror via prosekit, loaded from esm.sh, no bundler) - mirrors
its HTML into a paired hidden input so it reuses the existing
RwSignal/on:input wiring.
- Pasting a project.uhhm.no/<owner>/<repo> URL in the editor embeds a
repo card, resolved server-side via a new /gitea-repo handler
(content::gitea_repo_handler) so the browser never needs Gitea API
CORS.
- New /automation/kv/{bucket} handler, bearer-token gated
(AUTOMATION_READ_TOKEN), for backing automations (n8n) to read a
NATS KV bucket without a browser session.
- Fix: a login started on one of apex/www set its session cookie
there, but Kanidm's redirect_uri is fixed to PUBLIC_URL - landing
the callback on a different, empty session ("no login in
progress"). Caddy now redirects www -> apex so every visit stays on
one canonical host.
questions is now Arc<ArcSwap<HashMap<...>>> - readers do a lock-free
atomic load (state.questions.load().get(&id).cloned()), never blocking
on or blocked by a reload. content::watch_for_reload subscribes to
portal.content.reload (published by the questions repo's own CI after
it lints a push - see that repo's lint-and-reload.yml) and swaps in a
freshly re-fetched HashMap on each message. A fetch/parse failure logs
and keeps serving the last-good content rather than clearing it.
Release builds were hitting rustc's recursion limit compiling the
hydrate target: impl IntoView doesn't erase a component's concrete
type within the same crate, so calling a component (or a <For>/<Show>
children closure) without wrapping the result in .into_any() lets the
caller's own type keep growing to include everything nested inside it.
AlternativeCard's requirement fields already did this correctly at
their own leaves; QuestionView's <For alternatives> call into
AlternativeCard, and AlternativeCard's own <For features> closure,
plus ResourceValue/AnswerRow's nested <For>s, didn't - so the depth
compounded across all of them. Verified with a real
`cargo leptos build --release` (was previously untested - only dev
builds had been run against this code).
Leptos/Axum app that renders a Question/Alternative/Feature schema
loaded from a sibling content repo (portal-content). Kanidm OIDC login,
content-driven authorization (Question.qualifies), a generic NATS
KV-backed resource + state-transition mechanism (no bespoke "applicant"
concept baked into the runtime - it's all content), a SHA-256 DAG chain
tying submissions and decisions together, and the "YES - Rasterized
Lines" piece (ported from the live uhhm.no site) as the landing hero.