Commit Graph

14 Commits

Author SHA1 Message Date
thejayman77 89c0fbe1f6 Sync repo to deployed state: SEO recovery, Publishing Desk, Play games, emoji picker
The deploy pipeline runs from the working tree, so a wave of shipped features
had never been committed. This snapshots git to what's actually running.

SEO impression recovery (live + verified):
- Duplicate /a/{id} now 301-redirect to their canonical twin instead of 404
  (a hard 404 silently dropped already-indexed URLs and tanked impressions).
- Dedup representative selection reworked: accepted/serveable -> established
  rep (URL stability) -> quality score, so an accepted page never retires to a
  rejected rep and an indexed canonical doesn't churn when a newer twin arrives.
- HEAD /a/{id} returns the same status as GET (api_route GET+HEAD) instead of
  falling through to the static mount and 404ing.
- `dedup --force-recluster`: cycle-locked, model-free re-cluster to re-apply the
  policy to the existing corpus (shared cycle_lock context manager).
- CLI honors GOODNEWS_DB for its default --db (was silently ignored).

Publishing Desk (admin tool to post highlights to X via Web Intents):
- publishing.py queue/rank/handle-resolution; admin UI; full searchable emoji
  picker (bundled data, no CDN) for the blurb editor.

Play games + site:
- Bloom (word-wheel), Memory Match, daily ritual set, Zen Den (dev-gated).
- English-only language gate; source prospecting; paywall + dedup hardening.

Tests: full suite green (349). Ignores tightened (node_modules, data/*.db).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:32:27 -04:00
thejayman77 64339aafb0 Games: in-progress hub status + distribution-aware word-search placement (Codex)
- Play hub: word cards now surface IN-PROGRESS games too (not just won/lost) so
  "continue on another device" shows at a glance — card reads "5:3…" and the
  selection option says "Continue · 3/6".
- Word Search generator: replace "prefer any crossing" with a SCORED placement —
  score = overlap*4 - local crowding (filled neighbours that aren't crossings) —
  then pick among the best ~20%. Keeps the organic interlocking but spreads words
  across the board instead of clumping around the first-placed (longest) words.
  Every word still placed (tests green). NOTE: changes today's grid layouts, so
  an in-progress word search resets once.

237 pytest + 11 vitest green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 15:18:04 -04:00
thejayman77 de59cf49d8 Play hub: sync game status at the hub, not only on game-open
Reported sync gaps: the hub showed "Play" until you opened each game, and a
game synced only if you'd reopened it on that device (so a desktop win that was
never reopened never reached the server). Root cause: the /play hub read card
status from localStorage only and never talked to the server — sync happened
exclusively inside the game components on mount.

Now the hub itself reconciles every game (word 5/6 + wordsearch small/med/large)
with the server on load (signed-in): pushes this device's local state and writes
the merged result back to localStorage, then refreshes the cards. So statuses
appear cross-device WITHOUT opening each game, and local progress uploads even
for games not reopened. Word Search card status derived from the (completion-
gated) ms. 237 pytest + 11 vitest green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:59:01 -04:00
thejayman77 28b0ef6766 Word Search select: put Today's theme in a standout accent card
The theme was floating between the title and the size options; give it its own
soft accent-tinted card so it reads as the day's headline, distinct from the
size choices.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 09:23:11 -04:00
thejayman77 52a8bc5326 Word Search mobile: focused viewport, theme placement, unique-per-size words
Per field feedback.
* Each day is now THREE distinct puzzles: the three sizes draw DISJOINT word
  slices from a date-shuffled pool (small/med/large = 6/9/13, sum 28 unique).
  Curated fallback themes expanded to 30 words each; LLM proposals accepted only
  if they supply >= 28 unique words, else fall back. No more repeats across sizes.
* Word Search is now a focused game screen on mobile (same as Daily Word): body
  scroll locked + footer hidden (generalized .playing-game), and the grid sizes
  to the largest square that fits between the theme and the palette (container
  query) — the whole puzzle is on screen, no page scroll.
* Theme placement: full "Today's theme · <name>" on the size-selection screen;
  just the theme name on the puzzle itself, saving vertical space for Large.
* cosy → cozy. 🇺🇸

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 09:15:06 -04:00
thejayman77 1dda91fd96 Play: app-safe in-app Back + canonicalize shareable URLs (Codex audit)
* In-app Back arrow is now deterministic on deep links: if there's in-app history
  it pops (history.back); otherwise it navigates to the parent screen (game →
  selection → hub) instead of leaving the site. Device Back stays native.
* Canonicalize ?game/?v: unknown game → hub; an invalid v for the game (e.g.
  word&v=large or wordsearch&v=5) → the game's default, via replaceState so the
  URL is clean and local-storage keys/status match. Derived variant/size are also
  clamped so a bad URL can never crash the game with an invalid variant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 08:57:12 -04:00
thejayman77 d6015dd44e Play: make Back step through the game screens (URL-driven views)
The Hub → Game Selection → Game screens were internal $state with no history
entries, so the device/browser Back button skipped straight out of /play. Now
the screen is derived from the URL (?game=…&v=…) and forward moves use goto, so
each screen is a real history entry: Back goes Game → Selection → Hub → site,
matching the rest of the app. The in-app Back button uses history.back() so it
mirrors the device button. Statuses refresh on every navigation (incl. Back).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 08:50:48 -04:00
thejayman77 067e77ed5a Daily Word mobile: true viewport + flat warm keyboard + height-aware tiles
Make Daily Word feel like a focused mobile app screen, not a page with a keyboard.
* True viewport: while view==='play' && game==='word', a $effect locks body scroll
  and hides the site footer (mobile only), so the keyboard is genuinely pinned, not
  riding the document scroll. Effect cleanup ALWAYS removes the class on re-run or
  unmount, so leaving /play (back button OR any navigation) can never strand it.
* Keyboard restyled on-brand + modern: flat off-white (--surface) keys with a
  hairline border, soft 11px radius, no heavy raised shadow, ~46px tall, ↵ / ⌫
  glyphs, centered (max-width 430) instead of a full-bleed beige slab.
* Tiles now size to fit BOTH width and the height left above the keyboard
  (--tile = min(cap, width/cols, (100dvh-budget)/rows), gap 4px), so the active row
  and keyboard are always visible — Long Word's 6×7 gets slightly smaller tiles.

Real-device Safari/Chrome is the final check (100dvh + safe-area handling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 07:27:39 -04:00
thejayman77 a52226ce61 Play: Game-Selection step + mobile sticky on-brand keyboard
Mobile polish for the games.
* New flow: Play Hub → Game Selection → Game. The 5/6 (word) and S/M/L (search)
  pickers move OFF the game screen onto a selection screen that shows each
  option's today-status. Back-button reads "Game Selection" in a game and
  "Play Hub" on the selection screen — buys vertical room for the keyboard.
* Daily Word on mobile now fills the height: the board scrolls in the middle and
  the keyboard is pinned at the bottom, always reachable (no scrolling down to
  type). Desktop stays inline.
* Keyboard restyled on-brand: warm cream keys (was cool generic grey), the label
  font, an accent-tinted Enter, and the same green/gold/grey feedback as the
  tiles; full-bleed, tactile press, safe-area aware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:58:44 -04:00
thejayman77 b909b7e64b Word Search bug-fixes + Codex polish
Two reported bugs, same root cause: the fixed-cell grid overflowed its wrapper
on Large, so (a) the last column spilled past the border and (b) the pointer→cell
math drifted across the row, recording finds "off by a letter".
* Grid now uses 1fr columns with max-width = n·32px: the board grows with the
  grid and can never overflow (shrinks to fit a narrow phone instead).
* cellAt() accounts for the grid padding/border, so selection is exact edge-to-edge.
* restore() now validates each saved find against the CURRENT grid and drops any
  whose cells no longer spell the word — clears stale highlights if the day's
  puzzle changed.

Codex follow-ups:
* _ws_propose now requires >= large.count + 4 valid words before accepting an LLM
  proposal (else falls back to a curated theme), so a thin LLM result can't
  underfill Large. Added a thin-LLM fallback test.
* Cleaned Svelte warnings: removed the now-unused .gamecard.soon CSS, added an
  ARIA role/label to the grid, declared gridEl with $state. Build is warning-clean.
* Added a stale-load guard in WordSearchGame.load() so rapid size switches can't
  let an older request overwrite the newer selection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:09:33 -04:00
thejayman77 9f7eb11155 Word Search polish: constant cell size, 28-word themes, per-size variety, palette
Playtesting fixes:
* Constant cell size (~32px) — the board GROWS with the grid instead of shrinking
  letters into a fixed box. Fixes Small's oversized spacing; on a narrow phone the
  largest grid gently scales to fit (the standard word-search compromise).
* Themes now gather ~28 words (LLM asked for 28; curated fallbacks ~22 each), and
  each size samples its OWN subset — so every tier is a distinct puzzle. Large is
  now reliably full (14 words on 14×14), fixing the "13 words / 11 listed" mismatch.
* Tiers: small 8×8/6, med 11×11/10, large 14×14/14.
* Word list is now a framed "Find these · n/total" palette panel (pill chips that
  take on each found word's colour) instead of loose text under the grid.
* Size chips use qualitative labels (cosy / balanced / a longer sit) so no count
  can ever contradict the actual puzzle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 20:57:44 -04:00
thejayman77 f43f645d69 Games batch: neutral words/themes, Word Search sizes + per-word colours
From playtesting findings:
* Pools nearly doubled (115/104 → 228/201) with calm/neutral everyday words
  (claps, dance, drench, beach…), not just strictly-upbeat ones — more variety,
  ~7-month runway. The post-solve "why" prompt reworded to fit neutral words.
* Word Search now stores one theme + word list per day; the grid is built per
  request for three SIZE tiers — Small (8×8, 6 words), Medium (11×11, 9),
  Large (14×14, 13). Large packs more words = a longer sit ("too fast" fix).
  All sizes share the day's theme; every size still code-placed + solvable.
* Word Search themes can now be neutral everyday scenes ("Around the house",
  "At the beach", "In the kitchen", "A walk outdoors", "Making music"…), not
  only hopeful — same shape as the articles.
* Each found word gets its own colour from a calm palette, in the grid and its
  word-list chip. Per-size local progress + best time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 20:32:53 -04:00
thejayman77 90cd0291a3 Play hub Phase 2: Word Search (LLM theme/words, code places the grid)
A calm second daily game, same philosophy as Daily Word — LLM proposes, code
disposes.

* LLM proposes a hopeful theme + ~8 words; code validates (alpha/length/dedup)
  and PLACES every word in a date-seeded grid, so the puzzle is always solvable.
  Curated fallback themes if the LLM is thin. Only placed words are returned;
  the solution cells (placements) are never sent to the client.
* GET /api/puzzle/wordsearch → {theme, words, grid, size}. No answer to hide:
  the grid and word list are meant to be seen — the play is finding them, which
  the client validates by reading the selected line off the grid.
* WordSearchGame.svelte: pointer-drag selection snapped to the 8 straight
  directions (mouse + touch), found-word highlighting, no-fail, no pressure
  timer — time is recorded quietly and shown at the end with a personal best.
  Spoiler-free share. localStorage progress (restores found cells + timer).
* Hub's Word Search card is now live with today's status; cycle pre-generates
  both games with the LLM.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 20:15:19 -04:00
thejayman77 215a5c4d64 Play hub + Daily Word game (Phase 1 of the games feature)
A calm /play space — "after the brief, a small thing to enjoy." Framework-ready
for more games (Word Search next; zen/coloring later).

* Daily Word (5 letters / 6 guesses) + Long Word (6 / 7) — same Wordle mechanic,
  Upbeat Bytes flavor (no "Wordle" in the UI). Hopeful answers; after solving, a
  one-line "why this word matters."
* LLM proposes, code disposes: answers are picked deterministically by date-seed
  from a hand-curated hopeful pool that's pre-validated ⊆ the guess dictionary
  (always typeable), avoiding recent repeats; the LLM only adds the optional
  "why" (with fallback). daily_puzzles(date, game, variant, payload) stores them
  so everyone gets the same daily; the cycle pre-generates with the "why".
* Bundled guess dictionaries (words-5/6.json, ~12.6k/22.4k) for client-side guess
  validation — never the LLM. Answer lightly obfuscated (base64) in the payload.
* Private, gentle stats (played/solved/streak, guess distribution); spoiler-free
  emoji-grid share. No leaderboard, no timer, no streak-loss drama.
* Play in the bottom nav (replacing Browse, still on the lane rail) + the header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:06:20 -04:00