BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs(wip): changelog

+74 -450
+51
CHANGELOG.md
··· 1 + # CHANGELOG 2 + 3 + ## v0.1.0 - Unreleased 4 + 5 + ### 2026-04-07 6 + 7 + - Group notifications by reason & post, with clickable links to subjects and posts 8 + 9 + ### 2026-04-06 10 + 11 + - Post drafts in composer, with account scoped list, autosave and keyboard shortcuts. 12 + - Add favicons to lexicon collections in the protocol explorer. 13 + 14 + ### 2026-04-05 15 + 16 + - Make embedding model/semantic search opt-in with a dedicated preflight screen. 17 + 18 + ### 2026-04-04 19 + 20 + - Add "Danger Zone" to clear cache & app data and reset the application 21 + - Profile search with autocomplete/typeahead 22 + - Hashtag and filterable post search for posts fetched through the BlueSky API 23 + 24 + ### 2026-03-31 25 + 26 + - Multicolumn views with independent scrollable columns for feeds, search results, and explorer panels 27 + - DM/Messages column with privacy blur 28 + - Profile view with multi-tab view for posts, followers, following, and activity. Includes a parallax 29 + header/cover image and dynamic sticky header. 30 + 31 + ### 2026-03-30 32 + 33 + - Profile context/diagnostics using the [Constellation](https://constellation.microcosm.blue) API for 34 + backlinks 35 + - Blocking, Blocked by, Lists 36 + - Dedicated Settings screen 37 + - Log viewer with level filtering 38 + - Theme and appearance settings 39 + - Exportable data 40 + 41 + ### 2026-03-29 42 + 43 + - Polled notifications, with tabbed view for near real-time interaction updates. 44 + - [pdsls](https://pds.ls) inspired AT Protocol explorer 45 + - Local & network post search via vectors & embedding, and xrpc, respectively with tabbed panel for Saved & Liked posts 46 + 47 + ### 2026-03-28 48 + 49 + - Multi-account support and OAuth-based, loopback authentication using [jacquard](https://tangled.org/nonbinary.computer/jacquard) 50 + - Home screen/timeline view with saved feeds and customizable feed filters and preferences. 51 + - Feed management (reorder, pin/unpin) with full list of feeds.
+3 -15
docs/tasks/01-backend-setup.md
··· 2 2 3 3 Spec: [mvp.md](../specs/mvp.md) 4 4 5 - ## Steps 5 + Completed on March 28, 2026. See [CHANGELOG](../CHANGELOG.md) for more details. 6 6 7 - - [x] Add Cargo dependencies: `jacquard`, `rusqlite` (bundled), `sqlite-vec`, `fastembed`, `tokio` 8 - - [x] Add Tauri plugins: `tauri-plugin-deep-link`, `tauri-plugin-notification`, `tauri-plugin-log` 9 - - [x] Add frontend deps: `solid-motionone` (animation), install via npm 10 - - [x] Create `src-tauri/src/db.rs` - initialize SQLite, run migrations, load `sqlite-vec` extension 11 - - [x] Create migration system: `accounts`, `posts`, `posts_fts`, `posts_vec` tables 12 - - Embedded files via `include_str!` for SQL schema 13 - - [x] Create `src-tauri/src/state.rs` - `AppState` struct holding DB pool, active session, account list 14 - - [x] Register `AppState` as Tauri managed state 15 - - [x] Create Tauri command scaffold with error handling pattern using `thiserror` crate 16 - - [x] Set up dark/light theme: CSS custom properties, OS preference detection via `prefers-color-scheme` 17 - - Follow the design spec 18 - - [x] Create global error toast component using `Presence` for enter/exit animations 19 - - [x] Verify build compiles on macOS with `pnpm tauri dev` 7 + ## Parking Lot 20 8 21 - - Note: `tauri-plugin-updater` will come after the first release 9 + - [ ] `tauri-plugin-updater` will come after the first release
+1 -22
docs/tasks/02-auth.md
··· 2 2 3 3 Spec: [auth.md](../specs/auth.md) 4 4 5 - ## Steps 6 - 7 - - [x] Implement `PersistentAuthStore` backed by SQLite (impl `jacquard::oauth::authstore` trait) 8 - - [x] Create Tauri command `login(handle: String)`: 9 - - Resolve handle → authorization server 10 - - Build `AtprotoClientMetadata` for Lazurite 11 - - Start loopback OAuth via `LoopbackConfig` 12 - - Store session tokens, insert into `accounts` table 13 - - Return account info to frontend 14 - - [x] Create Tauri command `logout(did: String)` - revoke tokens, remove from DB 15 - - [x] Create Tauri command `switch_account(did: String)` - swap active `OAuthSession` in state 16 - - [x] Create Tauri command `list_accounts()` → `Vec<Account>` 17 - - [x] On app launch: restore sessions from DB, auto-refresh tokens for active account 18 - - [x] Register `at://` scheme via deep-link plugin in `tauri.conf.json` 19 - - [x] Handle deep-link events: parse `at://` URI, emit Tauri event to frontend for navigation 20 - 21 - ### Frontend 22 - 23 - - [x] login form with `Motion` spring shake on invalid handle 24 - - [x] account switcher dropdown in sidebar with `Presence` avatar enter/exit 25 - - [x] skeleton shimmer on profile card during session restore 26 - - [x] inline re-auth prompt with pulse animation on session expiry 5 + Completed on March 28, 2026. See [CHANGELOG](../CHANGELOG.md) for more details.
+1 -47
docs/tasks/03-feeds.md
··· 2 2 3 3 Spec: [feeds.md](../specs/feeds.md) 4 4 5 - ## Steps 6 - 7 - ### Backend - `src-tauri/src/feed.rs` 8 - 9 - - [x] `get_preferences()` - calls `app.bsky.actor.getPreferences`, extracts `savedFeedsPrefV2` items and `feedViewPref` entries 10 - - [x] `get_feed_generators(uris: Vec<String>)` - calls `app.bsky.feed.getFeedGenerators` to hydrate display names/avatars 11 - - [x] `get_timeline(cursor: Option<String>, limit: u32)` - calls `app.bsky.feed.getTimeline` 12 - - [x] `get_feed(uri: String, cursor: Option<String>, limit: u32)` - calls `app.bsky.feed.getFeed` for custom feed generators 13 - - [x] `get_list_feed(uri: String, cursor: Option<String>, limit: u32)` - calls `app.bsky.feed.getListFeed` 14 - - [x] `get_post_thread(uri: String)` - thread view 15 - - [x] `get_author_feed(did: String, cursor: Option<String>)` 16 - - [x] `create_post(text: String, reply_to: Option<ReplyRef>, embed: Option<Embed>)` - with richtext facet detection via `jacquard::richtext` 17 - - [x] `like_post(uri: String, cid: String)` / `unlike_post(uri: String)` 18 - - [x] `repost(uri: String, cid: String)` / `unrepost(uri: String)` 19 - 20 - ### Frontend - Feed Tabs & Content 21 - 22 - - [x] Feed tab bar - pinned feeds as tabs, hydrated with generator display names/avatars; `1`–`9` keyboard shortcuts to switch 23 - - [x] Feed content loader - dispatches to correct endpoint based on feed type (`timeline` / `feed` / `list`) 24 - - [x] Infinite scroll with cursor pagination and scroll-position preservation 25 - - [x] `Presence` crossfade animation on tab switch 26 - - [x] Skeleton screens while feeds load 27 - 28 - ### Frontend - Post Card & Actions 29 - 30 - - [x] Post card component (author, text, embeds, timestamps, action bar) - `Motion` fade-in on viewport enter 31 - - [x] Like/repost icon `Motion` scale pop animation (1.0 -> 1.3 -> 1.0) 32 - - [x] `j/k` keyboard navigation between posts, `l` like, `r` reply, `t` repost, `o` open thread 33 - 34 - ### Frontend - Thread View 35 - 36 - - [x] Thread view with nested replies 37 - - [x] Navigate into thread from post card (`o` / `Enter`) with route-backed thread URLs 38 - 39 - ### Frontend - Post Composer 40 - 41 - - [x] Composer with `Presence` slide-up/down, `n` keyboard shortcut to open 42 - - [x] Mention/hashtag autocomplete 43 - - [x] Reply threading with parent/root refs 44 - - [x] Quote post embed 45 - - [x] Tray button and global keyboard shortcut to open composer from anywhere 46 - 47 - ### Frontend - Feed Preferences 48 - 49 - - [x] Per-feed display toggles (hide reposts/replies/quotes) via `feedViewPref` 50 - - [x] Feeds drawer for accessing saved (unpinned) feeds 51 - - [x] Feed generator management (pin/unpin, reorder) via `savedFeedsPrefV2` 5 + Completed on March 28, 2026. See [CHANGELOG](../CHANGELOG.md) for more details.
+1 -23
docs/tasks/04-notifications.md
··· 2 2 3 3 Spec: [feeds.md](../specs/feeds.md) 4 4 5 - ## Tasks 6 - 7 - ### Tauri 8 - 9 - - [x] Create `src-tauri/src/notifications.rs` 10 - - `src-tauri/src/commands/notifications.rs` for Tauri commands 11 - - [x] `list_notifications(cursor: Option<String>)` - `app.bsky.notification.listNotifications` 12 - - [x] `update_seen()` - `app.bsky.notification.updateSeen` 13 - - [x] `get_unread_count()` - `app.bsky.notification.getUnreadCount` 14 - - [x] Background polling: spawn async task on login, poll every 30s, emit Tauri event on new notifications 15 - - [x] System notifications via `tauri-plugin-notification` for mentions when app is in background 16 - 17 - ## Frontend 18 - 19 - - [x] notifications panel with two tabs - Mentions / Activity (Aeronaut pattern) 20 - - [x] unread badge on sidebar notification icon with `Motion` scale-in pop 21 - - [x] new notification items `Motion` slide-in from top 22 - - [x] tab switch `Presence` crossfade between Mentions/Activity 23 - 24 - ## Tests 25 - 26 - - [x] Frontend tests for notification payload parsing, rail unread badge, route wiring, and notifications panel behavior 27 - - [x] Rust unit tests for mention-notification formatting and dedupe helpers 5 + Completed on March 29, 2026. See [CHANGELOG](../CHANGELOG.md) for more details.
+2 -19
docs/tasks/05-explorer.md
··· 2 2 3 3 Spec: [explorer.md](../specs/explorer.md) 4 4 5 - ## Tasks 5 + ## Parking Lot 6 6 7 - - [x] Create `src-tauri/src/explorer.rs` for business logic 8 - - `src-tauri/src/commands/explorer.rs` - Tauri commands for AT data browsing 9 - - [x] `resolve_input(input: String)` - detect if input is at:// URI, handle, DID, or PDS URL; resolve accordingly 10 - - [x] `describe_server(pds_url: String)` - `com.atproto.server.describeServer` 11 - - [x] `describe_repo(did: String)` - `com.atproto.repo.describeRepo` 12 - - [x] `list_records(did: String, collection: String, cursor: Option<String>)` - `com.atproto.repo.listRecords` 13 - - [x] `get_record(did: String, collection: String, rkey: String)` - `com.atproto.repo.getRecord` 14 - - [x] `export_repo_car(did: String)` - `com.atproto.sync.getRepo`, save to file 15 - - [x] `query_labels(uri: String)` - `com.atproto.label.queryLabels` 16 - - [x] Wire deep-link handler: `at://` URI → parse → call `resolve_input` → emit navigation event 17 - - [x] **Frontend**: explorer URL bar with input parsing, `Cmd+L` to focus 18 - - [x] **Frontend**: PDS view - server info + hosted account list, skeleton loading 19 - - [ ] **Frontend**: repo view - collection list with record counts 20 - - [x] **Frontend**: collection view - paginated record list 21 - - [x] **Frontend**: record view - syntax-highlighted JSON with collapsible sections, type-specific rendering 22 - - [x] **Frontend**: breadcrumb navigation bar with `Motion` width animation on segment changes 23 - - [x] **Frontend**: `Presence` crossfade transitions between explorer view levels 24 - - [x] **Frontend**: keyboard shortcuts - `Backspace` up a level, `Cmd+[/]` back/forward 7 + - [ ] Repo view should show collection list with record counts
+1 -27
docs/tasks/06-settings.md
··· 2 2 3 3 Spec: [settings.md](../specs/settings.md) 4 4 5 - ## Steps 6 - 7 - ### Backend - `src-tauri/src/settings.rs` 8 - 9 - - [x] `get_settings()` - read user preferences from SQLite `settings` table, return as typed struct 10 - - [x] `update_setting(key: String, value: String)` - upsert a key-value pair in `settings` table 11 - - [x] `clear_cache()` - delete cached feed data, embedded vectors, and FTS5 index; vacuum database 12 - - [x] `reset_app()` - drop all user data tables and re-run migrations; clear auth tokens 13 - - [x] `export_data(format: String, path: String)` - export user data as JSON or CSV to chosen path 14 - - [x] `get_log_entries(limit: u32, level: Option<String>)` - read recent log entries for the in-app log viewer 15 - - [x] SQLite migration: `settings` table (`key TEXT PRIMARY KEY, value TEXT, updated_at TEXT`) 16 - 17 - ### Frontend - Settings View 18 - 19 - - [x] Settings route (`/settings`) accessible from app rail icon (`Icon` with kind `settings`) 20 - - [x] Section-based layout using `surface_container` cards with `lg` radius: 21 - 1. **Appearance** - Theme toggle (light/dark/auto), `Motion` crossfade on theme switch 22 - 2. **Timeline** - Refresh interval selector (30s, 1m, 2m, 5m, manual) 23 - 3. **Notifications** - Toggle desktop notifications, badge count, notification sound 24 - 4. **Data** - Clear cache (with size display), export (JSON/CSV), reset app (with confirmation dialog) 25 - 5. **Accounts** - List active accounts, add/remove account flows (reuses OAuth from Milestone 02) 26 - 6. **Logs** - Collapsible log viewer with level filtering (`info`, `warn`, `error`) 27 - 7. **Services** - Constellation instance URL, Spacedust instance URL 28 - 8. **About** - Version info, license (MIT), contributors, support links 29 - - [x] `Presence` slide transitions between setting sections 30 - - [x] Keyboard shortcut: `,` to open settings from anywhere 31 - - [x] Confirmation modal for destructive actions (clear cache, reset app, remove account) using glass overlay 5 + Completed on March 30, 2026. See [CHANGELOG](../CHANGELOG.md) for more details.
+1 -77
docs/tasks/07-search.md
··· 2 2 3 3 Spec: [search.md](../specs/search.md) 4 4 5 - ## Tasks 6 - 7 - ### Backend 8 - 9 - #### Network Search 10 - 11 - - [x] Create 12 - - `src-tauri/src/search.rs` for business logic 13 - - `src-tauri/src/commands/search.rs` 14 - - [x] Implement network search commands (not indexed - direct API calls): 15 - - `search_posts_network(query, sort?, since?, until?, mentions?, author?, tags?, limit?, cursor?)` → `app.bsky.feed.searchPosts` 16 - - `search_actors(query, limit?, cursor?)` → `app.bsky.actor.searchActors` 17 - - `search_starter_packs(query, limit?, cursor?)` → `app.bsky.graph.searchStarterPacks` 18 - - Note: `searchActorsTypeahead` already exists in auth module 19 - - Always available - no local setup required 20 - 21 - #### Search Routing 22 - 23 - - [x] Add URL-synced network post search state on `/search` 24 - - `q`, `tab`, `mode`, `sort`, `since`, `until`, `mentions`, `author`, repeatable `tags` 25 - - [x] Add dedicated `/hashtag/:hashtag` route backed by `searchPosts` with `q=#tag` 26 - - [x] Render hashtag facets as internal links to the hashtag route 27 - 28 - #### Local Data Pipeline (Base) 29 - 30 - - [x] Add `sync_state` table to migrations (stores cursor per `(did, source)`) 31 - - [x] Implement `sync_posts(did: String, source: "like"|"bookmark")`: 32 - - Resume from stored cursor in `sync_state` (never re-fetch full history) 33 - - Paginate `app.bsky.feed.getActorLikes` (or bookmarks) for the **authenticated user's own** likes/saves 34 - - Upsert into `posts` table 35 - - FTS index is maintained automatically via triggers 36 - - Persist the new cursor back to `sync_state` 37 - 38 - #### Embeddings 39 - 40 - - [x] Implement `embed_pending_posts()` 41 - - Query posts without embeddings 42 - - Batch through `fastembed` TextEmbedding model (`nomic-embed-text-v1.5`) 43 - - Insert into `posts_vec` via `zerocopy::AsBytes` 44 - - [x] Implement `reindex_embeddings()`: 45 - - Clear all rows from `posts_vec` 46 - - Re-embed every post in `posts` table 47 - - Triggered manually by user (reindex button in UI) 48 - - [x] Implement `set_embeddings_enabled(enabled: bool)`: 49 - - Persist preference; when disabled, skip model download + embedding on sync 50 - - Keyword search remains fully functional regardless 51 - 52 - #### Search Result Context 53 - 54 - - [x] Implement `search_posts(query, mode, limit)`: 55 - - `keyword`: FTS5 MATCH query (always available) 56 - - `semantic`: embed query string → vec similarity search (requires embeddings enabled) 57 - - `hybrid`: run both, merge via reciprocal rank fusion (falls back to keyword-only if embeddings disabled) 58 - - [x] `get_sync_status(did)` → last sync time, post counts, cursor state 59 - - [x] Model management: download `nomic-embed-text-v1.5` ONNX after explicit opt-in to `<app_data_dir>/models/` (skipped when embeddings disabled) 60 - - [x] Background sync: trigger after login, then every 15 min 61 - 62 - ### Frontend 63 - 64 - #### Search UI 65 - 66 - - [x] search bar (`/` or `CTRL/CMD + F` to focus) with mode selector (network / keyword / semantic / hybrid), `Motion` sliding indicator underline 67 - - [x] search results with staggered `Motion` fade-in, highlighted keyword matches 68 - 69 - #### Embeddings 70 - 71 - - [x] embeddings opt-in toggle in settings/search UI (keeps semantic search off by default, skips model download until enabled) 72 - - [x] model download progress bar (percentage + ETA) during first semantic-search setup 73 - - Semantic search is off by default 74 - - Dedicated preflight route explains what semantic search provides before download starts 75 - 76 - #### Sync Indexing 77 - 78 - - [x] sync status indicator with animated progress bar, `Presence` fade-out on complete 79 - - [x] reindex button: triggers `reindex_embeddings()`, shown in search settings or sync status area 80 - - [x] empty state illustration when no posts synced yet 81 - - [x] `Tab` cycles search mode (network → keyword → semantic → hybrid), `Escape` clears 5 + Completed on April 5, 2026. See [CHANGELOG](../CHANGELOG.md) for more details.
+9 -67
docs/tasks/08-multicolumn.md
··· 2 2 3 3 Spec: [multicolumn.md](../specs/multicolumn.md) 4 4 5 - ## Overview 6 - 7 - TweetDeck-style multicolumn layout allowing users to view multiple feeds and/or AT Explorer panels side by side. Each column is an independent, scrollable pane that can display any feed (timeline, custom feed, list feed) or an explorer view (PDS browser, repo browser, collection/record views). 8 - 9 - ## Steps 10 - 11 - ### Backend - `src-tauri/src/columns.rs` + `src-tauri/src/commands/columns.rs` 5 + For more details on completed work, see the [CHANGELOG](../CHANGELOG.md) 12 6 13 - - [x] SQLite migration: `columns` table (`id TEXT PRIMARY KEY, account_did TEXT, kind TEXT, config TEXT, position INTEGER, width TEXT, created_at TEXT`) 14 - - `kind`: `feed` | `explorer` | `diagnostics` | `messages` | `search` | `profile` - determines the column type 15 - - `config`: JSON blob - for feeds: `{ feed_uri, feed_type }`, for explorer: `{ target_uri }`, for diagnostics: `{ did }`, for messages: `{}`, for search: `{ query, mode }`, for profile: `{ actor, handle?, did?, displayName? }` 16 - - `width`: `narrow` | `standard` | `wide` 17 - - [x] `get_columns(account_did: String)` - return ordered column list for the active account 18 - - [x] `add_column(account_did: String, kind: String, config: String, position: Option<u32>)` - insert at position or append 19 - - [x] `remove_column(id: String)` - delete column by ID 20 - - [x] `reorder_columns(ids: Vec<String>)` - bulk update positions 21 - - [x] `update_column(id: String, config: Option<String>, width: Option<String>)` - modify column settings 22 - 23 - ### Frontend - Column Layout 7 + ## Parking Lot 24 8 25 - - [x] Multicolumn route (`/deck`) accessible from app rail icon (`i-ri-layout-column-line`) 26 - - [x] Horizontal scrolling container with snap points per column 27 - - [x] Three column width presets: narrow (320px), standard (420px), wide (560px) 28 - - [x] Column header bar: feed/explorer name, width toggle, close button, drag handle 9 + - [ ] Right-click column header for context menu (resize, duplicate, close) 10 + - [ ] Responsive: collapse to single-column on narrow windows with horizontal swipe navigation 29 11 - [ ] Drag-and-drop column reordering with `Motion` position animation (move left/right via header buttons works; true DnD is parking lot) 30 - - [x] `Motion` scale-in animation when adding a column 31 - - [ ] Responsive: collapse to single-column on narrow windows with horizontal swipe navigation 32 12 33 - ### Frontend - Column Types 34 - 35 - #### Feed Column 36 - 37 - - [x] Reuse existing feed content loader and post card components from Milestone 03 38 - - [x] Independent scroll position and cursor pagination per column 39 - - [ ] Column-specific feed preferences (hide reposts/replies/quotes) 40 - - [ ] Inline thread expansion (click post to expand thread within the column) 41 - 42 - #### Explorer Column 13 + ### Explorer Column 43 14 44 - - [x] Reuse existing explorer views from Milestone 05 (PDS, repo, collection, record) 45 - - [x] Independent navigation stack per column (breadcrumbs, back/forward) 46 15 - [ ] Compact record rendering mode for narrower column widths 47 16 48 - #### Diagnostics Column 17 + ### Diagnostics Column 49 18 50 19 - [ ] Reuse social diagnostics panel from Milestone 12 (stub in place — updates when Milestone 12 lands) 51 20 - [ ] Tab navigation within column for lists/labels/blocks/starter packs/backlinks 52 21 - [ ] Compact card layout adapted to column width 53 22 54 - #### Messages Column 55 - 56 - - [x] Reuse the existing messages panel inside deck columns 57 - - [x] Blur DM content until hovered or focused 58 - 59 - #### Search Column 60 - 61 - - [x] Reuse the existing search panel inside deck columns 62 - - [x] Persist search query + mode in column config 63 - 64 - #### Profile Column 65 - 66 - - [x] Reuse the existing profile panel inside deck columns 67 - - [x] Add profile column creation via actor typeahead 68 - 69 - ### Frontend - Column Management 23 + ### Feed Column 70 24 71 - - [x] "Add column" button (`i-ri-add-line`) opens a picker panel: 72 - - Feed picker: lists pinned feeds, saved feeds, list feeds 73 - - Explorer picker: input field for at:// URI, handle, DID, or PDS URL 74 - - Diagnostics picker: input field for handle or DID 75 - - Messages picker: opens DM inbox 76 - - Search picker: accepts query + mode 77 - - Profile picker: typeahead-first actor selection 78 - - [ ] Right-click column header for context menu (resize, duplicate, close) 79 - - [x] Keyboard shortcuts: `Ctrl+Shift+N` add column, `Ctrl+Shift+W` close focused column 80 - - [x] Persist column layout to SQLite per account - restore on app launch 81 - 82 - ### Parking Lot 83 - 84 - - [x] Search results column type 25 + - [ ] Column-specific feed preferences (hide reposts/replies/quotes) 26 + - [ ] Inline thread expansion (click post to expand thread within the column)
+1 -45
docs/tasks/09-profile.md
··· 2 2 3 3 Spec: [profile.md](../specs/profile.md) 4 4 5 - Depends on: Milestone 03 (Feeds - post card, feed loading), Milestone 02 (Auth - session, account context) 6 - 7 - ## Steps 8 - 9 - ### Backend - `src-tauri/src/commands/profile.rs` 10 - 11 - - [x] `get_profile(actor: String)` - `app.bsky.actor.getProfile` 12 - - [x] `get_author_feed(actor: String, cursor: Option<String>, limit: Option<u32>)` - `app.bsky.feed.getAuthorFeed` 13 - - [x] `get_actor_likes(actor: String, cursor: Option<String>, limit: Option<u32>)` - `app.bsky.feed.getActorLikes` 14 - - [x] `follow_actor(did: String)` - create `app.bsky.graph.follow` record, return record URI 15 - - [x] `unfollow_actor(uri: String)` - delete follow record via `com.atproto.repo.deleteRecord` 16 - - [x] `get_followers(actor: String, cursor: Option<String>, limit: Option<u32>)` - `app.bsky.graph.getFollowers` 17 - - [x] `get_follows(actor: String, cursor: Option<String>, limit: Option<u32>)` - `app.bsky.graph.getFollows` 18 - 19 - ### Frontend - Profile Hero & Scroll Behavior 20 - 21 - - [x] Profile hero section: banner image with parallax, avatar, display name, handle, bio, metadata row, stat counters 22 - - [x] Scroll-driven avatar condensation: avatar shrinks from 128px and shifts right as user scrolls 23 - - [x] **Fix**: Avatar, display name, and handle must move together as a joined group on scroll — as the avatar shrinks, the name and handle slide up beside it to form a compact sticky header 24 - - [x] Badge row for relationship indicators (Following, Follows you, Muted, etc.) 25 - - [x] Responsive: reduced padding on narrow widths, shorter banner on medium widths 26 - 27 - ### Frontend - Profile Tabs & Feed 28 - 29 - - [x] Four-tab layout: Posts, Replies, Media, Likes 30 - - [x] Sticky tab bar with backdrop blur below the condensed hero 31 - - [x] Per-tab feed filtering (posts excludes replies, replies only, media only) 32 - - [x] Likes tab uses separate `getActorLikes` endpoint 33 - - [x] Cursor-based pagination with "Load more" button 34 - - [x] Skeleton loading states for both hero and feed content 35 - - [x] Error state with message for profile load failures 36 - 37 - ### Frontend - Follow / Unfollow 38 - 39 - - [x] Follow/unfollow button on non-self profiles 40 - - [x] Visual states: "Follow" (outline), "Following" (filled), "Unfollow" (hover state) 41 - - [x] Optimistic UI update with rollback on error 42 - - [x] Badge row updates immediately on follow/unfollow 43 - 44 - ### Frontend - Following & Follower Lists 45 - 46 - - [x] Tappable follower/following stat counts open list overlay 47 - - [x] Paginated actor list with compact cards (avatar, name, handle, bio snippet, follow button) 48 - - [x] `Presence` slide-up overlay with backdrop blur 49 - - [x] Cursor-based pagination with infinite scroll or "Load more" 5 + Completed on March 31, 2026. See [CHANGELOG](../CHANGELOG.md) for more details.
+1 -71
docs/tasks/12-social-diagnostics.md
··· 2 2 3 3 Spec: [social-diagnostics.md](../specs/social-diagnostics.md) 4 4 5 - ## Steps 6 - 7 - ### Backend - Constellation Client (`src-tauri/src/constellation.rs`) 8 - 9 - - [x] Constellation HTTP client struct with configurable base URL (default: `https://constellation.microcosm.blue`) 10 - - [x] `get_backlinks_count(subject: String, source: String)` - `blue.microcosm.links.getBacklinksCount` 11 - - [x] `get_backlinks(subject: String, source: String, limit: Option<u32>)` - `blue.microcosm.links.getBacklinks` 12 - - [x] `get_distinct_dids(subject: String, source: String, limit: Option<u32>, cursor: Option<String>)` - `blue.microcosm.links.getDistinct` 13 - - [x] `get_many_to_many_counts(subject: String, source: String, path_to_other: String)` - `blue.microcosm.links.getManyToManyCounts` 14 - - [x] `get_many_to_many(subject: String, source: String, path_to_other: String, limit: Option<u32>)` - `blue.microcosm.links.getManyToMany` 15 - 16 - ### Backend - Diagnostics Commands (`src-tauri/src/commands/diagnostics.rs`) 17 - 18 - - [x] `get_account_lists(did: String)` - query Constellation for `app.bsky.graph.listitem:subject` backlinks, extract list URIs, hydrate via `app.bsky.graph.getList` 19 - - [x] `get_account_labels(did: String)` - query `com.atproto.label.queryLabels` (Bluesky API) 20 - - [x] `get_account_blocked_by(did: String, limit: Option<u32>, cursor: Option<String>)` - Constellation `getDistinct` for `app.bsky.graph.block:subject` 21 - - [x] `get_account_blocking(did: String, cursor: Option<String>)` - `com.atproto.repo.listRecords` on target's `app.bsky.graph.block` collection 22 - - [x] `get_account_starter_packs(did: String)` - Constellation backlinks from starter pack collections 23 - - [x] `get_record_backlinks(uri: String)` - Constellation backlinks grouped by interaction type (likes, reposts, replies, quotes) 24 - 25 - ### Backend - Settings 26 - 27 - - [x] `constellation_url` field in settings table (default: `https://constellation.microcosm.blue`) 28 - - [x] `set_constellation_url(url: String)` / `get_constellation_url()` commands 29 - 30 - ### Frontend - Diagnostics Panel 31 - 32 - - [x] Tabbed panel component with 5 tabs: Lists, Labels, Blocks, Starter Packs, Backlinks 33 - - [x] Tab switching with `Motion` sliding indicator underline 34 - - [x] Number key shortcuts (`1`–`5`) for tab switching 35 - - [x] `Escape` to close panel 36 - 37 - ### Frontend - Lists Tab 38 - 39 - - [x] List cards: name, owner, description, purpose badge, member count 40 - - [x] Grouped by purpose (curation / moderation / reference) 41 - - [x] Skeleton loading matching card dimensions 42 - - [x] Neutral framing - no aggregate risk scoring or warning badges 43 - 44 - ### Frontend - Labels Tab 45 - 46 - - [x] Label chips with source attribution (labeling service name) 47 - - [x] Uniform muted styling - no severity color-coding 48 - - [x] Tooltip with label definition, source, and visibility effect 49 - - [x] Explanatory empty state (what labels are, not "no labels found") 50 - - [x] `Motion` scale-in on load 51 - 52 - ### Frontend - Blocks Tab 53 - 54 - - [x] Counts-only default view (no names or profile cards on first load) 55 - - [x] "Show details" expand with contextualizing copy (*"Blocks are a normal part of social media..."*) 56 - - [x] `Presence` height animation on expand with staggered card fade-in 57 - - [x] No warning banners, color-coding, or language implying abnormality 58 - - [x] Self-view framing: "Your boundaries" (not "Who blocked you") 59 - 60 - ### Frontend - Starter Packs Tab 61 - 62 - - [x] Compact starter pack cards: title, creator, description, member count 63 - - [x] Link to view in AT Explorer 64 - 65 - ### Frontend - Backlinks Tab (Record Context) 66 - 67 - - [x] Grouped by type: likes, reposts, replies, quote posts 68 - - [x] Count per type with expandable sections 69 - - [x] Individual actor/record cards within sections 70 - 71 - ### Frontend - Integration Points 72 - 73 - - [x] Profile view: "Context" tab (alongside Posts/Replies/Media/Likes) - not the default tab 74 - - [x] AT Explorer record view: backlinks supplementary panel (engagement data only, no moderation data) 75 - - [x] AT Explorer repo view: follower/following counts from Constellation (no block counts in summaries) 5 + Completed March 30, 2026. See [CHANGELOG](../CHANGELOG.md) for more details.
+2 -37
docs/tasks/14-drafts.md
··· 4 4 5 5 Depends on: Milestone 03 (Feeds — composer, `create_post`) 6 6 7 - ## Steps 8 - 9 - ### Backend - `src-tauri/src/drafts.rs` + `src-tauri/src/commands/drafts.rs` 10 - 11 - - [x] SQLite migration: `drafts` table (`id TEXT PRIMARY KEY, account_did TEXT NOT NULL, text TEXT NOT NULL, reply_parent_uri TEXT, reply_parent_cid TEXT, reply_root_uri TEXT, reply_root_cid TEXT, quote_uri TEXT, quote_cid TEXT, title TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL`) 12 - - [x] `Draft` and `DraftInput` structs mirroring the schema 13 - - [x] `list_drafts(account_did: String)` — return all drafts for the account, ordered by `updated_at` desc 14 - - [x] `get_draft(id: String)` — single draft by ID 15 - - [x] `save_draft(input: DraftInput)` — upsert: if `id` is present and exists, update; otherwise insert with new UUID 16 - - [x] `delete_draft(id: String)` — hard delete 17 - - [x] `submit_draft(id: String)` — load draft, call `create_post`, delete draft on success, return `CreateRecordResult` 7 + Complete 2026-04-07. See [CHANGELOG.md](../CHANGELOG.md) 18 8 19 - ### Frontend - Drafts List Panel 20 - 21 - - [x] Drafts list panel component with `Presence` slide-up from composer 22 - - [x] Draft cards: title or text preview, reply/quote context indicator, relative timestamp, delete button 23 - - [x] Tap draft to load into composer (confirmation if composer has content) 24 - - [x] Delete with confirmation 25 - - [x] Empty state: *"No drafts yet. Saved posts will appear here."* 26 - - [x] `Ctrl/Cmd+D` keyboard shortcut to open drafts list 27 - 28 - ### Frontend - Composer Integration 29 - 30 - - [ ] Autosave: debounced (3s inactivity) save to draft while composing, tracked by draft `id` in composer state 31 - - [ ] Autosave indicator in composer footer: "Saved" / "Saving..." text 32 - - [ ] "Save as draft" button in composer header — explicit save + close composer 33 - - [ ] Draft count badge on drafts list button 34 - - [ ] `Ctrl/Cmd+S` keyboard shortcut to save current composer as draft 35 - - [ ] On app launch, detect unsaved autosave draft → toast: *"You have an unsaved post. Restore?"* with Restore / Discard 36 - 37 - ### Frontend - Draft Lifecycle 38 - 39 - - [ ] Loading a draft into the composer tracks the draft `id` so subsequent autosaves update (not duplicate) 40 - - [ ] Successful post submission deletes the associated draft 41 - - [ ] Explicit discard from composer deletes the autosave draft 42 - - [ ] Account switch clears composer state; autosave draft persists for the original account 43 - 44 - ### Parking Lot 9 + ## Parking Lot 45 10 46 11 - [ ] Media attachments in drafts (requires local blob caching + re-upload on submit) 47 12 - [ ] Thread builder (compose multi-post threads as a single draft)