Open
Conversation
Lets users mark high-value items across Spool — agent sessions and external captures alike — and surface them via a dedicated Starred view, a scoped filter on global search, and a persistent top-right entry button. Schema: single `stars(item_type, item_uuid, starred_at)` table with a CHECK constraint on item_type. Keyed by natural UUID, no FK — orphan stars are filtered at read time so transient referent absence (e.g. transcript file removed then restored) doesn't destroy state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fix home star entry pushing logo below center — use absolute
positioning instead of adding a flex slot
- Split multi-statement-per-line useCallback bodies (project uses
no-trailing-semicolon style; avoid semi-chaining even within braces)
- Replace \`const formatDate = formatRelativeDate\` aliases with direct
calls
- Promote dense filter predicate to \`isSameStarredItem\` helper
- Match existing .then/.catch style in refreshStarred{Uuids,Items}
instead of cramped try/catch single-liners
- Extract \`highlighted\` boolean in StarredEntryButton; drop the
spread-ternary on the Star icon props
- Drop JSDoc on self-evident StarButton props; keep only the
non-obvious \`insideAnchor\` WHY comment
- Type IPC star handlers with \`StarKind\` instead of repeating the
literal union
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- StarButton md variant: 26px -> 24px (h-6 w-6) to match the adjacent Copy Session ID button height in the session detail header; sm stays 20px - Replace default focus ring with focus-visible:ring-accent/40 so a mouse-clicked star no longer leaves an orange outline box - Starred view rows: move the star button inline with the first-line meta flex (same row as the date) instead of vertical-centering it against the whole 3-line block. Session rows switch the outer element to role=button with keyboard handler (buttons cannot nest in HTML); capture rows keep the <a> root and pass insideAnchor=true so the star click does not navigate. - Starred header: leading-none on the label / count / filter-note so the star optical-aligns with text centers - Session row star size md -> sm; reserves the md variant for the detail header Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Search result rows: star now sits at the right end of the meta line, after the date (badge > meta > date > star). Previous layout put the star between meta and date, which visually split the time from its source-context group. - StarredEntryButton count: translate-y-[0.5px] to optical-center the digit with the adjacent filled Star icon Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #72.
Summary
Adds a star primitive that spans both first-party sessions (Claude Code / Codex / Gemini transcripts) and connector-indexed captures (YouTube likes, Twitter bookmarks, GitHub stars, Reddit saves, XHS notes, …). Users mark high-value items in-place from the search result row, from the session detail header, or from the Starred view itself; and they resurface those items via a persistent top-right entry, a scoped filter tab on search results, and a full-text search scoped to starred content.
star-demo.mp4
Design
Why unified, not parallel tables. An earlier iteration had
session_stars; the final schema is a singlestars(item_type, item_uuid, starred_at)table. Sessions and captures are product-level peers in Spool (both appear in the same search results and source-chip row), so treating their stars as peers makes the Starred view a one-liner (ORDER BY starred_at DESCacross both kinds) instead of a JS-level merge of two lists. Parallel tables made sense forsessions/capturesbecause those shapes genuinely diverge; a star record is(referent, time)and doesn't need to.Why no FK.
deleteSessionByFilePathdrops session rows when transcripts disappear, and captures can vanish when a connector is uninstalled. A cascade delete would destroy stars for items that may come back. The table keeps its rows; all read pathsJOINto filter orphans so the UI never surfaces dead references.Why not also starrable: messages, projects, anything else. Out of scope for this PR.
item_typeis constrained via aCHECKso future additions are explicit schema changes.Features
Starring an item
e.preventDefault()keeps the<a>wrapper from navigating.Finding starred items
starred_at DESC. Empty state, filter state, and post-search state all have copy. Clicking a session opens it; clicking a capture opens the URL.Searching within starred
onlyStarred=true) across message content of starred sessions and text of starred captures. Results render via the existingFragmentResultscomponent with snippet highlighting.Implementation notes
Core (
packages/core)v4createsstarsand drops any earliersession_starsleft over from intermediate dev builds.starItem / unstarItem / isStarred / getStarredUuidsByType / listStarredItems— the full API in five functions.listStarredItemsorphan-filters in SQL (WHERE ... AND EXISTS (...)) soLIMITcounts only live rows. Details for sessions and captures are batch-fetched viaIN (...), then reassembled instarred_atorder.searchFragmentsandsearchCapturesgained anonlyStarredoption that injects anEXISTS (SELECT 1 FROM stars ...)predicate into every search path (FTS, FTS-fallback, LIKE-fallback).Renderer (
packages/app/src/renderer)StarButtonis a single shared component; used five places, no copy-paste.Badges.tsxhosts the sharedSourceBadge/PlatformBadge.shared/formatDate.tshas the relative-date helper that was copy-pasted across the codebase.starredSessions: Set<string>,starredCaptures: Set<string>(complete, cheap, refreshed on mount), andstarredItems: StarredItem[](up to 200, refreshed only when entering the Starred view). Set updates are identity-preserving when content hasn't changed, so search-result rows don't re-render on unrelated star changes.Main (
packages/app/src/main)star-item,unstar-item,list-starred-items,get-starred-uuids.onlyStarred, andstar-item / unstar-itemclear it to keep scoped results consistent.Test plan
pnpm -F @spool-lab/core test— 166 passing (10 new instars.test.tsincluding CHECK constraint, cross-type UUID independence, orphan filtering, persistence across DB reopen).tsc --noEmitclean.tsc -p tsconfig.json --noEmitclean.pnpm -F @spool/app run build:electronbuilds.Notes
spool star <uuid>CLI command; deferred until the UI flow has settled.