Commit Graph

1803 Commits

Author SHA1 Message Date
Jeff Emmett 7ada95b46a fix(infra): sablier-support must be no-op until traefik wiring is ready
Copying the sablier-encryptid.yml labels would immediately apply
traefik.enable=false to the live encryptid container, breaking auth
before Sablier has a middleware route in place. Reduced to empty
services:{} with a comment explaining what's needed to activate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 18:42:44 -04:00
Jeff Emmett 4f2aebe46e chore(infra): add sablier-support compose override referenced in .env
.env on Netcup sets COMPOSE_FILE=docker-compose.yml:docker-compose.sablier-support.yml
but the file was never created, causing docker compose commands to fail
without explicit -f flag. Adds the missing override with Sablier labels for
encryptid services (matching the existing sablier-encryptid.yml pattern).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 18:42:09 -04:00
Jeff Emmett 7b6d1d60f9 chore(rschedule): bump JS cache version to v=2 after Phase B-H build
Phase B's full booking UI + admin dashboard + cancel page bundles are
now built. Bumping cache version forces Cloudflare + browser to fetch
the new code instead of the Phase A stub.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 18:31:33 -04:00
Jeff Emmett 69ce497aa8 feat(rschedule): complete native port of schedule-jeffemmett
Phases D-H of the rSchedule booking module:

- **Google Calendar sync** (`lib/gcal-sync.ts`): reuses rspace OAuth, syncs busy
  times into config doc, creates booking events, deletes on cancel.
- **Admin UI** (`components/folk-schedule-admin.ts`): 6-tab passkey-gated dashboard
  (overview, availability rules + overrides, bookings, invitations, gcal, settings).
  Timezone-shift banner when browser tz diverges from host tz.
- **Emails** (`lib/emails.ts`, `lib/calendar-links.ts`): confirmation with
  Google/Outlook/Yahoo add-to-calendar buttons + .ics attachment; cancellation
  with 3 suggested slots from availability engine; 24h reminder.
- **Cron** (`lib/cron.ts`): in-process 5-min reminder sweep + 10-min gcal sweep
  per connected space, started from onInit.
- **Invitations + timezone shift** (mod.ts, admin UI): PATCH
  /api/invitations/:id accept/decline; POST /api/admin/timezone/shift with
  optional booking relabel; invitations tab shows cross-space invites.

Full public booking flow (`components/folk-schedule-booking.ts`): month calendar,
date → slot drill, booking form, confirmation view, timezone picker.

EncryptID passkey gates admin routes via resolveCallerRole ≥ moderator.
Per-entity model: each space (and user-space) hosts its own bookable page;
bookings mirror into invitee spaces' :invitations docs so cross-space visibility
works without cross-space reads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 18:19:10 -04:00
Jeff Emmett 700e372260 fix(rschedule): add missing lib/availability + component updates
lib/availability.ts (getAvailableSlots) was imported by mod.ts but never
committed — caused module load to fail on server, leaving renderShell
callers with undefined 'modules' and producing 500s on every rschedule
route. Plus small updates to booking/admin/cancel components and mod.ts
wiring.
2026-04-16 17:35:05 -04:00
Jeff Emmett f45cb753e1 feat(rschedule,rtasks): wire Calendly-style rSchedule + rTasks canvas port
rSchedule (Calendly-style public booking module):
- modules/rschedule/mod.ts — routes, docSchemas (config/bookings/invitations),
  standaloneDomain rschedule.online, landing, feeds, outputPaths
- components/folk-schedule-booking.ts — public booking page
- components/folk-schedule-admin.ts — admin availability/bookings/gcal
- components/folk-schedule-cancel.ts — guest self-cancel page
- booking.css, landing.ts (marketing)
- Registered via registerModule(scheduleModule) in server/index.ts
- Component bundles declared in vite.config.ts

rTasks canvas port (uses new shared folk-app-canvas/folk-widget):
- components/folk-tasks-canvas.ts, folk-tasks-activity.ts, folk-tasks-backlog.ts
- modules/rtasks/mod.ts now renders <folk-app-canvas app-id=rtasks> shell

Plus minor rnetwork delegation-manager / power-indices tweaks.
2026-04-16 17:29:42 -04:00
Jeff Emmett 8042e86815 wip(rschedule): schemas + folk-app-canvas/folk-widget shared components
Schema for the new Calendly-style rSchedule port plus two new shared
canvas components (folk-app-canvas, folk-widget) that replace per-rApp
tab-based nav with a unified widget-on-canvas surface.
2026-04-16 17:26:45 -04:00
Jeff Emmett 3a614e2866 rename: finish rschedule → rminders migration
Complete the rename started in dda7760 (which removed rschedule/ but
left callers unmigrated and the rminders/ dir uncommitted). Updates
vite.config.ts build entries, API base fetches in folk-comment-pin,
folk-rapp widget map, module-display meta, calendar reminder-drop
route, docs comment-panel, e2e fixtures, shell/landing/mcp-server
references, and backlog/ONTOLOGY docs.

Fixes vite build failure: "Could not resolve entry module
modules/rschedule/components/folk-schedule-app.ts".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:18:17 -04:00
Jeff Emmett 312ea4b535 fix(build): commit rminders module dir (orphaned during rschedule rename)
Files existed on disk and were referenced by vite.config.ts entry
config, but were never git-added when the rschedule→rminders rename
happened in dda7760. Build on a fresh clone failed with "Could not
resolve entry module modules/rschedule/components/folk-schedule-app.ts"
because vite was picking up the stale pre-rename config on disk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:16:15 -04:00
Jeff Emmett df7d0b021f mobile: touch + pen compatibility pass across rApps
Global shell.css: @media (pointer:coarse) baseline — inputs ≥16px
(no iOS zoom), buttons/role-buttons ≥44×44, touch-action: manipulation,
transparent tap highlight. New utility classes: .rapp-hscroll and
.rapp-drawer for bottom-sheet patterns.

folk-drawfast + folk-makereal: branch on pointerType, honor real pen
pressure (constant 0.5 for mouse/touch), single-pointer capture, palm
rejection (touch ignored while pen is down), pointercancel cleanup.

rNotes: mobile drill-down layout below 768px — three panels collapse
to one full-width view per selection stage (vaults → files → preview)
with back buttons. 16px fonts, 44px touch targets.

rSheets: sticky row/col headers, min-width max-content table, visible
scroll thumb, 72px min cell width on coarse pointers.

rMaps: bottom-sheet handle touchstart/touchend → unified PointerEvents
so pen users get drag-to-expand. Pointer capture + horizontal-swipe
reject.

rPhotos lightbox: pinch-zoom (2-pointer), pan when zoomed, horizontal
swipe between photos, double-tap to toggle zoom, prev/next buttons on
desktop (swipe-only on mobile).

Bump cache versions: folk-notes-app v=10→11, folk-map-viewer v=7→8,
folk-photo-gallery v=3→4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:13:35 -04:00
Jeff Emmett eee89f9f32 fix(rpast): always https for non-loopback hosts
Traefik → container hop in the rSpace stack sets x-forwarded-proto: http
even when the real client request is https. That was leaking into the
"Open in <rApp>" links emitted in chronicle.mw. Simplify: any non-local
host gets https, period.
2026-04-16 17:06:25 -04:00
Jeff Emmett dda7760433 infra(traefik): scope rate limit per CF-Connecting-IP, raise to 600/150
Previous limits (avg 120/min, burst 30) had no sourceCriterion. Traefik
default groups by request Host, so ALL users of rspace.online shared a
single 120/min bucket — tripped almost immediately under normal load
(repeated 429s in rpast, api/mi/models, etc).

Scope per Cloudflare client IP (CF-Connecting-IP header) and raise to
600/min average with 150 burst — interactive use can spike above 120/min
from one client easily (module loads + polling + autosave).
2026-04-16 17:04:15 -04:00
Jeff Emmett 68648608a9 test(rsocials): Playwright smoke suite + planner reliability fixes
Adds e2e/tests/rsocials-campaign-flow.spec.ts — 13 tests covering the
unified campaign flow UX: dashboard → planner navigation, brief canvas
node (+ preview banner), markdown import modal, wizard handoff, and
API shape. 36 passed / 3 AI-skipped across chromium/firefox/mobile.

Bug fixes uncovered by the suite:
- markDownstreamStale only redraws when a node actually flips stale,
  so typing in an input node no longer destroys the open inline-edit
  overlay.
- executeSave wraps the local-first write in try/catch and nulls the
  client on failure, so a half-initialised client (WS down, IDB
  unavailable) falls through to localStorage instead of throwing
  "Document not open".
- init-failure path also nulls the client so the first save after a
  failed subscribe doesn't hit a doc that was never opened.

Test infra:
- server/security.ts + server/index.ts honour DISABLE_RATE_LIMIT=1
  (and NODE_ENV=test) to bypass HTTP rate limiter and anon WS-per-IP
  cap so the suite can run under 8 parallel workers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 16:59:59 -04:00
Jeff Emmett 317e7a5579 fix(deploy): track registry image + default rpast links to https
- docker-compose.yml: rspace service now uses
  localhost:3000/jeffemmett/rspace-online:${IMAGE_TAG:-latest} so
  CI's `docker pull ... && docker compose up -d --no-build` actually
  picks up the new image without a manual retag step.
- modules/rpast/mod.ts: base URL defaults to https for any non-loopback
  host, honoring x-forwarded-proto only when set to http|https. Fixes
  "Open in <rApp>" links previously emitted as http:// on CF/Traefik.
2026-04-16 16:58:01 -04:00
Jeff Emmett 595188b9d8 chore(sw): bump cache version to v10 to purge stale rpast 404 shells
Users on an older SW built before rpast existed saw a cached
/rpast shell with an outdated module list (rmail/rfunds/rwork/...).
Bumping the cache version forces activation-time cleanup to drop
the old entries on next visit.
2026-04-16 16:36:54 -04:00
Jeff Emmett 98d6d22e55 ui(space-settings): Invite label + breathing room under username search
- Rename 'Add' to 'Invite' in the By-Username add-member row to match
  the By-Email path and reflect that it goes through an invite flow.
- Add 10px top margin to .add-member-row so the role dropdown and
  button don't visually collide with the search input/selected-user
  area above.
2026-04-16 16:36:52 -04:00
Jeff Emmett 351565c934 fix(space-settings): refresh invitations tab after sending invite
Email-invite path previously only showed 'Invite sent' without
refreshing the list. Username-invite path reloaded members but not
invitations. Both now call #loadInvitations after a successful POST.
2026-04-16 16:34:05 -04:00
Jeff Emmett f388812927 fix(spaces): show username instead of truncated DID in members list
GET /api/spaces/:slug/members now enriches entries missing displayName
by looking up the username from EncryptID (/api/internal/user-email).
Old member records (particularly owners created before displayName was
passed to setMember) were showing as 'jAV6y4tg8UbKJEkN0npv...' in the
space settings Members tab.
2026-04-16 16:29:32 -04:00
Jeff Emmett 14ffee4101 fix(rpast): serve interactive viewer at space root
Adds `GET /` to rpast routes. Without it, /rpast on a space subdomain
404'd because the sub-app had no root handler. Now matches the rNotes
pattern: bare domain → marketing landing, in-space URL → app shell with
<rpast-viewer>.
2026-04-16 16:21:19 -04:00
Jeff Emmett d950e1a656 chore(backlog): add DAO power-indices follow-up tasks (144-150)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 16:13:29 -04:00
Jeff Emmett 5e5fcd7681 feat(rsocials): campaign planner polish
Expanded folk-campaign-planner with richer live state, styled dashboard
refresh, and schema + mod updates to match the new planner flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 16:13:20 -04:00
Jeff Emmett 3a69234819 feat(rpast): chronicle-of-self timeline + markwhen projection
- New rpast module renders a cross-rApp personal timeline
- shared/markwhen/ projection layer hydrates from syncServer docs
- rCal gets a Timeline applet wiring into the same markwhen view
- rstack-markwhen-view component for embedding elsewhere
- Smoke-test fixtures under output/ and scripts/smoke-rpast.ts
- Adds @markwhen/{parser,timeline,calendar,mw} deps

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 16:13:11 -04:00
Jeff Emmett 3d11acd48b chore(sw): bump cache version to v9 to purge stale HTML fragments
Stale cached HTML fragments referenced old asset hashes (e.g. 404 on
canvas-PlYnCtxh.js while server has canvas-R4rXE5Sc.js) after frequent
rebuilds. Bumping CACHE_VERSION and the SW registration query string
forces the new SW to install and purge all old versioned caches on
activate.
2026-04-16 16:11:29 -04:00
Jeff Emmett e3f322e7ff fix(rmeets): use subdomain-aware URLs everywhere
All internal rMeets links were hardcoded to /{space}/rmeets, so clicking
Quick Meet on https://demo.rspace.online/rmeets landed on
https://demo.rspace.online/demo/rmeets/<id> — a banned path-with-subdomain
URL. Added rmeetsBase(c) that returns /rmeets on {space}.rspace.online and
/{space}/rmeets elsewhere; swept all five `base` sites plus the inline
meeting page's meetsBase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 15:55:27 -04:00
Jeff Emmett dd608f831c tweak(mi-voice): quiet feminine — Emma, volume 0.25, neutral pitch
User preference: quiet + feminine. Swap Andrew (male) for Emma
(calmer, softer female than Aria). Drop volume 0.3 -> 0.25, reset
pitch to natural (deeper shift made it sound male). Rate stays slow.
2026-04-16 15:51:31 -04:00
Jeff Emmett 07b4714f48 fix(rpubs): canonical subdomain URLs for published pages
Published hosted_url, pdf_url, epub_url, and all links in the reader
page now use {space}.rspace.online/rpubs/... (subdomain form) instead
of path-scoped rspace.online/{space}/rpubs/... — matching the site
convention that space slugs always appear as subdomains.

The server already rewrites subdomain → path-scope internally for
routing, so Hono route mounts stay the same.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 15:51:28 -04:00
Jeff Emmett d55b9f74c0 tweak(mi-voice): brief but well-formed, not telegraphic
Prior voice-mode prompt leaned too far into caveman/telegraphic speech
('drop articles'). User wants brevity with proper grammar — short
complete sentences, not neanderthal fragments.
2026-04-16 15:48:48 -04:00
Jeff Emmett 7cbf96de8b feat(rmeets): live captions + MI internal-key auth
- Add Web Speech API captions overlay in the default Jitsi meeting view,
  broadcasting final/interim transcripts over Jitsi's data channel so each
  participant sees everyone's speech in real time.
- Toggle via the MI FAB dropdown; degrades gracefully where SR is unsupported.
- miApiFetch + /api/mi-proxy now forward X-MI-Internal-Key so the Meeting
  Intelligence recordings/search/meetings lists resolve from the backend
  without per-user tokens.
- docker-compose exposes MEETING_INTELLIGENCE_API_URL, MI_INTERNAL_KEY,
  JITSI_URL to the rspace container.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 15:46:30 -04:00
Jeff Emmett 39fbd99897 feat(mi-voice): quieter+smoother voice, caveman terseness in voice mode
- Default voice en-US-AvaMultilingualNeural -> en-US-AndrewMultilingualNeural
  (smoother, less feminine timbre).
- Volume 0.55 -> 0.3, rate -8% -> -10%, pitch -2Hz -> -6Hz.
- Browser fallback matches: pitch 0.85, volume 0.3.
- Client passes voiceMode flag to /api/mi/ask; server appends a VOICE
  MODE section to the system prompt demanding ≤1-2 short sentences,
  no lists/markdown/emoji/preamble — because listening to long replies
  is tedious.
2026-04-16 15:44:11 -04:00
Jeff Emmett 71782b1cf1 fix(mi-voice): await AudioContext.resume before source.start
Suspended contexts silently queued audio that never played. Await resume,
split connect() chain to avoid undefined-return on older browsers, and
re-check state after source setup in case first resume lost the gesture.
2026-04-16 15:30:16 -04:00
Jeff Emmett 858711e783 feat(rpubs): EPUB export + Publish-to-Space hosted URLs
- epub-gen.ts: reflowable (markdown → styled xhtml) and fixed-layout
  (Typst per-page PNGs wrapped as pre-paginated EPUB3). No new deps.
- typst-compile.ts: compileDocumentToPages() rasterizes Typst directly
  to PNG via the CLI (no poppler/mupdf needed).
- Persistent publications store at /data/rpubs-publications/{space}/{slug}
  with public reader page, PDF + EPUB downloads at /{space}/rpubs/publications/{slug}.
- Preview/Press step now has quick EPUB download buttons next to PDF.
- Publish panel: "Publish to {space}" is now the primary action, showing
  hosted URL + copy-link after publishing. EPUB variants remain in downloads.
- Dockerfile: new PUBS_DIR volume for persistence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 15:23:49 -04:00
Jeff Emmett 1f084fa674 fix(mi-voice): big-endian header parse, apply gain, softer voice defaults
- Parse WS frame header length as big-endian (server uses struct.pack('>I')).
  Previous little-endian read always failed, silently forcing the browser
  Web Speech fallback.
- Apply header volume via GainNode so quieter actually plays quieter.
- Default voice en-US-AriaNeural -> en-US-AvaMultilingualNeural, with
  rate -8% / pitch -2Hz / volume 0.55 for a calmer, less grating output.
  Browser fallback gets matching rate/pitch/volume.
2026-04-16 15:18:40 -04:00
Jeff Emmett 9daeb60895 fix(collab): dedup cursors by username + same-page-only visibility
- Deduplicate cursor rendering using #uniquePeers() (was showing
  multiple cursors per user from different tabs/sessions)
- Strict same-page filtering: cursors only visible when peers share
  the same effectiveViewId (viewId ?? moduleId)
- Users on different rApps no longer see each other's cursors
- Applied same fixes to focus rings and panel "different view" badge

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 15:00:11 -04:00
Jeff Emmett c885773844 fix(shell): move tab bar up 2px more, reduce left padding for full-width span
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 14:56:05 -04:00
Jeff Emmett c5a58e1908 feat(rfeeds): add landing page and standalone domain (rfeeds.online)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 12:47:05 -04:00
Jeff Emmett 1bc2a0af8c fix(encryptid): add missing welcome-email.ts to Docker build
Dockerfile.encryptid was missing COPY for server/welcome-email.ts,
causing container crash on startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 12:44:25 -04:00
Jeff Emmett 97c1b02c58 feat(rnetwork): power indices for DAO governance analysis
Banzhaf & Shapley-Shubik power index computation via DP, integrated
into trust engine 5-min cycle. Power tab in rNetwork 3D graph viewer
with animated bar chart, Gini/HHI gauges, and Banzhaf-scaled node
sizes. On-demand computation when DB empty. Left-drag now rotates.

New files:
- src/encryptid/power-indices.ts (pure math: Banzhaf DP, SS DP, Gini, HHI)
- modules/rnetwork/components/folk-power-indices.ts (standalone component)

API: GET /api/power-indices, GET /api/power-indices/:did,
     POST /api/power-indices/simulate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 12:39:01 -04:00
Jeff Emmett b0049ba8f4 fix(shell): close header-tabbar gap, minimize all 3 header bars together
- Move tab row up 1px (top: 55px) to overlap header border, eliminating gap
- Minimize toggle now hides header + tab row + subnav (was leaving subnav visible)
- Floating restore button (top-right, semi-transparent) when headers minimized
- Smooth transition on subnav hide/show

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 12:19:26 -04:00
Jeff Emmett a2f74faa3e feat(rtime): split-screen layout — commitment form left, pool viz right
Restructured rTime from pool+weaving side-by-side to commitment entry
form (left 50%) and pool orb visualization (right 50%). Inline form
replaces modal for pledging time. Commitments list shows below form.
Weaving SVG moved to separate toggled view via "Weave" button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 11:04:14 -04:00
Jeff Emmett 8592abd467 feat(rmeets): chat popup notifications + right-side chat panel
- Enable chat notifications (brief popup for all participants)
- Move chat panel to right side via CHAT_PANEL_POSITION
- Applied to both clean room mode and folk-jitsi-room shell mode

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 09:36:29 -04:00
Jeff Emmett 12cc724291 fix(rmeets): add recording button + post-meeting transcript link
- Add "recording" to Jitsi toolbarButtons in both clean room mode
  and folk-jitsi-room shell mode so users can trigger Jibri recording
- Add "View Transcript & Summary" link on meeting-ended screen
- Jibri network connectivity fixed on Netcup (was on wrong Docker
  network, couldn't reach Prosody XMPP)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 09:09:30 -04:00
Jeff Emmett b1dfbfd3e9 feat(rfeeds): Phase 1+2 — RSS dashboard with rApp activity adapters & user feed profiles
Phase 1: External RSS/Atom import, manual posts, OPML import, combined
Atom 1.0 output feed, background sync loop, reshare curation.

Phase 2: 8 activity adapters (rcal, rtasks, rdocs, rnotes, rsocials,
rwallet, rphotos, rfiles) read Automerge docs directly — no circular
deps. Activity cache in FeedsDoc, configurable per-module toggles.
User feed profiles with personal Atom feeds at /user/{did}/feed.xml
subscribable cross-space via existing RSS import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 08:47:29 -04:00
Jeff Emmett d49b4ea2dc feat(rsocials): graph-based campaign creation with brief-to-canvas pipeline
Add campaign planner as primary creation tool. Brief inputs in collapsible
sidebar generate editable node graph via Gemini 2.5 Flash. New input node
types (goal, message, tone) with feeds edges to downstream post/thread nodes.
Stale tracking propagates when inputs change; batch regen + single-node AI fill.

- schemas: goal/message/tone node types, feeds edge, stale tracking, schema v8
- mod: 3 API endpoints (from-brief, regen-nodes, ai-fill-node) + /campaign-flow page
- planner: ports, wiring, rendering, inline config, brief sidebar, context menu
- css: brief panel, platform chips, stale badges, feeds edge styles

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 08:45:22 -04:00
Jeff Emmett fec934b8a3 fix(persistence): flush docs on shutdown, fix eviction race, mobile flush
- Add saveDocImmediate() for synchronous awaitable saves (no debounce)
- Add SyncServer.flushAll() to iterate all in-memory docs
- Fix eviction race: onDocEvict now uses saveDocImmediate instead of
  debounced saveDoc (which could fire after doc deleted from memory)
- Add SIGTERM/SIGINT handlers with 10s timeout safety net
- Add visibilitychange flush on client (reliable on mobile/bfcache)
- Flush pending IDB saves in DocSyncManager.disconnect()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 08:45:01 -04:00
Jeff Emmett 63c6fcc941 fix(auth): preserve current page on login instead of redirecting
Only redirect to personal dashboard when on demo landing page.
Logging in from any module page now reloads in place.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 08:17:24 -04:00
Jeff Emmett 3ec8ac5308 fix(auth): auto-focus passkey input for mobile autofill
Remove readonly and auto-focus the username webauthn input so mobile
browsers show passkey suggestions immediately when the sign-in modal
opens, matching the desktop experience.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 21:29:40 -04:00
Jeff Emmett efe3615228 fix(auth,rcred): passkey autofill for mobile + rcred write access
- Add conditional mediation to sign-in modal so mobile browsers show
  saved passkeys with usernames in the autofill area (desktop parity)
- Add publicWrite to rcred module so recompute route's own auth runs
  instead of being blocked by the global write-access middleware

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 19:23:28 -04:00
Jeff Emmett 932e550c66 fix(shell): eliminate phantom tab persistence — validate + dedup on restore
Filter restored tabs against valid moduleIds from moduleList, deduplicate
on every restore path (localStorage, server sync, BroadcastChannel).
Add closed-module tracking to renderExternalAppShell to prevent server
sync from resurrecting tabs closed in the current session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 17:32:15 -04:00
Jeff Emmett cb521cad12 feat(rtime): shell tab bar + pool legend, remove internal tabs/stats bar
- Add shell tabs: Commitment Pool, Fulfillment, Open in Cyclos
- Move stats (hours, contributors, skill breakdown) into pool legend
- Remove internal tab-bar and stats-bar from component
- Listen for rapp-tab-change shell events for view switching
- Legacy routes redirect to new tab paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 17:27:46 -04:00
Jeff Emmett c82eca38fa fix(server): add styled 404 page + stop SPA fallback serving stale HTML
Add app.notFound() handler with themed 404 page instead of Hono's
plain "404 Not Found". Restrict canvas.html SPA fallback to /rspace
sub-paths only — was serving index.html for all unmatched routes,
causing stale "Activated" page to appear on navigation errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 17:19:41 -04:00