Skip to content

feat: session bookmarks and custom tags - #80

Open
kannupriyakalra wants to merge 1 commit into
mainfrom
feat/bookmarks-and-tags
Open

feat: session bookmarks and custom tags#80
kannupriyakalra wants to merge 1 commit into
mainfrom
feat/bookmarks-and-tags

Conversation

@kannupriyakalra

Copy link
Copy Markdown
Collaborator

Summary

Closes #69 — session bookmarks and custom tags.

Adds persistent bookmarking to the session browser: star any session, attach tags and a freeform note, then filter the list by bookmark or tag. Bookmark data lives in its own SQLite file (~/.config/copilot-lens/bookmarks.db) separate from the derived-data cache, so it survives Refresh / POST /api/cache/clear.

What was added

Backend (src/bookmarks.ts + src/server.ts)

Route Purpose
GET /api/bookmarks list all bookmarks (id, tags[], note, createdAt)
GET /api/bookmarks/tags all distinct tag values, sorted — used for <datalist> auto-suggest
PUT /api/bookmarks/:id upsert { tags, note } — creates or updates, never resets createdAt
DELETE /api/bookmarks/:id remove bookmark, returns { ok: bool }

GET /api/sessions now includes bookmarked: true and tags[] on enriched sessions.
GET /api/sessions/:id/export includes tags and note in the JSONL output when bookmarked.

Frontend

  • ☆/★ star button on every session card — single click to bookmark or unbookmark
  • Tag chips appear on bookmarked cards
  • Filter bar below the controls: ★ Bookmarked pill + per-tag pills — clicking toggles each filter, filters compose with existing time/status/dir selectors
  • Detail pane bookmark section: tag input (Enter or comma to add, ✕ to remove, <datalist> auto-suggest from existing tags), freeform note textarea, Save button
  • All UI state updates without a page reload

Tests (src/__tests__/bookmarks.test.ts)

27 unit tests covering upsertBookmark, getBookmark, listBookmarks, deleteBookmark, getBookmarkMap, and listAllTags. Tests redirect the DB to a mkdtemp directory via COPILOT_LENS_DB_DIR and wipe it after each case.

All 134 tests pass. Zero TypeScript errors. Clean build.

Test plan

  • Start server (npm start), open Sessions tab — filter bar visible, all pills inactive
  • Click ☆ on a card → becomes ★, filter bar shows the session's tags when Save used
  • Open detail pane → Bookmark section visible; add a tag + note → Save → card shows tag chip
  • Click ★ Bookmarked pill → only starred sessions shown
  • Click a tag pill → filtered to that tag; click again to clear
  • Click ★ on a card again → unbookmarked, card returns to ☆
  • Click Refresh → bookmarks persist (not cleared with cache)
  • Export JSONL for a bookmarked session → output includes tags and note fields

🤖 Generated with Claude Code

@kannupriyakalra

Copy link
Copy Markdown
Collaborator Author

Verification: Session bookmarks and custom tags

Verdict: PASS

Claim: Adds persistent SQLite-backed bookmarks to the session browser. PUT/DELETE/GET bookmark routes. Sessions list enriched with bookmarked flag and tags. JSONL export includes tags+note. Bookmark data survives POST /api/cache/clear. Tag normalisation (lowercase, trim, empty filter). Malformed input is handled gracefully.

Method: Cold start — npm run buildnode dist/cli.js → curl against the live API. 1 commit on feat/bookmarks-and-tags vs origin/main. 7 files changed, 705 insertions.


Steps

  1. Server started👓 Copilot Lens is running at http://localhost:3000, 10 sessions loaded.

  2. GET /api/bookmarks (empty)[]

  3. GET /api/bookmarks/tags (empty)[]

  4. PUT /api/bookmarks/:id — create bookmark

    Input:  { tags: ["architecture", "important"], note: "Key auth decision session" }
    Result: sessionId=760468c9... tags=['architecture', 'important']
            note='Key auth decision session' createdAt=2026-06-10
    
  5. Two bookmarks, GET /api/bookmarks lists both

    count=2
      id=0acf9868... tags=['important', 'bugfix'] note=''
      id=760468c9... tags=['architecture', 'important'] note='Key auth decision...'
    
  6. GET /api/bookmarks/tags — deduplicated, sorted

    ["architecture","bugfix","important"]
    
  7. GET /api/sessions — bookmarked sessions enriched

    total=10  bookmarked=2
      id=760468c9... bookmarked=True tags=['architecture', 'important']
      id=0acf9868... bookmarked=True tags=['important', 'bugfix']
    
  8. PUT idempotent update — createdAt preserved

    createdAt same: True
    tags updated:   ['revised']
    note updated:   revised note
    
  9. DELETE /api/bookmarks/:id{"ok":true}, count drops from 2 to 1

  10. JSONL export — bookmarked session includes tags and note

    has tags:  True (['important', 'bugfix'])
    has note:  True ('')
    has messages: 3
    
  11. JSONL export — non-bookmarked session has NO tags/note keys

    has tags:  False
    has note:  False
    
  12. Bookmarks survive POST /api/cache/clear

    before clear: count=2
    after cache clear: count=2, tags=[['survives-clear'], ['important', 'bugfix']]
    
  13. 🔍 DELETE unknown session{"ok":false} — no crash, clean response

  14. 🔍 PUT with empty body {}tags=[] note='' — graceful fallback to defaults

  15. 🔍 PUT with tags as string instead of arraytags=[] — type coercion doesn't crash, defaults to empty

  16. 🔍 Tag normalisation[" Arch ", "TYPESCRIPT", "", " "]['arch', 'typescript'] — lowercased, trimmed, empties removed


Response shape sample

{
  "sessionId": "760468c9-...",
  "tags": ["architecture", "important"],
  "note": "Key auth decision session",
  "createdAt": "2026-06-10T..."
}

GET /api/sessions enriched field sample

{ "id": "760468c9-...", "bookmarked": true, "tags": ["architecture", "important"], ... }

Findings

  • Bookmark DB path is ~/.config/copilot-lens/bookmarks.db — separate from the in-memory TTL cache and VS Code session DBs. The directory is created with mkdirSync({ recursive: true }) on first use, so no manual setup required.

  • Sessions without a bookmark have no bookmarked or tags key in the sessions list response (they are simply absent, not false/null). The frontend's !!bookmarks[s.id] check handles this correctly.

  • DELETE on an unknown session returns {"ok":false} rather than 404. This is a deliberate design choice (idempotent delete), and the frontend handles both outcomes gracefully.

  • Tag normalisation is server-side (upsertBookmark lowercases and trims before storing), so tags are always canonical regardless of what the client sends.

🤖 Verified with Claude Code

Adds persistent bookmarks to the session browser:

Backend
- src/bookmarks.ts — SQLite-backed store at ~/.config/copilot-lens/bookmarks.db
  (separate from the derived-data cache, survives POST /api/cache/clear)
  CRUD: listBookmarks, getBookmark, upsertBookmark, deleteBookmark,
  getBookmarkMap, listAllTags
- GET  /api/bookmarks       — list all bookmarks
- GET  /api/bookmarks/tags  — all known tag values (for auto-suggest)
- PUT  /api/bookmarks/:id   — upsert { tags, note }
- DELETE /api/bookmarks/:id — remove
- GET  /api/sessions        — enriched with bookmarked:true and tags[]
- GET  /api/sessions/:id/export — JSONL now includes tags + note when bookmarked

Frontend
- ☆/★ star button on every session card — click to bookmark/unbookmark
- Tag chips on bookmarked cards
- Filter bar below the controls: "★ Bookmarked" pill + per-tag pills
- Detail pane: Bookmark section with tag input (Enter to add, ✕ to remove,
  datalist auto-suggest from existing tags), freeform note, Save button

Persistence
- bookmarks.db lives in ~/.config/copilot-lens/ — separate from session
  cache so Refresh never loses user data

Closes #69

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kannupriyakalra
kannupriyakalra force-pushed the feat/bookmarks-and-tags branch from d0fea49 to 6e92538 Compare June 18, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: session bookmarks and custom tags

1 participant