···40404141## Additional Features
42424343-- **Backlinks**: show records that reference the current record (requires relay/constellation)
4343+- **Backlinks** (via Constellation): show records that reference the current record, grouped by interaction type (likes, reposts, replies, quotes) with counts and expandable actor lists. Uses `blue.microcosm.links.getBacklinks` and `getBacklinksCount` endpoints.
4444- **Jetstream live view**: stream new records in real-time via `jacquard::jetstream`
4545- **CAR export**: download repo as CAR archive via `com.atproto.sync.getRepo`
4646- **Moderation labels**: query and display labels via `com.atproto.label.queryLabels`
+123
docs/specs/multicolumn.md
···11+# Multicolumn Views
22+33+TweetDeck-style layout allowing users to view multiple feeds, AT Explorer panels, and social diagnostics panels side by side. Each column is an independent, scrollable pane with its own state.
44+55+## Layout Model
66+77+A horizontally scrolling container of columns. Each column has a fixed width class and independent scroll position.
88+99+### Column Widths
1010+1111+| Width | Pixels | Use Case |
1212+| ---------- | ------ | --------------------------------- |
1313+| `narrow` | 320px | Notification-style, compact feeds |
1414+| `standard` | 420px | Default for feeds and diagnostics |
1515+| `wide` | 560px | AT Explorer, thread views |
1616+1717+Columns snap to their width boundaries. The container scrolls horizontally when total column width exceeds the viewport. On narrow windows (< 768px), collapse to single-column with horizontal swipe navigation.
1818+1919+### Column Anatomy
2020+2121+```text
2222+┌───────────────────────────┐
2323+│ [≡] Column Title [-][×]│ ← header: drag handle, title, width toggle, close
2424+├───────────────────────────┤
2525+│ │
2626+│ Column Content │ ← independent scrollable content area
2727+│ (feed / explorer / │
2828+│ diagnostics) │
2929+│ │
3030+└───────────────────────────┘
3131+```
3232+3333+- **Drag handle** (`i-ri-draggable`): reorder columns via drag-and-drop
3434+- **Title**: feed name, explorer path, or diagnostics target
3535+- **Width toggle**: cycle narrow → standard → wide
3636+- **Close** (`i-ri-close-line`): remove column with confirmation if it has unsaved state
3737+3838+## Column Types
3939+4040+### Feed Column
4141+4242+Reuses feed content loader and post card components from the feeds module.
4343+4444+- Independent cursor pagination and scroll position
4545+- Column-specific feed preferences (hide reposts/replies/quotes)
4646+- Inline thread expansion (click post → expand thread within column)
4747+- Supports: timeline, custom feed generators, list feeds
4848+4949+### Explorer Column
5050+5151+Reuses AT Explorer views (PDS, repo, collection, record).
5252+5353+- Independent navigation stack per column (breadcrumbs, back/forward)
5454+- Compact record rendering mode when column width is `narrow`
5555+- Full JSON view available in `standard` and `wide`
5656+5757+### Diagnostics Column
5858+5959+Displays a social diagnostics panel for a target DID.
6060+6161+- Shows all diagnostics tabs (lists, labels, blocks, starter packs, backlinks)
6262+- Tab navigation within the column
6363+- Compact card layout adapted to column width
6464+6565+## Column Management
6666+6767+### Adding Columns
6868+6969+"Add column" button (`i-ri-add-line`) in the deck toolbar opens a picker:
7070+7171+- **Feed picker**: pinned feeds, saved feeds, list feeds
7272+- **Explorer picker**: input field accepting at:// URI, handle, DID, or PDS URL
7373+- **Diagnostics picker**: input field accepting handle or DID
7474+7575+New columns append to the right by default. Optional position insertion via drag during add.
7676+7777+### Persistence
7878+7979+Column layout is stored per account in SQLite:
8080+8181+```sql
8282+CREATE TABLE columns (
8383+ id TEXT PRIMARY KEY,
8484+ account_did TEXT NOT NULL,
8585+ kind TEXT NOT NULL, -- 'feed' | 'explorer' | 'diagnostics'
8686+ config TEXT NOT NULL, -- JSON: feed → { feed_uri, feed_type }, explorer → { target_uri }, diagnostics → { did }
8787+ position INTEGER NOT NULL,
8888+ width TEXT NOT NULL DEFAULT 'standard',
8989+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
9090+);
9191+```
9292+9393+Layout restored on app launch per active account. Account switch swaps the full column set.
9494+9595+### Context Menu
9696+9797+Right-click column header:
9898+9999+- Resize (narrow / standard / wide)
100100+- Duplicate column
101101+- Move left / Move right
102102+- Close
103103+104104+## Keyboard Shortcuts
105105+106106+| Key | Action |
107107+| ---------------- | ---------------------- |
108108+| `Ctrl+Shift+N` | Add column |
109109+| `Ctrl+Shift+W` | Close focused column |
110110+| `Ctrl+[` / `]` | Focus prev/next column |
111111+| `Ctrl+Shift+[/]` | Move column left/right |
112112+113113+Focus is indicated by a subtle `primary` glow on the column header (ambient glow, per design spec).
114114+115115+## UX Polish
116116+117117+- Drag-and-drop reorder: `Motion` position animation with spring easing
118118+- Add column: `Presence` scale-in from center
119119+- Remove column: `Presence` scale-out with adjacent columns sliding to fill gap via `Motion`
120120+- Column focus transition: `Motion` glow fade on header
121121+- Horizontal scroll: smooth scroll-snap with momentum
122122+- Responsive collapse: `Presence` crossfade when switching between multicolumn and single-column modes
123123+- Skeleton screens per column while content loads
+14-8
docs/specs/mvp.md
···11-# Lazurite Desktop — MVP Spec
11+# Lazurite Desktop - MVP Spec
2233-A native desktop BlueSky/AT Protocol client built with **Tauri v2** (Rust) + **SolidJS**, focused on power-user features: multi-account, local semantic search, AT Protocol data exploration, and long-form content via standard.site lexicons.
33+A native desktop BlueSky/AT Protocol client built with **Tauri v2** (Rust) + **SolidJS**, focused on power-user features: multi-account, local semantic search, AT Protocol data exploration, and Constellation-powered social diagnostics.
4455## Architecture
66···99│ SolidJS Frontend (WebView) │
1010│ ├─ Timeline / Feed views │
1111│ ├─ AT Explorer (pds.ls-style) │
1212+│ ├─ Social Diagnostics panel │
1213│ ├─ Search UI (FTS + semantic) │
1414+│ ├─ Multicolumn deck view │
1315│ └─ Account switcher │
1416├─────────────────────────────────────────────┤
1517│ Tauri IPC (Commands + Events) │
1618├─────────────────────────────────────────────┤
1719│ Rust Backend │
1818-│ ├─ jacquard — XRPC client + types │
1919-│ ├─ jacquard::oauth — OAuth 2.1 loopback │
2020-│ ├─ rusqlite + sqlite-vec — local storage │
2121-│ ├─ fastembed — nomic-embed-text │
2222-│ └─ tauri plugins — deep-link, log, │
2020+│ ├─ jacquard - XRPC client + types │
2121+│ ├─ jacquard::oauth - OAuth 2.1 loopback │
2222+│ ├─ rusqlite + sqlite-vec - local storage │
2323+│ ├─ fastembed - nomic-embed-text │
2424+│ ├─ constellation - backlink index API │
2525+│ └─ tauri plugins - deep-link, log, │
2326│ updater │
2427└─────────────────────────────────────────────┘
2528```
···3437| `sqlite-vec` | Vector similarity search extension for SQLite |
3538| `fastembed` | Local ONNX inference for `nomic-embed-text-v1.5` embeddings |
3639| `tauri-plugin-deep-link` | Register `at://` URI scheme handler |
4040+| `constellation` | Constellation HTTP client for global ATProto backlink queries |
3741| `solid-motionone` | Animation primitives (`Motion`, `Presence`) for SolidJS via Motion One |
38423943## Cross-Cutting Concerns
···5458- [Authentication & Accounts](./auth.md)
5559- [Feeds & Social](./feeds.md)
5660- [AT Explorer](./explorer.md)
6161+- [Settings](./settings.md)
5762- [Search & Embeddings](./search.md)
5858-- [standard.site Integration](./standard-site.md)
6363+- [Social Diagnostics](./social-diagnostics.md)
6464+- [Multicolumn Views](./multicolumn.md)
+6-6
docs/specs/search.md
···2233Search has two scopes:
4455-1. **Network search**: server-side search via Bluesky APIs — no local indexing. Always available.
55+1. **Network search**: server-side search via Bluesky APIs - no local indexing. Always available.
662. **Local search**: full-text + semantic search over the **authenticated user's own** liked and bookmarked/saved posts, stored locally in SQLite.
7788Local semantic search (embeddings) is **opt-out**: enabled by default, but can be disabled in settings. When disabled, only local keyword (FTS) search is available and the embedding model is not downloaded.
991010## Network Search (not indexed)
11111212-Server-side Bluesky search APIs. These are thin wrappers — no local storage or indexing.
1212+Server-side Bluesky search APIs. These are thin wrappers - no local storage or indexing.
13131414### `app.bsky.feed.searchPosts`
1515···6767## Local Data Pipeline
686869691. **Sync**: on login and periodically, fetch the authenticated user's own likes (`app.bsky.feed.getActorLikes`) and bookmarks. Paginate using the API cursor, store posts in SQLite.
7070-2. **Cursor persistence**: store the last-seen API cursor per `(did, source)` in the `sync_state` table. On subsequent syncs, resume from the stored cursor so we only fetch new posts — never re-fetch the full history.
7070+2. **Cursor persistence**: store the last-seen API cursor per `(did, source)` in the `sync_state` table. On subsequent syncs, resume from the stored cursor so we only fetch new posts - never re-fetch the full history.
71713. **Index FTS**: insert post text into SQLite FTS5 virtual table for keyword search (always active).
72724. **Embed** _(opt-out)_: run post text through `fastembed` with `nomic-embed-text-v1.5` (768-dim). Store vectors in `sqlite-vec` virtual table. Skipped when embeddings are disabled.
73735. **Reindex**: a manual "Reindex" action clears all embeddings from `posts_vec` and re-embeds every post. Useful after model updates or if the index becomes corrupted.
···100100-- Full-text search (always active)
101101CREATE VIRTUAL TABLE posts_fts USING fts5(text, uri UNINDEXED, content=posts, content_rowid=rowid);
102102103103--- Vector embeddings (opt-out — only populated when embeddings enabled)
103103+-- Vector embeddings (opt-out - only populated when embeddings enabled)
104104CREATE VIRTUAL TABLE posts_vec USING vec0(
105105 uri TEXT PRIMARY KEY,
106106 embedding float[768]
···111111112112| Mode | Scope | How |
113113| -------- | ------ | ----------------------------------------------------------------------------------------- |
114114-| Network | Remote | Server-side via Bluesky APIs (posts, actors, starter packs) — not indexed locally |
114114+| Network | Remote | Server-side via Bluesky APIs (posts, actors, starter packs) - not indexed locally |
115115| Keyword | Local | `SELECT * FROM posts_fts WHERE posts_fts MATCH ?` |
116116| Semantic | Local | Embed query → `SELECT * FROM posts_vec WHERE embedding MATCH ? ORDER BY distance LIMIT k` |
117117| Hybrid | Local | Run keyword + semantic, merge results by reciprocal rank fusion |
···126126## Tauri Commands
127127128128```rs
129129-// Network search (not indexed — direct API calls)
129129+// Network search (not indexed - direct API calls)
130130search_posts_network(query: String, sort: Option<String>, limit: Option<u32>, cursor: Option<String>) -> NetworkSearchResult
131131search_actors(query: String, limit: Option<u32>, cursor: Option<String>) -> ActorSearchResult
132132search_starter_packs(query: String, limit: Option<u32>, cursor: Option<String>) -> StarterPackSearchResult
+149
docs/specs/settings.md
···11+# Settings
22+33+Central configuration surface for the app. Settings are stored in the existing `app_settings` SQLite table (key-value) and take effect immediately — no save/apply button.
44+55+## Storage
66+77+The `app_settings` table already exists (migration `006_app_settings.sql`):
88+99+```sql
1010+CREATE TABLE IF NOT EXISTS app_settings (
1111+ key TEXT PRIMARY KEY,
1212+ value TEXT NOT NULL
1313+);
1414+```
1515+1616+All settings are stored as text values. The backend exposes typed accessors that parse/serialize as needed (booleans as `"0"`/`"1"`, integers as decimal strings, JSON for structured values).
1717+1818+### Settings Keys
1919+2020+| Key | Type | Default | Description |
2121+| ----------------------- | ------- | ------------------------------------------ | ------------------------------------------ |
2222+| `theme` | string | `"auto"` | `"light"`, `"dark"`, or `"auto"` (OS sync) |
2323+| `timeline_refresh_secs` | integer | `60` | Feed auto-refresh interval in seconds |
2424+| `notifications_desktop` | boolean | `true` | Show OS desktop notifications |
2525+| `notifications_badge` | boolean | `true` | Show unread badge on app icon / tray |
2626+| `notifications_sound` | boolean | `false` | Play sound on new notification |
2727+| `embeddings_enabled` | boolean | `true` | Enable semantic search (already exists) |
2828+| `constellation_url` | string | `"https://constellation.microcosm.blue"` | Constellation instance base URL |
2929+| `spacedust_url` | string | `"https://spacedust.microcosm.blue"` | Spacedust instance base URL |
3030+| `spacedust_instant` | boolean | `false` | Bypass Spacedust 21-second debounce buffer |
3131+| `spacedust_enabled` | boolean | `false` | Use Spacedust for real-time notifications |
3232+| `global_shortcut` | string | `"Ctrl+Shift+N"` | Global composer shortcut |
3333+3434+## Tauri Commands
3535+3636+```rust
3737+// Read all settings as a typed struct
3838+get_settings() -> AppSettings
3939+4040+// Update a single setting (validates key + value before persisting)
4141+update_setting(key: String, value: String) -> ()
4242+4343+// Data management
4444+get_cache_size() -> CacheSize // { feeds_bytes, embeddings_bytes, fts_bytes, total_bytes }
4545+clear_cache(scope: CacheClearScope) -> () // "all" | "feeds" | "embeddings" | "fts"
4646+export_data(format: ExportFormat, path: String) -> () // "json" | "csv"
4747+reset_app() -> () // full wipe — requires confirmation on frontend
4848+4949+// Log viewer
5050+get_log_entries(limit: u32, level: Option<String>) -> Vec<LogEntry>
5151+```
5252+5353+## Sections
5454+5555+### 1. Appearance
5656+5757+Theme toggle: light / dark / auto (synced with OS).
5858+5959+- Three-way segmented control, not a dropdown
6060+- `Motion` crossfade on the entire app surface when switching themes
6161+- Auto mode listens to `prefers-color-scheme` media query and Tauri's `Theme::changed` event
6262+6363+### 2. Timeline
6464+6565+Feed auto-refresh interval.
6666+6767+- Segmented control: 30s, 1m, 2m, 5m, manual
6868+- "Manual" disables auto-refresh — user pulls to refresh or uses keyboard shortcut
6969+- Setting applies globally across all feed views and multicolumn feed columns
7070+7171+### 3. Notifications
7272+7373+- **Desktop notifications**: toggle OS-level notifications via `tauri-plugin-notification`
7474+- **Badge count**: toggle unread count on tray icon / dock badge
7575+- **Sound**: toggle notification sound (system default sound, not custom)
7676+7777+### 4. Accounts
7878+7979+Reuses the OAuth flow from the auth module (Task 02).
8080+8181+- List of all linked accounts with avatar, handle, DID, and PDS URL
8282+- Active account indicator
8383+- "Add account" button → triggers OAuth loopback flow
8484+- "Remove account" → confirmation dialog → revoke tokens, delete stored data for that account
8585+- "Switch" action on each account row (also accessible from the sidebar account switcher)
8686+8787+### 5. Search & Embeddings
8888+8989+- **Embeddings toggle**: opt-out of semantic search. When disabled, the embedding model is not downloaded and only keyword search is available. Toggling off does not delete existing embeddings — a separate "Clear embeddings" action handles that.
9090+- **Model status**: shows whether `nomic-embed-text-v1.5` is downloaded, its size on disk, and a "Download now" / "Remove model" action
9191+- **Reindex**: triggers a full re-embed of all synced posts. Shows progress bar during operation.
9292+9393+### 6. Services
9494+9595+External service instance configuration for self-hosters.
9696+9797+- **Constellation URL**: text input with URL validation. Default: `https://constellation.microcosm.blue`
9898+- **Spacedust URL**: text input with URL validation. Default: `https://spacedust.microcosm.blue`
9999+- **Spacedust real-time**: toggle to use Spacedust for push notifications vs. polling `listNotifications`
100100+- **Spacedust instant mode**: toggle to bypass the 21-second debounce buffer
101101+- Each URL field has a "Test connection" button that makes a health-check request and shows success/failure inline
102102+103103+### 7. Data
104104+105105+- **Cache size display**: breakdown by category (feeds, embeddings, FTS index) with total
106106+- **Clear cache**: scoped clearing — all, or by category. Confirmation dialog for "clear all"
107107+- **Export**: export user data (liked posts, bookmarks, settings) as JSON or CSV. Uses Tauri's save dialog to pick destination.
108108+- **Reset app**: full data wipe — drops all user tables, clears auth tokens, re-runs migrations. Behind a two-step confirmation: type "RESET" to confirm. This is the nuclear option.
109109+110110+### 8. Logs
111111+112112+In-app log viewer for debugging.
113113+114114+- Reads log entries from `tauri-plugin-log` log files
115115+- Level filter: segmented control for `info`, `warn`, `error`, `all`
116116+- Scrollable log output in monospace, newest at top
117117+- "Copy all" and "Open log file" actions
118118+- Collapsible by default — expands inline within the settings view
119119+120120+### 9. About
121121+122122+- App version (from `tauri.conf.json`)
123123+- License (MIT)
124124+- Link to source repository
125125+- "Check for updates" button (triggers `tauri-plugin-updater` check)
126126+- Credits / contributors
127127+128128+## Layout
129129+130130+Settings is a single scrollable view, not a sidebar+content split. Each section is a `surface_container` card with `xl` radius, separated by `spacing-8` (2rem). Sections are ordered top-to-bottom as listed above.
131131+132132+On narrow viewports, cards stack full-width. On wider viewports (> 768px), cards have a comfortable max-width (~640px) centered in the content area.
133133+134134+## Keyboard Shortcuts
135135+136136+| Key | Action |
137137+| ------- | ----------------------------- |
138138+| `,` | Open/focus settings from anywhere |
139139+| `Escape`| Close settings (navigate back) |
140140+141141+## UX Polish
142142+143143+- `Presence` slide transitions when navigating to/from settings
144144+- Theme switch: `Motion` crossfade on the entire app surface
145145+- Destructive action modals: glass overlay (`surface_container_highest` at 70% + `backdrop-blur: 20px`)
146146+- Toggle switches: `Motion` spring on the thumb element
147147+- Cache size: animated number transitions when values update after clearing
148148+- Export/reindex: progress bars with percentage, cancelable where possible
149149+- Log viewer: smooth scroll, syntax-highlighted log levels
+147
docs/specs/social-diagnostics.md
···11+# Social Diagnostics (Constellation-Powered)
22+33+A tabbed panel surfacing social context and network-side artifacts for any account or record. Powered by [Constellation](https://constellation.microcosm.blue/) - a global ATProto backlink index.
44+55+## Design Philosophy
66+77+This feature exists to give users **context**, the goal being informed decision-making, understanding who someone is in the network before you follow, reply, or trust.
88+99+**Guiding principles:**
1010+1111+- **Inform, don't alarm.**
1212+ - Present data neutrally. Avoid language or visual treatments that frame normal social dynamics as threats (e.g., being on lists or being blocked is routine, not inherently suspicious).
1313+- **No composite risk scores.**
1414+ - Do not reduce a person's social standing to a number or traffic-light rating. Show the data; let the user interpret it.
1515+- **Context over counts.**
1616+ - A raw block count is meaningless without knowing the account's visibility. Prefer showing _what kind_ of lists/labels over _how many_.
1717+- **Discoverable, not pushed.**
1818+ - Diagnostics are available when sought, but the app should not proactively surface "warnings" about accounts in feeds or profiles. No unsolicited badges, banners, or alerts based on diagnostics data.
1919+- **Respect the viewed account.**
2020+ - This panel shows public protocol data, but the presentation should not feel like a dossier. Avoid dense tables of blockers/blocked. Default to aggregate summaries; expand to specifics only on request.
2121+- **Self-diagnostics first.**
2222+ - The most natural entry point is "what does the network say about _me_?" - help users understand their own footprint before inspecting others.
2323+2424+## Constellation Integration
2525+2626+Constellation indexes all references between AT Protocol records across the entire network. Lazurite queries it to answer "who/what points at this thing?" without running its own relay.
2727+2828+### XRPC Endpoints Used
2929+3030+| Endpoint | Purpose |
3131+| ------------------------------------------ | ---------------------------------------------------- |
3232+| `blue.microcosm.links.getBacklinksCount` | Count backlinks from a specific collection+path |
3333+| `blue.microcosm.links.getBacklinks` | List backlink records (with pagination) |
3434+| `blue.microcosm.links.getDistinct` | Distinct DIDs linking to a target (with pagination) |
3535+| `blue.microcosm.links.getManyToManyCounts` | Counts for join records (e.g., list items → lists) |
3636+| `blue.microcosm.links.getManyToMany` | List join records linking target to secondary target |
3737+3838+All endpoints accept a `subject` (DID, AT-URI, or URL) and a `source` in `collection:path` format (e.g., `app.bsky.graph.listitem:subject`).
3939+4040+### Constellation Client
4141+4242+A thin HTTP client in `src-tauri/src/constellation.rs` targeting a configurable Constellation instance (default: `https://constellation.microcosm.blue`). User-configurable in settings to support self-hosted instances.
4343+4444+## Tabs
4545+4646+### 1. Lists
4747+4848+What lists is this account on? Lists are how the network organizes and curates accounts - being on lists is normal and often positive (curation, topical grouping, community membership).
4949+5050+**Query:** `getBacklinks` with `subject={DID}`, `source=app.bsky.graph.listitem:subject`
5151+→ extract list URIs from backlink records → hydrate via `app.bsky.graph.getList`
5252+5353+**Display:**
5454+5555+- List card: name, owner handle, description, purpose (curate/modlist/reference), member count
5656+- Sort by member count or recency
5757+- Grouped by purpose (curation lists vs. moderation lists vs. reference)
5858+- No aggregate "risk" scoring - show the lists and let the user read them
5959+6060+### 2. Labels
6161+6262+Labels are moderation metadata applied by labeling services. They affect content visibility and carry context about how the network's moderation infrastructure sees an account. Present them factually - a label is a data point, not a verdict.
6363+6464+**Query:** `com.atproto.label.queryLabels` with subject DID (Bluesky API, not Constellation)
6565+6666+**Display:**
6767+6868+- Label chips with source attribution (which labeling service applied it)
6969+- Muted, uniform styling - avoid color-coding that implies judgment (no red/amber/green severity scale)
7070+- Tooltip with label definition, the labeling service that applied it, and what effect it has on visibility
7171+- If no labels: brief explanatory text about what labels are, not an empty state that implies "clean"
7272+7373+### 3. Blocks & Boundaries
7474+7575+Blocking is a normal, healthy social boundary. This tab helps users understand interaction boundaries - especially useful when replies or follows silently fail. The framing should be matter-of-fact, never voyeuristic.
7676+7777+**Blocked-by (incoming):**
7878+Constellation indexes backlinks. A block record created by DID-A targeting DID-B has `subject: DID-B`. Querying backlinks to DID-B from `app.bsky.graph.block:subject` returns the blocking accounts.
7979+8080+`getBacklinksCount` with `subject={DID}`, `source=app.bsky.graph.block:subject` → count
8181+`getDistinct` with same params → paginated list of DIDs
8282+8383+**Blocking (outgoing):**
8484+Not available via Constellation backlinks (the account's own block records point away from them). Use `com.atproto.repo.listRecords` on the account's `app.bsky.graph.block` collection.
8585+8686+**Display:**
8787+8888+- Show counts only by default - no names, no profile cards on first load
8989+- "Show details" expands to a profile card list, with a brief note: _"Blocks are a normal part of social media. This data is public on the AT Protocol."_
9090+- No color-coding, warning banners, or language implying that high counts are abnormal
9191+- When viewing your own account: frame as "your boundaries" rather than "who blocked you"
9292+9393+### 4. Starter Packs
9494+9595+How are people discovering this account?
9696+9797+**Query:** `getBacklinks` with `subject={DID}`, `source=app.bsky.graph.starterpack:listItemsSample[].subject`
9898+(Starter packs reference DIDs in their `listItemsSample` array.)
9999+100100+Alternatively, check list memberships - starter packs are backed by lists, so list membership from tab 1 can be cross-referenced.
101101+102102+**Display:**
103103+104104+- Compact starter pack cards: title, creator, description, member count
105105+- Link to view full starter pack in AT Explorer
106106+107107+### 5. Backlinks (Record Context)
108108+109109+When viewing a specific record (post, profile, etc.), show all references to it.
110110+111111+**Query:** `getBacklinks` with `subject={AT-URI}`, various sources:
112112+113113+- `app.bsky.feed.like:subject.uri` - likes
114114+- `app.bsky.feed.repost:subject.uri` - reposts
115115+- `app.bsky.feed.post:reply.parent.uri` - direct replies
116116+- `app.bsky.feed.post:embed.record.uri` - quote posts
117117+118118+**Display:**
119119+120120+- Grouped by interaction type with counts
121121+- Expandable sections showing individual records/actors
122122+- Useful in AT Explorer record view as a supplementary panel
123123+124124+## Integration Points
125125+126126+- **Profile view**: "Context" tab alongside Posts/Replies/Media/Likes - available but not the default tab
127127+- **AT Explorer record view**: backlinks panel showing references to current record (engagement data, not moderation data)
128128+- **AT Explorer repo view**: follower/following counts from Constellation (no block counts in summary views - those belong in the dedicated diagnostics panel only)
129129+- **No feed-level enrichment**: diagnostics data should never appear inline on posts in feeds. Users navigate to it intentionally.
130130+131131+## Keyboard Shortcuts
132132+133133+| Key | Action |
134134+| -------------------- | ------------------------------- |
135135+| `CMD/CTRL` + `1`–`5` | Switch between diagnostics tabs |
136136+| `Escape` | Close diagnostics panel |
137137+138138+## UX Polish
139139+140140+- Tab switch: `Motion` sliding indicator underline
141141+- List/card loading: skeleton screens matching card dimensions
142142+- Detail expansion: `Presence` height animation with staggered card fade-in
143143+- Label chips: `Motion` scale-in on load
144144+- Counts: animated number transition (`Motion` on value change)
145145+- Error states: inline retry per section, not full-panel error
146146+- **Tone**: use neutral, descriptive copy throughout. Avoid words like "risk", "warning", "suspicious", "flagged". Prefer "context", "details", "public data"
147147+- **Progressive disclosure**: all sensitive sections (blocks, moderation labels) default to summary/count view. Expanding to specifics requires a deliberate click, with a brief contextualizing note
···77- [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
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
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`
+2-2
docs/tasks/02-auth.md
···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
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`
+16-16
docs/tasks/03-feeds.md
···4455## Steps
6677-### Backend — `src-tauri/src/feed.rs`
77+### Backend - `src-tauri/src/feed.rs`
8899-- [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
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`
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)`
19192020-### Frontend — Feed Tabs & Content
2020+### Frontend - Feed Tabs & Content
21212222-- [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`)
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
27272828-### Frontend — Post Card & Actions
2828+### Frontend - Post Card & Actions
29293030-- [x] Post card component (author, text, embeds, timestamps, action bar) — `Motion` fade-in on viewport enter
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
33333434-### Frontend — Thread View
3434+### Frontend - Thread View
35353636- [x] Thread view with nested replies
3737- [x] Navigate into thread from post card (`o` / `Enter`) with route-backed thread URLs
38383939-### Frontend — Post Composer
3939+### Frontend - Post Composer
40404141- [x] Composer with `Presence` slide-up/down, `n` keyboard shortcut to open
4242- [x] Mention/hashtag autocomplete
···4444- [x] Quote post embed
4545- [x] Tray button and global keyboard shortcut to open composer from anywhere
46464747-### Frontend — Feed Preferences
4747+### Frontend - Feed Preferences
48484949- [x] Per-feed display toggles (hide reposts/replies/quotes) via `feedViewPref`
5050- [x] Feeds drawer for accessing saved (unpinned) feeds
+4-4
docs/tasks/04-notifications.md
···8899- [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`
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
16161717## Frontend
18181919-- [x] notifications panel with two tabs — Mentions / Activity (Aeronaut pattern)
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
-8
docs/tasks/05-explorer.md
···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
2525-- [ ] **Frontend**: Jetstream live-tail view with `Motion` slide-in for new records
2626-2727-### Parking Lot
2828-2929-These require update to the spec & more research before implementation.
3030-3131-- [ ] **Frontend**: Firehose Viewer
3232-- [ ] **Frontend**: [Spacedust](https://spacedust.microcosm.blue/) Viewer
···11+# Task 06: Settings
22+33+Spec: [settings.md](../specs/settings.md)
44+55+## Steps
66+77+### Backend - `src-tauri/src/settings.rs`
88+99+- [ ] `get_settings()` - read user preferences from SQLite `settings` table, return as typed struct
1010+- [ ] `update_setting(key: String, value: String)` - upsert a key-value pair in `settings` table
1111+- [ ] `clear_cache()` - delete cached feed data, embedded vectors, and FTS5 index; vacuum database
1212+- [ ] `reset_app()` - drop all user data tables and re-run migrations; clear auth tokens
1313+- [ ] `export_data(format: String, path: String)` - export user data as JSON or CSV to chosen path
1414+- [ ] `get_log_entries(limit: u32, level: Option<String>)` - read recent log entries for the in-app log viewer
1515+- [ ] SQLite migration: `settings` table (`key TEXT PRIMARY KEY, value TEXT, updated_at TEXT`)
1616+1717+### Frontend - Settings View
1818+1919+- [ ] Settings route (`/settings`) accessible from app rail icon (`i-ri-settings-3-line`)
2020+- [ ] Section-based layout using `surface_container` cards with `xl` 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 Task 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+- [ ] `Presence` slide transitions between setting sections
3030+- [ ] Keyboard shortcut: `,` to open settings from anywhere
3131+- [ ] Confirmation modal for destructive actions (clear cache, reset app, remove account) using glass overlay
-19
docs/tasks/07-standard-site.md
···11-# Task 07: Standard.site Integration
22-33-Spec: [standard-site.md](../specs/standard-site.md)
44-55-## Steps
66-77-- [ ] Create `src-tauri/src/publications.rs`
88-- [ ] `get_publications(did: String)` — list `site.standard.publication` records from repo
99-- [ ] `get_documents(did: String, cursor: Option<String>)` — list `site.standard.document` records
1010-- [ ] `get_document(did: String, rkey: String)` — fetch single document content
1111-- [ ] `subscribe_publication(did: String, rkey: String)` — create `site.standard.graph.subscription` record
1212-- [ ] `unsubscribe_publication(uri: String)` — delete subscription record
1313-- [ ] `list_subscriptions()` — list user's publication subscriptions
1414-- [ ] **Frontend**: publication card with `Motion` scale-up on hover
1515-- [ ] **Frontend**: document list with staggered `Motion` fade-in
1616-- [ ] **Frontend**: markdown reader view with `Presence` slide-in from right
1717-- [ ] **Frontend**: subscribe/unsubscribe `Motion` pop on icon toggle
1818-- [ ] **Frontend**: "Publications" tab on profile views (when records exist)
1919-- [ ] **Search integration**: index document text in `posts_fts` and `posts_vec`
-30
docs/tasks/08-settings.md
···11-# Task 08: Settings
22-33-Spec: TBD
44-55-## Steps
66-77-### Backend — `src-tauri/src/settings.rs`
88-99-- [ ] `get_settings()` — read user preferences from SQLite `settings` table, return as typed struct
1010-- [ ] `update_setting(key: String, value: String)` — upsert a key-value pair in `settings` table
1111-- [ ] `clear_cache()` — delete cached feed data, embedded vectors, and FTS5 index; vacuum database
1212-- [ ] `reset_app()` — drop all user data tables and re-run migrations; clear auth tokens
1313-- [ ] `export_data(format: String, path: String)` — export user data as JSON or CSV to chosen path
1414-- [ ] `get_log_entries(limit: u32, level: Option<String>)` — read recent log entries for the in-app log viewer
1515-- [ ] SQLite migration: `settings` table (`key TEXT PRIMARY KEY, value TEXT, updated_at TEXT`)
1616-1717-### Frontend — Settings View
1818-1919-- [ ] Settings route (`/settings`) accessible from app rail icon (`i-ri-settings-3-line`)
2020-- [ ] Section-based layout using `surface_container` cards with `xl` 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 Task 02)
2626- 6. **Logs** — Collapsible log viewer with level filtering (`info`, `warn`, `error`)
2727- 7. **About** — Version info, license (MIT), contributors, support links
2828-- [ ] `Presence` slide transitions between setting sections
2929-- [ ] Keyboard shortcut: `,` to open settings from anywhere
3030-- [ ] Confirmation modal for destructive actions (clear cache, reset app, remove account) using glass overlay
+86
docs/tasks/08-social-diagnostics.md
···11+# Task 08: Social Diagnostics
22+33+Spec: [social-diagnostics.md](../specs/social-diagnostics.md)
44+55+## Steps
66+77+### Backend - Constellation Client (`src-tauri/src/constellation.rs`)
88+99+- [ ] Constellation HTTP client struct with configurable base URL (default: `https://constellation.microcosm.blue`)
1010+- [ ] `get_backlinks_count(subject: String, source: String)` - `blue.microcosm.links.getBacklinksCount`
1111+- [ ] `get_backlinks(subject: String, source: String, limit: Option<u32>)` - `blue.microcosm.links.getBacklinks`
1212+- [ ] `get_distinct_dids(subject: String, source: String, limit: Option<u32>, cursor: Option<String>)` - `blue.microcosm.links.getDistinct`
1313+- [ ] `get_many_to_many_counts(subject: String, source: String, path_to_other: String)` - `blue.microcosm.links.getManyToManyCounts`
1414+- [ ] `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+- [ ] `get_account_lists(did: String)` - query Constellation for `app.bsky.graph.listitem:subject` backlinks, extract list URIs, hydrate via `app.bsky.graph.getList`
1919+- [ ] `get_account_labels(did: String)` - query `com.atproto.label.queryLabels` (Bluesky API)
2020+- [ ] `get_account_blocked_by(did: String, limit: Option<u32>, cursor: Option<String>)` - Constellation `getDistinct` for `app.bsky.graph.block:subject`
2121+- [ ] `get_account_blocking(did: String, cursor: Option<String>)` - `com.atproto.repo.listRecords` on target's `app.bsky.graph.block` collection
2222+- [ ] `get_account_starter_packs(did: String)` - Constellation backlinks from starter pack collections
2323+- [ ] `get_record_backlinks(uri: String)` - Constellation backlinks grouped by interaction type (likes, reposts, replies, quotes)
2424+2525+### Backend - Settings
2626+2727+- [ ] `constellation_url` field in settings table (default: `https://constellation.microcosm.blue`)
2828+- [ ] `set_constellation_url(url: String)` / `get_constellation_url()` commands
2929+3030+### Frontend - Diagnostics Panel
3131+3232+- [ ] Tabbed panel component with 5 tabs: Lists, Labels, Blocks, Starter Packs, Backlinks
3333+- [ ] Tab switching with `Motion` sliding indicator underline
3434+- [ ] Number key shortcuts (`1`–`5`) for tab switching
3535+- [ ] `Escape` to close panel
3636+3737+### Frontend - Lists Tab
3838+3939+- [ ] List cards: name, owner, description, purpose badge, member count
4040+- [ ] Grouped by purpose (curation / moderation / reference)
4141+- [ ] Skeleton loading matching card dimensions
4242+- [ ] Neutral framing - no aggregate risk scoring or warning badges
4343+4444+### Frontend - Labels Tab
4545+4646+- [ ] Label chips with source attribution (labeling service name)
4747+- [ ] Uniform muted styling - no severity color-coding
4848+- [ ] Tooltip with label definition, source, and visibility effect
4949+- [ ] Explanatory empty state (what labels are, not "no labels found")
5050+- [ ] `Motion` scale-in on load
5151+5252+### Frontend - Blocks Tab
5353+5454+- [ ] Counts-only default view (no names or profile cards on first load)
5555+- [ ] "Show details" expand with contextualizing copy (*"Blocks are a normal part of social media..."*)
5656+- [ ] `Presence` height animation on expand with staggered card fade-in
5757+- [ ] No warning banners, color-coding, or language implying abnormality
5858+- [ ] Self-view framing: "Your boundaries" (not "Who blocked you")
5959+6060+### Frontend - Starter Packs Tab
6161+6262+- [ ] Compact starter pack cards: title, creator, description, member count
6363+- [ ] Link to view in AT Explorer
6464+6565+### Frontend - Backlinks Tab (Record Context)
6666+6767+- [ ] Grouped by type: likes, reposts, replies, quote posts
6868+- [ ] Count per type with expandable sections
6969+- [ ] Individual actor/record cards within sections
7070+7171+### Frontend - Integration Points
7272+7373+- [ ] Profile view: "Context" tab (alongside Posts/Replies/Media/Likes) - not the default tab
7474+- [ ] AT Explorer record view: backlinks supplementary panel (engagement data only, no moderation data)
7575+- [ ] AT Explorer repo view: follower/following counts from Constellation (no block counts in summaries)
7676+7777+### UX Tone Review
7878+7979+- [ ] Audit all copy for neutral language - no "risk", "warning", "suspicious", "flagged"
8080+- [ ] Ensure all sensitive sections use progressive disclosure (summary → details on click)
8181+- [ ] Verify self-view ("my account") uses empowering framing, not anxiety-inducing
8282+8383+### Parking Lot
8484+8585+- [ ] Network relationship diff over time (requires historical snapshots)
8686+- [ ] Profile/identity history timeline (handle/DID/PDS changes)
+29
docs/tasks/09-jetstream.md
···11+# Task 09: Jetstream
22+33+Spec: [explorer.md](../specs/explorer.md)
44+55+## Tasks
66+77+### Backend - Jetstream (`src-tauri/src/jetstream.rs`)
88+99+- [ ] `jetstream_subscribe(config: JetstreamConfig)` - open a WebSocket connection to a Jetstream instance via `jacquard::jetstream`, emit Tauri events for each incoming record
1010+ - `JetstreamConfig`: `{ url: String, collections: Option<Vec<String>>, dids: Option<Vec<String>> }` - filter by collection NSIDs and/or DIDs
1111+- [ ] `jetstream_unsubscribe()` - close the active WebSocket connection, clean up
1212+- [ ] Tauri event emission: `jetstream:record` with serialized record payload (collection, rkey, DID, operation, record JSON)
1313+- [ ] Connection lifecycle events: `jetstream:connected`, `jetstream:disconnected`, `jetstream:error`
1414+- [ ] Reconnection with backoff on disconnect (reuse `jacquard::jetstream` reconnect behavior if available)
1515+1616+### Frontend - Jetstream Live-Tail
1717+1818+- [ ] Jetstream live-tail view with `Motion` slide-in for new records
1919+- [ ] Filter controls: collection NSID filter, DID filter, operation type (create/update/delete)
2020+- [ ] Pause/resume button to freeze the stream without disconnecting
2121+- [ ] Record count and events-per-second indicator
2222+- [ ] Click record to navigate to its full record view in the explorer
2323+2424+### Parking Lot
2525+2626+These require update to the spec & more research before implementation.
2727+2828+- [ ] **Frontend**: Firehose Viewer
2929+- [ ] **Frontend**: Spacedust integration (see [Task 10](./10-spacedust.md))
···11-# Task 09: Multicolumn Views
11+# Task 11: Multicolumn Views
2233-Spec: TBD
33+Spec: [multicolumn.md](../specs/multicolumn.md)
4455## Overview
66···8899## Steps
10101111-### Backend — `src-tauri/src/columns.rs`
1111+### Backend - `src-tauri/src/columns.rs`
12121313- [ ] 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` — determines the column type
1515- - `config`: JSON blob — for feeds: `{ feed_uri, feed_type }`, for explorer: `{ target_uri }`
1414+ - `kind`: `feed` | `explorer` | `diagnostics` - determines the column type
1515+ - `config`: JSON blob - for feeds: `{ feed_uri, feed_type }`, for explorer: `{ target_uri }`, for diagnostics: `{ did }`
1616 - `width`: `narrow` | `standard` | `wide`
1717-- [ ] `get_columns(account_did: String)` — return ordered column list for the active account
1818-- [ ] `add_column(account_did: String, kind: String, config: String, position: Option<u32>)` — insert at position or append
1919-- [ ] `remove_column(id: String)` — delete column by ID
2020-- [ ] `reorder_columns(ids: Vec<String>)` — bulk update positions
2121-- [ ] `update_column(id: String, config: Option<String>, width: Option<String>)` — modify column settings
1717+- [ ] `get_columns(account_did: String)` - return ordered column list for the active account
1818+- [ ] `add_column(account_did: String, kind: String, config: String, position: Option<u32>)` - insert at position or append
1919+- [ ] `remove_column(id: String)` - delete column by ID
2020+- [ ] `reorder_columns(ids: Vec<String>)` - bulk update positions
2121+- [ ] `update_column(id: String, config: Option<String>, width: Option<String>)` - modify column settings
22222323-### Frontend — Column Layout
2323+### Frontend - Column Layout
24242525- [ ] Multicolumn route (`/deck`) accessible from app rail icon (`i-ri-layout-column-line`)
2626- [ ] Horizontal scrolling container with snap points per column
···3030- [ ] `Presence` scale-in animation when adding a column, scale-out on removal
3131- [ ] Responsive: collapse to single-column on narrow windows with horizontal swipe navigation
32323333-### Frontend — Column Types
3333+### Frontend - Column Types
34343535#### Feed Column
3636···4545- [ ] Independent navigation stack per column (breadcrumbs, back/forward)
4646- [ ] Compact record rendering mode for narrower column widths
47474848-### Frontend — Column Management
4848+#### Diagnostics Column
4949+5050+- [ ] Reuse social diagnostics panel from Task 08
5151+- [ ] Tab navigation within column for lists/labels/blocks/starter packs/backlinks
5252+- [ ] Compact card layout adapted to column width
5353+5454+### Frontend - Column Management
49555056- [ ] "Add column" button (`i-ri-add-line`) opens a picker panel:
5157 - Feed picker: lists pinned feeds, saved feeds, list feeds
5258 - Explorer picker: input field for at:// URI, handle, DID, or PDS URL
5959+ - Diagnostics picker: input field for handle or DID
5360- [ ] Right-click column header for context menu (resize, duplicate, close)
5461- [ ] Keyboard shortcuts: `Ctrl+Shift+N` add column, `Ctrl+Shift+W` close focused column, `Ctrl+[/]` focus prev/next column
5555-- [ ] Persist column layout to SQLite per account — restore on app launch
6262+- [ ] Persist column layout to SQLite per account - restore on app launch
56635764### Parking Lot
5865
+8-8
docs/tasks/10-release.md
docs/tasks/12-release.md
···11-# Task 10: Release
11+# Task 12: Release
2233## Overview
44···1212 - macOS: `icon.icns` (16–1024px)
1313 - Windows: `icon.ico` (16–256px)
1414 - Linux: `icon.png` at 32, 128, 256, 512px
1515-- [ ] Update `tauri.conf.json` — `productName`, `identifier`, window title, bundle metadata (description, copyright, category)
1515+- [ ] Update `tauri.conf.json` - `productName`, `identifier`, window title, bundle metadata (description, copyright, category)
1616- [ ] Splash / welcome screen for first-launch flow
17171818### macOS
19192020- [ ] Code signing via Apple Developer certificate (`APPLE_CERTIFICATE`, `APPLE_CERTIFICATE_PASSWORD` secrets)
2121- [ ] Notarization via `notarytool` (`APPLE_ID`, `APPLE_PASSWORD`, `APPLE_TEAM_ID` secrets)
2222-- [ ] DMG packaging — `tauri build` produces `.dmg` by default on macOS
2222+- [ ] DMG packaging - `tauri build` produces `.dmg` by default on macOS
2323- [ ] Universal binary (x86_64 + aarch64) via `--target universal-apple-darwin`
2424- [ ] Verify Gatekeeper passes on clean macOS install
25252626### Windows
27272828-- [ ] NSIS installer — `tauri build` default on Windows; configure install path, start menu shortcut, desktop shortcut
2828+- [ ] NSIS installer - `tauri build` default on Windows; configure install path, start menu shortcut, desktop shortcut
2929- [ ] Optional: MSI installer via `bundle > targets` configuration
3030- [ ] Code signing via certificate (`WINDOWS_CERTIFICATE`, `WINDOWS_CERTIFICATE_PASSWORD` secrets)
3131 - Evaluate EV vs OV certificate for SmartScreen reputation
···34343535### Linux
36363737-- [ ] AppImage packaging — portable, no-install binary (primary distribution format)
3838-- [ ] `.deb` package for Debian/Ubuntu — configure dependencies (libwebkit2gtk, libssl)
3737+- [ ] AppImage packaging - portable, no-install binary (primary distribution format)
3838+- [ ] `.deb` package for Debian/Ubuntu - configure dependencies (libwebkit2gtk, libssl)
3939- [ ] `.rpm` package for Fedora/RHEL
4040- [ ] Desktop entry file with icon, categories, and MIME type for `at://` deep links
4141- [ ] Verify launch on Ubuntu 22.04+, Fedora 38+, and Arch (via AppImage)
42424343-### Auto-Update — `tauri-plugin-updater`
4343+### Auto-Update - `tauri-plugin-updater`
44444545- [ ] Add `tauri-plugin-updater` to `Cargo.toml` dependencies (currently commented out)
4646- [ ] Configure update endpoint pointing to GitHub Releases (`latest.json` / release assets)
···4949- [ ] Differential updates where supported (Tauri v2 update mechanism)
5050- [ ] Signing update bundles with Tauri's update keypair (`TAURI_SIGNING_PRIVATE_KEY`, `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`)
51515252-### CI/CD — GitHub Actions
5252+### CI/CD - GitHub Actions
53535454- [ ] Matrix build workflow: `[macos-latest, windows-latest, ubuntu-latest]`
5555 - macOS job: build universal binary, sign, notarize, produce `.dmg`
+53
docs/tasks/10-spacedust.md
···11+# Task 10: Spacedust
22+33+Spec: TBD (see [Spacedust API docs](../../.sandbox/spacedust.md))
44+55+## Overview
66+77+[Spacedust](https://spacedust.microcosm.blue/) is a configurable ATProto notifications firehose by microcosm.blue. It streams real-time backlink events (likes, reposts, follows, replies, etc.) for specific subjects, with a built-in 21-second debounce buffer to filter out quickly-undone interactions.
88+99+Where Jetstream (Task 09) streams raw firehose records, Spacedust streams *resolved backlinks* - making it ideal for live notification feeds and real-time engagement counters.
1010+1111+## Tasks
1212+1313+### Backend - Spacedust Client (`src-tauri/src/spacedust.rs`)
1414+1515+- [ ] Spacedust WebSocket client struct with configurable base URL (default: `https://spacedust.microcosm.blue`)
1616+- [ ] `spacedust_subscribe(config: SpacedustConfig)` - open WebSocket to `/subscribe` with query params:
1717+ - `wantedSources`: link sources to receive (e.g., `app.bsky.feed.like:subject.uri`)
1818+ - `wantedSubjectDids`: DIDs to receive links about
1919+ - `wantedSubjects`: AT-URIs to receive links about (URL-encoded)
2020+ - `instant`: optional boolean to bypass the 21-second delay buffer
2121+- [ ] `spacedust_unsubscribe()` - close WebSocket, clean up
2222+- [ ] Tauri event emission: `spacedust:link` with payload (source collection+path, subject, linking DID, operation)
2323+- [ ] Connection lifecycle events: `spacedust:connected`, `spacedust:disconnected`, `spacedust:error`
2424+- [ ] Reconnection with backoff on disconnect
2525+2626+### Backend - Notification Integration
2727+2828+- [ ] On login, auto-subscribe to Spacedust for the authenticated user's DID with common social sources:
2929+ - `app.bsky.feed.like:subject.uri` (likes on your posts)
3030+ - `app.bsky.feed.repost:subject.uri` (reposts)
3131+ - `app.bsky.feed.post:reply.parent.uri` (replies)
3232+ - `app.bsky.graph.follow:subject` (new followers)
3333+- [ ] Map incoming Spacedust events to notification records in SQLite
3434+- [ ] Deduplicate with existing notifications from `app.bsky.notification.listNotifications`
3535+3636+### Frontend - Live Engagement (Explorer Integration)
3737+3838+- [ ] When viewing a record in AT Explorer, optionally subscribe to Spacedust for that record's URI
3939+- [ ] Real-time counter updates (likes, reposts, replies ticking up) via `Motion` number transition
4040+- [ ] Toggle: "Watch live" button to start/stop per-record subscription
4141+- [ ] Visual pulse on counter increment
4242+4343+### Frontend - Settings
4444+4545+- [ ] Spacedust instance URL configuration (alongside Constellation URL in settings)
4646+- [ ] Toggle: use Spacedust for real-time notifications (vs. polling `listNotifications`)
4747+- [ ] Toggle: `instant` mode (bypass 21-second buffer - faster but noisier)
4848+4949+### Parking Lot
5050+5151+- [ ] Spacedust as a column type in multicolumn view (live notification stream)
5252+- [ ] Aggregate Spacedust events into a "live activity" dashboard
5353+- [ ] Spacedust for real-time search result updates
+13-8
docs/tasks/mvp.md
···1212- [Feeds](./03-feeds.md) - Pinned feed tabs, post rendering, composer, keyboard shortcuts, scroll animations
1313- [Notifications](./04-notifications.md) - Mentions, activity, system notifications, badge animations
14141515-## Phase 3: Power Features
1515+## Phase 3: Core Features
16161717- [AT Explorer](./05-explorer.md) - pds.ls-style data browser, at:// deep links, view transitions, keyboard nav
1818-- [Search & Embeddings](./06-search.md) - FTS5, fastembed, sqlite-vec, sync pipeline, result animations
1818+- [Settings](./06-settings.md) - Theme, notifications, data export, cache management, account management, Constellation/Spacedust instance, logs
1919+2020+## Phase 4: Power Features
2121+2222+- [Search & Embeddings](./07-search.md) - FTS5, fastembed, sqlite-vec, sync pipeline, result animations
2323+- [Social Diagnostics](./08-social-diagnostics.md) - Constellation-powered lists, labels, blocks, starter packs, backlinks
19242020-## Phase 4: Long-Form & Polish
2525+## Phase 5: Live Data
21262222-- [Standard.site](./07-standard-site.md) - Publication/document views, subscriptions, reading view transitions
2323-- [Settings](./08-settings.md) - Theme, notifications, data export, cache management, account management, logs
2424-- [Multicolumn Views](./09-multicolumn.md) - TweetDeck-style side-by-side feeds and AT Explorer panels
2727+- [Jetstream](./09-jetstream.md) - WebSocket live-tail of AT Protocol firehose, filtered record streaming
2828+- [Spacedust](./10-spacedust.md) - Real-time backlink notifications via microcosm Spacedust
25292626-## Phase 5: Release
3030+## Phase 6: Polish & Release
27312828-- [Release](./10-release.md) - Cross-platform build (macOS, Windows, Linux), code signing, auto-update, CI/CD
3232+- [Multicolumn Views](./11-multicolumn.md) - TweetDeck-style side-by-side feeds, explorer, and diagnostics panels
3333+- [Release](./12-release.md) - Cross-platform build (macOS, Windows, Linux), code signing, auto-update, CI/CD