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
This commit is contained in:
prosolis
2026-06-25 23:22:55 -07:00
parent 95123e8c49
commit 4c288834c0
23 changed files with 1021 additions and 32 deletions

View File

@@ -8,7 +8,7 @@ const checkpointSystemPrompt = `You are a warm, encouraging writing assistant he
`Analyze the text below and identify up to 5 issues: grammar errors, unnatural phrasing, ` +
`incorrect idiom usage, or unclear sentences that are common ESL patterns.
Be specific, friendly, and explain WHY each suggestion improves the writing.
Be specific, friendly, and explain WHY each suggestion improves the writing.%s
Respond ONLY with valid JSON. No preamble, no markdown fences. Format:
{
@@ -24,11 +24,32 @@ Respond ONLY with valid JSON. No preamble, no markdown fences. Format:
If the writing looks good, return: {"suggestions": []}`
// toneGuidance returns a sentence steering the checkpoint toward the writer's
// chosen tone, or "" for the neutral default. The clause is appended to the
// checkpoint instructions so the model's phrasing suggestions fit the target
// register (e.g. an academic essay vs a casual journal). Unknown values fall
// back to no steering, so a stray tone string is harmless.
func toneGuidance(tone string) string {
clause, ok := map[string]string{
"academic": "formal, academic, and objective — suited to a school essay or research paper",
"professional": "polished and professional — suited to a workplace email or report",
"casual": "relaxed, friendly, and conversational",
"humorous": "light, playful, and good-humored",
"creative": "vivid, expressive, and imaginative — suited to a story or personal narrative",
"persuasive": "confident and persuasive — suited to an argument or opinion piece",
}[tone]
if !ok {
return ""
}
return "\n\nThe writer wants this document to read as " + clause + ". When phrasing could " +
"be improved, prefer suggestions that fit that tone, and gently flag wording that clashes with it."
}
// CheckpointMessages builds the message array for a grammar checkpoint over the
// given (already-truncated) document text.
func CheckpointMessages(contentText string) []Message {
// given (already-truncated) document text, steered toward the document's tone.
func CheckpointMessages(contentText, tone string) []Message {
return []Message{
{Role: "system", Content: checkpointSystemPrompt},
{Role: "system", Content: fmt.Sprintf(checkpointSystemPrompt, toneGuidance(tone))},
{Role: "user", Content: contentText},
}
}