diff --git a/internal/db/db.go b/internal/db/db.go
index 15530d8..fc389ea 100644
--- a/internal/db/db.go
+++ b/internal/db/db.go
@@ -3,8 +3,10 @@ package db
import (
"database/sql"
"errors"
+ "fmt"
"os"
"path/filepath"
+ "strings"
_ "modernc.org/sqlite"
)
@@ -63,7 +65,7 @@ func (s *Store) migrate() error {
pinned INTEGER NOT NULL DEFAULT 0,
deleted_at TEXT,
card_type TEXT
- CHECK (card_type IN ('snippet', 'template', 'checklist', 'decision', 'link')
+ CHECK (card_type IN ('snippet', 'template', 'checklist', 'decision', 'link', 'note')
OR card_type IS NULL),
card_data TEXT,
use_count INTEGER NOT NULL DEFAULT 0,
@@ -91,6 +93,58 @@ func (s *Store) migrate() error {
s.db.Exec(`ALTER TABLE entities ADD COLUMN title TEXT`)
s.db.Exec(`ALTER TABLE entities ADD COLUMN description TEXT`)
+ // Migrate CHECK constraint to include 'note' card type
+ var needsMigrate bool
+ row := s.db.QueryRow(`SELECT sql FROM sqlite_master WHERE type='table' AND name='entities'`)
+ var ddl string
+ if row.Scan(&ddl) == nil {
+ hasNote := strings.Contains(ddl, "'link', 'note'")
+ hasModified := strings.Contains(ddl, "modified_at")
+ needsMigrate = !hasNote || !hasModified
+ }
+ if needsMigrate {
+ tx, err := s.db.Begin()
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback()
+
+ if _, err := tx.Exec(`ALTER TABLE entities RENAME TO _entities_migrate`); err != nil {
+ return fmt.Errorf("migrate rename: %w", err)
+ }
+ if _, err := tx.Exec(`CREATE TABLE entities (
+ id TEXT PRIMARY KEY,
+ created_at TEXT NOT NULL,
+ modified_at TEXT NOT NULL,
+ body TEXT NOT NULL,
+ glyph TEXT NOT NULL
+ CHECK (glyph IN ('todo', 'event', 'note')),
+ time_anchor TEXT,
+ completed_at TEXT,
+ pinned INTEGER NOT NULL DEFAULT 0,
+ deleted_at TEXT,
+ card_type TEXT
+ CHECK (card_type IN ('snippet', 'template', 'checklist', 'decision', 'link', 'note')
+ OR card_type IS NULL),
+ card_data TEXT,
+ use_count INTEGER NOT NULL DEFAULT 0,
+ last_used_at TEXT,
+ title TEXT,
+ description TEXT
+ )`); err != nil {
+ return fmt.Errorf("migrate create: %w", err)
+ }
+ if _, err := tx.Exec(`INSERT INTO entities SELECT * FROM _entities_migrate`); err != nil {
+ return fmt.Errorf("migrate copy: %w", err)
+ }
+ if _, err := tx.Exec(`DROP TABLE _entities_migrate`); err != nil {
+ return fmt.Errorf("migrate drop: %w", err)
+ }
+ if err := tx.Commit(); err != nil {
+ return fmt.Errorf("migrate commit: %w", err)
+ }
+ }
+
return nil
}
diff --git a/internal/db/entities.go b/internal/db/entities.go
index 4669416..c257d7a 100644
--- a/internal/db/entities.go
+++ b/internal/db/entities.go
@@ -27,6 +27,7 @@ const (
CardChecklist CardType = "checklist"
CardDecision CardType = "decision"
CardLink CardType = "link"
+ CardNote CardType = "note"
)
func ValidGlyph(s string) bool {
@@ -39,7 +40,7 @@ func ValidGlyph(s string) bool {
func ValidCardType(s string) bool {
switch CardType(s) {
- case CardSnippet, CardTemplate, CardChecklist, CardDecision, CardLink:
+ case CardSnippet, CardTemplate, CardChecklist, CardDecision, CardLink, CardNote:
return true
}
return false
diff --git a/internal/display/glyph.go b/internal/display/glyph.go
index 9ba2bfc..e3cb21d 100644
--- a/internal/display/glyph.go
+++ b/internal/display/glyph.go
@@ -15,6 +15,7 @@ var cardGlyphMap = map[db.CardType]string{
db.CardChecklist: "☐",
db.CardDecision: "⚖",
db.CardLink: "↗",
+ db.CardNote: "¶",
}
func DisplayGlyph(glyph db.Glyph, cardType *db.CardType) string {
diff --git a/internal/parse/grammar.go b/internal/parse/grammar.go
index ee30ea0..0556320 100644
--- a/internal/parse/grammar.go
+++ b/internal/parse/grammar.go
@@ -27,6 +27,8 @@ var validCardTypes = map[string]string{
"checklist": "checklist",
"decision": "decision",
"link": "link",
+ "note": "note",
+ "n": "note",
}
func Parse(input string) (*Result, error) {
diff --git a/web/app.js b/web/app.js
index dfaa3bb..adbe789 100644
--- a/web/app.js
+++ b/web/app.js
@@ -4,14 +4,14 @@
const GLYPHS = {
note: '—', todo: '○', event: '◇', reminder: '△',
snippet: '◆', template: '◈', checklist: '☐',
- decision: '⚖', link: '↗',
+ decision: '⚖', link: '↗', note: '¶',
};
const GLYPH_CLASSES = {
note: 'glyph-note', todo: 'glyph-todo', event: 'glyph-event', reminder: 'glyph-reminder',
snippet: 'glyph-snippet', template: 'glyph-template',
checklist: 'glyph-checklist', decision: 'glyph-decision',
- link: 'glyph-link',
+ link: 'glyph-link', note: 'glyph-note',
};
const PAGE_SIZE = 50;
@@ -121,7 +121,7 @@
// ========== Grammar parser (mirrors Go parser) ==========
- const VALID_CARDS = { card: 'snippet', c: 'snippet', snippet: 'snippet', template: 'template', checklist: 'checklist', decision: 'decision', link: 'link' };
+ const VALID_CARDS = { card: 'snippet', c: 'snippet', snippet: 'snippet', template: 'template', checklist: 'checklist', decision: 'decision', link: 'link', note: 'note', n: 'note' };
function validateTime(s) {
const parts = s.split(':');
diff --git a/web/index.html b/web/index.html
index f54638d..9794a4b 100644
--- a/web/index.html
+++ b/web/index.html
@@ -64,6 +64,11 @@
decision
record a choice + rationale
+