Commit Graph

34 Commits

Author SHA1 Message Date
prosolis
adc0cdff1c Add butterfly/parrot/wiggle-dog companions; scale mascot + tips
- Three new Lottie companions: Wiggle Dog (摇尾狗), Butterfly (蝴蝶),
  Parrot (鹦鹉). Parrot is mirrored via a new `flip` flag on Companion,
  threaded through LottiePlayer (scaleX(-1)) since the asset faces left.
- Mascot size now scales with the viewport: --petal-companion-size
  clamp(10rem, 17vw, 20rem); art, emoji fallback, and the sleep "z" all
  derive from it.
- Larger, more legible tip bubble (1.4rem zh / 1.15rem en, wider bubble).
- Bubbles linger longer for a bilingual ESL read: baseline 9s→14s,
  cheer 6s→9s, per-char 45→55ms, cap 20s→32s.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 11:08:55 -07:00
prosolis
9d6698dcc6 Merge fix/suggestion-renag-anchoring: stop re-nagging resolved suggestions + typographic anchoring
Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 10:40:30 -07:00
prosolis
6783ce7a51 Suggestions: stop re-nagging resolved edits + fix typographic anchoring
Two fixes for the "accept Petal's change, then it nags about the same
sentence moments later" report:

- replacePending now suppresses any freshly-generated suggestion whose
  original->replacement matches one the user already accepted or
  dismissed for that doc. The model has no memory between passes, so
  without this it re-proposes the identical edit on the next checkpoint.

- findRange anchored suggestions by exact string match, which missed
  whenever the model echoed an `original` with plain ASCII (straight
  quotes, --, ...) while the document held the Typography-converted
  glyphs (curly quotes, em-dash, single-char ellipsis). The miss meant
  no highlight AND a silent no-op on accept, which then fed the re-nag
  above. foldTypography canonicalizes those variants (with a source
  index map for length changes) so matching survives the mismatch.

Covered by a server-side regression test (accept+dismiss then re-check
returns nothing) and frontend unit tests for the fold and anchoring.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 10:35:57 -07:00
prosolis
3ac6382696 Merge feat/writer-power-ups: editor power-ups (find/replace, read-aloud, backup, phonetic)
Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 09:47:50 -07:00
prosolis
db737fa612 Editor: Find & Replace, read-aloud, backup, typography, phonetic, org niceties
Writer power-ups (Phase 11), plus the selection-bubble vs copy/paste fix.

- Find & Replace (Ctrl/Cmd+F): SearchHighlight decoration extension +
  FindReplace bar (match-case, replace-all back-to-front, scroll without
  popping the selection bubble).
- Read-aloud (Web Speech, offline) on the word card and selection bubble.
- Keyboard/touch access to the ESL helpers: Ctrl/Cmd+D look up word at caret,
  Ctrl/Cmd+J rewrite selection, touch long-press lookup. Refactored the
  right-click handler into a shared openWordLookup(pos).
- Whole-corpus backup: GET /api/docs/export-all zips every doc (md/docx),
  de-dupes filenames, dated name; sidebar download links. TestExportAll.
- Smart typography input rules (curly quotes/em-dash/ellipsis), ASCII-only so
  CJK is untouched.
- Duplicate doc, sidebar sort (Recent/Title/Longest), toolbar outline popover.
- English phonetic (chosen over pinyin for an English learner): ECDICT-built
  phonetic.json.gz (46,579 words) + Result.Phonetic + WordCard IPA line;
  scripts/build_phonetic.py (full build + --seed fallback).
- Selection bubble no longer blocks copy/paste: deferred to pointer-up and made
  click-through except on its buttons.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 09:47:26 -07:00
prosolis
6e6e4edce7 Editor: font-size presets + image insert/export support
Two editor features that were in flight alongside the sound work:
- FontSize TipTap extension (rides on textStyle) with Small/Normal/Large/
  Title presets in the toolbar; StatusBar + CSS support
- Image handling: internal/images handler, upload route + config, client
  API, EditorCore wiring, and md/html/docx export support for images

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 09:05:40 -07:00
prosolis
58c326d0cf Notification sounds: hand-picked mp3 palette + error/milestone cues
Replace the synthesized WAV blips with curated mp3 effects:
- error ("haiya") plays on LLM-down/timeout and save failures, debounced
  and on-transition-only, with a reassuring bilingual bubble
- milestone (Chinese fanfare) now fires every 100 words
- all other notifications draw from a rotating pop pool that never repeats
  the last sound, so batches of suggestions/tips stay lively

playPop() replaces the per-suggestion-type sound map; thread llmDown through
PetalCompanion -> useCompanion for the error cue. Old synth WAVs removed.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 09:05:05 -07:00
prosolis
045ec19b92 Petals: 2D sprite blossoms instead of shader blobs
The procedural WebGL petals read as faint smudges. Replace with a 2D
canvas that pre-renders real notched sakura petal sprites (tip→base
gradient, soft central sheen, transparent edges) and blits falling,
rotating, swaying instances. Count scales with viewport; pauses when
hidden; skipped under prefers-reduced-motion.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 07:47:13 -07:00
prosolis
f6fa73b17b Petals: saturated blossom tints + glossy shading
The pale pastel tints washed out to grey over the cream canvas. Use
saturated cherry-blossom colors, add a luminous centre + soft base
shadow per petal, and render at full opacity so they read as colorful.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 07:41:53 -07:00
prosolis
8cf78130bf Merge: ambient WebGL petals + cute notification sounds
Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 07:34:46 -07:00
prosolis
2487e73551 Ambient WebGL petals + cute notification sounds
Add a fixed, GPU-driven petal layer that drifts soft blossoms over the
page (sparse, translucent, paused when hidden, skipped under
prefers-reduced-motion). Add a synthesized cute sound palette — bubble
pop, water droplet, soft bell, sparkle, cheer — generated as embedded
WAV assets (scripts/gen_sounds.py) and played through a quiet Web Audio
bus with soft-clipping and a persisted mute toggle in the status bar.

Sounds fire per suggestion type when fresh advice arrives (staggered,
old pending advice stays silent) and on companion bubbles by tone.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 07:34:23 -07:00
prosolis
0a1ea225dd Merge: companion rules-based writing advice + tests 2026-06-26 07:16:45 -07:00
prosolis
69ecb79da1 Companion: rules-based, context-aware writing advice
Replace the kitten's random generic tips with a deterministic, LLM-free
prose checker that reads the actual document and surfaces one gentle,
bilingual note at a time — quoting a slice of her own sentence so the
advice is clearly about *this* draft.

Rules (all conservative, tuned for a Mandarin-native ESL writer):
run-ons, comma splices, unclear antecedents, Oxford comma, a/an,
uncountable plurals, capitalize languages/days/months, subject-verb
agreement, plural-after-number, double determiner, "there is" + plural,
its/it's, than/then, comma-after-transition, doubled words, lowercase
"i", sentence capitals, and spacing around punctuation.

Each rule is paired with false-positive guards (e.g. "a university",
"After dinner, I went home and read", existing Oxford lists) and pinned
by a 45-case Vitest suite (npm test).

Also: bubbles now linger proportional to how much there is to read
(zh+en) and pause-on-hover, so denser advice no longer vanishes mid-read.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 07:16:23 -07:00
prosolis
f3c4fe2c22 Merge Phase 10: organization & polish (search, tags, touch, warm failures)
Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 06:30:21 -07:00
prosolis
9e141e4169 Phase 10: organization & polish — cross-doc search, tags, touch, warm failures
Cross-document FTS5 search (trigram tokenizer for EN + space-free CJK, kept in
sync by triggers, back-filled from existing docs). GET /api/search uses the FTS
index for queries >=3 runes and a LIKE fallback for 1-2 (so 2-char Chinese words
resolve); snippets are built in Go with rune-aware boundaries and sentinel
highlights.

Tags: user-scoped tags + document_tags join (both cascade), idempotent
create/assign, per-tag doc counts. Doc list and search carry each doc's tags
(one tagsByDoc query). Frontend: useTags, TagChip/TagPicker/SearchBox, rewritten
DocList with chips + filter bar + search.

Tablet/touch: responsive sidebar drawer (hamburger + scrim <768px), coarse-
pointer tap targets, tap-to-open + outside-pointerdown-close for suggestion
cards.

Warm LLM-down state: useCheckpoint llmDown flag drives a gentle bilingual
StatusBar note (writing still saves locally).

Migration 0004 (tags + FTS). Tests: tags lifecycle, search EN/CJK/LIKE/update-
reindex. go build/vet/test, tsc, vite all clean; verified live on deployment host.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 06:29:56 -07:00
prosolis
60eba25fee Phase 9: ESL superpowers — Chinese gloss + tone-rewrite
Inline Chinese gloss (offline) and a "say it more naturally" / tone-rewrite,
the two ESL features for the Mandarin-speaking writer.

Gloss: embedded English→Chinese dictionary (gloss.json.gz, 57k common words
built from ECDICT via scripts/build_gloss.py). lexicon gains Gloss()/Result.Gloss
and a lightweight GET /api/gloss/{word}; the right-click WordCard leads with the
中文; GlossTip shows it on a 350ms hover (reuses wordAt, so CJK is never glossed).
Offline + instant, works with the LLM down.

Rewrite: selecting text pops a SelectionBubble (更自然 + the tone vocabulary);
picking a style calls POST /api/docs/:id/rewrite (llm.RunRewrite, stateless,
owner-scoped) and shows a RewritePreview (original→rewrite, accept/cancel/retry).
Accept applies it in-editor.

Tests added in lexicon and suggestions. go build/vet/test, tsc, vite all clean;
live smoke vs a fake vLLM verified gloss + rewrite + 400/404/502 paths.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 00:07:26 -07:00
prosolis
8e1111d768 Phase 8: version history + export (trust foundation)
Version history: new document_versions table (migration 0003) holding
full-body snapshots that cascade with the doc. Throttled auto-snapshots
on save (>=3min apart, max 40/doc, pruned), explicit manual restore
points, and a pre_restore safety copy taken before each restore so
restoring is itself undoable. Endpoints under /api/docs/:id/versions,
all owner-scoped. Empties and bare renames never snapshot.

Export: pure-Go Tiptap-JSON -> Markdown / HTML / plain-text / docx
(no cgo/pandoc, single-binary intact), CJK-safe with RFC 5987
filenames. docx is a hand-built OOXML zip. PDF is handled client-side
via the browser print dialog + an @media print stylesheet so CJK
renders with the reader's own fonts.

Frontend: ExportMenu (downloads + Print/PDF) and HistoryPanel
(snapshot list, preview, restore) wired into the title row; bilingual
zh-first to match chrome. Restore remounts the editor via editorEpoch.

Stop saving empty docs: blank Untitled drafts now reuse-on-create and
self-discard when navigated away from (refs avoid stale closures).

Tests: versions_test.go, export_test.go (incl. valid-zip docx).
go build/vet/test, tsc, vite build all clean; live end-to-end smoke
verified snapshot/throttle/restore/export.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 23:44:15 -07:00
prosolis
cf7720ea77 Merge: editor tone, word lookup, expanded stats
Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 23:23:34 -07:00
prosolis
4c288834c0 Editor: document tone, right-click word lookup, expanded stats
Four enhancements to make the editor fit real school usage:

- Per-document tone (academic/professional/casual/humorous/creative/
  persuasive/general): new documents.tone column (migration 0002), threaded
  through the docs API, a bilingual ToneSelect dropdown on the title row, and
  injected into the grammar-checkpoint LLM prompt so advice fits the register.
  The voice pass stays tone-agnostic.

- Right-click word lookup: a new offline `lexicon` package serves definitions
  (Wordset, modern ESL-friendly glosses) and synonyms (WordNet synsets first,
  then frequency+stopword-ranked Moby for breadth) from gzipped embedded data,
  behind /api/word/{word} with light morphology. The WordCard popover shows the
  definition and tappable synonym pills that swap the word in place.

- Expanded writing stats: clicking the word count opens a StatsPanel with page
  count, sentences, paragraphs, reading time, average word length, word variety,
  and Flesch-Kincaid reading level — all computed client-side.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 23:22:55 -07:00
0d59b645bb Delete petal-spec.md 2026-06-26 06:00:21 +00:00
prosolis
95123e8c49 Grow the companion, fix idle fallback, add update banner
Companion: bump the corner mascot ~12% (128→144) and make animation
resolution robust — fall back to the idle clip when a mood has no clip of
its own, and pin the always-asleep cat to one pose so its Lottie never
reloads. This stops a bare emoji from replacing the cute companion on idle.

Update notifications: serve a build id at /api/version (a hash of the
embedded dist/index.html, which changes on every frontend deploy). The
client records its load-time id, polls every 90s (and on tab focus), and
floats a Mandarin-first "new version available" banner offering a refresh
when the id changes.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 22:45:23 -07:00
prosolis
a634994d25 Companion: double the mascot size + drop the sleepy-face nap
- Mascot badge 64→128px (Lottie 56→112, emoji fallback 32→64); zzz glyph
  scaled + repositioned to match. It read too small on screen.
- Awake companions (no sleeping clip, e.g. Happy Dog) no longer show a 😴
  face or drifting "z" when the engine naps them — they hold their idle pose
  via a renderMood redirect. Only the always-asleep cat (real sleep clip)
  keeps the zzz, preserving that gag.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 22:35:38 -07:00
prosolis
660f00d452 Phase 7: browser-side spell check (nspell, en-US)
Vendored Hunspell en.aff/en.dic into web/public/dictionaries/en/ (served as a
static asset + embedded in the binary, kept out of the JS bundle). dictionary-en
moved to a devDep — only used to source the files.

- useSpellChecker (App-level, loads once/session): fetches the dict, builds an
  nspell instance, replays a localStorage personal word list; addWord persists
  and bumps a version so consumers re-decorate. Ambient types in
  src/types/nspell.d.ts (the package ships none).
- SpellCheck Tiptap extension: misspellings as ProseMirror decorations (no
  stored marks), recomputed on edit / caret move / checker swap. Latin-only
  tokenizer so CJK is never flagged; skips short tokens + all-caps acronyms;
  exempts the caret word to avoid mid-typing jitter. Reuses mapOffset (now
  exported from SuggestionHighlight); wordAt resolves the exact span on click.
- MisspellCard: soft rose wavy underline, bilingual popover with up to 5 nspell
  corrections (click to replace) + add-to-dictionary. Closes on outside-pointer,
  edit, or doc switch.

Chinese spell check intentionally omitted — nspell is dictionary-based and
English-only; Chinese typos (homophone 别字) need an LLM, out of v1 scope.

tsc/vite/go build+vet clean; live server serves both dict files; nspell
behavior smoke-tested.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 22:18:21 -07:00
prosolis
183219b5de Companion: selectable companions with a picker
Click the mascot to open a bilingual picker and switch between the Sleepy Cat
(always asleep, still talks in her sleep) and the Happy Dog (awake/bouncy).
Choice persists in localStorage.

- companions.ts is the roster; add one by dropping a pure-vector Lottie JSON
  in animations/ and appending a COMPANIONS entry.
- napping = companion.alwaysAsleep || mood === 'sleeping' drives the sway/zzz.
- Removed the old single MOOD_ANIMATIONS map (animations/index.ts).

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 22:07:48 -07:00
prosolis
97f99b27e4 Companion: wire real Lottie assets — the always-sleeping cat
- LottiePlayer now auto-crops each asset to its content bbox (union getBBox
  across 6 frames -> square viewBox), so stock files with empty artboard
  padding fill the badge instead of floating tiny in the middle.
- Wire sleeping-cat.json to every mood: she's always asleep in the corner yet
  still mumbles tips and cheers through the bubble. napping hardcoded true so
  she keeps the calm sway + drifting zzz even while "talking".
- happy-dog.json parked in the folder as an awake-mascot alternate.
- Enable resolveJsonModule for the JSON imports.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 22:03:02 -07:00
prosolis
8a32dde587 Add companion kitten — reactive corner mascot
A cozy bottom-right mascot that reacts to the writing session: cheers on
accepted suggestions and word-count milestones, drops Mandarin-first writing
tips, suggests screen breaks after long stretches, and naps when idle.

- useCompanion: library-agnostic behavior engine (priority + cooldown paced
  so it never nags); tips.ts holds all copy, bilingual zh-first.
- LottiePlayer wraps lottie-web's light build (offline, no eval/CDN) so it
  bundles into the Go binary. Ships with an emoji-kitten placeholder per mood;
  dropping a Lottie cat JSON into animations/index.ts is the only swap needed.
- Speech bubble uses the CJK-first font stack (spec Note #17).

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 21:43:58 -07:00
prosolis
534f7262ab Phase 6: design polish — accept confetti + distraction-free mode
- Accept confetti: CSS-only 4-dot burst spawned at the accepted card,
  trajectory via inline --dx/--dy, auto-cleared after 720ms.
- Distraction-free mode: editor focus collapses the doc-list sidebar
  (width→0 slide), canvas re-centers full-width; Escape or a click
  outside the canvas (header/gutter/status bar) restores it.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 21:21:46 -07:00
prosolis
0fa70979a0 Phase 5: voice consistency pass
Tier-1 voice-consistency pass: whole-document LLM review surfacing passages
that read tonally out of place (formal/over-polished/paraphrased-too-closely),
as honey-decorated `voice` flags with no correction (awareness-only).

- internal/llm/voice.go: RunVoice sends the whole document (no TruncateDoc),
  MaxTokens 2048, 20s per-doc floor (VoiceInterval). Standalone voice prompt
  in prompts.go (not bundled with the grammar checkpoint, per spec).
- internal/suggestions: POST /api/docs/:id/voice. replacePending is now
  family-scoped (pendingScope) so grammar and voice never clobber each other's
  pending flags; both passes return the unified pending set. check/voice share
  one runPass helper. TestVoicePassCoexists covers both directions.
- Frontend: api.voiceDoc, useCheckpoint voicing/runVoice, honey "Check my
  voice" toolbar pill, breathing honey dot in StatusBar.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 21:16:53 -07:00
prosolis
3c5f3ecb96 Phase 4: Ask Petal SSE chat
Conversational follow-up on a suggestion, streamed token-by-token.

Backend (interface-only; handlers never touch a concrete LLM client):
- internal/llm/chat.go: StreamAskPetal with conversational sampling
  (max_tokens 512, temp 0.7, rep 1.15, top_p 0.92, stop "\n\n\n"),
  reusing AskPetalSystemPrompt + TrimHistory.
- internal/suggestions/chat.go: POST /api/suggestions/:id/chat. One
  user-scoped join loads the suggestion + parent content_text;
  surroundingParagraph extracts the \n\n-bounded paragraph at from_pos
  (whole-doc fallback when unlocated) and injects it server-side.
  Streams event: token / event: done SSE frames with JSON-encoded data
  so token newlines can't break framing; real http.Flusher per chunk.
  LLM-unreachable -> 502 before SSE headers; unknown suggestion -> 404.

Frontend:
- streamSuggestionChat: fetch + ReadableStream SSE parser (not
  EventSource, needs POST), abortable.
- AskPetal.tsx: whole conversation in component state (no persistence,
  cleared on close), Petal's first bubble pre-seeded with the
  explanation, rose/lavender bubbles, CJK font stack on the bubbles
  only (Note #17), streaming caret.
- SuggestionCard "Ask Petal" pill pins the card open while chatting
  (hover-close suppressed, click-away closes) and widens it to 340px.

Tests: chat_test.go covers streamed-text concat + done event,
server-side context injection on the system message, sampling params,
404, and surroundingParagraph. go build/vet/test clean, tsc clean,
vite build OK. Live SSE smoke-tested against a fake streaming vLLM:
tokens flushed individually through the chi middleware stack, done
terminator, 502 on LLM-down, 404 on unknown suggestion.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 21:05:39 -07:00
prosolis
3f7e705028 llm/ollama: disable thinking so reasoning models return content
Qwen 3.5 — the spec's recommended model — is a reasoning model. With
thinking on, Ollama streams its chain-of-thought into a separate
`thinking` field and hits num_predict before emitting any answer into
`content`, so Complete() got an empty string and the checkpoint failed
with "no JSON object in model output". Sending `"think": false` on every
/api/chat request fixes it; non-thinking models (qwen2.5) ignore the flag.

Validated end-to-end on deployment hardware (Ollama, qwen3.5:9b): the
grammar checkpoint now caught all five ESL errors in a 3-sentence sample
with correct JSON and string-anchoring, ~8.5s warm.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 20:53:01 -07:00
prosolis
a4069d5755 Phase 3: LLM grammar checkpoint
Backend (internal/llm): backend-agnostic LLMClient interface + factory
with vLLM (OpenAI-compat) and Ollama (native) clients, each Complete +
Stream. prompts.go holds the checkpoint and Ask Petal templates;
checkpoint.go salvages JSON from model output (brace-matched), enforces a
per-doc 30s RateLimiter, and truncates the doc to a latency cap.

internal/suggestions: POST /api/docs/:id/check runs a checkpoint and
replaces the doc's pending suggestions in one tx (accepted/rejected kept
as history); GET /api/docs/:id/suggestions lists pending;
POST /api/suggestions/:id/{accept,dismiss} resolves one. Throttled checks
return the current set rather than erroring.

Frontend: useCheckpoint (4s debounce, loads existing on open, stale-guard
tokens); SuggestionHighlight renders ProseMirror decorations re-anchored
by the `original` string on every doc change (not stored marks), with
precise textblock-offset→PM-position mapping; SuggestionCard shows the
type tag + diff + explanation and applies the replacement in-editor on
accept; breathing rose checkpoint dot in the StatusBar; fade-float +
breathe animations.

Tests: llm parse/rate-limit/truncate; suggestions full flow + rate-limit
over httptest with a stub client. Smoke-tested end-to-end against a fake
vLLM endpoint (anchoring verified) and the LLM-unreachable 502 path.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 20:45:30 -07:00
prosolis
5e00cdce88 Phase 2: document CRUD + auto-save
Backend internal/docs: chi sub-router (list/create/get/update/delete)
mounted at /api/docs, scoped to the local user. Create uses RETURNING;
update is a COALESCE partial-update so rename and full editor save share
one PUT. JSON 404/400 errors; handlers_test.go walks the lifecycle.

Frontend: api/client.ts, useAutoSave (1.5s debounce + saveNow flush
before doc switch), EditorCore (Tiptap StarterKit/Underline/TextAlign/
Placeholder/CharacterCount) + Toolbar, DocList/DocListItem, StatusBar,
and an App.tsx that orchestrates load/select/create/delete with
optimistic sidebar patching. content + content_text + word_count are
emitted together on every edit. .petal-prose styling (Lora body, Nunito
headings).

Verified: tsc clean, vite build, go build, full CRUD smoke test incl.
CJK title round-trip and SPA serve.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 20:30:39 -07:00
prosolis
9c98e97030 Phase 1: data layer (SQLite, migrations, models, seed) 2026-06-25 20:25:14 -07:00
prosolis
e72d48d64e Phase 0: project scaffold
- Go module + chi server with embedded SPA serving and /api/health
- internal/config env loader (local-dev defaults; auth/copyleaks deferred)
- React 19 + Vite 6 + Tailwind v4 frontend with full Petal design tokens
- Frontend embedded into the binary via web/embed.go (go:embed all:dist)
- README dev workflow, .env.example, BUILD_PLAN progress tracker
- Verified end-to-end: binary serves health, embedded SPA, and SPA fallback
2026-06-25 20:20:04 -07:00