Author SHA1 Message Date
Bendik Aagaard LynghaugandClaude Fable 5.1 531dbbf2de The front door opens modestly, with plain titles
The first card says what redoal is, in two features for the two people
it exists for (the one who draws, the one who leaves a voice) and no
button; the second is a look around (the live count, releases, the
iPhone beta, varde, UHHM); only then the drawing and the notes. Bold
statement titles become what-you-get titles: "Meet a network you
carry in your hand" and "Follow the build as it lands" fold into the
opener and the look-around, "Hear from us when it matters" is "Get a
note when something lands", "Hear from this address" is "Get a note
when a voice lands here", "What just happened" is "What your drawing
became", "Come draw on the wall" is "See it in person at Podium".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
2026-09-23 18:25:04 +02:00
Bendik Aagaard LynghaugandClaude Fable 5.1 0a51852cb2 deploy: carry KANIDM_API_TOKEN into the instance env
Deploy instance / deploy (push) Successful in 2s
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 1s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
2026-09-23 12:27:36 +02:00
Bendik Aagaard LynghaugandClaude Fable 5.1 2d4f46d152 Lint with iris v0.3.39 (iris is versioned as portal from here on)
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
2026-09-23 06:04:41 +02:00
Bendik Aagaard LynghaugandClaude Fable 5.1 32547d782f Lint: run iris from the cargo target dir (the checkout is noexec on this runner)
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
2026-09-22 17:58:27 +02:00
Bendik Aagaard LynghaugandClaude Fable 5.1 5ec32823b4 Lint with iris v0.1.0
Lint and reload / lint (push) Failing after 2s
Lint and reload / reload (push) Skipped
The lint now lives in uhhm/iris, pinned to the portal release it
matches; question_lint leaves the portal tarball.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
2026-09-22 17:42:31 +02:00
Bendik Aagaard LynghaugandClaude Fable 5.1 c674348b38 Pin portal v0.3.36
Deploy instance / deploy (push) Successful in 2s
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
Question nav offers a followup only once it is earned; bounded
outbound fetches; uploads over 2 MB start working.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
2026-09-22 17:02:32 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 b12a601cae iPhone beta joins via the TestFlight public link
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
The 'redoal testers' external group now has a public link
(https://testflight.apple.com/join/eR9dvHkz) — one tap on an iPhone,
no invitation flow. Replace the Apple-ID form (and its action/
record_as plumbing) with the link; no email collection, nothing for
us to process. The n8n named-invite workflow stays shelved in
redoal/redoal ops/ for a future curated group.

Lint: OK (9 questions, 5 aggregates).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByshXuDEzH8hXh5qp4VgGk
2026-09-12 22:16:44 +02:00
Bendik Aagaard LynghaugandClaude Opus 4.8 59fc40e0cb Testers signup in the Releases alternative; redoal_testers bucket
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
Desktop releases run free (the gitea_releases listing, no sign-up); a
'Join the public testers' form on the same alternative collects the
Apple ID for a TestFlight invite, recorded to redoal_testers. New
confirmation page /joined-testers, and an aggregates entry with
attended_by pointing at the n8n TestFlight onboarding automation
(events.redoal_testers.* → App Store Connect).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByshXuDEzH8hXh5qp4VgGk
2026-09-12 16:44:20 +02:00
Bendik Aagaard Lynghaug 45a9526ea2 Pin portal v0.3.35
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
2026-09-12 12:21:16 +02:00
Bendik Aagaard LynghaugandClaude Fable 5.1 a546c2b08f Pin portal v0.3.34
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
Question nav offers followups by reach (on the visitor's verified
path, or further down from the current page), not to anyone carrying
any chain.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
2026-09-11 19:43:44 +02:00
bl af572e91c8 Merge pull request 'Fix voices-echoed card: emit bare object, not wrapped array' (#5) from fix-pulse-jq-nested-array into main
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
Reviewed-on: #5
2026-09-11 12:07:47 +00:00
Bendik Aagaard LynghaugandClaude Opus 4.8 6c3aa46ab0 Fix voices-echoed card: emit bare object, not wrapped array
Lint and reload / lint (pull_request) Successful in 3s
Lint and reload / reload (pull_request) Skipped
The portal's apply_jq collects the jq output stream into an array
itself. The wrapping [ … ] made the filter emit one array-valued
output, which nested to [[{…}]] and failed the array-of-objects card
renderer, dumping raw JSON. Emit a bare object like the releases jq
does, so it collects into [{…}] and renders as a card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByshXuDEzH8hXh5qp4VgGk
2026-09-11 11:47:56 +02:00
bl c8d181b8a5 Merge pull request 'Flaunt 'voices echoed so far' on the index (live /pulse count)' (#4) from voices-echoed-count into main
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
Reviewed-on: #4
2026-09-11 08:21:18 +00:00
Bendik Aagaard LynghaugandClaude Fable 5 bafdb87102 Flaunt 'voices echoed so far' on the index, live from the relay's /pulse
Lint and reload / lint (pull_request) Successful in 2s
Lint and reload / reload (pull_request) Skipped
A 'The count so far' feature in the index's 'Meet a network' alternative
fetches https://relay.redoal.com/pulse (redoal ADR-0023) with a kind:url
resource and shapes { voices_echoed: N } into one card — the number as
the heading, 'voices echoed so far' beneath. Lint-clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 07:36:23 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 2cee770041 Pin portal v0.3.33: head preloads for fonts and wordmark
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 1s
Deploy instance / deploy (push) Successful in 1s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-09-02 11:52:43 +02:00
bl d96a020d0b Update questions/reported.yaml
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
2026-09-01 21:13:59 +00:00
Bendik Aagaard Lynghaug f1855d3f8b Pin portal v0.3.32
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
2026-09-01 22:15:10 +02:00
Bendik Aagaard Lynghaug 86ca5228de Pin portal v0.3.31
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
2026-09-01 21:51:02 +02:00
Bendik Aagaard Lynghaug e68e5c0c37 Pin portal v0.3.30
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
2026-09-01 21:45:06 +02:00
Bendik Aagaard Lynghaug a732d1eb1b Pin portal v0.3.29
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 1s
2026-09-01 21:39:39 +02:00
Bendik Aagaard Lynghaug ec78a76ae9 Pin portal v0.3.28
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
2026-09-01 20:33:30 +02:00
Bendik Aagaard Lynghaug 5db9e42a6b Pin portal v0.3.27
Deploy instance / deploy (push) Successful in 0s
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
2026-09-01 20:29:11 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 10f220b928 wordmark_invert: true (white-stroke mark)
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 1s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-09-01 20:26:44 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 16bd2e41eb Pin portal v0.3.24
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-09-01 20:03:57 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 e58aa9d43c Pin portal v0.3.23
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-09-01 17:26:12 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 7f4058901b Pin portal v0.3.21: markdown images, optional sign-in, hero centering
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 1s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-09-01 17:13:51 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 19eccfd212 Pin portal v0.3.18: convergence animation, overlay results
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-09-01 16:01:03 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 c2f2fdb546 Report a recording: per-voice review link, desk, and lifecycle
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 1s
Every recording card links to /report/<digest>. Filing opens a
redoal_reports record; the Reports desk decides — Remove it acts for
real (the relay deletes the recording everywhere), Keep it closes
with the voice in place. Pins portal v0.3.17 for markdown in cards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-08-31 17:22:48 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 02d3901610 One card: Built in the open at UHHM
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
Merges the facilitator credit and the young-and-honest card.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-08-31 13:51:29 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 bc96601001 Credit UHHM as facilitator; varde link follows its repo home
Lint and reload / lint (push) Successful in 4s
Lint and reload / reload (push) Successful in 0s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-08-31 13:33:40 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 2007e80851 Pin portal v0.3.16
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
Hidden fields lose their stray label row (the Address listing under
Email in the watcher card) and the announce sweeper refreshes open
event records from content.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y42TyF8Zu7NGRR2893vNcZ
2026-08-30 23:16:01 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 dcbeda34b7 Watchers: double opt-in
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 0s
redoal_watchers starts pending; /watching (the confirmation mail's
landing page) confirms to open or drops. Only open watchers are told.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 19:37:10 +02:00
Bendik Aagaard Lynghaug 865c426266 Pin portal v0.3.14 - icon column
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
2026-08-30 16:58:21 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 bee1ba406b Hero: clip the bleed at the header, never overflow the document
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 16:48:33 +02:00
Bendik Aagaard Lynghaug 137415ee92 Pin portal v0.3.13 - voice keeps on submit
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 3s
Lint and reload / reload (push) Successful in 1s
2026-08-30 16:38:17 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 57c7bc7528 Place page: preview-of-the-app copy, review before keeping
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 16:37:44 +02:00
Bendik Aagaard Lynghaug 98c1213d02 Pin portal v0.3.12
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
2026-08-30 16:08:59 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 66c31786ce Places: /shape/<key>, voices, address newsletter
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
The landing gesture leads to /shape/{curve.key} - the place page:
what was left there (the relay's /place, playable), leave your voice
(type: voice at value "{key}"), hear from exactly this address
(redoal_watchers, exact key), and the cards. Email leaves the landing
page. A card for iroh through varde. Desks for voices and watchers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 16:05:26 +02:00
Bendik Aagaard Lynghaug 605eb549f8 Pin portal v0.3.11 - templated action lint fix
Deploy instance / deploy (push) Successful in 0s
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
2026-08-30 16:04:59 +02:00
Bendik Aagaard Lynghaug 8303626e78 Pin portal v0.3.10 - voice, places
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
2026-08-30 16:00:07 +02:00
Bendik Aagaard Lynghaug aae9ea5592 Pin portal v0.3.9 - templated actions, pickable places
Lint and reload / lint (push) Successful in 2s
Deploy instance / deploy (push) Successful in 1s
Lint and reload / reload (push) Successful in 0s
2026-08-30 15:42:12 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 6bac75891f Hero: the field bleeds past its box
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 1s
25% overscan on every side, drawing scaled to the piece so nothing
grows; html overflow-x: clip keeps the bleed from scrolling sideways.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 15:17:26 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 671b20b3c2 Exhibition: 23 October 19:00, «Big Tech må vekk», linked
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 14:47:40 +02:00
Bendik Aagaard Lynghaug 0dff54e2cd Pin portal v0.3.8 - markdown descriptions
Lint and reload / lint (push) Successful in 2s
Deploy instance / deploy (push) Successful in 1s
Lint and reload / reload (push) Successful in 0s
2026-08-30 14:46:59 +02:00
Bendik Aagaard Lynghaug bef058af8f Pin portal v0.3.7 - feature links
Deploy instance / deploy (push) Successful in 1s
Lint and reload / lint (push) Successful in 2s
Lint and reload / reload (push) Successful in 0s
2026-08-30 14:42:01 +02:00
bl b02a3c2a42 Merge pull request 'Landing page: talking points drawn from the zine' (#1) from zine-talking-points into main
Lint and reload / reload (push) Successful in 0s
Lint and reload / lint (push) Successful in 3s
Reviewed-on: #1
2026-08-30 12:35:23 +00:00
16 changed files with 494 additions and 151 deletions
+5 -1
View File
@@ -18,7 +18,7 @@ on:
- .gitea/workflows/deploy.yml
env:
PORTAL_RELEASE: v0.3.6
PORTAL_RELEASE: v0.3.36
INSTANCE: redoal-portal
jobs:
@@ -57,6 +57,10 @@ jobs:
KANIDM_URL=${{ vars.KANIDM_URL }}
OAUTH2_CLIENT_ID=${{ vars.OAUTH2_CLIENT_ID }}
OAUTH2_CLIENT_SECRET=${{ secrets.OAUTH2_CLIENT_SECRET }}
# Onboarding from a desk (portal 0.3.40+, `invite:` alternatives): a
# Kanidm service-account token in idm_people_on_boarding. Empty
# means invites are not configured on this instance.
KANIDM_API_TOKEN=${{ secrets.KANIDM_API_TOKEN }}
PUBLIC_URL=${{ vars.PUBLIC_URL }}
COOKIE_SECURE=true
# This repo is its own instance's content source; branding
+11 -6
View File
@@ -15,13 +15,18 @@ jobs:
steps:
- uses: actions/checkout@v4
# Same bare-metal runner/host as this repo's own Deploy workflow,
# which ships question_lint alongside the portal binary in every
# pinned release - so content is validated by the exact portal
# version this instance runs, not a hand-maintained yq/jq subset
# of the same rules.
# iris (uhhm/iris) is the lint: it reads content with the portal
# release it is pinned to, so IRIS_RELEASE should match the portal
# this site runs (PORTAL_RELEASE in deploy.yml).
- name: Lint questions
run: /srv/app/redoal-portal/current/question_lint --path questions
env:
IRIS_RELEASE: v0.3.39
run: |
# The checkout is on a noexec mount for this runner; the cargo
# target dir is where it may run what it fetched.
bin="/var/local/cargo-target/iris-$IRIS_RELEASE"
[ -x "$bin" ] || { curl -sfL "${{ github.server_url }}/uhhm/iris/releases/download/$IRIS_RELEASE/iris" -o "$bin" && chmod +x "$bin"; }
"$bin" check --path questions
reload:
runs-on: bare
+40
View File
@@ -14,3 +14,43 @@ aggregates:
unsubscribed: { event: unsubscribed }
transitions:
open: [unsubscribed]
# Double opt-in: a watcher is pending until the address confirms from
# the mail n8n sends (redoal: confirm watching); only open watchers
# are told when something lands at their shape.
- bucket: redoal_watchers
initial: pending
states:
pending: { event: requested }
open: { event: confirmed }
unsubscribed: { event: unsubscribed }
transitions:
pending: [open, unsubscribed]
open: [unsubscribed]
# Reports on recordings. The relay watches this bucket: a record
# moved to "removed" deletes the recording from its store, the varde
# pin, and discovery. "kept" closes the report with the recording in
# place. Decisions are human, never automatic.
- bucket: redoal_reports
initial: open
states:
open: { event: reported }
removed: { event: removed }
kept: { event: kept }
transitions:
open: [removed, kept]
# TestFlight beta signups (index.yaml's "Test it on your iPhone").
# Attended by automation, not a human desk: a new "requested" signup
# is handed to the App Store Connect API to invite the Apple ID to the
# closed beta; "withdrawn" (the /joined-testers self-transition) drops
# them from the invite list.
- bucket: redoal_testers
attended_by: >
n8n TestFlight onboarding workflow — ops/n8n/testflight-onboarding
in redoal/redoal. Consumes NATS events.redoal_testers.* and calls
App Store Connect POST /v1/betaTesters.
initial: open
states:
open: { event: requested }
withdrawn: { event: withdrawn }
transitions:
open: [withdrawn]
+12 -3
View File
@@ -18,18 +18,27 @@
//
// prefers-reduced-motion freezes the field fully drawn either way.
import { makePaths, drawPaths } from './paths.js';
import { makePaths, drawPaths, OVERSCAN } from './paths.js';
const STYLE_ID = 'redoal-hero-style';
const CYCLE_MS = 28000;
const SEED = 7;
const COUNT = 47;
const BLEED = `${((OVERSCAN - 1) / 2) * -100}%`;
const STYLE_TEXT = `
/* The field bleeds 25% past the hero piece on every side. Sideways
it is clipped at the header box (clip, not hidden: no scroll
container, and overflow-y stays visible) - an element wider than
the viewport, even unscrollable, makes phones re-size the layout
viewport when it appears, which read as the page jumping as the
input loaded. Vertically it may run into the copy below. */
.hero-module { overflow-x: clip; overflow-y: visible; }
.redoal-paths {
position: absolute;
inset: 0;
overflow: hidden;
inset: ${BLEED};
overflow: visible;
pointer-events: none;
--redoal-t: 0;
--redoal-seed: ${SEED};
+6 -1
View File
@@ -8,6 +8,11 @@
// engine likes, and unseeded randomness would reshuffle the picture on
// every repaint.
// The field bleeds past its box: the painted element is this much
// larger than the hero piece on each axis (25% each side), and the
// drawing scales to the piece, not the element, so nothing grows.
export const OVERSCAN = 1.5;
export function mulberry32(seed) {
let a = seed >>> 0;
return () => {
@@ -118,7 +123,7 @@ const smooth = (u) => u * u * (3 - 2 * u);
export function drawPaths(ctx, width, height, paths, t, colors) {
ctx.clearRect(0, 0, width, height);
const s = Math.min(width, height) * 0.9;
const s = (Math.min(width, height) / OVERSCAN) * 0.9;
ctx.save();
ctx.translate(width / 2, height / 2);
ctx.lineWidth = 1;
+22 -14
View File
@@ -1,29 +1,37 @@
# The group exhibition at Podium, connected to the conference: opens
# 21 October 2026 and runs for a month. Announced in every header
# while it runs (portal's event: window); afterwards a followup, and
# a "post what happened" task lands on /review.
# The group exhibition at Podium, part of Attac's «Big Tech må vekk»
# 2026: opens 23 October 2026 at 19:00 and runs for a month. Announced
# in every header while it runs (portal's event: window); afterwards a
# followup, and a "post what happened" task lands on /review.
name: Where can you see it in person?
description: >
redoal is part of the group exhibition at Podium, opening 21 October
and running for a month alongside the conference. Draw on the
screens, leave a voice at a shape, hear who came before you.
redoal is part of the group exhibition at Podium, opening 23 October
and running for a month — the art strand of the conference
«Big Tech må vekk». Draw on the screens, leave a voice at a shape,
hear who came before you.
event:
starts: 2026-10-21T18:00:00+02:00
starts: 2026-10-23T19:00:00+02:00
duration: 31d
place: Podium, Oslo
place: Podium, Hausmanns gate 34, Oslo
alternatives:
- name: Come draw on the wall
- name: See it in person at Podium
description: >
The installation runs the same code as this site, on screens
you can stand in front of together — the network at the scale
of a room.
features:
- name: Opening 21 October
- name: Opening 23 October, 19:00
description: >
The exhibition opens on the 21st and stays up for a month,
within the conference programme. Come to the opening, or
any day after.
At [Podium](https://podium.enterprises/), Hausmanns gate 34,
and up for a month. Come to the opening, or any Saturday
or Sunday after.
icon: lucide:calendar-days
- name: Part of «Big Tech må vekk»
description: >
Attac's conference on digital counterculture returns to
Hausmania — the same building — on 31 October; the
exhibition is its art strand. Programme and details on
[attac.no](https://attac.no/arrangement/big-tech-ma-vekk-2026/).
icon: lucide:megaphone
- name: Draw, then listen
description: >
Draw a shape on the touch screen and arrive at a place.
+108 -94
View File
@@ -1,24 +1,123 @@
id: /
name: What shapes us?
name: What is redoal, and what can you do here?
description: >
redoal is a gesture-addressed network. Draw a line and it becomes
an address — the drawing itself, ready to use the moment your hand
lifts. Similar drawings land near each other, and everything else
follows from that. Draw one below and see who's near.
follows from that. What it is comes first; the drawing is further
down the page.
responsible:
name: Bendik Aagaard Lynghaug
contact: bl@uhhm.no
alternatives:
# The opener: what redoal is, in two features and no button. The
# two people it exists for are the one who draws and the one who
# leaves a voice; both keep what they made.
- name: What redoal is
description: >
The one trick, and what it enables — the parts that run today,
said plainly.
features:
- name: A drawing is an address, and yours to use
description: >
The computer measures how your line curves, turns, and keeps
rhythm, and boils it down to a twenty-byte fingerprint.
Similar drawings get similar fingerprints — two spirals land
like two houses on the same street, and that closeness is
the whole network. Your machine asks the machines around it,
"has anyone been near this shape?", and your spiral finds my
spiral directly, peer to peer.
icon: lucide:map-pin
- name: A voice stays where you left it, sealed by your own hand
description: >
A drawing is a place, and places hold voices. Listen to the
recordings people left at a gesture, leave your own on top,
and when someone stands at the same drawing right now, just
talk. Every recording carries a signature from the leaver's
own machine: keep the key and you're recognized, delete it
and you were never here. A child, an elder, or a newcomer
still learning the local script arrives on the same terms.
icon: lucide:mic
# The look around: nothing to fill in.
- name: Have a look around first
description: >
The network as it runs right now, and where it is going.
features:
- name: The count so far
description: >
Live from the front-door relay — the running tally of what's
been left at places.
icon: lucide:activity
resource:
source:
kind: url
url: https://relay.redoal.com/pulse
public: true
# /pulse (redoal ADR-0023) returns { voices_echoed: N }; shape
# it into one card — the number as the heading, the phrase
# beneath. Emit a bare object, not a wrapped [ … ]: the portal
# collects the jq output stream into an array itself, so a
# wrapping [ ] nests it ([[{…}]]) and drops to a raw JSON dump.
jq: >
{ "name": (.voices_echoed | tostring),
"description": "voices echoed so far" }
empty: The first voice hasn't echoed yet.
- name: Releases
description: Run one of the desktop applications, free, no sign-up.
icon: lucide:package
resource:
source:
kind: gitea_releases
owner: redoal
repo: redoal
public: true
empty: Nothing released yet — the first cut lands here.
# url pinned to null while the repo is private - a private
# release's html_url 404s for anonymous visitors, and the
# card renders an unlinked heading instead (same lesson as
# uhhm/questions 9b7162f).
jq: >
.[] | {name: .name, description: .body, tag: .tag_name,
published: .published_at, url: null}
- name: The iPhone app, in beta
description: >
The iPhone build rides TestFlight (Apple's beta program) —
[join the redoal testers](https://testflight.apple.com/join/eR9dvHkz)
with one tap on your iPhone; no invitation, no email, no
waiting on us.
icon: lucide:smartphone
- name: Carried by iroh, through varde
description: >
What is left at a place is kept and mirrored by
[varde](https://project.uhhm.no/uhhm/varde), a small
daemon that moves bytes between consenting machines over
[iroh](https://iroh.computer) — verified, rate-limited, and
legible to the network it runs on. Recordings travel that way,
with no company carrying them.
icon: lucide:waypoints
- name: Built in the open at UHHM
description: >
[UHHM](https://uhhm.no) builds and carries the gesture math,
the relay, and the portal this page runs on — and hosts the
network's first node. It is a prototype and an artwork before
it is a product: what you can touch is real and running, the
transport that reaches across the internet is being built
now, and the rough edges are where the interesting questions
live. A client, a relay, an instrument — if you want to put
the network on a surface it isn't on yet, say so through the
path below.
icon: lucide:anvil
# The offers.
- name: Draw a path and see who's near
description: >
By associating similar movements while facilitating discourse we
discover signal within chaos. Draw a path — the network answers
with the shapes it heard nearby.
action: /shape
# Announced, not yet open: the network answers with echoes today,
# what follows a shape is still being built.
disabled: true
consequence: [Find what follows]
with the shape it heard, and with the places kept nearby. Pick
one of those and it becomes your address.
action: /shape/{curve.key}
consequence: [Go to the place]
record_as: redoal_gestures
encouragements:
- What you don't know — your collaborators will.
@@ -36,93 +135,8 @@ alternatives:
type: gesture
relay: wss://relay.redoal.com
optional: true
- name: email
label: Email, if the echoes should reach you later
type: email
optional: true
- name: Meet a network you carry in your hand
description: >
The one trick, and what it enables — the parts that run today,
said plainly.
features:
- name: A drawing is an address
description: >
The computer measures how your line curves, turns, and keeps
rhythm, and boils it down to a twenty-byte fingerprint.
Similar drawings get similar fingerprints — two spirals land
like two houses on the same street, and that closeness is
the whole network.
icon: lucide:map-pin
- name: Your machine asks the room
description: >
To find someone, your machine asks the machines around it:
"has anyone been near this shape?" Your spiral finds my
spiral directly, peer to peer. The whole directory is the
shapes people know, passed along on a napkin.
icon: lucide:users
- name: Voices stay where you left them
description: >
A drawing is a place, and places hold voices. Listen to the
recordings people left at a gesture, leave your own on top,
and when someone stands at the same drawing right now, just
talk — live, machine to machine.
icon: lucide:mic
- name: Sealed by your own hand
description: >
Every recording and every stored stroke carries a signature
from the leaver's own machine — proof that it arrived
intact and that the same hand made the same things. The key
lives only with you: keep it and you're recognized, delete
it and you were never here.
icon: lucide:stamp
- name: Draw and speak, and you're in
description: >
The primitives are the drawn line and the human voice. A
child, an elder, or a newcomer still learning the local
script arrives on the same terms as everyone else — that is
the whole entry requirement.
icon: lucide:hand
- name: Young, and honest about it
description: >
A prototype and an artwork before it is a product. The
gesture math, the fingerprints, the voice recordings, and the
machine-to-machine fetching are real and running; the
transport that reaches across the internet is being built
now. The edges are rough, and the edges are where the
interesting questions live.
icon: lucide:sprout
- name: Follow the build as it lands
description: >
The network as it is being built — and we need collaborators on
every new surface it grows.
features:
- name: Communicate
description: >
A client, a relay, an instrument — if you want to put the
network on a surface it isn't on yet, say so through the
path above and we'll find each other.
icon: lucide:message-circle
- name: Releases
description: Start exploring by running one of our applications.
icon: lucide:package
resource:
source:
kind: gitea_releases
owner: redoal
repo: redoal
public: true
empty: Nothing released yet — the first cut lands here.
# url pinned to null while the repo is private - a private
# release's html_url 404s for anonymous visitors, and the
# card renders an unlinked heading instead (same lesson as
# uhhm/questions 9b7162f).
jq: >
.[] | {name: .name, description: .body, tag: .tag_name,
published: .published_at, url: null}
- name: Hear from us when it matters
- name: Get a note when something lands
description: >
Notes from the build — sent only when there is something worth
saying.
+30
View File
@@ -0,0 +1,30 @@
id: /joined-testers
route: ^/joined-testers
followup: true
name: What happens next?
description: >
You asked for a place in the closed beta — here's the shape of it.
alternatives:
- name: Your invite is on its way
description: ""
features:
- name: Check your mail
description: >
A TestFlight invite goes to the Apple ID you left. Tap it on
your iPhone, install TestFlight if you don't have it, and
redoal lands on your home screen.
icon: lucide:mail
- name: It's rough on purpose
description: >
A beta, and an artwork before a product — the networked edges
are where the interesting questions live. Tell us what breaks.
icon: lucide:hammer
- name: Withdraw from the beta
description: >
Changed your mind? Confirm below and we'll drop your Apple ID from
the invite list.
self_transition:
bucket: redoal_testers
to: withdrawn
label: Remove me from the beta
+47
View File
@@ -0,0 +1,47 @@
# One recording, held up for human review: /report/<digest>. Reached
# from the place page's per-recording link. Filing creates an open
# record in redoal_reports; the desk decides, and a removal decision
# deletes the recording from the relay, varde, and discovery.
name: Should this stay?
description: >
Every voice here was left by a person, and a person looks at every
report. Tell us what you heard and why it needs eyes on it — the
recording stays reachable until someone has listened and decided.
alternatives:
- name: Ask for a review
description: >
Goes straight to the review desk. If the recording breaks
something — a person's privacy, the law, basic decency — it is
removed everywhere this network keeps it.
action: /reported
consequence: [Send it to review]
record_as: redoal_reports
encouragements:
- A person will listen.
features:
- name: What you heard
description: >
A sentence or two is plenty. Leave an email if you want to
hear what was decided.
icon: lucide:flag
requirements:
- name: digest
label: Recording
type: hidden
value: "{digest}"
- name: reason
label: Why
type: textarea
placeholder: What is wrong with this recording?
- name: email
label: Email
type: email
optional: true
- name: Go back to listening
description: >
The place you came from holds the rest of its voices.
features:
- name: Nothing sent
description: >
Leaving this page files nothing — only the button does.
icon: lucide:undo-2
+11
View File
@@ -0,0 +1,11 @@
# Landing after filing a report.
followup: true
name: What happens now?
description: >
Your report is on the review desk. A person listens, decides, and
the decision acts everywhere this network keeps the recording.
alternatives:
- name: Draw another
description: >
The paper keeps listening.
action: /
+64
View File
@@ -38,6 +38,70 @@ alternatives:
- to: unsubscribed
label: Unsubscribe
- name: Voices
description: >
Recordings left through the web, one answer per upload: the
address, the digest, the length. The audio itself lives in the
relay's node and in varde.
features:
- name: ""
description: ""
resource:
source:
kind: kv
bucket: redoal_voices
requires_group: owners
- name: Reports
description: >
Recordings a visitor asked a person to listen to. "Remove it"
acts for real: the relay watches this bucket and deletes the
recording from its store, the varde pin, and discovery. "Keep
it" closes the report with the voice in place.
action: /review
consequence: [Decide]
features:
- name: ""
description: ""
resource:
source:
kind: kv
bucket: redoal_reports
requires_group: owners
transitions:
- from: open
to: removed
label: Remove it
- from: open
to: kept
label: Keep it
- name: Watchers
description: >
Who asked to hear from one exact address. Unsubscribing is
theirs (the link in their receipt); the desk can retire an
address that bounces.
action: /review
consequence: [Confirm]
features:
- name: ""
description: ""
resource:
source:
kind: kv
bucket: redoal_watchers
requires_group: owners
transitions:
- from: pending
to: open
label: Confirm by hand
- from: pending
to: unsubscribed
label: Drop
- from: open
to: unsubscribed
label: Unsubscribe
- name: Announced pages
description: >
Pages with an `event:` window, kept here by portal itself. When a
-32
View File
@@ -1,32 +0,0 @@
id: /shape
route: ^/shape
followup: true
name: Where does the path lead?
description: >
Your shape is in the room now. Echoes of it reach whoever draws
nearby — and theirs reach you, whenever you come back to draw again.
alternatives:
- name: What just happened
description: ""
features:
- name: An address, not an upload
description: >
The stroke became a 20-byte gesture key — a compressed
encoding of its shape. Similar shapes land on nearby keys;
that proximity is the whole network.
- name: Nothing to undo
description: >
A drawn path is a fact, not an account. There is no profile
behind it unless you left an email.
- name: What arrives here next
description: >
In the full network a drawing is a place that holds voices —
recordings left by whoever came before, and live talk with
whoever is standing here now. On this site it holds echoes
today; the voices follow.
- name: Draw another
description: >
The network gets more interesting with every shape it hears.
action: /
consequence: [Back to the canvas]
+101
View File
@@ -0,0 +1,101 @@
# The place a drawn (or picked) path leads to: /shape/<gesture key>.
# Reached through the landing alternative's templated action
# (/shape/{curve.key}); the key is the URL. What is kept here comes
# from the relay - the first redoal node - over its /place endpoint,
# and a voice left here goes back the same way (ADR-0015 in redoal).
name: Where does the path lead?
description: >
This is a place: the 20-byte address your path became — or the one
you picked from what the network keeps nearby. Voices left here play
here, for whoever draws their way to this shape. What you see on
this site is a preview of the app: the same network, reached
through a relay instead of your own machine.
alternatives:
- name: Listen to what was left here
description: ""
features:
- name: ""
description: ""
icon: lucide:ear
resource:
source:
kind: url
url: "https://relay.redoal.com/place/{key}"
public: true
empty: Nothing has been left at this address yet — yours could be the first voice here.
jq: >
.recordings[] | {name: ((.duration_ms / 1000 | round | tostring) + " seconds of voice"),
description: ("sealed by " + .author[0:8] + "… · [have it reviewed](/report/" + .digest + ")"),
audio: ("https://relay.redoal.com" + .audio)}
- name: Leave your voice here
description: >
A preview of what the app will give you in full. Record in your
browser, listen back, and press *Keep it here* — nothing leaves
your phone before that. The relay, the network's first node,
keeps it at exactly this address and mirrors it on, sealed with
its own key on your behalf; a key of your own comes with the app.
action: /shape/{voice.key}
consequence: [Keep it here]
record_as: redoal_voices
encouragements:
- It stays where you left it.
features:
- name: ""
description: >
Up to a minute. Record, listen back, record again if you like
— it is sealed as it is once you keep it.
icon: lucide:mic
requirements:
- name: voice
label: Voice
type: voice
relay: wss://relay.redoal.com
value: "{key}"
optional: true
- name: Get a note when a voice lands here
description: >
The fallback for a phone without the app: one note when
something is left at exactly this shape — and nothing about
anywhere else.
action: /subscribed
consequence: [Watch this address]
record_as: redoal_watchers
encouragements:
- Your shape now has a listener.
features:
- name: Where the note goes
description: >
The note is about one exact address, so it needs one exact
place to land. One mail comes first, asking you to confirm —
nothing is sent before you do, and at most one note a day
after.
icon: lucide:mail
requirements:
- name: email
label: Email
type: email
- name: key
label: Address
type: hidden
value: "{key}"
- name: What your drawing became
description: ""
features:
- name: An address, not an upload
description: >
The stroke became a 20-byte gesture key — an ordered chain of
headings, drawn back for you under the stroke. Similar shapes
land on nearby keys; picking a kept place makes its key yours.
- name: Nothing to undo
description: >
A drawn path is a fact, not an account. There is no profile
behind it unless you asked to hear from an address.
- name: Draw another
description: >
The network gets more interesting with every shape it hears.
action: /
consequence: [Back to the canvas]
+9
View File
@@ -21,3 +21,12 @@ alternatives:
bucket: redoal_subscribers
to: unsubscribed
label: Unsubscribe me
- name: Stop watching an address
description: >
Using the link from a note about a shape? Confirm below and that
address goes quiet for you.
self_transition:
bucket: redoal_watchers
to: unsubscribed
label: Stop watching
+27
View File
@@ -0,0 +1,27 @@
# Landing page of the confirmation mail: /watching?chain=<record>&email=<address>.
# A followup - never in the nav; the record id in the link is the only
# way here, and it plus the matching email is what authorizes the
# transition (portal's self_transition).
followup: true
name: Are you watching this address?
description: >
Someone asked to hear from a shape on redoal with your address. Say
yes and you get one note when something is left there — nothing
else, ever. Say it wasn't you and the request is dropped.
alternatives:
- name: Yes, watch it
description: >
One note per day at most, only when a recording or a program
lands at exactly this shape.
self_transition:
bucket: redoal_watchers
to: open
label: Yes, watch it
- name: That wasn't me
description: >
The request is dropped and the address stays unused.
self_transition:
bucket: redoal_watchers
to: unsubscribed
label: Drop it
+1
View File
@@ -4,3 +4,4 @@ wordmark: https://project.uhhm.no/redoal/questions/raw/branch/main/wordmark.svg
hero:
kind: module
module: hero.js # this repo's sine-swings piece, served at /site/hero.js
wordmark_invert: true