feat: add title and description fields to capture grammar
Implement | prefix for titles and // separator for descriptions across the full stack: parser, schema, API, CLI, and web frontend. - Parser: line-aware extraction for |title, |title // desc, // leading desc, body // inline desc. URL-safe (skips :// lines). Modifiers (#tag, @time, ^card) extracted from all segments. - Schema: ALTER TABLE migration adds title, description columns - DB: Entity/EntityUpdate structs, all CRUD queries updated - API: title/description on create/update/response, body validation relaxed (title-only entries valid) - CLI: shows title as scan label when present - Web: parseInput mirrors Go parser, list shows title, detail pane renders title + description with double-click inline editing - Tests: 10 new cases (grammar, entity, API) — 71 total, all pass
This commit is contained in:
+44
-21
@@ -49,6 +49,8 @@ type Entity struct {
|
||||
CreatedAt time.Time
|
||||
ModifiedAt time.Time
|
||||
Body string
|
||||
Title *string
|
||||
Description *string
|
||||
Glyph Glyph
|
||||
TimeAnchor *string
|
||||
CompletedAt *time.Time
|
||||
@@ -85,14 +87,16 @@ func DefaultListParams() ListParams {
|
||||
}
|
||||
|
||||
type EntityUpdate struct {
|
||||
Body *string
|
||||
Glyph *Glyph
|
||||
TimeAnchor *string
|
||||
ClearTime bool
|
||||
Pinned *bool
|
||||
CardType *CardType
|
||||
CardData *string
|
||||
Tags *[]string
|
||||
Body *string
|
||||
Title *string
|
||||
Description *string
|
||||
Glyph *Glyph
|
||||
TimeAnchor *string
|
||||
ClearTime bool
|
||||
Pinned *bool
|
||||
CardType *CardType
|
||||
CardData *string
|
||||
Tags *[]string
|
||||
}
|
||||
|
||||
func (s *Store) Create(e *Entity) error {
|
||||
@@ -111,13 +115,16 @@ func (s *Store) Create(e *Entity) error {
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.Exec(`
|
||||
INSERT INTO entities (id, created_at, modified_at, body, glyph, time_anchor,
|
||||
completed_at, pinned, deleted_at, card_type, card_data, use_count, last_used_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
INSERT INTO entities (id, created_at, modified_at, body, title, description,
|
||||
glyph, time_anchor, completed_at, pinned, deleted_at,
|
||||
card_type, card_data, use_count, last_used_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
e.ID,
|
||||
e.CreatedAt.Format(time.RFC3339),
|
||||
e.ModifiedAt.Format(time.RFC3339),
|
||||
e.Body,
|
||||
e.Title,
|
||||
e.Description,
|
||||
string(e.Glyph),
|
||||
e.TimeAnchor,
|
||||
formatTimePtr(e.CompletedAt),
|
||||
@@ -144,14 +151,17 @@ func (s *Store) Get(id string) (*Entity, error) {
|
||||
var createdAt, modifiedAt string
|
||||
var completedAt, deletedAt, lastUsedAt sql.NullString
|
||||
var timeAnchor, cardType, cardData sql.NullString
|
||||
var title, description sql.NullString
|
||||
var pinned int
|
||||
|
||||
err := s.db.QueryRow(`
|
||||
SELECT id, created_at, modified_at, body, glyph, time_anchor,
|
||||
completed_at, pinned, deleted_at, card_type, card_data, use_count, last_used_at
|
||||
SELECT id, created_at, modified_at, body, title, description,
|
||||
glyph, time_anchor, completed_at, pinned, deleted_at,
|
||||
card_type, card_data, use_count, last_used_at
|
||||
FROM entities WHERE id = ?`, id).Scan(
|
||||
&e.ID, &createdAt, &modifiedAt, &e.Body, &e.Glyph, &timeAnchor,
|
||||
&completedAt, &pinned, &deletedAt, &cardType, &cardData, &e.UseCount, &lastUsedAt,
|
||||
&e.ID, &createdAt, &modifiedAt, &e.Body, &title, &description,
|
||||
&e.Glyph, &timeAnchor, &completedAt, &pinned, &deletedAt,
|
||||
&cardType, &cardData, &e.UseCount, &lastUsedAt,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrNotFound
|
||||
@@ -162,6 +172,8 @@ func (s *Store) Get(id string) (*Entity, error) {
|
||||
|
||||
e.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
|
||||
e.ModifiedAt, _ = time.Parse(time.RFC3339, modifiedAt)
|
||||
e.Title = nullToPtr(title)
|
||||
e.Description = nullToPtr(description)
|
||||
e.TimeAnchor = nullToPtr(timeAnchor)
|
||||
e.CompletedAt = parseTimePtr(completedAt)
|
||||
e.Pinned = pinned != 0
|
||||
@@ -234,9 +246,9 @@ func (s *Store) List(params ListParams) ([]*Entity, error) {
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
SELECT e.id, e.created_at, e.modified_at, e.body, e.glyph, e.time_anchor,
|
||||
e.completed_at, e.pinned, e.deleted_at, e.card_type, e.card_data,
|
||||
e.use_count, e.last_used_at
|
||||
SELECT e.id, e.created_at, e.modified_at, e.body, e.title, e.description,
|
||||
e.glyph, e.time_anchor, e.completed_at, e.pinned, e.deleted_at,
|
||||
e.card_type, e.card_data, e.use_count, e.last_used_at
|
||||
FROM entities e
|
||||
%s
|
||||
ORDER BY %s %s
|
||||
@@ -256,18 +268,21 @@ func (s *Store) List(params ListParams) ([]*Entity, error) {
|
||||
var createdAt, modifiedAt string
|
||||
var completedAt, deletedAt, lastUsedAt sql.NullString
|
||||
var timeAnchor, cardType, cardData sql.NullString
|
||||
var title, description sql.NullString
|
||||
var pinned int
|
||||
|
||||
if err := rows.Scan(
|
||||
&e.ID, &createdAt, &modifiedAt, &e.Body, &e.Glyph, &timeAnchor,
|
||||
&completedAt, &pinned, &deletedAt, &cardType, &cardData,
|
||||
&e.UseCount, &lastUsedAt,
|
||||
&e.ID, &createdAt, &modifiedAt, &e.Body, &title, &description,
|
||||
&e.Glyph, &timeAnchor, &completedAt, &pinned, &deletedAt,
|
||||
&cardType, &cardData, &e.UseCount, &lastUsedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
|
||||
e.ModifiedAt, _ = time.Parse(time.RFC3339, modifiedAt)
|
||||
e.Title = nullToPtr(title)
|
||||
e.Description = nullToPtr(description)
|
||||
e.TimeAnchor = nullToPtr(timeAnchor)
|
||||
e.CompletedAt = parseTimePtr(completedAt)
|
||||
e.Pinned = pinned != 0
|
||||
@@ -308,6 +323,14 @@ func (s *Store) Update(id string, u *EntityUpdate) error {
|
||||
sets = append(sets, "body = ?")
|
||||
args = append(args, *u.Body)
|
||||
}
|
||||
if u.Title != nil {
|
||||
sets = append(sets, "title = ?")
|
||||
args = append(args, *u.Title)
|
||||
}
|
||||
if u.Description != nil {
|
||||
sets = append(sets, "description = ?")
|
||||
args = append(args, *u.Description)
|
||||
}
|
||||
if u.Glyph != nil {
|
||||
sets = append(sets, "glyph = ?")
|
||||
args = append(args, string(*u.Glyph))
|
||||
|
||||
Reference in New Issue
Block a user