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: reorient v0.1

+707 -128
+1 -1
docs/specs/explorer.md
··· 40 40 41 41 ## Additional Features 42 42 43 - - **Backlinks**: show records that reference the current record (requires relay/constellation) 43 + - **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. 44 44 - **Jetstream live view**: stream new records in real-time via `jacquard::jetstream` 45 45 - **CAR export**: download repo as CAR archive via `com.atproto.sync.getRepo` 46 46 - **Moderation labels**: query and display labels via `com.atproto.label.queryLabels`
+123
docs/specs/multicolumn.md
··· 1 + # Multicolumn Views 2 + 3 + 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. 4 + 5 + ## Layout Model 6 + 7 + A horizontally scrolling container of columns. Each column has a fixed width class and independent scroll position. 8 + 9 + ### Column Widths 10 + 11 + | Width | Pixels | Use Case | 12 + | ---------- | ------ | --------------------------------- | 13 + | `narrow` | 320px | Notification-style, compact feeds | 14 + | `standard` | 420px | Default for feeds and diagnostics | 15 + | `wide` | 560px | AT Explorer, thread views | 16 + 17 + 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. 18 + 19 + ### Column Anatomy 20 + 21 + ```text 22 + ┌───────────────────────────┐ 23 + │ [≡] Column Title [-][×]│ ← header: drag handle, title, width toggle, close 24 + ├───────────────────────────┤ 25 + │ │ 26 + │ Column Content │ ← independent scrollable content area 27 + │ (feed / explorer / │ 28 + │ diagnostics) │ 29 + │ │ 30 + └───────────────────────────┘ 31 + ``` 32 + 33 + - **Drag handle** (`i-ri-draggable`): reorder columns via drag-and-drop 34 + - **Title**: feed name, explorer path, or diagnostics target 35 + - **Width toggle**: cycle narrow → standard → wide 36 + - **Close** (`i-ri-close-line`): remove column with confirmation if it has unsaved state 37 + 38 + ## Column Types 39 + 40 + ### Feed Column 41 + 42 + Reuses feed content loader and post card components from the feeds module. 43 + 44 + - Independent cursor pagination and scroll position 45 + - Column-specific feed preferences (hide reposts/replies/quotes) 46 + - Inline thread expansion (click post → expand thread within column) 47 + - Supports: timeline, custom feed generators, list feeds 48 + 49 + ### Explorer Column 50 + 51 + Reuses AT Explorer views (PDS, repo, collection, record). 52 + 53 + - Independent navigation stack per column (breadcrumbs, back/forward) 54 + - Compact record rendering mode when column width is `narrow` 55 + - Full JSON view available in `standard` and `wide` 56 + 57 + ### Diagnostics Column 58 + 59 + Displays a social diagnostics panel for a target DID. 60 + 61 + - Shows all diagnostics tabs (lists, labels, blocks, starter packs, backlinks) 62 + - Tab navigation within the column 63 + - Compact card layout adapted to column width 64 + 65 + ## Column Management 66 + 67 + ### Adding Columns 68 + 69 + "Add column" button (`i-ri-add-line`) in the deck toolbar opens a picker: 70 + 71 + - **Feed picker**: pinned feeds, saved feeds, list feeds 72 + - **Explorer picker**: input field accepting at:// URI, handle, DID, or PDS URL 73 + - **Diagnostics picker**: input field accepting handle or DID 74 + 75 + New columns append to the right by default. Optional position insertion via drag during add. 76 + 77 + ### Persistence 78 + 79 + Column layout is stored per account in SQLite: 80 + 81 + ```sql 82 + CREATE TABLE columns ( 83 + id TEXT PRIMARY KEY, 84 + account_did TEXT NOT NULL, 85 + kind TEXT NOT NULL, -- 'feed' | 'explorer' | 'diagnostics' 86 + config TEXT NOT NULL, -- JSON: feed → { feed_uri, feed_type }, explorer → { target_uri }, diagnostics → { did } 87 + position INTEGER NOT NULL, 88 + width TEXT NOT NULL DEFAULT 'standard', 89 + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP 90 + ); 91 + ``` 92 + 93 + Layout restored on app launch per active account. Account switch swaps the full column set. 94 + 95 + ### Context Menu 96 + 97 + Right-click column header: 98 + 99 + - Resize (narrow / standard / wide) 100 + - Duplicate column 101 + - Move left / Move right 102 + - Close 103 + 104 + ## Keyboard Shortcuts 105 + 106 + | Key | Action | 107 + | ---------------- | ---------------------- | 108 + | `Ctrl+Shift+N` | Add column | 109 + | `Ctrl+Shift+W` | Close focused column | 110 + | `Ctrl+[` / `]` | Focus prev/next column | 111 + | `Ctrl+Shift+[/]` | Move column left/right | 112 + 113 + Focus is indicated by a subtle `primary` glow on the column header (ambient glow, per design spec). 114 + 115 + ## UX Polish 116 + 117 + - Drag-and-drop reorder: `Motion` position animation with spring easing 118 + - Add column: `Presence` scale-in from center 119 + - Remove column: `Presence` scale-out with adjacent columns sliding to fill gap via `Motion` 120 + - Column focus transition: `Motion` glow fade on header 121 + - Horizontal scroll: smooth scroll-snap with momentum 122 + - Responsive collapse: `Presence` crossfade when switching between multicolumn and single-column modes 123 + - Skeleton screens per column while content loads
+14 -8
docs/specs/mvp.md
··· 1 - # Lazurite Desktop — MVP Spec 1 + # Lazurite Desktop - MVP Spec 2 2 3 - 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. 3 + 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. 4 4 5 5 ## Architecture 6 6 ··· 9 9 │ SolidJS Frontend (WebView) │ 10 10 │ ├─ Timeline / Feed views │ 11 11 │ ├─ AT Explorer (pds.ls-style) │ 12 + │ ├─ Social Diagnostics panel │ 12 13 │ ├─ Search UI (FTS + semantic) │ 14 + │ ├─ Multicolumn deck view │ 13 15 │ └─ Account switcher │ 14 16 ├─────────────────────────────────────────────┤ 15 17 │ Tauri IPC (Commands + Events) │ 16 18 ├─────────────────────────────────────────────┤ 17 19 │ Rust Backend │ 18 - │ ├─ jacquard — XRPC client + types │ 19 - │ ├─ jacquard::oauth — OAuth 2.1 loopback │ 20 - │ ├─ rusqlite + sqlite-vec — local storage │ 21 - │ ├─ fastembed — nomic-embed-text │ 22 - │ └─ tauri plugins — deep-link, log, │ 20 + │ ├─ jacquard - XRPC client + types │ 21 + │ ├─ jacquard::oauth - OAuth 2.1 loopback │ 22 + │ ├─ rusqlite + sqlite-vec - local storage │ 23 + │ ├─ fastembed - nomic-embed-text │ 24 + │ ├─ constellation - backlink index API │ 25 + │ └─ tauri plugins - deep-link, log, │ 23 26 │ updater │ 24 27 └─────────────────────────────────────────────┘ 25 28 ``` ··· 34 37 | `sqlite-vec` | Vector similarity search extension for SQLite | 35 38 | `fastembed` | Local ONNX inference for `nomic-embed-text-v1.5` embeddings | 36 39 | `tauri-plugin-deep-link` | Register `at://` URI scheme handler | 40 + | `constellation` | Constellation HTTP client for global ATProto backlink queries | 37 41 | `solid-motionone` | Animation primitives (`Motion`, `Presence`) for SolidJS via Motion One | 38 42 39 43 ## Cross-Cutting Concerns ··· 54 58 - [Authentication & Accounts](./auth.md) 55 59 - [Feeds & Social](./feeds.md) 56 60 - [AT Explorer](./explorer.md) 61 + - [Settings](./settings.md) 57 62 - [Search & Embeddings](./search.md) 58 - - [standard.site Integration](./standard-site.md) 63 + - [Social Diagnostics](./social-diagnostics.md) 64 + - [Multicolumn Views](./multicolumn.md)
+6 -6
docs/specs/search.md
··· 2 2 3 3 Search has two scopes: 4 4 5 - 1. **Network search**: server-side search via Bluesky APIs — no local indexing. Always available. 5 + 1. **Network search**: server-side search via Bluesky APIs - no local indexing. Always available. 6 6 2. **Local search**: full-text + semantic search over the **authenticated user's own** liked and bookmarked/saved posts, stored locally in SQLite. 7 7 8 8 Local 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. 9 9 10 10 ## Network Search (not indexed) 11 11 12 - Server-side Bluesky search APIs. These are thin wrappers — no local storage or indexing. 12 + Server-side Bluesky search APIs. These are thin wrappers - no local storage or indexing. 13 13 14 14 ### `app.bsky.feed.searchPosts` 15 15 ··· 67 67 ## Local Data Pipeline 68 68 69 69 1. **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. 70 - 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. 70 + 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. 71 71 3. **Index FTS**: insert post text into SQLite FTS5 virtual table for keyword search (always active). 72 72 4. **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. 73 73 5. **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. ··· 100 100 -- Full-text search (always active) 101 101 CREATE VIRTUAL TABLE posts_fts USING fts5(text, uri UNINDEXED, content=posts, content_rowid=rowid); 102 102 103 - -- Vector embeddings (opt-out — only populated when embeddings enabled) 103 + -- Vector embeddings (opt-out - only populated when embeddings enabled) 104 104 CREATE VIRTUAL TABLE posts_vec USING vec0( 105 105 uri TEXT PRIMARY KEY, 106 106 embedding float[768] ··· 111 111 112 112 | Mode | Scope | How | 113 113 | -------- | ------ | ----------------------------------------------------------------------------------------- | 114 - | Network | Remote | Server-side via Bluesky APIs (posts, actors, starter packs) — not indexed locally | 114 + | Network | Remote | Server-side via Bluesky APIs (posts, actors, starter packs) - not indexed locally | 115 115 | Keyword | Local | `SELECT * FROM posts_fts WHERE posts_fts MATCH ?` | 116 116 | Semantic | Local | Embed query → `SELECT * FROM posts_vec WHERE embedding MATCH ? ORDER BY distance LIMIT k` | 117 117 | Hybrid | Local | Run keyword + semantic, merge results by reciprocal rank fusion | ··· 126 126 ## Tauri Commands 127 127 128 128 ```rs 129 - // Network search (not indexed — direct API calls) 129 + // Network search (not indexed - direct API calls) 130 130 search_posts_network(query: String, sort: Option<String>, limit: Option<u32>, cursor: Option<String>) -> NetworkSearchResult 131 131 search_actors(query: String, limit: Option<u32>, cursor: Option<String>) -> ActorSearchResult 132 132 search_starter_packs(query: String, limit: Option<u32>, cursor: Option<String>) -> StarterPackSearchResult
+149
docs/specs/settings.md
··· 1 + # Settings 2 + 3 + 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. 4 + 5 + ## Storage 6 + 7 + The `app_settings` table already exists (migration `006_app_settings.sql`): 8 + 9 + ```sql 10 + CREATE TABLE IF NOT EXISTS app_settings ( 11 + key TEXT PRIMARY KEY, 12 + value TEXT NOT NULL 13 + ); 14 + ``` 15 + 16 + 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). 17 + 18 + ### Settings Keys 19 + 20 + | Key | Type | Default | Description | 21 + | ----------------------- | ------- | ------------------------------------------ | ------------------------------------------ | 22 + | `theme` | string | `"auto"` | `"light"`, `"dark"`, or `"auto"` (OS sync) | 23 + | `timeline_refresh_secs` | integer | `60` | Feed auto-refresh interval in seconds | 24 + | `notifications_desktop` | boolean | `true` | Show OS desktop notifications | 25 + | `notifications_badge` | boolean | `true` | Show unread badge on app icon / tray | 26 + | `notifications_sound` | boolean | `false` | Play sound on new notification | 27 + | `embeddings_enabled` | boolean | `true` | Enable semantic search (already exists) | 28 + | `constellation_url` | string | `"https://constellation.microcosm.blue"` | Constellation instance base URL | 29 + | `spacedust_url` | string | `"https://spacedust.microcosm.blue"` | Spacedust instance base URL | 30 + | `spacedust_instant` | boolean | `false` | Bypass Spacedust 21-second debounce buffer | 31 + | `spacedust_enabled` | boolean | `false` | Use Spacedust for real-time notifications | 32 + | `global_shortcut` | string | `"Ctrl+Shift+N"` | Global composer shortcut | 33 + 34 + ## Tauri Commands 35 + 36 + ```rust 37 + // Read all settings as a typed struct 38 + get_settings() -> AppSettings 39 + 40 + // Update a single setting (validates key + value before persisting) 41 + update_setting(key: String, value: String) -> () 42 + 43 + // Data management 44 + get_cache_size() -> CacheSize // { feeds_bytes, embeddings_bytes, fts_bytes, total_bytes } 45 + clear_cache(scope: CacheClearScope) -> () // "all" | "feeds" | "embeddings" | "fts" 46 + export_data(format: ExportFormat, path: String) -> () // "json" | "csv" 47 + reset_app() -> () // full wipe — requires confirmation on frontend 48 + 49 + // Log viewer 50 + get_log_entries(limit: u32, level: Option<String>) -> Vec<LogEntry> 51 + ``` 52 + 53 + ## Sections 54 + 55 + ### 1. Appearance 56 + 57 + Theme toggle: light / dark / auto (synced with OS). 58 + 59 + - Three-way segmented control, not a dropdown 60 + - `Motion` crossfade on the entire app surface when switching themes 61 + - Auto mode listens to `prefers-color-scheme` media query and Tauri's `Theme::changed` event 62 + 63 + ### 2. Timeline 64 + 65 + Feed auto-refresh interval. 66 + 67 + - Segmented control: 30s, 1m, 2m, 5m, manual 68 + - "Manual" disables auto-refresh — user pulls to refresh or uses keyboard shortcut 69 + - Setting applies globally across all feed views and multicolumn feed columns 70 + 71 + ### 3. Notifications 72 + 73 + - **Desktop notifications**: toggle OS-level notifications via `tauri-plugin-notification` 74 + - **Badge count**: toggle unread count on tray icon / dock badge 75 + - **Sound**: toggle notification sound (system default sound, not custom) 76 + 77 + ### 4. Accounts 78 + 79 + Reuses the OAuth flow from the auth module (Task 02). 80 + 81 + - List of all linked accounts with avatar, handle, DID, and PDS URL 82 + - Active account indicator 83 + - "Add account" button → triggers OAuth loopback flow 84 + - "Remove account" → confirmation dialog → revoke tokens, delete stored data for that account 85 + - "Switch" action on each account row (also accessible from the sidebar account switcher) 86 + 87 + ### 5. Search & Embeddings 88 + 89 + - **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. 90 + - **Model status**: shows whether `nomic-embed-text-v1.5` is downloaded, its size on disk, and a "Download now" / "Remove model" action 91 + - **Reindex**: triggers a full re-embed of all synced posts. Shows progress bar during operation. 92 + 93 + ### 6. Services 94 + 95 + External service instance configuration for self-hosters. 96 + 97 + - **Constellation URL**: text input with URL validation. Default: `https://constellation.microcosm.blue` 98 + - **Spacedust URL**: text input with URL validation. Default: `https://spacedust.microcosm.blue` 99 + - **Spacedust real-time**: toggle to use Spacedust for push notifications vs. polling `listNotifications` 100 + - **Spacedust instant mode**: toggle to bypass the 21-second debounce buffer 101 + - Each URL field has a "Test connection" button that makes a health-check request and shows success/failure inline 102 + 103 + ### 7. Data 104 + 105 + - **Cache size display**: breakdown by category (feeds, embeddings, FTS index) with total 106 + - **Clear cache**: scoped clearing — all, or by category. Confirmation dialog for "clear all" 107 + - **Export**: export user data (liked posts, bookmarks, settings) as JSON or CSV. Uses Tauri's save dialog to pick destination. 108 + - **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. 109 + 110 + ### 8. Logs 111 + 112 + In-app log viewer for debugging. 113 + 114 + - Reads log entries from `tauri-plugin-log` log files 115 + - Level filter: segmented control for `info`, `warn`, `error`, `all` 116 + - Scrollable log output in monospace, newest at top 117 + - "Copy all" and "Open log file" actions 118 + - Collapsible by default — expands inline within the settings view 119 + 120 + ### 9. About 121 + 122 + - App version (from `tauri.conf.json`) 123 + - License (MIT) 124 + - Link to source repository 125 + - "Check for updates" button (triggers `tauri-plugin-updater` check) 126 + - Credits / contributors 127 + 128 + ## Layout 129 + 130 + 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. 131 + 132 + On narrow viewports, cards stack full-width. On wider viewports (> 768px), cards have a comfortable max-width (~640px) centered in the content area. 133 + 134 + ## Keyboard Shortcuts 135 + 136 + | Key | Action | 137 + | ------- | ----------------------------- | 138 + | `,` | Open/focus settings from anywhere | 139 + | `Escape`| Close settings (navigate back) | 140 + 141 + ## UX Polish 142 + 143 + - `Presence` slide transitions when navigating to/from settings 144 + - Theme switch: `Motion` crossfade on the entire app surface 145 + - Destructive action modals: glass overlay (`surface_container_highest` at 70% + `backdrop-blur: 20px`) 146 + - Toggle switches: `Motion` spring on the thumb element 147 + - Cache size: animated number transitions when values update after clearing 148 + - Export/reindex: progress bars with percentage, cancelable where possible 149 + - Log viewer: smooth scroll, syntax-highlighted log levels
+147
docs/specs/social-diagnostics.md
··· 1 + # Social Diagnostics (Constellation-Powered) 2 + 3 + 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. 4 + 5 + ## Design Philosophy 6 + 7 + 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. 8 + 9 + **Guiding principles:** 10 + 11 + - **Inform, don't alarm.** 12 + - 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). 13 + - **No composite risk scores.** 14 + - Do not reduce a person's social standing to a number or traffic-light rating. Show the data; let the user interpret it. 15 + - **Context over counts.** 16 + - A raw block count is meaningless without knowing the account's visibility. Prefer showing _what kind_ of lists/labels over _how many_. 17 + - **Discoverable, not pushed.** 18 + - 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. 19 + - **Respect the viewed account.** 20 + - 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. 21 + - **Self-diagnostics first.** 22 + - The most natural entry point is "what does the network say about _me_?" - help users understand their own footprint before inspecting others. 23 + 24 + ## Constellation Integration 25 + 26 + 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. 27 + 28 + ### XRPC Endpoints Used 29 + 30 + | Endpoint | Purpose | 31 + | ------------------------------------------ | ---------------------------------------------------- | 32 + | `blue.microcosm.links.getBacklinksCount` | Count backlinks from a specific collection+path | 33 + | `blue.microcosm.links.getBacklinks` | List backlink records (with pagination) | 34 + | `blue.microcosm.links.getDistinct` | Distinct DIDs linking to a target (with pagination) | 35 + | `blue.microcosm.links.getManyToManyCounts` | Counts for join records (e.g., list items → lists) | 36 + | `blue.microcosm.links.getManyToMany` | List join records linking target to secondary target | 37 + 38 + All endpoints accept a `subject` (DID, AT-URI, or URL) and a `source` in `collection:path` format (e.g., `app.bsky.graph.listitem:subject`). 39 + 40 + ### Constellation Client 41 + 42 + 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. 43 + 44 + ## Tabs 45 + 46 + ### 1. Lists 47 + 48 + 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). 49 + 50 + **Query:** `getBacklinks` with `subject={DID}`, `source=app.bsky.graph.listitem:subject` 51 + → extract list URIs from backlink records → hydrate via `app.bsky.graph.getList` 52 + 53 + **Display:** 54 + 55 + - List card: name, owner handle, description, purpose (curate/modlist/reference), member count 56 + - Sort by member count or recency 57 + - Grouped by purpose (curation lists vs. moderation lists vs. reference) 58 + - No aggregate "risk" scoring - show the lists and let the user read them 59 + 60 + ### 2. Labels 61 + 62 + 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. 63 + 64 + **Query:** `com.atproto.label.queryLabels` with subject DID (Bluesky API, not Constellation) 65 + 66 + **Display:** 67 + 68 + - Label chips with source attribution (which labeling service applied it) 69 + - Muted, uniform styling - avoid color-coding that implies judgment (no red/amber/green severity scale) 70 + - Tooltip with label definition, the labeling service that applied it, and what effect it has on visibility 71 + - If no labels: brief explanatory text about what labels are, not an empty state that implies "clean" 72 + 73 + ### 3. Blocks & Boundaries 74 + 75 + 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. 76 + 77 + **Blocked-by (incoming):** 78 + 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. 79 + 80 + `getBacklinksCount` with `subject={DID}`, `source=app.bsky.graph.block:subject` → count 81 + `getDistinct` with same params → paginated list of DIDs 82 + 83 + **Blocking (outgoing):** 84 + 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. 85 + 86 + **Display:** 87 + 88 + - Show counts only by default - no names, no profile cards on first load 89 + - "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."_ 90 + - No color-coding, warning banners, or language implying that high counts are abnormal 91 + - When viewing your own account: frame as "your boundaries" rather than "who blocked you" 92 + 93 + ### 4. Starter Packs 94 + 95 + How are people discovering this account? 96 + 97 + **Query:** `getBacklinks` with `subject={DID}`, `source=app.bsky.graph.starterpack:listItemsSample[].subject` 98 + (Starter packs reference DIDs in their `listItemsSample` array.) 99 + 100 + Alternatively, check list memberships - starter packs are backed by lists, so list membership from tab 1 can be cross-referenced. 101 + 102 + **Display:** 103 + 104 + - Compact starter pack cards: title, creator, description, member count 105 + - Link to view full starter pack in AT Explorer 106 + 107 + ### 5. Backlinks (Record Context) 108 + 109 + When viewing a specific record (post, profile, etc.), show all references to it. 110 + 111 + **Query:** `getBacklinks` with `subject={AT-URI}`, various sources: 112 + 113 + - `app.bsky.feed.like:subject.uri` - likes 114 + - `app.bsky.feed.repost:subject.uri` - reposts 115 + - `app.bsky.feed.post:reply.parent.uri` - direct replies 116 + - `app.bsky.feed.post:embed.record.uri` - quote posts 117 + 118 + **Display:** 119 + 120 + - Grouped by interaction type with counts 121 + - Expandable sections showing individual records/actors 122 + - Useful in AT Explorer record view as a supplementary panel 123 + 124 + ## Integration Points 125 + 126 + - **Profile view**: "Context" tab alongside Posts/Replies/Media/Likes - available but not the default tab 127 + - **AT Explorer record view**: backlinks panel showing references to current record (engagement data, not moderation data) 128 + - **AT Explorer repo view**: follower/following counts from Constellation (no block counts in summary views - those belong in the dedicated diagnostics panel only) 129 + - **No feed-level enrichment**: diagnostics data should never appear inline on posts in feeds. Users navigate to it intentionally. 130 + 131 + ## Keyboard Shortcuts 132 + 133 + | Key | Action | 134 + | -------------------- | ------------------------------- | 135 + | `CMD/CTRL` + `1`–`5` | Switch between diagnostics tabs | 136 + | `Escape` | Close diagnostics panel | 137 + 138 + ## UX Polish 139 + 140 + - Tab switch: `Motion` sliding indicator underline 141 + - List/card loading: skeleton screens matching card dimensions 142 + - Detail expansion: `Presence` height animation with staggered card fade-in 143 + - Label chips: `Motion` scale-in on load 144 + - Counts: animated number transition (`Motion` on value change) 145 + - Error states: inline retry per section, not full-panel error 146 + - **Tone**: use neutral, descriptive copy throughout. Avoid words like "risk", "warning", "suspicious", "flagged". Prefer "context", "details", "public data" 147 + - **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
+1 -1
docs/specs/v0.2.md
··· 159 159 160 160 ## Phased Breakdown 161 161 162 - 1. Phase 1 - lists, labels, blocked-by, starter packs (via Constellation + Bluesky APIs) 162 + 1. Phase 1 - standard.site integration: publication/document views, subscriptions, reading view (see [standard-site.md](./standard-site.md)) 163 163 2. Phase 2 - profile history, apps & collections tab (via UFOs) 164 164 3. Phase 3 - NSID explorer and collection insights (via UFOs) 165 165 4. Phase 4 - network relationship diffs and provenance graphs (Constellation)
+2 -2
docs/tasks/01-backend-setup.md
··· 7 7 - [x] Add Cargo dependencies: `jacquard`, `rusqlite` (bundled), `sqlite-vec`, `fastembed`, `tokio` 8 8 - [x] Add Tauri plugins: `tauri-plugin-deep-link`, `tauri-plugin-notification`, `tauri-plugin-log` 9 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 10 + - [x] Create `src-tauri/src/db.rs` - initialize SQLite, run migrations, load `sqlite-vec` extension 11 11 - [x] Create migration system: `accounts`, `posts`, `posts_fts`, `posts_vec` tables 12 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 13 + - [x] Create `src-tauri/src/state.rs` - `AppState` struct holding DB pool, active session, account list 14 14 - [x] Register `AppState` as Tauri managed state 15 15 - [x] Create Tauri command scaffold with error handling pattern using `thiserror` crate 16 16 - [x] Set up dark/light theme: CSS custom properties, OS preference detection via `prefers-color-scheme`
+2 -2
docs/tasks/02-auth.md
··· 11 11 - Start loopback OAuth via `LoopbackConfig` 12 12 - Store session tokens, insert into `accounts` table 13 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 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 16 - [x] Create Tauri command `list_accounts()` → `Vec<Account>` 17 17 - [x] On app launch: restore sessions from DB, auto-refresh tokens for active account 18 18 - [x] Register `at://` scheme via deep-link plugin in `tauri.conf.json`
+16 -16
docs/tasks/03-feeds.md
··· 4 4 5 5 ## Steps 6 6 7 - ### Backend — `src-tauri/src/feed.rs` 7 + ### Backend - `src-tauri/src/feed.rs` 8 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 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 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` 16 + - [x] `create_post(text: String, reply_to: Option<ReplyRef>, embed: Option<Embed>)` - with richtext facet detection via `jacquard::richtext` 17 17 - [x] `like_post(uri: String, cid: String)` / `unlike_post(uri: String)` 18 18 - [x] `repost(uri: String, cid: String)` / `unrepost(uri: String)` 19 19 20 - ### Frontend — Feed Tabs & Content 20 + ### Frontend - Feed Tabs & Content 21 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`) 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 24 - [x] Infinite scroll with cursor pagination and scroll-position preservation 25 25 - [x] `Presence` crossfade animation on tab switch 26 26 - [x] Skeleton screens while feeds load 27 27 28 - ### Frontend — Post Card & Actions 28 + ### Frontend - Post Card & Actions 29 29 30 - - [x] Post card component (author, text, embeds, timestamps, action bar) — `Motion` fade-in on viewport enter 30 + - [x] Post card component (author, text, embeds, timestamps, action bar) - `Motion` fade-in on viewport enter 31 31 - [x] Like/repost icon `Motion` scale pop animation (1.0 -> 1.3 -> 1.0) 32 32 - [x] `j/k` keyboard navigation between posts, `l` like, `r` reply, `t` repost, `o` open thread 33 33 34 - ### Frontend — Thread View 34 + ### Frontend - Thread View 35 35 36 36 - [x] Thread view with nested replies 37 37 - [x] Navigate into thread from post card (`o` / `Enter`) with route-backed thread URLs 38 38 39 - ### Frontend — Post Composer 39 + ### Frontend - Post Composer 40 40 41 41 - [x] Composer with `Presence` slide-up/down, `n` keyboard shortcut to open 42 42 - [x] Mention/hashtag autocomplete ··· 44 44 - [x] Quote post embed 45 45 - [x] Tray button and global keyboard shortcut to open composer from anywhere 46 46 47 - ### Frontend — Feed Preferences 47 + ### Frontend - Feed Preferences 48 48 49 49 - [x] Per-feed display toggles (hide reposts/replies/quotes) via `feedViewPref` 50 50 - [x] Feeds drawer for accessing saved (unpinned) feeds
+4 -4
docs/tasks/04-notifications.md
··· 8 8 9 9 - [x] Create `src-tauri/src/notifications.rs` 10 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` 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 14 - [x] Background polling: spawn async task on login, poll every 30s, emit Tauri event on new notifications 15 15 - [x] System notifications via `tauri-plugin-notification` for mentions when app is in background 16 16 17 17 ## Frontend 18 18 19 - - [x] notifications panel with two tabs — Mentions / Activity (Aeronaut pattern) 19 + - [x] notifications panel with two tabs - Mentions / Activity (Aeronaut pattern) 20 20 - [x] unread badge on sidebar notification icon with `Motion` scale-in pop 21 21 - [x] new notification items `Motion` slide-in from top 22 22 - [x] tab switch `Presence` crossfade between Mentions/Activity
-8
docs/tasks/05-explorer.md
··· 22 22 - [x] **Frontend**: breadcrumb navigation bar with `Motion` width animation on segment changes 23 23 - [x] **Frontend**: `Presence` crossfade transitions between explorer view levels 24 24 - [x] **Frontend**: keyboard shortcuts - `Backspace` up a level, `Cmd+[/]` back/forward 25 - - [ ] **Frontend**: Jetstream live-tail view with `Motion` slide-in for new records 26 - 27 - ### Parking Lot 28 - 29 - These require update to the spec & more research before implementation. 30 - 31 - - [ ] **Frontend**: Firehose Viewer 32 - - [ ] **Frontend**: [Spacedust](https://spacedust.microcosm.blue/) Viewer
+1 -1
docs/tasks/06-search.md docs/tasks/07-search.md
··· 1 - # Task 06: Search & Embeddings 1 + # Task 07: Search & Embeddings 2 2 3 3 Spec: [search.md](../specs/search.md) 4 4
+31
docs/tasks/06-settings.md
··· 1 + # Task 06: Settings 2 + 3 + Spec: [settings.md](../specs/settings.md) 4 + 5 + ## Steps 6 + 7 + ### Backend - `src-tauri/src/settings.rs` 8 + 9 + - [ ] `get_settings()` - read user preferences from SQLite `settings` table, return as typed struct 10 + - [ ] `update_setting(key: String, value: String)` - upsert a key-value pair in `settings` table 11 + - [ ] `clear_cache()` - delete cached feed data, embedded vectors, and FTS5 index; vacuum database 12 + - [ ] `reset_app()` - drop all user data tables and re-run migrations; clear auth tokens 13 + - [ ] `export_data(format: String, path: String)` - export user data as JSON or CSV to chosen path 14 + - [ ] `get_log_entries(limit: u32, level: Option<String>)` - read recent log entries for the in-app log viewer 15 + - [ ] SQLite migration: `settings` table (`key TEXT PRIMARY KEY, value TEXT, updated_at TEXT`) 16 + 17 + ### Frontend - Settings View 18 + 19 + - [ ] Settings route (`/settings`) accessible from app rail icon (`i-ri-settings-3-line`) 20 + - [ ] Section-based layout using `surface_container` cards with `xl` 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 Task 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 + - [ ] `Presence` slide transitions between setting sections 30 + - [ ] Keyboard shortcut: `,` to open settings from anywhere 31 + - [ ] Confirmation modal for destructive actions (clear cache, reset app, remove account) using glass overlay
-19
docs/tasks/07-standard-site.md
··· 1 - # Task 07: Standard.site Integration 2 - 3 - Spec: [standard-site.md](../specs/standard-site.md) 4 - 5 - ## Steps 6 - 7 - - [ ] Create `src-tauri/src/publications.rs` 8 - - [ ] `get_publications(did: String)` — list `site.standard.publication` records from repo 9 - - [ ] `get_documents(did: String, cursor: Option<String>)` — list `site.standard.document` records 10 - - [ ] `get_document(did: String, rkey: String)` — fetch single document content 11 - - [ ] `subscribe_publication(did: String, rkey: String)` — create `site.standard.graph.subscription` record 12 - - [ ] `unsubscribe_publication(uri: String)` — delete subscription record 13 - - [ ] `list_subscriptions()` — list user's publication subscriptions 14 - - [ ] **Frontend**: publication card with `Motion` scale-up on hover 15 - - [ ] **Frontend**: document list with staggered `Motion` fade-in 16 - - [ ] **Frontend**: markdown reader view with `Presence` slide-in from right 17 - - [ ] **Frontend**: subscribe/unsubscribe `Motion` pop on icon toggle 18 - - [ ] **Frontend**: "Publications" tab on profile views (when records exist) 19 - - [ ] **Search integration**: index document text in `posts_fts` and `posts_vec`
-30
docs/tasks/08-settings.md
··· 1 - # Task 08: Settings 2 - 3 - Spec: TBD 4 - 5 - ## Steps 6 - 7 - ### Backend — `src-tauri/src/settings.rs` 8 - 9 - - [ ] `get_settings()` — read user preferences from SQLite `settings` table, return as typed struct 10 - - [ ] `update_setting(key: String, value: String)` — upsert a key-value pair in `settings` table 11 - - [ ] `clear_cache()` — delete cached feed data, embedded vectors, and FTS5 index; vacuum database 12 - - [ ] `reset_app()` — drop all user data tables and re-run migrations; clear auth tokens 13 - - [ ] `export_data(format: String, path: String)` — export user data as JSON or CSV to chosen path 14 - - [ ] `get_log_entries(limit: u32, level: Option<String>)` — read recent log entries for the in-app log viewer 15 - - [ ] SQLite migration: `settings` table (`key TEXT PRIMARY KEY, value TEXT, updated_at TEXT`) 16 - 17 - ### Frontend — Settings View 18 - 19 - - [ ] Settings route (`/settings`) accessible from app rail icon (`i-ri-settings-3-line`) 20 - - [ ] Section-based layout using `surface_container` cards with `xl` 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 Task 02) 26 - 6. **Logs** — Collapsible log viewer with level filtering (`info`, `warn`, `error`) 27 - 7. **About** — Version info, license (MIT), contributors, support links 28 - - [ ] `Presence` slide transitions between setting sections 29 - - [ ] Keyboard shortcut: `,` to open settings from anywhere 30 - - [ ] Confirmation modal for destructive actions (clear cache, reset app, remove account) using glass overlay
+86
docs/tasks/08-social-diagnostics.md
··· 1 + # Task 08: Social Diagnostics 2 + 3 + Spec: [social-diagnostics.md](../specs/social-diagnostics.md) 4 + 5 + ## Steps 6 + 7 + ### Backend - Constellation Client (`src-tauri/src/constellation.rs`) 8 + 9 + - [ ] Constellation HTTP client struct with configurable base URL (default: `https://constellation.microcosm.blue`) 10 + - [ ] `get_backlinks_count(subject: String, source: String)` - `blue.microcosm.links.getBacklinksCount` 11 + - [ ] `get_backlinks(subject: String, source: String, limit: Option<u32>)` - `blue.microcosm.links.getBacklinks` 12 + - [ ] `get_distinct_dids(subject: String, source: String, limit: Option<u32>, cursor: Option<String>)` - `blue.microcosm.links.getDistinct` 13 + - [ ] `get_many_to_many_counts(subject: String, source: String, path_to_other: String)` - `blue.microcosm.links.getManyToManyCounts` 14 + - [ ] `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 + - [ ] `get_account_lists(did: String)` - query Constellation for `app.bsky.graph.listitem:subject` backlinks, extract list URIs, hydrate via `app.bsky.graph.getList` 19 + - [ ] `get_account_labels(did: String)` - query `com.atproto.label.queryLabels` (Bluesky API) 20 + - [ ] `get_account_blocked_by(did: String, limit: Option<u32>, cursor: Option<String>)` - Constellation `getDistinct` for `app.bsky.graph.block:subject` 21 + - [ ] `get_account_blocking(did: String, cursor: Option<String>)` - `com.atproto.repo.listRecords` on target's `app.bsky.graph.block` collection 22 + - [ ] `get_account_starter_packs(did: String)` - Constellation backlinks from starter pack collections 23 + - [ ] `get_record_backlinks(uri: String)` - Constellation backlinks grouped by interaction type (likes, reposts, replies, quotes) 24 + 25 + ### Backend - Settings 26 + 27 + - [ ] `constellation_url` field in settings table (default: `https://constellation.microcosm.blue`) 28 + - [ ] `set_constellation_url(url: String)` / `get_constellation_url()` commands 29 + 30 + ### Frontend - Diagnostics Panel 31 + 32 + - [ ] Tabbed panel component with 5 tabs: Lists, Labels, Blocks, Starter Packs, Backlinks 33 + - [ ] Tab switching with `Motion` sliding indicator underline 34 + - [ ] Number key shortcuts (`1`–`5`) for tab switching 35 + - [ ] `Escape` to close panel 36 + 37 + ### Frontend - Lists Tab 38 + 39 + - [ ] List cards: name, owner, description, purpose badge, member count 40 + - [ ] Grouped by purpose (curation / moderation / reference) 41 + - [ ] Skeleton loading matching card dimensions 42 + - [ ] Neutral framing - no aggregate risk scoring or warning badges 43 + 44 + ### Frontend - Labels Tab 45 + 46 + - [ ] Label chips with source attribution (labeling service name) 47 + - [ ] Uniform muted styling - no severity color-coding 48 + - [ ] Tooltip with label definition, source, and visibility effect 49 + - [ ] Explanatory empty state (what labels are, not "no labels found") 50 + - [ ] `Motion` scale-in on load 51 + 52 + ### Frontend - Blocks Tab 53 + 54 + - [ ] Counts-only default view (no names or profile cards on first load) 55 + - [ ] "Show details" expand with contextualizing copy (*"Blocks are a normal part of social media..."*) 56 + - [ ] `Presence` height animation on expand with staggered card fade-in 57 + - [ ] No warning banners, color-coding, or language implying abnormality 58 + - [ ] Self-view framing: "Your boundaries" (not "Who blocked you") 59 + 60 + ### Frontend - Starter Packs Tab 61 + 62 + - [ ] Compact starter pack cards: title, creator, description, member count 63 + - [ ] Link to view in AT Explorer 64 + 65 + ### Frontend - Backlinks Tab (Record Context) 66 + 67 + - [ ] Grouped by type: likes, reposts, replies, quote posts 68 + - [ ] Count per type with expandable sections 69 + - [ ] Individual actor/record cards within sections 70 + 71 + ### Frontend - Integration Points 72 + 73 + - [ ] Profile view: "Context" tab (alongside Posts/Replies/Media/Likes) - not the default tab 74 + - [ ] AT Explorer record view: backlinks supplementary panel (engagement data only, no moderation data) 75 + - [ ] AT Explorer repo view: follower/following counts from Constellation (no block counts in summaries) 76 + 77 + ### UX Tone Review 78 + 79 + - [ ] Audit all copy for neutral language - no "risk", "warning", "suspicious", "flagged" 80 + - [ ] Ensure all sensitive sections use progressive disclosure (summary → details on click) 81 + - [ ] Verify self-view ("my account") uses empowering framing, not anxiety-inducing 82 + 83 + ### Parking Lot 84 + 85 + - [ ] Network relationship diff over time (requires historical snapshots) 86 + - [ ] Profile/identity history timeline (handle/DID/PDS changes)
+29
docs/tasks/09-jetstream.md
··· 1 + # Task 09: Jetstream 2 + 3 + Spec: [explorer.md](../specs/explorer.md) 4 + 5 + ## Tasks 6 + 7 + ### Backend - Jetstream (`src-tauri/src/jetstream.rs`) 8 + 9 + - [ ] `jetstream_subscribe(config: JetstreamConfig)` - open a WebSocket connection to a Jetstream instance via `jacquard::jetstream`, emit Tauri events for each incoming record 10 + - `JetstreamConfig`: `{ url: String, collections: Option<Vec<String>>, dids: Option<Vec<String>> }` - filter by collection NSIDs and/or DIDs 11 + - [ ] `jetstream_unsubscribe()` - close the active WebSocket connection, clean up 12 + - [ ] Tauri event emission: `jetstream:record` with serialized record payload (collection, rkey, DID, operation, record JSON) 13 + - [ ] Connection lifecycle events: `jetstream:connected`, `jetstream:disconnected`, `jetstream:error` 14 + - [ ] Reconnection with backoff on disconnect (reuse `jacquard::jetstream` reconnect behavior if available) 15 + 16 + ### Frontend - Jetstream Live-Tail 17 + 18 + - [ ] Jetstream live-tail view with `Motion` slide-in for new records 19 + - [ ] Filter controls: collection NSID filter, DID filter, operation type (create/update/delete) 20 + - [ ] Pause/resume button to freeze the stream without disconnecting 21 + - [ ] Record count and events-per-second indicator 22 + - [ ] Click record to navigate to its full record view in the explorer 23 + 24 + ### Parking Lot 25 + 26 + These require update to the spec & more research before implementation. 27 + 28 + - [ ] **Frontend**: Firehose Viewer 29 + - [ ] **Frontend**: Spacedust integration (see [Task 10](./10-spacedust.md))
+21 -14
docs/tasks/09-multicolumn.md docs/tasks/11-multicolumn.md
··· 1 - # Task 09: Multicolumn Views 1 + # Task 11: Multicolumn Views 2 2 3 - Spec: TBD 3 + Spec: [multicolumn.md](../specs/multicolumn.md) 4 4 5 5 ## Overview 6 6 ··· 8 8 9 9 ## Steps 10 10 11 - ### Backend — `src-tauri/src/columns.rs` 11 + ### Backend - `src-tauri/src/columns.rs` 12 12 13 13 - [ ] 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` — determines the column type 15 - - `config`: JSON blob — for feeds: `{ feed_uri, feed_type }`, for explorer: `{ target_uri }` 14 + - `kind`: `feed` | `explorer` | `diagnostics` - determines the column type 15 + - `config`: JSON blob - for feeds: `{ feed_uri, feed_type }`, for explorer: `{ target_uri }`, for diagnostics: `{ did }` 16 16 - `width`: `narrow` | `standard` | `wide` 17 - - [ ] `get_columns(account_did: String)` — return ordered column list for the active account 18 - - [ ] `add_column(account_did: String, kind: String, config: String, position: Option<u32>)` — insert at position or append 19 - - [ ] `remove_column(id: String)` — delete column by ID 20 - - [ ] `reorder_columns(ids: Vec<String>)` — bulk update positions 21 - - [ ] `update_column(id: String, config: Option<String>, width: Option<String>)` — modify column settings 17 + - [ ] `get_columns(account_did: String)` - return ordered column list for the active account 18 + - [ ] `add_column(account_did: String, kind: String, config: String, position: Option<u32>)` - insert at position or append 19 + - [ ] `remove_column(id: String)` - delete column by ID 20 + - [ ] `reorder_columns(ids: Vec<String>)` - bulk update positions 21 + - [ ] `update_column(id: String, config: Option<String>, width: Option<String>)` - modify column settings 22 22 23 - ### Frontend — Column Layout 23 + ### Frontend - Column Layout 24 24 25 25 - [ ] Multicolumn route (`/deck`) accessible from app rail icon (`i-ri-layout-column-line`) 26 26 - [ ] Horizontal scrolling container with snap points per column ··· 30 30 - [ ] `Presence` scale-in animation when adding a column, scale-out on removal 31 31 - [ ] Responsive: collapse to single-column on narrow windows with horizontal swipe navigation 32 32 33 - ### Frontend — Column Types 33 + ### Frontend - Column Types 34 34 35 35 #### Feed Column 36 36 ··· 45 45 - [ ] Independent navigation stack per column (breadcrumbs, back/forward) 46 46 - [ ] Compact record rendering mode for narrower column widths 47 47 48 - ### Frontend — Column Management 48 + #### Diagnostics Column 49 + 50 + - [ ] Reuse social diagnostics panel from Task 08 51 + - [ ] Tab navigation within column for lists/labels/blocks/starter packs/backlinks 52 + - [ ] Compact card layout adapted to column width 53 + 54 + ### Frontend - Column Management 49 55 50 56 - [ ] "Add column" button (`i-ri-add-line`) opens a picker panel: 51 57 - Feed picker: lists pinned feeds, saved feeds, list feeds 52 58 - Explorer picker: input field for at:// URI, handle, DID, or PDS URL 59 + - Diagnostics picker: input field for handle or DID 53 60 - [ ] Right-click column header for context menu (resize, duplicate, close) 54 61 - [ ] Keyboard shortcuts: `Ctrl+Shift+N` add column, `Ctrl+Shift+W` close focused column, `Ctrl+[/]` focus prev/next column 55 - - [ ] Persist column layout to SQLite per account — restore on app launch 62 + - [ ] Persist column layout to SQLite per account - restore on app launch 56 63 57 64 ### Parking Lot 58 65
+8 -8
docs/tasks/10-release.md docs/tasks/12-release.md
··· 1 - # Task 10: Release 1 + # Task 12: Release 2 2 3 3 ## Overview 4 4 ··· 12 12 - macOS: `icon.icns` (16–1024px) 13 13 - Windows: `icon.ico` (16–256px) 14 14 - Linux: `icon.png` at 32, 128, 256, 512px 15 - - [ ] Update `tauri.conf.json` — `productName`, `identifier`, window title, bundle metadata (description, copyright, category) 15 + - [ ] Update `tauri.conf.json` - `productName`, `identifier`, window title, bundle metadata (description, copyright, category) 16 16 - [ ] Splash / welcome screen for first-launch flow 17 17 18 18 ### macOS 19 19 20 20 - [ ] Code signing via Apple Developer certificate (`APPLE_CERTIFICATE`, `APPLE_CERTIFICATE_PASSWORD` secrets) 21 21 - [ ] Notarization via `notarytool` (`APPLE_ID`, `APPLE_PASSWORD`, `APPLE_TEAM_ID` secrets) 22 - - [ ] DMG packaging — `tauri build` produces `.dmg` by default on macOS 22 + - [ ] DMG packaging - `tauri build` produces `.dmg` by default on macOS 23 23 - [ ] Universal binary (x86_64 + aarch64) via `--target universal-apple-darwin` 24 24 - [ ] Verify Gatekeeper passes on clean macOS install 25 25 26 26 ### Windows 27 27 28 - - [ ] NSIS installer — `tauri build` default on Windows; configure install path, start menu shortcut, desktop shortcut 28 + - [ ] NSIS installer - `tauri build` default on Windows; configure install path, start menu shortcut, desktop shortcut 29 29 - [ ] Optional: MSI installer via `bundle > targets` configuration 30 30 - [ ] Code signing via certificate (`WINDOWS_CERTIFICATE`, `WINDOWS_CERTIFICATE_PASSWORD` secrets) 31 31 - Evaluate EV vs OV certificate for SmartScreen reputation ··· 34 34 35 35 ### Linux 36 36 37 - - [ ] AppImage packaging — portable, no-install binary (primary distribution format) 38 - - [ ] `.deb` package for Debian/Ubuntu — configure dependencies (libwebkit2gtk, libssl) 37 + - [ ] AppImage packaging - portable, no-install binary (primary distribution format) 38 + - [ ] `.deb` package for Debian/Ubuntu - configure dependencies (libwebkit2gtk, libssl) 39 39 - [ ] `.rpm` package for Fedora/RHEL 40 40 - [ ] Desktop entry file with icon, categories, and MIME type for `at://` deep links 41 41 - [ ] Verify launch on Ubuntu 22.04+, Fedora 38+, and Arch (via AppImage) 42 42 43 - ### Auto-Update — `tauri-plugin-updater` 43 + ### Auto-Update - `tauri-plugin-updater` 44 44 45 45 - [ ] Add `tauri-plugin-updater` to `Cargo.toml` dependencies (currently commented out) 46 46 - [ ] Configure update endpoint pointing to GitHub Releases (`latest.json` / release assets) ··· 49 49 - [ ] Differential updates where supported (Tauri v2 update mechanism) 50 50 - [ ] Signing update bundles with Tauri's update keypair (`TAURI_SIGNING_PRIVATE_KEY`, `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`) 51 51 52 - ### CI/CD — GitHub Actions 52 + ### CI/CD - GitHub Actions 53 53 54 54 - [ ] Matrix build workflow: `[macos-latest, windows-latest, ubuntu-latest]` 55 55 - macOS job: build universal binary, sign, notarize, produce `.dmg`
+53
docs/tasks/10-spacedust.md
··· 1 + # Task 10: Spacedust 2 + 3 + Spec: TBD (see [Spacedust API docs](../../.sandbox/spacedust.md)) 4 + 5 + ## Overview 6 + 7 + [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. 8 + 9 + Where Jetstream (Task 09) streams raw firehose records, Spacedust streams *resolved backlinks* - making it ideal for live notification feeds and real-time engagement counters. 10 + 11 + ## Tasks 12 + 13 + ### Backend - Spacedust Client (`src-tauri/src/spacedust.rs`) 14 + 15 + - [ ] Spacedust WebSocket client struct with configurable base URL (default: `https://spacedust.microcosm.blue`) 16 + - [ ] `spacedust_subscribe(config: SpacedustConfig)` - open WebSocket to `/subscribe` with query params: 17 + - `wantedSources`: link sources to receive (e.g., `app.bsky.feed.like:subject.uri`) 18 + - `wantedSubjectDids`: DIDs to receive links about 19 + - `wantedSubjects`: AT-URIs to receive links about (URL-encoded) 20 + - `instant`: optional boolean to bypass the 21-second delay buffer 21 + - [ ] `spacedust_unsubscribe()` - close WebSocket, clean up 22 + - [ ] Tauri event emission: `spacedust:link` with payload (source collection+path, subject, linking DID, operation) 23 + - [ ] Connection lifecycle events: `spacedust:connected`, `spacedust:disconnected`, `spacedust:error` 24 + - [ ] Reconnection with backoff on disconnect 25 + 26 + ### Backend - Notification Integration 27 + 28 + - [ ] On login, auto-subscribe to Spacedust for the authenticated user's DID with common social sources: 29 + - `app.bsky.feed.like:subject.uri` (likes on your posts) 30 + - `app.bsky.feed.repost:subject.uri` (reposts) 31 + - `app.bsky.feed.post:reply.parent.uri` (replies) 32 + - `app.bsky.graph.follow:subject` (new followers) 33 + - [ ] Map incoming Spacedust events to notification records in SQLite 34 + - [ ] Deduplicate with existing notifications from `app.bsky.notification.listNotifications` 35 + 36 + ### Frontend - Live Engagement (Explorer Integration) 37 + 38 + - [ ] When viewing a record in AT Explorer, optionally subscribe to Spacedust for that record's URI 39 + - [ ] Real-time counter updates (likes, reposts, replies ticking up) via `Motion` number transition 40 + - [ ] Toggle: "Watch live" button to start/stop per-record subscription 41 + - [ ] Visual pulse on counter increment 42 + 43 + ### Frontend - Settings 44 + 45 + - [ ] Spacedust instance URL configuration (alongside Constellation URL in settings) 46 + - [ ] Toggle: use Spacedust for real-time notifications (vs. polling `listNotifications`) 47 + - [ ] Toggle: `instant` mode (bypass 21-second buffer - faster but noisier) 48 + 49 + ### Parking Lot 50 + 51 + - [ ] Spacedust as a column type in multicolumn view (live notification stream) 52 + - [ ] Aggregate Spacedust events into a "live activity" dashboard 53 + - [ ] Spacedust for real-time search result updates
+13 -8
docs/tasks/mvp.md
··· 12 12 - [Feeds](./03-feeds.md) - Pinned feed tabs, post rendering, composer, keyboard shortcuts, scroll animations 13 13 - [Notifications](./04-notifications.md) - Mentions, activity, system notifications, badge animations 14 14 15 - ## Phase 3: Power Features 15 + ## Phase 3: Core Features 16 16 17 17 - [AT Explorer](./05-explorer.md) - pds.ls-style data browser, at:// deep links, view transitions, keyboard nav 18 - - [Search & Embeddings](./06-search.md) - FTS5, fastembed, sqlite-vec, sync pipeline, result animations 18 + - [Settings](./06-settings.md) - Theme, notifications, data export, cache management, account management, Constellation/Spacedust instance, logs 19 + 20 + ## Phase 4: Power Features 21 + 22 + - [Search & Embeddings](./07-search.md) - FTS5, fastembed, sqlite-vec, sync pipeline, result animations 23 + - [Social Diagnostics](./08-social-diagnostics.md) - Constellation-powered lists, labels, blocks, starter packs, backlinks 19 24 20 - ## Phase 4: Long-Form & Polish 25 + ## Phase 5: Live Data 21 26 22 - - [Standard.site](./07-standard-site.md) - Publication/document views, subscriptions, reading view transitions 23 - - [Settings](./08-settings.md) - Theme, notifications, data export, cache management, account management, logs 24 - - [Multicolumn Views](./09-multicolumn.md) - TweetDeck-style side-by-side feeds and AT Explorer panels 27 + - [Jetstream](./09-jetstream.md) - WebSocket live-tail of AT Protocol firehose, filtered record streaming 28 + - [Spacedust](./10-spacedust.md) - Real-time backlink notifications via microcosm Spacedust 25 29 26 - ## Phase 5: Release 30 + ## Phase 6: Polish & Release 27 31 28 - - [Release](./10-release.md) - Cross-platform build (macOS, Windows, Linux), code signing, auto-update, CI/CD 32 + - [Multicolumn Views](./11-multicolumn.md) - TweetDeck-style side-by-side feeds, explorer, and diagnostics panels 33 + - [Release](./12-release.md) - Cross-platform build (macOS, Windows, Linux), code signing, auto-update, CI/CD