Commit Graph
6 Commits
Author SHA1 Message Date
Bendik Aagaard LynghaugandClaude Fable 5 0221253cba Gesture input type, redoal-relay client, gitea_releases, content-driven branding
Four coupled additions that let one portal build serve a second face
(redoal.com) next to uhhm.no:

- type: gesture requirement - gesture.js draws a single stroke on a
  DPR-aware canvas (pointer events, touch-action: none), mirrors
  {points, key} into the paired hidden input prosekit-style, and -
  when content declares relay: wss://... - speaks the redoal-relay
  protocol: announce on stroke end, ghost the ack's decoded key path,
  show echoes of similar strokes as thumbnails. Offline/broken relay
  degrades to a plain drawing input; the widget handle's stop()
  closes the socket on SPA navigation (yes.js lifecycle, not
  prosekit's fire-and-forget). Submit re-parses gesture values so the
  bucket stores a real object, not double-encoded JSON.
- gitea_releases resource source - token-authenticated
  /repos/{owner}/{repo}/releases, for advertising a private repo's
  releases (content pins url: null - private html_urls 404 publicly).
- site.yaml branding - optional, at the content repo root: title,
  wordmark, hero {kind: yes|gesture|plain, relay}. Absent file means
  the historical uhhm look, so uhhm changes nothing without a content
  edit. Hot-swapped with questions/aggregates on content reload;
  question_lint validates it in both --path and --repo modes.
- deploy.yml ships the same build twice: uhhm-portal (3010) as
  before, redoal-portal (3020, CONTENT_REPO=redoal/questions,
  redoal.com vhost). Needs host prep + REDOAL_OAUTH2_* repo
  secrets/vars before the new steps succeed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 13:03:29 +02:00
Bendik Aagaard LynghaugandClaude Sonnet 5 84c3fad339 Make aggregate state graphs content-driven, not compiled Rust
Deploy / deploy (push) Successful in 59s
Six hand-maintained copies of the bucket->aggregate-type table
(4 per-type State enums, content::aggregate_type_for_bucket +
is_valid_transition_target, answers.rs's two dispatch matches,
backfill_events.rs's own migration table) collapse into one:
questions/aggregates.yaml, loaded and hot-swapped in AppState.aggregates
the same way AppState.questions already is. aggregates/mod.rs's
replay/create/transition now run off a runtime-loaded AggregateSchema
instead of a compile-time AggregateKind trait impl per type - the
CAS/JetStream mechanics underneath are unchanged. A new aggregate type
is now a content-only change, no portal deploy required.

backfill_events.rs is deleted outright rather than ported - its
migration job was already done and production data here is minimal
and expendable (the EVENTS stream gets purged by hand post-deploy).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 21:43:32 +02:00
Bendik Aagaard Lynghaug 3242482c8e Redirect back to the originating page after sign-in, not always /
Deploy / deploy (push) Successful in 29s
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 "/".
2026-08-05 14:40:44 +02:00
Bendik Aagaard Lynghaug 44c8c9d410 Add prosekit rich-text field, Gitea repo embeds, automation KV read endpoint; fix apex/www session-cookie mismatch on /auth/callback
Deploy / deploy (push) Successful in 29s
- 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.
2026-08-05 13:45:56 +02:00
Bendik Aagaard Lynghaug 18025bf870 Hot-reload content on a NATS trigger instead of requiring a restart
Deploy / deploy (push) Successful in 29s
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.
2026-08-05 07:20:40 +02:00
Bendik Aagaard Lynghaug aa1a7fa572 Initial commit: content-driven onboarding portal
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.
2026-07-29 19:38:40 +02:00