๐
games-mahjong
Georgianna's American Mahjong โ tutorial + practice + online multiplayer
- Primary: https://mahjong.kenlill.com
- GitHub: https://github.com/klill6506/games-mahjong
- Local:
D:\Personal\games-mahjong
README
No README.
STATUS
# games-mahjong โ Status ## Current state - Live at **https://mahjong.kenlill.com** on sherpa-server / Coolify. Pushes to `main` auto-deploy via GitHub webhook (verified 2026-06-05; see sherpa-server project). Old Render service `georgianna-mahjong` is suspended. - **KenBot Stage 1 (text chat) is integrated and verified** โ STOPPED for Ken's review before any Stage 2 (voice) work, per INTEGRATION.md. ## KenBot โ Stage 1 done 2026-06-11, awaiting Ken's review Ken's decisions: React island just for KenBot (game stays vanilla JS); new `ask_kenbot` Supabase Edge Function; help screen only. Model pick was delegated โ **Claude `claude-opus-4-8`** (the project already had `ANTHROPIC_API_KEY` in Supabase Edge Function secrets for `ask_claude`, so no new infrastructure). What shipped: - Deps: `react@19`, `react-dom@19`, `sherpa-kenbot` (github dep, builds on install). Island in `src/kenbot/island.js`, lazy-loaded by `src/screens/kenbot.js` โ KenBot+React is its own ~336 kB chunk that only downloads when help is first opened; main bundle unchanged. - Game screen ๐ฌ button (relabeled "Ken") toggles KenBot bottom-right (`wander=false`); hidden on exit to menu = "help screen only". This **replaces the old AI Help panel** โ `src/screens/help.js` and the `#helpOverlay` markup/CSS are now dormant dead code (ask Ken before deleting). - New Edge Function **`ask_kenbot`** (deployed, ACTIVE, verify_jwt=true): Anthropic proxy with conversation-history support; system prompt = ask_claude's mahjong/app knowledge + KenBot persona (speaks as Ken, 1โ3 sentence answers, TTS-friendly). `src/net/kenbot.js` is the client bridge (anon session + functions.invoke). - Verified: `scripts/smoke-kenbot.js` passes (question + history follow-up); browser flow verified end-to-end (summon โ greeting โ ask "what's a pung?" โ answer in bubble โ toggle preserves chat โ hides on Menu). No console errors. `npm test` 10/10; `npm run build` clean. - `.env.local` recreated (lost โฆ(truncated for upload size)
DECISIONS
# Architectural decisions Append-only running log. Each entry: context, decision, rationale, follow-ups. --- ## 2026-04-23 ยท Phase 1 โ Extract engine, keep single-player working ### D-001 ยท Vanilla ES modules, not a framework rewrite The monolith is ~1200 lines of plain JS/HTML/CSS. Introducing React or Vue would be a larger rewrite than the multiplayer work itself, and would separate us from the original monolith we must keep working. We split into ES modules and keep `renderTile` / `renderGame` as plain functions that take state in and write DOM out. ### D-002 ยท Pure engine, no DOM or globals Every module under `src/engine/` is pure. Functions take state + rng + timestamp as arguments and return new state (or mutate the state object they were given โ we're not chasing immutability, just referential transparency). This is the hard gate: the engine must run in Deno. ### D-003 ยท Seeded RNG (mulberry32) Using `Math.random()` inside engine code makes Edge Function behavior non-reproducible, breaks tests, and prevents client-side speculative rendering. Mulberry32 is ~8 lines, no dependency, and plenty good enough for shuffle and AI randomness. The game's `state.seed` is the seed of record; per-turn rngs are derived from `(seed ^ turn_index ^ seat)`. ### D-004 ยท Preserve original player indexing The original monolith stores players as `[human(South), East, North, West]` with turn order `(i+1) % 4`, which yields East โ North โ West โ South instead of the traditional East โ South โ West โ North. Changing this now would be a behavioral regression in single-player. We keep the old indexing. If it bothers us for online play, the Edge Function can remap seats to their canonical turn order at that boundary; we won't do it as part of the extraction. ### D-005 ยท Mahjong validation stays `checkSimpleWin` The monolith shipped a placeholder `matchPattern` (always false) and used `checkSimpleWin` as the actual win check. That's the current behavior and we match it. Real NMJL โฆ(truncated for upload size)
MEMORY
# games-mahjong โ Memory ## What this is American Mahjong tutorial + practice-vs-AI + online multiplayer, built for Ken's **wife** Georgianna (NOT his sister โ early docs had this wrong). Vanilla JS + Vite (NO React in the game itself), Supabase (`nidovyzcsgmcqovukzvy`) for multiplayer via Edge Functions (Deno). Engine is pure/dom-free in `src/engine/` and is synced into Edge Functions by `npm run sync-engine`. Tests: `npm test` (node:test, engine only). ## Hosting - Live at **https://mahjong.kenlill.com** on Ken's self-hosted sherpa-server (Coolify; `D:\dev\sherpa-server` project). Pushes to `main` auto-deploy via GitHub webhook โ **a push IS a production deploy**. - Edge Functions deploy separately: `npm run deploy-functions` (Supabase CLI) or the Supabase MCP `deploy_edge_function` tool (used 2026-06-11 โ works, pass `ask_kenbot/index.ts` + `_shared/cors.ts` with entrypoint `ask_kenbot/index.ts` to preserve the `../_shared/` import). - Old Render service `georgianna-mahjong` is suspended; render.yaml remains in the repo but is inactive. ## Standing facts - GitHub repo (private) is source of truth; local restored 2026-06-08 to `D:\Personal\games-mahjong`. - `.env.local` is gitignored and was lost in the re-clone; recreated 2026-06-11 with the **publishable** key (`sb_publishable_โฆ`). Values come from the Supabase MCP (`get_project_url` / `get_publishable_keys`). - The app has TWO Anthropic-backed Edge Functions: `ask_claude` (legacy single-shot help, still deployed, no longer wired to UI) and `ask_kenbot` (KenBot chat with history, `claude-opus-4-8`). `ANTHROPIC_API_KEY` is set in Supabase Edge Function secrets and shared by both. - **KenBot fully integrated (chat + voice), Ken-approved 2026-06-11** โ React island pattern, help-screen-only, replaces the old AI Help panel. Voice = `kenbot_tts` Edge Function (ElevenLabs proxy, voice ID defaulted in code); `ELEVENLABS_API_KEY` secret is set (Ken approved setting it from sherpa-kenbo โฆ(truncated for upload size)
CLAUDE.md
# Georgianna's Mahjong โ Project Guide
American Mahjong tutorial, practice vs AI, and online multiplayer. Built for
Ken's wife Georgianna for her first game night.
## Live
- **App:** https://mahjong.kenlill.com (Coolify on sherpa-server โ see `D:\dev\sherpa-server`)
- **Repo:** https://github.com/klill6506/games-mahjong (private)
- **Supabase project:** `nidovyzcsgmcqovukzvy` โ https://supabase.com/dashboard/project/nidovyzcsgmcqovukzvy
- **Old hosting:** Render service `georgianna-mahjong` is suspended; `render.yaml` remains but is inactive
## Running locally
```bash
npm install
npm run dev # vite dev server on http://localhost:5173
npm test # engine unit tests (node:test, no framework)
npm run build # production build โ dist/
npm run preview # serve dist/ locally
```
Single-player ("Practice vs AI") works offline with no Supabase credentials.
Multiplayer is wired in Phase 4+ and requires env vars below.
## Where things live
```
index.html # shell with every screen div
src/
main.js # entry โ wires screens and events
ui/
styles.css # all CSS (lifted from the original monolith)
render.js # renderTile + renderGame, called with explicit state
engine/ # pure, dom-free, importable from Deno
constants.js # SUITS, WINDS, DRAGONS, PATTERNS, seat layout
rng.js # mulberry32 seeded RNG
tiles.js # createDeck, shuffle, tileKey, sortHand
deal.js # newGameState (post-deal, pre-Charleston)
charleston.js # applyCharlestonPass, maybeEndCharleston
turn.js # applyDraw, applyDiscard, applyClaim, applyMahjong
ai.js # aiPickDiscard, aiDecideClaim, aiCharlestonPick
patterns.js # findBestPattern (used only for hints)
engine.test.js # node --test
screens/
menu.js, setup.js # (wiring
โฆ(truncated for upload size)
Diary mentions
2026-06-08
# 2026-06-08 ## sherpa-hub Shipped the UI v2 redesign โ the hub went from plain light-Tailwind to a dark, polished dashboard matching the mockup I liked, without giving up the "stays-current-by-itself" guarantee that's the whole point of the rebuild. ### What we did Full brainstorm โ spec โ plan โ subagent-driven build cycle: - Brainstormed the look using the visual companion. Looked at three directions first (minimal / dense-dark / playful), then I dropped in a reference mockup (the sherpa-dashboard-style dark dashboard with sidebar + summary cards + icon tiles + two-button cards) and we built around that. - Key call: **Path 3 (hybrid).** Adopt the mockup's look, but keep status, activity buckets, and all counts 100% auto-derived from GitHub + Render. The only hand-set data is set-once and non-rotting: per-app icon (glyph + color) in `icons.yaml`, and category tags from GitHub repo topics. This preserves the thing that matters most to me โ it never goes stale on its own. - Wrote the spec (`docs/superpowers/specs/2026-05-20-ui-redesign-v2-design.md`, decisions UI-1..UI-15) and a 10-task TDD plan, then executed subagent-driven with spec + code-quality review on the load-bearing tasks. ### What shipped - Dark theme, violet accent, left sidebar (bucket filters with live counts + Diary/Health links) - 4 summary stat cards: Total / Live / Active / Needs Attention (the last links to /health and shows a green โ when nothing's wrong) - App cards: colored Lucide icon tile, name, description, category tag pills, status pill, and two buttons โ "Open App โ" (jumps to the app's URL) and "โ Status" (drills into the detail page) - Client-side search + grid/list toggle (no page reload, no backend) - `/apps` list view dropped โ home IS the apps view now (301 redirect) - Detail, diary, and health pages all restyled dark ### How the icons work (for future me) `web/app/icons.yaml` maps each slug to a Lucide icon name + a color. The SVGs are self-hosted via the `lucide-static` npm package and inlined server-side โ no CDN, no icon flash, glyphs color from the tile via currentColor. Lookup is case-insensitive (matters for repos like `Pips`/`Tanks` that GitHub stores with capitals). Add a line per new app; anything missing falls back to a slate box. `reload_icons()` fires on every sync upload so icon edits show up without a restart. ### Decisions recorded - D-012: UI v2 dark dashboard, Path 3 hybrid (auto-derived + set-once icons) - D-013: icons.yaml is a deliberate, bounded exception to D-001 (hub-owns-nothing) โ an icon is a display preference with no source of truth elsewhere, and it doesn't rot, so it doesn't reintroduce the legacy failure mode - D-014: self-host Lucide via lucide-static, inlined (no CDN, no runtime JS) ### Numbers - 10 plan tasks, all subagent-driven with review gates - 99 tests pass (49 web + 50 sync) - Local full-page smoke render confirmed real Lucide SVGs inline on every page (17 on the dashboard) โ proved the icon pipeline end-to-end, which the unit tests alone couldn't - Pushed to main; Render auto-deploy in flight ## ideas - Once the deploy lands, fill in `icons.yaml` for any app still showing the slate box, and sprinkle GitHub topics on repos I want category tags for. Both set-once. - Favorites was in the mockup but I cut it โ no data source and it'd need upkeep. If I ever want it, the cheap version is a `favorites: [slugs]` list in a hub config file + a star toggle that's really just a filter. Only if I'll actually use it. - The "Needs Attention" card is a nice nudge โ when it's >0 it's red with a count, when clean it's a green check. Worth watching whether it actually drives me to fix orphans/missing-memory, or whether I just ignore it like the legacy hub. ## notes - Reviewer caught a good one on the icon module: it suggested lowercasing the YAML keys to fix `Pips`/`Tanks`, but that would've BROKEN the match (GitHub slugs preserve case, so the cache has `Pips` with a capital). Right fix was case-insensitive lookup on both sides. Good reminder to verify review feedback against the actual data rather than implementing it blindly. - Subagent-driven dev keeps paying off: the model-vs-data reasoning above, the CVE catch last session, the schema-datetime tightening โ all came from the review gates, not the first-pass implementation. - Hub URL unchanged: https://sherpa-hub-6psj.onrender.com (dark UI live after the deploy finishes). Still need to eyeball it + run a fresh sync. --- ## sherpa-hub โ UI v2.1 polish (later same day) Same-day follow-up after I looked at the live v2 dashboard. Three quick refinements, brainstormed with the visual companion then built subagent-driven (5 tasks, 103 tests green). - **Buttons toned down.** The big filled-purple "Open App" bar was too much. Now both buttons are small outlined; "Open โ" keeps a faint violet tint as the primary, "Status" is a quiet outline. - **Light mode.** Added a sidebar Light/Dark toggle (sun-moon). Default dark, remembered in the browser, applied before paint so there's no flash on reload. The light palette is a token-override block plus explicit light variants for the status pills and the open button (their dark translucent colors were invisible on white). - **The Yeti.** Went down a fun rabbit hole on the app icon โ the plain Lucide "mountain" looked like a pencil. Generated a few yetis; landed on a flat-style "Coding Yeti" logo (shades + hoodie + laptop with `</>`). It's now sherpa-hub's icon on its card, the sidebar brand, and the favicon. Built a small extension: icons.yaml entries can now carry an `image` field that renders a standalone PNG instead of a recolored Lucide glyph โ bounded one-app exception, every other app keeps the clean Lucide tiles. ### Notes / lessons - The icon detour was a good reminder that "give me an icon" and "here's a gorgeous AI illustration" are different things. Detailed full-color mascots are hero images, not 24px tiles. The flat two-tone logo (which even shipped with its own small-size preview) was the right kind of asset. - Verified review feedback against real data again: a reviewer wanted me to lowercase the icons.yaml keys to "fix" Pips/Tanks, but GitHub slugs preserve case so the cache really has `Pips` โ case-insensitive lookup was the correct fix, not lowercasing the keys. - Source yeti images saved in D:\dev\sherpa-server\Images\ (yeti-laptop.png is the keeper). Crop recipe is in the v2.1 plan. ### Still to do - Eyeball the live site after deploy: yeti everywhere, smaller buttons, theme toggle. Run a fresh sync so the sherpa-hub *card* picks up the yeti. - If the yeti's dark hoodie is low-contrast on a dark card, there's a ready one-line `.icon-tile:has(.icon-img)` plate fix noted in the plan. --- ## sherpa-hub โ end of day: two bugs on the live v2.1 site (paused, resume tomorrow) Looked at the deployed v2.1 site. Light/dark mode works. Two issues; both root-caused (systematic-debugging), no fixes applied yet โ stopped for the day. **Bug 1 โ yeti not showing.** Server side is verified correct: the image returns 200 image/png live, and the render test proves the sidebar HTML includes the `<img>`. So it's a browser cache (a stale failed-load from the brief window before the asset deployed). To do: hard-refresh (Ctrl+Shift+R) and confirm. Likely no code change. **Bug 2 โ "Open" links go to GitHub, not the live app.** Real root cause: only 13 of 43 apps match a Render service. The matcher requires the Render service name to EXACTLY equal the repo slug, and many don't โ georgianna-mahjong=games-mahjong, ken-half=half-training-app, four CFB* services=cfb-picks, plus ~27 repos not on Render (some at kenlill.com). All of those fall back to GitHub. The hub can't guess these โ only I know the mappings/custom domains. Plan: a manual per-app `url:` override in hub config (like icons.yaml), precedence url > render > github. Need to decide tomorrow: (A) manual override only, or (B) also auto-pull Render custom domains. Leaning A. Then I supply the real URLs and re-sync. Also: the deployed cache is stale (last sync Jun 5) โ a fresh sync is needed after the fix regardless. --- ## games-mahjong + sherpa-hub โ evening wrap **Hub link fix shipped.** Built the per-app `url:` override in icons.yaml (precedence url > Render > GitHub), read at render time so no re-sync needed. First mapping: games-mahjong โ https://mahjong.kenlill.com (verified live there โ "Georgianna's Mahjong Tutorial"; the old Render service is suspended). 55 web tests green, pushed. Remaining GitHub-linking apps just need their real URLs added one line each. **kenlill.com apex finding:** the root domain is serving a *different, empty* sherpa-hub instance โ no data, and NO auth (open to the world, though only the empty state shows). The real hub (sherpa-hub-6psj on Render) has the data and Basic auth. Recommended fix: move the kenlill.com domain to the real hub service. Decide tomorrow. **KenBot โ mahjong: investigated, correctly STOPPED at pre-flight.** Ken dropped `D:\Personal\sherpa-kenbot\INTEGRATION.md` (animated talking help character for the mahjong help screen). Ran the doc's pre-flight gates; both fail: 1. KenBot is React-19-only; the mahjong app is vanilla JS + Vite (no React). Recommended path: a tiny React island just for the bot (package ships built ESM, so no JSX tooling needed in the host). 2. The doc requires an existing AI help backend; mahjong has none (Supabase Edge Functions for game moves only). Natural fit: a new `ask_kenbot` Edge Function calling an LLM with keys in Supabase secrets; the ElevenLabs voice proxy can be an Edge Function too (doc's Django proxy doesn't apply). Two decisions for Ken tomorrow: React-island vs vanilla-build, and which LLM/knowledge should power the bot's answers. Also: re-cloned games-mahjong to D:\Personal\games-mahjong (local copy had been lost in the D: cleanup โ it was a repo-no-local orphan on the hub Health page), and created its missing STATUS.md + MEMORY.md with all of the above. Tomorrow's agenda: hub URL list + kenlill.com domain move + KenBot decisions.