Commit Graph

4 Commits

Author SHA1 Message Date
prosolis
82e2bcc777 Suggestions: right-margin comment rail + Mandarin explanations
Surface every outstanding suggestion as a card in the right-hand
whitespace, vertically aligned to the text it flags — so the writer sees
the whole queue at once instead of hovering each highlight. Cards stack
with collision avoidance, link both ways with their highlight (hover/click
↔ soft text wash, driven through the decoration plugin so it survives
edit repaints), and carry the same Accept / Dismiss / Ask Petal actions.
The rail is a progressive enhancement: it mounts only when there's room
beside the editor, otherwise the existing inline hover card is unchanged.
Stacked cards that reach the bottom-right corner tuck behind the
companion mascot (z-order).

When a card is expanded, the Ask Petal bubble now opens with the
Simplified-Chinese translation of the explanation (the English stays in
the card body) instead of repeating the same text twice — a new
POST /api/suggestions/{id}/translate one-shot LLM endpoint, loaded
lazily on open with an English fallback.

Verified live against the local LLM via the uitest harness: rail
stacking, hover↔text wash, expand/Ask Petal, accept-from-rail, narrow
fallback, and the Mandarin bubble.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 12:23:02 -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
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
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