← HOME

PROJECT

L

Lore

ACTIVE

Discord-style chat wrapped in a book — genre-themed servers, literary naming throughout

FastifyPrismaPostgreSQLRedisLiveKitSocket.ioNext.jsReact 19ExpoTauri

ABOUT

Lore is a real-time chat platform built on Discord-style infrastructure — servers, channels, voice, DMs, roles, and bots — wrapped entirely in a book metaphor. Every concept has a literary name: servers are Books, channels are Chapters, voice rooms are Hearths, DMs are Pages.

Genre theming is the core differentiator. Each Book has a genre (fantasy, horror, sci-fi, romance, cyberpunk) that applies a full CSS variable palette, typography, and ambient effects to the entire UI — the room doesn't just look different, it feels different.

Core chat infrastructure is complete — messaging, voice via LiveKit, DMs, friends, roles, and bots all working. The literary UX rename is in place across all components. First collaborative project — built with her brother, who hosts the repo and live API on his homelab.

FEATURES

IN PROGRESS

  • Cross-Platform

    Web, mobile (Expo), and desktop in a single codebase. Shared logic with platform-specific shells.

  • Real-Time Messaging

    Live presence and messaging via Supabase Realtime. All clients stay in sync without polling.

  • Literary Aesthetic

    Typography-first interface. Visual identity draws from printed matter — quiet, space to think, not another chat app.

CHANGELOG

LOREfeatureinfrastructure

Rituals UI built — PATCH route, Hearth Calendar, push blocked on Gitea access

Features

  • PATCH /rituals/:id added to the API — accepts enabled, label, mood, cronExpr; validates ownership using the same 404-covers-both pattern as DELETE; updateSchema defined at module scope alongside createSchema
  • Ritual settings page (/settings/rituals) built — Hearth Calendar design: mood-accented left stripe (firepit/kitchen/den), large DOW + time display, label below, enabled toggle, delete on hover
  • AddRitualForm complete — 7 DOW pills, HH:MM time input, 3 mood chips, label input, server dropdown hidden when user is in only one server
  • Optimistic toggle (reverts on failure) and optimistic delete (re-fetches on failure) keep UI responsive without waiting on the API
  • Rituals nav link added to settings sidebar with divider separator
  • Mood type sourced from @lore/types rather than redefined locally — /simplify caught the duplication

Infrastructure

  • ISSUE-024 opened — push to harmjoy/lore blocked; Gitea returns "User permission denied for writing" from pre-receive hook despite valid token with write scope; repo-level collaborator access not set
  • ISSUE-023 opened — harmjoy/lorev2 is a separate older JavaScript codebase; easy to push to the wrong repo
  • Penpal message sent (ID 13) to Nathan requesting Write access on harmjoy/lore → Settings → Collaboration
  • Lore CLAUDE.md repository section corrected — harmjoy org, HTTPS-only, token-in-URL pattern for headless pushes documented
  • Global CLAUDE.md gotchas table updated with pre-push hook TTY limitation

Lessons

  • git credential reject requires piped input — bare command fails with "refusing to work with credential missing host field"; correct form: printf "protocol=https\nhost=<host>\n" | git credential reject
  • Gitea "User permission denied for writing" from the pre-receive hook is a repo-level collaborator permission issue, not a token scope issue — same error fires regardless of token; fix is Settings → Collaboration, not token regeneration
  • Lore's Vercel pre-push hook runs a full ~2 min build before git contacts Gitea — GCM has no TTY to show a credential dialog; token must be embedded directly in the remote URL for headless pushes, then reset after
  • harmjoy/lorev2 and harmjoy/lore are completely different repos (JS vs TypeScript monorepo) — git ls-remote on both showed divergent commit hashes; don't assume repo name matches project name

TODO

  • Nathan to grant nicole Write access on harmjoy/lore (ISSUE-024) — then git push origin feat/rituals-management and open PR on Gitea
  • Confirm with Nathan whether harmjoy/lorev2 should be archived to avoid future confusion (ISSUE-023)
  • Continue with Step 2 (place settings polish + arrival sound upload) once PR is unblocked
LOREinfrastructuretooling

Lore v2 orientation — architecture briefed, changelog pipeline live

Features

  • Lore v2 read in full — Rails-Off Redesign (PlaceCanvas, 3-mood system, ritual scheduler) and WhatYouMissed (visit heartbeat, catch-up summaries after 2h+ absence) architecture documented
  • lore-changelog source live in field-notes pipeline — weekly Gitea commit scan runs every Monday alongside GitHub Trending; enrichment trigger handles analysis automatically
  • Architecture briefing written to field-notes/briefs/lore-v2-briefing.md — component map, DB schema changes, API routes, socket events, known gaps, and contribution opportunities per area

Infrastructure

  • Local master fast-forwarded 80 commits to v2 state (668ce39) after switching remote from SSH to HTTPS
  • Scan handler at field-notes/app/api/scan/lore-changelog/route.ts — fetches last 7 days of Gitea commits, inserts as changelog-entry items in fn_intel_items
  • Vercel cron added (0 13 * * 1) in field-notes/vercel.json; GITEA_TOKEN added to Vercel env vars; source record registered in Supabase
  • project_current_state.md memory overwritten with full v2 state; global CLAUDE.md and lore CLAUDE.md repository section corrected

Lessons

  • SSH key not authorized on harmjoy org for Nicole — HTTPS is the only path; git fetch redirects automatically once the remote URL is corrected
  • Gitea API returns 404 (not 401) for private repos without auth — looks like a wrong URL but is actually an access issue; Authorization: token <TOKEN> header required on every request
  • Existing NATHAN_GITEA_TOKEN in peer/.env already covers read:repository scope for harmjoy org — no new token generation needed, value reused as GITEA_TOKEN
  • Supabase source record must exist before the scan handler can run — handler queries by slug and returns 404 if missing; SQL insert is a required deploy step alongside the code

TODO

  • Run 3 pending Prisma migrations on lore production DB before PlaceCanvas can go live
  • Nathan flips useNewLayout=true to smoke test PlaceCanvas; Nicole flips when ready
LOREbugfeature

Server settings modal — three root causes diagnosed and fixed

Bug Fixes

  • SceneLayer missing from channel page — dropped in Nathan's layout redesign merge (dde6164), wired back via useEffect + PlaceThemeRegistry side-effect imports in ChannelPage
  • Server settings modal closes immediately on open — React 18 synchronous flush processes the backdrop click in the same tick; fixed with setTimeout(() => setShowServerSettings(true), 0)
  • Server settings modal lost on channel navigation — local useState resets when ChannelPage remounts on route change; fixed by lifting to useUIStore.serverSettingsServerId in Zustand
  • TOC header (server name) invisible — absolute inset-0 texture overlay painted over the header div, which had no stacking context to escape; channel list visible because overflow-y: auto creates one; fixed with z-index: 0 on overlay and relative z-10 on header and footer

Infrastructure

  • .vercelignore at repo root: excludes apps/web/public/places/*/sound/ (~640MB WAV) and docs/poc/ (~320MB POC audio); Vercel CLI reads this from the invocation directory, not from rootDirectory in the dashboard — file at apps/web/ had no effect
  • Vercel deploy now succeeds — upload dropped from 641MB to 2.9KB after exclusions

Lessons

  • Three separate bugs shared one symptom (modal not accessible) — each required a different diagnosis: React timing, component lifecycle, CSS stacking order
  • CSS stacking order is non-obvious when overflow-y: auto creates a stacking context for some children but not others — same overlay covers some siblings and not others depending on their CSS properties
  • Local testing against a live API requires CORS allowance for the dev origin; without it, even token injection workarounds hit the 15-minute JWT expiry wall before anything can be verified
  • Vercel .vercelignore placement is not documented prominently — it must live where vercel deploy is invoked, which differs from where rootDirectory is set in the dashboard

TODO

  • Nathan to review and merge fix/server-settings-click-through PR on Gitea
  • Nathan to add http://localhost:3002 to EXTRA_ORIGINS to unblock local testing
LORESHIPPEDfeatureaudiophase

Immersive Places — all 3 places live in production

Features

  • Immersive Places integration shipped — Library, Tavern, and Meadow live at lore.harmjoy.us
  • SceneEffects.tsx: fBm WebGL fire glow shader with additive blending + canvas particle system (embers for Library/Tavern, fireflies for Meadow), presence-driven
  • AmbientSound expanded to 4-layer Howler.js system — base → detail1 → detail2 → full, with 1500ms presence-driven crossfades
  • Intermittent one-shot scheduler for page turns (Library) and bird calls (Meadow) — random intervals, not loops
  • Pull-back CSS filters on all hero images — cool mist + desaturation + blur dissolve as presence grows; scene shifts from cold/dormant to warm/alive
  • VignetteOverlay wires warm amber tint to normalizedPresence — the room responds to who's in it

Infrastructure

  • SceneAtmosphere.tsx deleted — replaced entirely by SceneEffects
  • 15 assets committed to apps/web/public/places/ (3 Ghibli Anchor A hero images, 12 sound files)
  • /start skill updated — fetches origin on every session start and reports upstream commits; silent for solo repos, visible for collaborative ones

Lessons

  • Single hero image per place was the right call — AI generation can't produce "same room at different fullness," CSS/shader manipulation of one image beats background swapping
  • Additive blending (SRC_ALPHA, ONE) adds light rather than compositing over it — fire glow reads correctly on dark scenes without washing out the background
  • "Your Books" on the home screen is vocabulary-correct but aesthetically misaligned — reads as literary organizing principle, not gathering space; worth revisiting
LOREfeatureaudiophase

Immersive Places POC complete — effects, sound, and composite all validated

Features

  • Effects POC validated with real Ghibli hero images — all 3 places render correctly behind frosted glass UI shell
  • Particle colors made place-aware — tavern embers now gold-amber, meadow fireflies warm cream-gold instead of uniform orange
  • Sound POC built — Howler.js 4-layer system (base → detail1 → detail2 → full ambient), each layer fading in as presence grows
  • 12 ambient audio assets sourced from Freesound and configured per place — library fire, clock, pages, room tone; tavern hearth, dinner table, murmur, crowd; meadow wind, bird, rustle, chorus
  • Intermittent sound model confirmed — pages and bird calls use scheduled one-shots with random intervals (12–35s, 10–30s), not loops
  • Composite POC complete — WebGL shader, canvas particles, Howler.js audio, and full Lore UI shell (sidebars, header, input bar) unified in a single page across all 3 places
  • Pipeline doc written (docs/plans/2026-04-18-immersive-places-pipeline.md) — integration plan mapped to existing SceneLayer / PlaceSceneRenderer / AmbientSound architecture, 10-step implementation order, asset manifest, per-place checklist

Bug Fixes

  • Clock volume capped at 25% — was audibly competing with fire base layer
  • Tavern clink replaced with dinner table ambience (Mr_Alden, Freesound 365676) — crystal clink was too repetitive regardless of random-seek approach; looping ambient table activity reads more naturally

Infrastructure

  • Howler.js downloaded locally (howler.min.js) — CDN load blocked on file:// protocol in Chrome
  • npx serve established as the local POC test pattern — browser blocks audio loading from file:// even with html5: true; must serve via HTTP

Lessons

  • Browser security blocks audio loading from file:// regardless of Howler config — every audio POC needs a local HTTP server, not a double-click
  • Intermittent detail sounds (page turns, bird calls) need random scheduling, not loops — a constant page-turning loop is immediately uncanny
  • Sound variety matters more than volume — random-seek on a single-sample clink still sounds repetitive; an ambient file with natural variation (dinner table) solves it better than any seek strategy
  • Windows hides file extensions by default — renaming library.jpg in Explorer silently produces library.jpg.jpg; turn on extensions before any rename workflow

TODO

  • Integration: create SceneEffects.tsx (port WebGL + canvas from POC), update LibraryScene.tsx, expand AmbientSound.tsx to 4-layer system — see pipeline doc for full order
LOREfeatureinfrastructurephase

Usability overhaul complete — all 8 steps shipped

Features

  • Gathering vocabulary applied across all UI strings — Passage, Fireside, Section, Echoes, Whispers, The Circle, Gathered/Resting/Gone, Keeper/Elder/Guide replace the full literary terminology set
  • TopBar component with labeled navigation — BookShelf, home, and DMs all discoverable without icon hunting
  • Home screen Books grid with empty state — replaces blank loading screen with actionable landing point
  • Google OAuth wired end-to-end — /auth/google + /auth/google/callback handle consent, code exchange, profile fetch, and upsert by googleId → email → create; login and register pages gain "Continue with Google"
  • Onboarding overlay added — 4-step AnimatePresence flow with expanding dot progress, skip button, and idempotent POST /users/@me/onboarded endpoint; shows once on first sign-in, never again
  • Landing page replaces redirect spinner — SceneLayer at low presence, frosted glass hero, feature tiles, footer; authenticated users redirect to /app; copy is lorem ipsum placeholder pending Opus brainstorm
  • Presence wiring complete — useServerPresenceCount hook cross-references presenceMap against server.memberIds[]; SceneLayer presence prop is now live data

Bug Fixes

  • Fantasy theme contrast fixed — --lore-text and --lore-muted darkened to readable contrast ratios on light parchment surfaces
  • Password change route guards against null passwordHash — OAuth-only users get a clear error instead of a crash

Infrastructure

  • Schema: googleId (nullable, unique), passwordHash (nullable for OAuth-only users), onboardedAt (nullable timestamp) — two migration files created manually without a local DB
  • memberIds: string[] added to server list API response — presence hook uses it to scope presenceMap to current server's members
  • loginWithTokens() action added to auth store — reads access_token/refresh_token from URL params on /auth/callback, stores and hydrates session
  • NEXT_STEPS_FOR_NATHAN.md written — migration commands, env vars, Google OAuth app setup, Docker rebuild steps, CORS note
  • Branch feature/usability-overhaul pushed to Gitea; Vercel production deploy triggered and succeeded

Lessons

  • Landing page copy is a writing problem, not a code problem — deferring to a dedicated Opus brainstorm session was the right call; lorem ipsum commits cleanly and doesn't block the PR
  • Manual Prisma migrations (no local DB) are viable as long as they match the schema diff exactly — TypeScript catches anything that doesn't line up after prisma generate
  • OAuth without extra packages is cleaner than it looks — native fetch handles token exchange and profile fetch in ~50 lines with full control over the redirect flow

TODO

  • Nathan: run pnpm db:migrate, add Google OAuth env vars, create Google OAuth app, docker compose up -d --build api
  • Landing page copy — Opus brainstorm session, reference docs/plans/2026-04-12-usability-overhaul-vision-brief.md
  • CORS: Nathan adds lore-drab.vercel.app to EXTRA_ORIGINS to enable end-to-end testing from Vercel preview
  • Open PR on Gitea — requires logging into git.harmjoy.us as Nicole first
LOREinfrastructuretooling

CLAUDE.md landed — /wrap fixed, lore sessions now reach the changelog

Features

  • CLAUDE.md added to lore repo root — stack, literary terminology, deployment targets, collaboration conventions, and place theme architecture all documented
  • Place theme system (Library, Meadow, Tavern) captured as confirmed art direction — color-mix() frosted glass pattern included so Nathan's session has it

Infrastructure

  • /wrap skill: lore added to project mapping table, git push added to Step 7 — entries now write, commit, and deploy in one step
  • save-state was 5 commits ahead of origin — pushed and deployed, changelog now current through April 3
  • Pulled Nathan's Phader voice style system merge — settings page, phader.ts, preferences store, 19 files, 4457 insertions

Lessons

  • A missing git push in a skill's commit step makes the pipeline invisible — entries were being written and committed locally but never reached the deployed site
  • Project directory → ID mapping tables in skills decay silently as projects are added; wire new projects in at setup time, not retroactively

TODO

  • CORS blocker: lore-drab.vercel.app origin not whitelisted in Nathan's API — login/register broken on Vercel preview until EXTRA_ORIGINS is set on VPS
  • Nathan has open branches to check: add-claude-md, docs/claude-md, feature/vps-migration, merge/phader-plus-place-themes
LOREfeatureinfrastructurelaunch

Place Theme System — atmosphere picker, SceneLayer bridge, full aesthetic

Features

  • Place theme system shipped end-to-end — each Book now has an atmosphere (the-library, the-tavern, the-meadow) independent of genre
  • PlaceThemePicker component renders color swatches, name, and description for each place — wired into Create Book and Book Settings modals
  • ThemeContext bridge: active server's placeTheme drives SceneLayer and CSS variables app-wide, with 160ms opacity crossfade when switching servers
  • Auth pages (login, register) moved to (auth) route group — SceneLayer background and frosted glass card on both
  • Invite page and 404 page get library scene background — every public URL now has the aesthetic
  • Root redirect page updated from plain spinner to SceneLayer-backed loading state

Infrastructure

  • placeTheme String @default("the-library") added to Server model — Prisma migration applied to production DB
  • API validates placeTheme on POST and PATCH /servers against enum of three valid place IDs
  • PlaceThemeId type and PLACE_THEMES array added to @lore/types
  • Merge conflict resolved with Nathan's commits — adopted his animationKey improvement (coarse key groups channels by server, not per-channel) and getStatus voice fix in ReaderList
  • PR opened and merged on Gitea — branch protection on master confirmed active

Lessons

  • Scoping CSS variables as inline style on a container element overrides root vars set by ThemeProvider — clean way to isolate palette without fighting the context
  • Tailwind opacity modifiers silently fail on CSS variable colors — color-mix(in srgb, var(--color) 80%, transparent) is the fix
  • lore.harmjoy.us is the right QA target — Nathan's production is VPS-deployed, not Vercel; lore-drab.vercel.app is Nicole's preview and isn't in EXTRA_ORIGINS
  • Nathan needs to pull and rebuild Docker on VM108 for changes to appear on his production URL — auto-deploy not set up yet

TODO

  • Ask Nathan to set up Gitea Actions runner for auto-deploy on merge to master
  • lore-drab.vercel.app CORS: Nathan adds it to EXTRA_ORIGINS if Vercel URL needs to work against live API
LOREfeatureinfrastructurephase

Art Direction to Production — SceneLayer wired, POC merged to master

Features

  • POC branch merged to master — place-based themes (The Library, The Tavern, The Meadow) now in the main codebase
  • SceneLayer threaded behind the authenticated app shell — photorealistic backgrounds render beneath all UI
  • Frosted glass pattern applied to all three chat UI panels: BookShelf, ChapterSidebar, ReaderList, channel header — color-mix() inline styles replace opaque surfaces
  • Scene is hardcoded to the-library pending theme system bridge (Step 3)

Bug Fixes

  • @types/react duplicate resolved — pnpm installed React 18 types (for Expo) and React 19 types (for web) as separate physical copies; TypeScript surfaced them as incompatible ReactNode types even in .tsx source files; fixed with pnpm.overrides forcing ^19.0.0 across the monorepo

Infrastructure

  • vercel.json added at repo root — framework, installCommand, buildCommand only; rootDirectory lives as a permanent Vercel project setting (not in config file)
  • .vercel/ added to .gitignore

Lessons

  • Tailwind v3 opacity modifiers (bg-lore-surface/80) don't work with CSS custom property colors — color-mix(in srgb, var(--color) 80%, transparent) in inline styles is the correct pattern
  • skipLibCheck: true doesn't protect against duplicate @types/react in source .tsx files — the TypeScript checker still sees both ReactNode shapes when resolving JSX; the fix is at the package resolution layer, not the compiler
  • Book spines preserved by design — each Book is a portal to a place; pulling a spine takes you into that world; the gathering vocabulary (Grounds, Passages, Firesides) applies inside, not at the server list level

TODO

  • Step 3: theme system bridge — placeTheme field in Prisma schema, ThemeContext reading PlaceThemeRegistry, server creation/settings UI
  • Step 4: presence wiring — useServerPresenceCount(serverId) feeding real online count into SceneLayer
  • Step 5: terminology rename — PassagesSidebar, GatheringArea, CircleList, flame icon channel prefix
  • Step 6: root CLAUDE.md from merged draft
  • Ambient sound: deferred brainstorm item
LORElaunchrefactorinfrastructure

Art direction POC deployed — /vision route live

Features

  • Art direction POC complete — all four plan steps shipped on feature/art-direction-poc
  • Three themed environments live: The Library, The Tavern, The Meadow — photo backgrounds with presence-responsive lighting
  • Four surface views: Landing, About, Server (mock chat UI layered over scene), Community (ambient presence dots)
  • Demo controls: theme crossfade, presence slider (0–50), surface tabs, auto-demo mode, sound toggle

Bug Fixes

  • Tavern scene: removed all positioned circular radial-gradient elements — last remaining source of floating orbs
  • Dev debug readout removed from VignetteOverlay before sharing

Infrastructure

  • Vercel CLI installed and project linked (nrisacher-langs-projects/lore) — repo on self-hosted Gitea requires manual CLI deploys, no GitHub integration
  • rootDirectory: apps/web set via Vercel API — not settable in vercel.json (causes deploy failure if attempted there)
  • Next.js updated 15.1.0 → 15.5.14 — Vercel blocks deployment of vulnerable versions (CVE-2025-29927, middleware auth bypass)
  • Preview live at https://lore-drab.vercel.app/vision

Lessons

  • Positioned circular radial-gradient divs always read as orbs against photorealistic backgrounds — blend modes don't fix it. Full-width linear gradients and full-scene mixBlendMode: overlay tints are the correct approach for shapeless presence indication.
  • rootDirectory is a Vercel project setting, not a vercel.json key — Vercel CLI v50 also removed --root-directory. Set it once via API: PATCH /v9/projects/{id} with {"rootDirectory": "apps/web"}.
  • Deploying from the monorepo root (not apps/web) is required — Vercel must see the pnpm lockfile at root to resolve workspace dependencies.

TODO

  • Source Lottie animation files (fire, candles, petals) — LottieLayer and Howler are both wired, waiting on assets
  • Open PR on Gitea for Nathan's review — draft description already written
LOREbrainstormnamingbrand

Gathering, Not Reading — art direction vision and vocabulary

Features

  • Art direction pivot from library/book aesthetic to campfire/gathering — Lore's identity rebuilt around the third-space concept
  • Full vocabulary rework — fire & oral tradition replaces literary metaphor: Grounds (server), Passage (text channel), Fireside (voice), Whisper (DM), Keeper/Elder/Guide (roles)
  • Community lifecycle as fire spectrum — Spark → Ember → Campfire → Bonfire → Beacon, with decline states (Banked, Coals, Cold) honoring soft-delete philosophy
  • Hearth as personal home designation — any community regardless of size can be your Hearth
  • Themes are places, not genres — each theme answers "where are we gathered tonight?" and is specific enough to drive every art decision
  • Three POC themes defined: The Library (Harry Potter common room), The Tavern (medieval warmth), The Meadow (Ghibli pastoral)
  • Presence-responsive environmental scaling — spaces get bigger and richer as people gather, driven by warm vignette that expands from cozy to expansive (never cold to warm)
  • Ambient sound in scope — layered audio per theme scales with presence alongside visuals
  • POC plan confirmed — 4 steps, /vision route on a branch, Lottie + Howler.js, scene components built production-ready from day one

Lessons

  • "Lore" was never about books — lore is oral tradition, stories passed around fires, knowledge that lives in communities because people showed up
  • The name unlocked the vocabulary: once "gathering" replaced "reading" as the core verb, every naming decision cascaded naturally
  • "Fantasy" is too broad to drive art decisions — "The Library" tells you exactly what to draw
  • The emotional arc of presence scaling is cozy → expansive, not cold → warm — an empty space should feel intimate, not lonely
  • Themes as places rather than genres solved the extensibility problem — any place people gather is a valid theme
  • Prior session's brainstorm work was lost to an unsaved close — planning artifacts must be saved to files mid-session, not accumulated for wrap
LORENEW PROJECTlaunchprojects

Lore — debut

Features

  • Lore is a real-time chat platform built on Discord-style infrastructure wrapped in a book metaphor — servers are Books, channels are Chapters, voice rooms are Hearths, DMs are Pages
  • Genre theming is the core differentiator — each Book carries a genre (fantasy, horror, sci-fi, romance, cyberpunk) that applies a full CSS palette and ambient effects throughout the UI
  • Core infrastructure complete — messaging, voice via LiveKit, DMs, friends, roles, bots, and the full literary UX rename in place across all components

Lessons

  • First collaborative project — built with her brother, who hosts the repo and live API on his homelab; the constraint of a protected master branch and PR-required workflow is the right default for shared work
  • The literary metaphor earns its keep by being total — partial renaming would read as affectation, but renaming every layer (server → Book, channel → Chapter, moderator → Narrator) makes the world feel consistent