Glossary System

Glossary System

A comprehensive terminology management system that stores, searches, and automatically links glossary terms within editor content via ProseMirror marks with hover tooltips.

User-Facing Behavior

Writers manage a project-level glossary of terms through a sidebar panel. They can create entries with rich metadata (definitions, categories, aliases, relationships, scoping), search and filter the list, and view full entry details. When writing in the editor, glossary terms are automatically underlined with dotted marks — hovering shows a tooltip with the short definition, and clicking (when enabled) navigates to the entry. Terms can be added directly from the editor context menu by selecting text and choosing "Add to Glossary." Invented terms are automatically added to the spellcheck dictionary.

Scope

Architecture

Entity Relationship

Project ──1:1──> Glossary ──1:N──> GlossaryEntry

Each project has exactly one Glossary entity (auto-created on first access). Each Glossary contains many GlossaryEntry records.

Layer Overview

Frontend (Svelte 5)
  Components → Store → API Client ──HTTP──> Backend (Go)
  ProseMirror Plugin ←─ Events                Handler → Repository → Ent ORM → SQLite
                                               Handler → Job Manager → TermScanHandler

Frontend path: User interacts with GlossaryPanel (state-machine view router) which delegates to GlossaryList, GlossaryEntryDetail, or GlossaryEntryForm. All data flows through glossaryStore which calls glossaryApi HTTP client methods.

Backend path: GlossaryHandler.Glossary() dispatches by path + HTTP method to CRUD handler methods. Each calls the ProjectStore repository interface. The SQLite implementation opens the per-project database, performs the operation via Ent ORM, and manages reciprocal relationships, FTS5 indexing, and slug generation.

Background path: After create/update/delete, the handler submits a term_scan job. The TermScanHandler processes it asynchronously — scanning all content documents for the term, applying or removing glossaryLink ProseMirror marks, and updating usage counts.

Editor path: The createGlossaryMarkPlugin() ProseMirror plugin provides hover tooltips (cached, fetched from API) and click-to-navigate behavior. Events (glossary:entry-saved, glossary:entry-deleted) trigger cache clearing and content reloading.

Key Design Decisions

Dataflow Diagrams

Event System

Three glossary-specific events flow through moduleEventBus:

Event Payload Dispatched By Subscribers
glossary:view-term { id } TextEditor (mark click) GlossaryPanel → navigateToEntry()
glossary:entry-saved { id } GlossaryEntryForm TextEditor (clear cache), Editor page (reload 3s), Detail (reload usage 4s)
glossary:entry-deleted { id } glossaryStore TextEditor (clear cache), Editor page (reload 3s)

Cross-module: content:selected used by GlossaryEntryDetail to navigate to content documents from occurrence results.

Spellcheck Integration

After creating or updating an entry, the form calls dictionaryStore.addWordIfMisspelled() for the term and all aliases (2s delay). Invented words are added to the custom dictionary; real dictionary words are skipped. Uses retry logic with exponential backoff (3 attempts).

Security

No authentication or authorization is enforced at the glossary level — all operations are scoped to the local project database. The per-project SQLite isolation means one project's glossary cannot access another's data. Input validation enforces non-empty term and shortDefinition on create, and at-least-one-field on update. Duplicate terms are rejected (case-insensitive). No user-supplied content is executed; ProseMirror marks use data attributes, not inline scripts.

The image_url field accepts arbitrary URLs. If this is ever rendered as <img src>, it could be an XSS vector. Currently it is only stored, not rendered in the UI.

Performance

12-Factor Compliance

The TermScanHandler callback pattern (entryLoader, contentLoader, bodySaver, countUpdater) is a good example of 12-Factor dependency injection — the handler depends on interfaces, not implementations.

Logging

All components use injected loggers following project conventions:

Layer Component Logger Name
Backend GlossaryHandler "glossary"
Backend TermScanHandler "term-scan"
Backend SQLite repository via Store's injected logger
Frontend glossaryStore 'glossary'
Frontend GlossaryPanel 'glossary-panel'
Frontend GlossaryList 'glossary-list'
Frontend GlossaryEntryDetail 'glossary-detail'
Frontend GlossaryEntryForm 'glossary-form'
Frontend GlossaryTermPicker 'term-picker'
Frontend ScopeTreePicker 'scope-tree-picker'
Frontend CheckboxTree 'checkbox-tree'