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