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