package db import "time" // User is an account. With auth deferred, the app runs as a single hardcoded // `local` user (see LocalUserID); the user_id columns and this type exist so // real auth can drop in later without a schema migration. type User struct { ID string `json:"id"` Email string `json:"email"` DisplayName string `json:"display_name"` CreatedAt time.Time `json:"created_at"` } // Document is a single piece of writing. `Content` is the Tiptap JSON document // (source of truth for the editor); `ContentText` is the flattened plain text // kept in sync on every save and fed to the LLM. type Document struct { ID string `json:"id"` UserID string `json:"user_id"` Title string `json:"title"` Content string `json:"content"` // Tiptap JSON ContentText string `json:"content_text"` // plain text for the LLM Tone string `json:"tone"` // target writing tone; steers LLM advice WordCount int `json:"word_count"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // DocumentVersion is a point-in-time snapshot of a document's body, captured so // a writer can recover from a bad edit or an unwanted change. `Content` mirrors // the document's Tiptap JSON at snapshot time; `Kind` records why it was taken // (see the kind constants). List responses omit the heavy Content/ContentText // fields (the `omitempty`-friendly zero strings) and load them only on preview // or restore. type DocumentVersion struct { ID string `json:"id"` DocID string `json:"doc_id"` Title string `json:"title"` Content string `json:"content,omitempty"` // Tiptap JSON; omitted in list view ContentText string `json:"content_text,omitempty"` // plain text; omitted in list view WordCount int `json:"word_count"` Kind string `json:"kind"` // auto | manual | pre_restore CreatedAt time.Time `json:"created_at"` } // Document version kinds, mirrored from the schema CHECK constraint. const ( VersionKindAuto = "auto" // throttled background snapshot on save VersionKindManual = "manual" // explicit "save a restore point" VersionKindPreRestore = "pre_restore" // safety copy taken just before a restore ) // Tag is a user-scoped label for organizing documents. `Color` is a palette key // (rose, mint, peach, lavender, sky, honey) the frontend maps to a CSS color; // storing the key (not a hex value) keeps tags in step with the design tokens. // `DocCount` is populated only by the tag-list endpoint (how many documents wear // the tag); it's omitted from per-document tag lists. type Tag struct { ID string `json:"id"` Name string `json:"name"` Color string `json:"color"` DocCount int `json:"doc_count,omitempty"` } // Tag color palette keys, mirrored on the frontend. Kept small and aligned with // the existing design tokens; unknown values fall back to rose client-side. const ( TagColorRose = "rose" TagColorMint = "mint" TagColorPeach = "peach" TagColorLavender = "lavender" TagColorSky = "sky" TagColorHoney = "honey" ) // Suggestion is a single LLM-proposed edit anchored to a span of the document. // // FromPos/ToPos are plaintext offsets into ContentText for server-side use only; // the frontend re-anchors by matching the `Original` string in ProseMirror // coordinates at render time (spec Note #6). `Replacement` is empty for `voice` // flags — those are awareness-only, with no correction to apply. type Suggestion struct { ID string `json:"id"` DocID string `json:"doc_id"` FromPos int `json:"from_pos"` ToPos int `json:"to_pos"` Original string `json:"original"` Replacement string `json:"replacement"` Explanation string `json:"explanation"` Type string `json:"type"` // grammar | phrasing | idiom | clarity | voice | collocation Status string `json:"status"` // pending | accepted | rejected CreatedAt time.Time `json:"created_at"` } // Suggestion type and status values, mirrored from the schema CHECK constraints. const ( SuggestionTypeGrammar = "grammar" SuggestionTypePhrasing = "phrasing" SuggestionTypeIdiom = "idiom" SuggestionTypeClarity = "clarity" SuggestionTypeVoice = "voice" SuggestionTypeCollocation = "collocation" SuggestionStatusPending = "pending" SuggestionStatusAccepted = "accepted" SuggestionStatusRejected = "rejected" )