← 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.

CHANGELOG

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