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: drafted posts

+129
+81
docs/specs/drafts.md
··· 1 + # Drafts 2 + 3 + ## Overview 4 + 5 + Draft posts are composed locally and persisted to SQLite, allowing users to save work-in-progress posts and resume them later. Drafts survive app restarts and account switches. 6 + 7 + AT Protocol has no concept of private or unpublished records today — all repo data is public and broadcast on the firehose. A "Permissioned Data" initiative is the protocol team's top priority for summer 2026, which may eventually enable server-side drafts. Until then, drafts are local-only. The schema mirrors `app.bsky.feed.post` fields to make future migration straightforward. 8 + 9 + Every other BlueSky client with drafts (Skeets, deck.blue) also uses client-local storage. 10 + 11 + ## Data Model 12 + 13 + Drafts live in a `drafts` SQLite table, scoped per account: 14 + 15 + | Column | Type | Notes | 16 + | ------------------ | ------------------ | ----------------------------------------------------- | 17 + | `id` | `TEXT PRIMARY KEY` | UUID | 18 + | `account_did` | `TEXT NOT NULL` | Owning account | 19 + | `text` | `TEXT NOT NULL` | Post body (may be empty string for embed-only drafts) | 20 + | `reply_parent_uri` | `TEXT` | Parent post URI if replying | 21 + | `reply_parent_cid` | `TEXT` | Parent post CID | 22 + | `reply_root_uri` | `TEXT` | Root post URI if replying | 23 + | `reply_root_cid` | `TEXT` | Root post CID | 24 + | `quote_uri` | `TEXT` | Quoted post URI if quote-posting | 25 + | `quote_cid` | `TEXT` | Quoted post CID | 26 + | `title` | `TEXT` | Optional user label for organizing drafts | 27 + | `created_at` | `TEXT NOT NULL` | ISO 8601 creation timestamp | 28 + | `updated_at` | `TEXT NOT NULL` | ISO 8601 last-modified timestamp | 29 + 30 + Media/blob references are excluded from v1 — they require `uploadBlob` which returns ephemeral blob refs that expire. Drafts with intended media should note this in the UI. 31 + 32 + ## Commands 33 + 34 + | Command | Args | Returns | Notes | 35 + | -------------- | ------------- | -------------------- | ---------------------------------------------------- | 36 + | `list_drafts` | `account_did` | `Vec<Draft>` | Ordered by `updated_at` desc | 37 + | `get_draft` | `id` | `Draft` | Single draft by ID | 38 + | `save_draft` | `DraftInput` | `Draft` | Upsert — creates or updates based on `id` presence | 39 + | `delete_draft` | `id` | `()` | Hard delete | 40 + | `submit_draft` | `id` | `CreateRecordResult` | Load draft → `create_post` → delete draft on success | 41 + 42 + `DraftInput` contains all writable fields (text, reply refs, quote ref, title). If `id` is provided, it updates; otherwise it creates with a new UUID. 43 + 44 + ## Autosave 45 + 46 + The composer autosaves to a draft after 3 seconds of inactivity (debounced). An active autosave draft is marked by storing its `id` in the composer state. When the user submits or explicitly discards, the autosave draft is deleted. 47 + 48 + On app launch, if an autosave draft exists for the active account, the composer offers to restore it via a non-blocking toast: _"You have an unsaved post. Restore?"_ with Restore / Discard actions. 49 + 50 + ## UI 51 + 52 + ### Drafts List 53 + 54 + Accessible from a button in the composer header or toolbar. Opens a `Presence` slide-up panel listing all drafts for the active account. 55 + 56 + Each draft card shows: 57 + 58 + - Title (or text preview if no title) 59 + - Reply/quote context indicator (icon + truncated parent) 60 + - Relative timestamp ("2 hours ago") 61 + - Delete action (with confirmation) 62 + 63 + Tap a draft to load it into the composer, replacing current content (with confirmation if composer is non-empty). 64 + 65 + ### Composer Integration 66 + 67 + - Autosave indicator in composer footer: subtle "Saved" / "Saving..." text 68 + - "Save as draft" button in composer header (explicit save + close) 69 + - Draft count badge on the drafts list button when drafts exist 70 + - When loading a draft into the composer, the draft ID is tracked so subsequent saves update the same draft rather than creating duplicates 71 + 72 + ### Keyboard Shortcuts 73 + 74 + | Key | Action | 75 + | ------------ | ----------------------------------------------- | 76 + | `Ctrl/Cmd+S` | Explicit save current composer content as draft | 77 + | `Ctrl/Cmd+D` | Open drafts list | 78 + 79 + ## Constraints 80 + 81 + - **No cross-device sync**: Drafts are local SQLite. If Permissioned Data ships, drafts could migrate to private repo records.
+48
docs/tasks/14-drafts.md
··· 1 + # Milestone 14: Draft Posts 2 + 3 + Spec: [drafts.md](../specs/drafts.md) 4 + 5 + Depends on: Milestone 03 (Feeds — composer, `create_post`) 6 + 7 + ## Steps 8 + 9 + ### Backend - `src-tauri/src/drafts.rs` + `src-tauri/src/commands/drafts.rs` 10 + 11 + - [ ] SQLite migration: `drafts` table (`id TEXT PRIMARY KEY, account_did TEXT NOT NULL, text TEXT NOT NULL, reply_parent_uri TEXT, reply_parent_cid TEXT, reply_root_uri TEXT, reply_root_cid TEXT, quote_uri TEXT, quote_cid TEXT, title TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL`) 12 + - [ ] `Draft` and `DraftInput` structs mirroring the schema 13 + - [ ] `list_drafts(account_did: String)` — return all drafts for the account, ordered by `updated_at` desc 14 + - [ ] `get_draft(id: String)` — single draft by ID 15 + - [ ] `save_draft(input: DraftInput)` — upsert: if `id` is present and exists, update; otherwise insert with new UUID 16 + - [ ] `delete_draft(id: String)` — hard delete 17 + - [ ] `submit_draft(id: String)` — load draft, call `create_post`, delete draft on success, return `CreateRecordResult` 18 + 19 + ### Frontend - Drafts List Panel 20 + 21 + - [ ] Drafts list panel component with `Presence` slide-up from composer 22 + - [ ] Draft cards: title or text preview, reply/quote context indicator, relative timestamp, delete button 23 + - [ ] Tap draft to load into composer (confirmation if composer has content) 24 + - [ ] Delete with confirmation 25 + - [ ] Empty state: *"No drafts yet. Saved posts will appear here."* 26 + - [ ] `Ctrl/Cmd+D` keyboard shortcut to open drafts list 27 + 28 + ### Frontend - Composer Integration 29 + 30 + - [ ] Autosave: debounced (3s inactivity) save to draft while composing, tracked by draft `id` in composer state 31 + - [ ] Autosave indicator in composer footer: "Saved" / "Saving..." text 32 + - [ ] "Save as draft" button in composer header — explicit save + close composer 33 + - [ ] Draft count badge on drafts list button 34 + - [ ] `Ctrl/Cmd+S` keyboard shortcut to save current composer as draft 35 + - [ ] On app launch, detect unsaved autosave draft → toast: *"You have an unsaved post. Restore?"* with Restore / Discard 36 + 37 + ### Frontend - Draft Lifecycle 38 + 39 + - [ ] Loading a draft into the composer tracks the draft `id` so subsequent autosaves update (not duplicate) 40 + - [ ] Successful post submission deletes the associated draft 41 + - [ ] Explicit discard from composer deletes the autosave draft 42 + - [ ] Account switch clears composer state; autosave draft persists for the original account 43 + 44 + ### Parking Lot 45 + 46 + - [ ] Media attachments in drafts (requires local blob caching + re-upload on submit) 47 + - [ ] Thread builder (compose multi-post threads as a single draft) 48 + - Cross-device sync via AT Protocol Permissioned Data (blocked on protocol — expected summer 2026)