Commits
Fill in three remaining routes from the old atmoquest app that were
missing after the feature-folded migration:
- features/about/{routes.go, pages/about.templ}: short "What's
ATProto?" explainer page linked from the guest landing.
- features/demo/routes.go: Datastar SSE clock endpoint that streams
the current UTC time into the statusbar #clock once a second.
- router/router.go: register the two new feature subrouters and add
the /healthz probe endpoint.
- features/index/pages/index.templ: restore the live clock by binding
#clock to data-on-load="@get('/demo/clock')" on both the authed
and guest statusbars (was a static --:--:-- placeholder).
Build clean, all tests pass, smoke-tested on :9090:
/healthz 200
/about-atproto 200 (2369 bytes)
/demo/clock SSE patch frames streaming
/ 200 (guest landing unchanged)
Port the home-page authed branch from atmoquest.
- features/index/pages/index.templ: introduce IndexView /
IndexEventCard, split into indexAuthed + indexGuest + indexEventCard
templ funcs; IndexPage(v) now branches on v.LoggedIn.
- features/index/handlers.go: soft-check session cookie via
h.Auth.Sessions.Get (no OAuth resume — home is public). When logged in,
enrich with bsky display name + avatar, /profile/qr.svg, and the user's
current ongoing event via checkin.Current + event.Get. Resolve handle via
identity.DefaultDirectory with shortDIDLabel fallback. Each step soft-fails
to slog.Debug so a Bluesky outage / empty event cache never 500s home.
- features/index/routes.go + router/router.go: thread *auth.Handlers into
index.SetupRoutes.
Build clean, all tests pass, GET / (anon) renders the guest landing
unchanged (verified via curl localhost:9090).
- port internal/admincrypto (ed25519 Signer + Sign/Verify, key resolution
via ADMIN_SIGNING_KEY env or data/admin_signing.key file)
- port internal/badge (Design + canonical signing payload + RenderSVG with
shield/circle/star/hexagon shapes)
- features/admin/handlers.go: AdminDashboard, AdminUsers, AdminUsersPromote,
AdminEvents, AdminEventNew, AdminEventCreate, AdminEventBadge,
AdminEventBadgeSave; RequireAdmin middleware
- features/admin/pages/{dashboard,users,events}.templ split from the old
single admin_events.templ — same window-chrome + nav shell
- main.go loads the signer (allowGenerate=true only in dev) and threads it
through router.SetupRoutes -> admin.SetupRoutes
routes (all behind RequireAdmin → 303 /signin?next=… for anon):
- GET /admin dashboard
- GET /admin/users (?q= ?admins= ?page=) user list
- POST /admin/users/{did}/promote toggle admin
- GET /admin/events event list
- GET /admin/events/new new-event form
- POST /admin/events create + redirect to badge
- GET /admin/events/{token}/badge badge designer
- POST /admin/events/{token}/badge save signed badge design
verified: all 9 unauthed routes 303 to /signin with correct next=path.
build clean. all tests pass.
- port internal/event (Record/CreateInput/Cache/Get/Put/LookupByQRToken/IsOngoing)
- port internal/checkin (Put + Current)
- features/event/{handlers,routes}.go: EventScan, EventQR, EventScanCheckin, EventFlushLocal
- features/event/pages/event.templ: EventScanView landing page
- wire TargetCurrentEvent + also-checkin into features/connect:
* Connect handler queries checkin.Current + event.Get to populate event chip
* ConnectConfirm re-validates claimed event_uri server-side then writes checkin
* connection record carries event_uri when also-checkin is honored
- router: register event.SetupRoutes after connect
routes (all unauthed responses match step 4 pattern):
- GET /e/{token} -> 404 unknown / 200 landing
- GET /e/{token}/qr.svg -> 404 unknown / SVG
- POST /e/{token}/checkin -> 302 /signin (unauthed) / 303 /?checkedin=1
- POST /event/flush-local -> 302 /signin (unauthed) / 200 JSON
admin event CRUD intentionally deferred to step 6.
tests: internal/event + internal/checkin pass.
Fill in three remaining routes from the old atmoquest app that were
missing after the feature-folded migration:
- features/about/{routes.go, pages/about.templ}: short "What's
ATProto?" explainer page linked from the guest landing.
- features/demo/routes.go: Datastar SSE clock endpoint that streams
the current UTC time into the statusbar #clock once a second.
- router/router.go: register the two new feature subrouters and add
the /healthz probe endpoint.
- features/index/pages/index.templ: restore the live clock by binding
#clock to data-on-load="@get('/demo/clock')" on both the authed
and guest statusbars (was a static --:--:-- placeholder).
Build clean, all tests pass, smoke-tested on :9090:
/healthz 200
/about-atproto 200 (2369 bytes)
/demo/clock SSE patch frames streaming
/ 200 (guest landing unchanged)
Port the home-page authed branch from atmoquest.
- features/index/pages/index.templ: introduce IndexView /
IndexEventCard, split into indexAuthed + indexGuest + indexEventCard
templ funcs; IndexPage(v) now branches on v.LoggedIn.
- features/index/handlers.go: soft-check session cookie via
h.Auth.Sessions.Get (no OAuth resume — home is public). When logged in,
enrich with bsky display name + avatar, /profile/qr.svg, and the user's
current ongoing event via checkin.Current + event.Get. Resolve handle via
identity.DefaultDirectory with shortDIDLabel fallback. Each step soft-fails
to slog.Debug so a Bluesky outage / empty event cache never 500s home.
- features/index/routes.go + router/router.go: thread *auth.Handlers into
index.SetupRoutes.
Build clean, all tests pass, GET / (anon) renders the guest landing
unchanged (verified via curl localhost:9090).
- port internal/admincrypto (ed25519 Signer + Sign/Verify, key resolution
via ADMIN_SIGNING_KEY env or data/admin_signing.key file)
- port internal/badge (Design + canonical signing payload + RenderSVG with
shield/circle/star/hexagon shapes)
- features/admin/handlers.go: AdminDashboard, AdminUsers, AdminUsersPromote,
AdminEvents, AdminEventNew, AdminEventCreate, AdminEventBadge,
AdminEventBadgeSave; RequireAdmin middleware
- features/admin/pages/{dashboard,users,events}.templ split from the old
single admin_events.templ — same window-chrome + nav shell
- main.go loads the signer (allowGenerate=true only in dev) and threads it
through router.SetupRoutes -> admin.SetupRoutes
routes (all behind RequireAdmin → 303 /signin?next=… for anon):
- GET /admin dashboard
- GET /admin/users (?q= ?admins= ?page=) user list
- POST /admin/users/{did}/promote toggle admin
- GET /admin/events event list
- GET /admin/events/new new-event form
- POST /admin/events create + redirect to badge
- GET /admin/events/{token}/badge badge designer
- POST /admin/events/{token}/badge save signed badge design
verified: all 9 unauthed routes 303 to /signin with correct next=path.
build clean. all tests pass.
- port internal/event (Record/CreateInput/Cache/Get/Put/LookupByQRToken/IsOngoing)
- port internal/checkin (Put + Current)
- features/event/{handlers,routes}.go: EventScan, EventQR, EventScanCheckin, EventFlushLocal
- features/event/pages/event.templ: EventScanView landing page
- wire TargetCurrentEvent + also-checkin into features/connect:
* Connect handler queries checkin.Current + event.Get to populate event chip
* ConnectConfirm re-validates claimed event_uri server-side then writes checkin
* connection record carries event_uri when also-checkin is honored
- router: register event.SetupRoutes after connect
routes (all unauthed responses match step 4 pattern):
- GET /e/{token} -> 404 unknown / 200 landing
- GET /e/{token}/qr.svg -> 404 unknown / SVG
- POST /e/{token}/checkin -> 302 /signin (unauthed) / 303 /?checkedin=1
- POST /event/flush-local -> 302 /signin (unauthed) / 200 JSON
admin event CRUD intentionally deferred to step 6.
tests: internal/event + internal/checkin pass.