Add deterministic mechanics suggestion family (rule-based, no LLM)
Reuse the companion's prose.ts rules engine as the single source of
deterministic detection instead of duplicating it. Applyable rules now
also emit exact-span fixes (original -> replacement) that surface as
suggestion cards; awareness-only rules (run-ons, splices, ...) stay
companion bubbles. The companion hides fix-bearing hints so a span is
never both a bubble and a card.
Spans are widened to a distinctive phrase ("a old" -> "an old",
"She have" -> "She has") so they re-anchor by string in the editor; a
lone lowercase "i" stays awareness-only since a single char can't anchor.
Backend: detection lives client-side, so the new persist-only
POST /docs/{id}/mechanics endpoint receives findings and stores them as
the 'mechanics' family with their exact offsets. It honours
actioned-suppression, leaves the LLM families untouched, and a checkpoint
no longer wipes it. fetchPending dedupes spans with mechanics winning any
collision against an LLM card (its span is exact). Migration 0008 adds the
'mechanics' suggestion type.
Client renders the mechanics fixes immediately (no LLM wait) and the cards
use a calm sage "Tidy-up" accent.
Verified end-to-end in a real browser on millenia: detect -> persist ->
render -> accept applies the fix.
Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
This commit is contained in:
@@ -336,6 +336,35 @@ CREATE INDEX idx_vocab_due ON vocab_words(user_id, due_at);
|
||||
name: "0007_vocab_definition",
|
||||
stmt: `
|
||||
ALTER TABLE vocab_words ADD COLUMN definition TEXT NOT NULL DEFAULT '';
|
||||
`,
|
||||
},
|
||||
{
|
||||
// Deterministic mechanics pass. Adds a 'mechanics' suggestion family for
|
||||
// rule-based fixes (doubled words, spacing/punctuation, lowercase "i",
|
||||
// curated confusables) detected in pure Go — no LLM. As with 0005, the
|
||||
// `type` CHECK can't be ALTERed in place, so rebuild the table with the
|
||||
// extended constraint, copy every row across, and recreate the index.
|
||||
name: "0008_mechanics_suggestion_type",
|
||||
stmt: `
|
||||
CREATE TABLE suggestions_new (
|
||||
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
||||
doc_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
||||
from_pos INTEGER NOT NULL,
|
||||
to_pos INTEGER NOT NULL,
|
||||
original TEXT NOT NULL,
|
||||
replacement TEXT NOT NULL,
|
||||
explanation TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK(type IN ('grammar','phrasing','idiom','clarity','voice','collocation','mechanics')),
|
||||
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','accepted','rejected')),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO suggestions_new (id, doc_id, from_pos, to_pos, original, replacement, explanation, type, status, created_at)
|
||||
SELECT id, doc_id, from_pos, to_pos, original, replacement, explanation, type, status, created_at FROM suggestions;
|
||||
|
||||
DROP TABLE suggestions;
|
||||
ALTER TABLE suggestions_new RENAME TO suggestions;
|
||||
CREATE INDEX idx_suggestions_doc_id ON suggestions(doc_id);
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ const (
|
||||
SuggestionTypeClarity = "clarity"
|
||||
SuggestionTypeVoice = "voice"
|
||||
SuggestionTypeCollocation = "collocation"
|
||||
SuggestionTypeMechanics = "mechanics" // deterministic rule-based pass (no LLM)
|
||||
|
||||
SuggestionStatusPending = "pending"
|
||||
SuggestionStatusAccepted = "accepted"
|
||||
|
||||
Reference in New Issue
Block a user