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: release checklist/plan

+392 -68
+280
docs/specs/release.md
··· 1 + # Release & Distribution 2 + 3 + Build, sign, package, and distribute Lazurite Desktop across macOS, Windows, and Linux. Covers direct distribution (GitHub Releases), Mac App Store, Microsoft Store, content moderation, and auto-update. 4 + 5 + ## Distribution Channels 6 + 7 + | Channel | Format | Support Button | NSFW Toggle | 8 + | --------------- | -------------------------- | -------------- | ----------- | 9 + | GitHub Release | DMG, NSIS `.exe`, AppImage | Yes | In-app | 10 + | Mac App Store | `.app` (sandboxed) | No | Web-only | 11 + | Microsoft Store | MSIX | No | In-app | 12 + | Linux (future) | Flatpak, Snap | Yes | In-app | 13 + 14 + The `DISTRIBUTION_CHANNEL` compile-time env var (`github`, `mac_app_store`, `microsoft_store`) gates channel-specific behavior. The frontend reads it via a Tauri command. 15 + 16 + ## Content Moderation 17 + 18 + Port the moderation system from the Flutter app (`lazurite`). The AT Protocol's label system is the backbone - posts, profiles, and media carry labels applied by labelers (official Bluesky labeler + user-subscribed custom labelers). 19 + 20 + ### Architecture 21 + 22 + ```text 23 + ┌─ Rust Backend ──────────────────────────────┐ 24 + │ ModerationService │ 25 + │ ├─ Fetch labeler policies (cached in DB) │ 26 + │ ├─ Evaluate labels → ModerationDecision │ 27 + │ ├─ Send atproto-accept-labelers header │ 28 + │ └─ Store preferences per account │ 29 + ├─────────────────────────────────────────────┤ 30 + │ Tauri Commands │ 31 + │ ├─ get_moderation_prefs() │ 32 + │ ├─ set_adult_content_enabled(bool) │ 33 + │ ├─ set_label_preference(labeler, label, │ 34 + │ │ visibility) │ 35 + │ ├─ subscribe_labeler(did) / unsubscribe │ 36 + │ ├─ create_report(subject, reason_type, │ 37 + │ │ reason) │ 38 + │ └─ moderate_content(subject, labels, │ 39 + │ context) → ModerationUI │ 40 + ├─────────────────────────────────────────────┤ 41 + │ SolidJS Frontend │ 42 + │ ├─ ModeratedBlurOverlay │ 43 + │ ├─ ModeratedAvatar │ 44 + │ ├─ ModerationBadgeRow │ 45 + │ ├─ ReportDialog │ 46 + │ └─ Moderation Settings section │ 47 + └─────────────────────────────────────────────┘ 48 + ``` 49 + 50 + ### Label Evaluation 51 + 52 + Each piece of content (post, profile, avatar, media embed) is evaluated against the user's moderation preferences to produce a `ModerationDecision`: 53 + 54 + - **Context-aware**: `contentList`, `contentView`, `contentMedia`, `avatar`, `profileList`, `profileView` - different contexts can produce different UI outcomes for the same label. 55 + - **Visibility levels**: `ignore` (no action), `warn` (badge + interstitial), `hide` (blur, removable for non-restricted content). 56 + - **Adult content gate**: labels marked `adultOnly: true` require the adult content master toggle to be on. If off, content is hidden with no reveal option. 57 + 58 + ### Moderation Preferences Storage 59 + 60 + ```sql 61 + -- Per-account moderation preferences (JSON blob) 62 + -- Key format: moderation_preferences::{accountDid} 63 + -- Stored in app_settings table 64 + ``` 65 + 66 + ### Labeler Cache 67 + 68 + New table: 69 + 70 + ```sql 71 + CREATE TABLE IF NOT EXISTS labeler_cache ( 72 + labeler_did TEXT PRIMARY KEY, 73 + policies_json TEXT NOT NULL, 74 + fetched_at INTEGER NOT NULL 75 + ); 76 + ``` 77 + 78 + Policies are fetched on login and periodically refreshed. The cache enables offline moderation. 79 + 80 + ### UI Components 81 + 82 + **ModeratedBlurOverlay** - wraps any content that may need blurring. 83 + 84 + - 14px Gaussian blur + semi-transparent overlay 85 + - `i-ri-eye-off-line` icon + label name 86 + - "Show content" button (only if content is revealable, not restricted) 87 + - Used on: post bodies, image embeds, video embeds, quoted posts 88 + 89 + **ModeratedAvatar** - wraps avatar images. 90 + 91 + - Shows `i-ri-shield-line` icon when avatar is hidden 92 + - Falls through to normal avatar when no moderation applies 93 + 94 + **ModerationBadgeRow** - inline badges on posts/profiles. 95 + 96 + - Alert tone (red): content warnings, blocks 97 + - Inform tone (blue): informational labels 98 + - Shows label source (labeler name) 99 + 100 + **ReportDialog** - modal for reporting content. 101 + 102 + - Subject: post URI or profile DID 103 + - Reason type: spam, violation, misleading, sexual, rude, other 104 + - Optional free-text reason 105 + - Calls `com.atproto.moderation.createReport` via backend 106 + 107 + ### Store-Specific Behavior 108 + 109 + **Mac App Store**: Adult content toggle is NOT available in the app. Users must enable it via Bluesky web settings (`bsky.app/settings/content-moderation`). The app reads the preference from the user's Bluesky account preferences (`app.bsky.actor.getPreferences`). A link to the web settings is shown in the moderation settings section. This satisfies Apple Guideline 1.2. 110 + 111 + **GitHub / Microsoft Store**: Adult content toggle is available in-app within the moderation settings section. 112 + 113 + ### Settings Keys 114 + 115 + | Key | Type | Default | Description | 116 + | ------------------------ | ---- | ------- | ----------------------------------------- | 117 + | `moderation_preferences` | JSON | `{}` | Per-account labeler + label prefs (keyed) | 118 + 119 + The moderation preferences JSON contains: `adultContentEnabled` (bool), `subscribedLabelers` (array of DIDs), and per-labeler label visibility overrides. 120 + 121 + ### Moderation Settings Section 122 + 123 + New section in Settings panel, between "Notifications" and "Search & Embeddings": 124 + 125 + - **Adult content**: toggle (hidden on MAS builds; shows link to web settings instead) 126 + - **Subscribed labelers**: list with add/remove. Built-in Bluesky labeler shown but not removable. Max 20 custom labelers. 127 + - **Label preferences**: expandable per labeler, three-way control per label (ignore / warn / hide). Labels marked `adultOnly` are gated behind the adult content toggle. 128 + 129 + ## Conditional Support Button 130 + 131 + A "Support Lazurite" link in the About section of Settings. Gated by distribution channel: 132 + 133 + ```ts 134 + // Frontend check 135 + const channel = await invoke<string>("get_distribution_channel"); 136 + const showSupport = channel === "github"; 137 + ``` 138 + 139 + On GitHub builds: shows a heart icon link to the sponsorship/support page. 140 + On store builds: omitted entirely (Apple and Microsoft have their own monetization rules). 141 + 142 + ## App Identity 143 + 144 + | Field | Value | 145 + | ------------ | ---------------------------- | 146 + | Product name | Lazurite | 147 + | Identifier | `com.owais.lazurite` | 148 + | Category | Social Networking | 149 + | Age rating | 17+ (user-generated content) | 150 + | Copyright | Copyright 2026 Owais | 151 + 152 + ## macOS Distribution 153 + 154 + ### Direct (GitHub Release) 155 + 156 + - Developer ID certificate for signing 157 + - Notarization via `notarytool` 158 + - Universal binary (`x86_64 + aarch64`) 159 + - Output: `.dmg` 160 + 161 + ### Mac App Store 162 + 163 + - Apple Distribution certificate + Mac App Store provisioning profile 164 + - Separate entitlements file (`Entitlements.mac-app-store.plist`): 165 + 166 + ```xml 167 + <?xml version="1.0" encoding="UTF-8"?> 168 + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "..."> 169 + <plist version="1.0"> 170 + <dict> 171 + <key>com.apple.security.app-sandbox</key> <true/> 172 + <key>com.apple.security.network.client</key> <true/> 173 + <key>com.apple.security.files.downloads.read-write</key> <true/> 174 + </dict> 175 + </plist> 176 + ``` 177 + 178 + - Separate Tauri config overlay (`tauri.mac-app-store.conf.json`) merged at build time 179 + - `codesign --force --options runtime --entitlements` before `productbuild` 180 + - No notarization needed (Apple handles during review) 181 + 182 + ## Windows Distribution 183 + 184 + ### Direct (GitHub Release) 185 + 186 + - NSIS installer with start menu + desktop shortcuts 187 + - Optional code signing (OV certificate) 188 + - Output: `.exe` 189 + 190 + ### Microsoft Store 191 + 192 + - MSIX package via `winappCli` wrapping the Tauri build 193 + - No self-signing needed (Microsoft signs on upload) 194 + - IARC age rating questionnaire completed in Partner Center 195 + - Published Terms of Service and Privacy Policy required 196 + 197 + ## Linux Distribution 198 + 199 + - AppImage (primary), `.deb`, `.rpm` 200 + - Desktop entry with `at://` MIME type 201 + - GitHub Release only (store submissions are parking lot) 202 + 203 + ## Auto-Update (`tauri-plugin-updater`) 204 + 205 + - Update endpoint: GitHub Releases (`latest.json`) 206 + - Check on app launch + configurable periodic check 207 + - Update notification with changelog, install-on-quit option 208 + - Update bundles signed with Tauri keypair 209 + - Disabled on Mac App Store and Microsoft Store builds (stores handle updates) 210 + 211 + ## CI/CD - GitHub Actions 212 + 213 + Matrix build with three tracks: 214 + 215 + ### GitHub Release Track 216 + 217 + Trigger: push to `release/*` or `workflow_dispatch`. 218 + 219 + | Job | OS | Output | 220 + | ------- | ---------------- | ------------------------------------ | 221 + | macOS | `macos-latest` | Universal `.dmg` (signed, notarized) | 222 + | Windows | `windows-latest` | NSIS `.exe` (signed) | 223 + | Linux | `ubuntu-latest` | AppImage, `.deb`, `.rpm` | 224 + 225 + Artifacts uploaded to GitHub Release (draft) with SHA256 checksums. Generates `latest.json` for updater. 226 + 227 + ### Mac App Store Track 228 + 229 + Trigger: manual `workflow_dispatch` with `target: mas`. 230 + 231 + - Builds with `tauri.mac-app-store.conf.json` overlay 232 + - Signs with Apple Distribution certificate 233 + - Packages with `productbuild` 234 + - Uploads `.pkg` to App Store Connect via `xcrun altool` or Transporter 235 + 236 + ### Microsoft Store Track 237 + 238 + Trigger: manual `workflow_dispatch` with `target: msstore`. 239 + 240 + - Builds standard Tauri NSIS output 241 + - Wraps in MSIX via `winappCli` 242 + - Uploads to Partner Center (manual or via Store API) 243 + 244 + ## Required Secrets 245 + 246 + | Secret | Used by | 247 + | ------------------------------------ | --------------- | 248 + | `APPLE_CERTIFICATE` | macOS signing | 249 + | `APPLE_CERTIFICATE_PASSWORD` | macOS signing | 250 + | `APPLE_ID` | Notarization | 251 + | `APPLE_PASSWORD` | Notarization | 252 + | `APPLE_TEAM_ID` | Notarization | 253 + | `APPLE_DISTRIBUTION_CERTIFICATE` | MAS signing | 254 + | `APPLE_PROVISIONING_PROFILE` | MAS builds | 255 + | `WINDOWS_CERTIFICATE` | Windows signing | 256 + | `WINDOWS_CERTIFICATE_PASSWORD` | Windows signing | 257 + | `TAURI_SIGNING_PRIVATE_KEY` | Update signing | 258 + | `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Update signing | 259 + 260 + ## Legal Requirements (Both Stores) 261 + 262 + - Published Terms of Service 263 + - Published Privacy Policy 264 + - In-app contact information 265 + - Content reporting mechanism (24-hour response for Apple) 266 + - User blocking functionality 267 + - Proactive content moderation via label system 268 + 269 + ## Smoke Test 270 + 271 + - Fresh install: download → install → first-launch welcome 272 + - OAuth login: loopback flow on each platform 273 + - Timeline: feed rendering, scroll, keyboard shortcuts 274 + - NSFW: labeled content is blurred by default, reveal works, adult-only hidden when disabled 275 + - Reporting: submit report flow completes 276 + - Search sync: FTS5 + embedding pipeline post-login 277 + - Auto-update: detection + install from prior version (GitHub builds only) 278 + - Deep links: `at://` URI opens app and navigates 279 + - Multicolumn: column persistence across restart 280 + - Store-specific: support button visible on GitHub, hidden on store builds
+90 -48
docs/tasks/13-release.md
··· 2 2 3 3 ## Overview 4 4 5 - Cross-platform build, signing, packaging, and auto-update pipeline targeting macOS, Windows, and Linux. All packaging uses `tauri build` with platform-specific configuration. CI/CD runs on GitHub Actions with separate jobs per platform. 5 + Cross-platform build, signing, packaging, and distribution targeting GitHub Releases, Mac App Store, and Microsoft Store. Content moderation (NSFW blurring, reporting, blocking) is a prerequisite for store submission. See [release spec](../specs/release.md) for full details. 6 6 7 7 ## Steps 8 8 9 + ### Content Moderation 10 + 11 + #### Backend 12 + 13 + - [ ] `ModerationService` in Rust - fetch labeler policies, evaluate labels into `ModerationDecision`, cache in `labeler_cache` table 14 + - [ ] Send `atproto-accept-labelers` header with all API requests (built-in Bluesky labeler + user-subscribed labelers) 15 + - [ ] Moderation preferences storage - per-account JSON in `app_settings` keyed by `moderation_preferences::{did}` 16 + - [ ] `create_report` command - calls `com.atproto.moderation.createReport` 17 + - [ ] `get_distribution_channel` command - returns compile-time `DISTRIBUTION_CHANNEL` env var 18 + 19 + #### Frontend 20 + 21 + - [ ] `ModeratedBlurOverlay` component - 14px blur, overlay icon, "Show content" button, label display 22 + - [ ] `ModeratedAvatar` component - shield icon fallback for hidden avatars 23 + - [ ] `ModerationBadgeRow` component - alert (red) and inform (blue) badges with label source 24 + - [ ] `ReportDialog` modal - reason type selector, free-text, submit 25 + - [ ] Wire moderation into `PostCard`, image/video embeds, profile views, notifications 26 + - [ ] Moderation Settings section - adult content toggle (hidden on MAS, link to web settings instead), labeler management, per-label preferences 27 + - [ ] Block user flow via `app.bsky.graph.block` 28 + 9 29 ### App Identity & Branding 10 30 11 31 - [x] Final app icon set: generate all required sizes from source SVG 12 - - macOS: `icon.icns` (16–1024px) 13 - - Windows: `icon.ico` (16–256px) 14 - - Linux: `icon.png` at 32, 128, 256, 512px 15 - - [ ] Update `tauri.conf.json` - `productName`, `identifier`, window title, bundle metadata (description, copyright, category) 32 + - [ ] Update `tauri.conf.json` - `productName: "Lazurite"`, `identifier: "com.owais.lazurite"`, window title, bundle metadata (description, copyright, category) 16 33 - [ ] Splash / welcome screen for first-launch flow 34 + - [ ] Conditional support button in About section (visible on `github` channel only) 17 35 18 - ### macOS 36 + ### macOS - Direct (GitHub Release) 37 + 38 + - [ ] Code signing via Developer ID certificate 39 + - [ ] Notarization via `notarytool` 40 + - [ ] DMG packaging 41 + - [ ] Universal binary (`x86_64 + aarch64`) via `--target universal-apple-darwin` 42 + - [ ] Verify Gatekeeper passes on clean install 19 43 20 - - [ ] Code signing via Apple Developer certificate (`APPLE_CERTIFICATE`, `APPLE_CERTIFICATE_PASSWORD` secrets) 21 - - [ ] Notarization via `notarytool` (`APPLE_ID`, `APPLE_PASSWORD`, `APPLE_TEAM_ID` secrets) 22 - - [ ] DMG packaging - `tauri build` produces `.dmg` by default on macOS 23 - - [ ] Universal binary (x86_64 + aarch64) via `--target universal-apple-darwin` 24 - - [ ] Verify Gatekeeper passes on clean macOS install 44 + ### macOS - App Store 25 45 26 - ### Windows 46 + - [ ] Apple Distribution certificate + provisioning profile 47 + - [ ] Sandbox entitlements file (`com.apple.security.app-sandbox`, `network.client`, `files.downloads.read-write`) 48 + - [ ] Separate Tauri config overlay (`tauri.mac-app-store.conf.json`) 49 + - [ ] `codesign --force --options runtime --entitlements` before `productbuild` 50 + - [ ] Adult content toggle disabled in MAS build (reads preference from Bluesky account prefs only) 51 + - [ ] Age rating: 17+ 52 + - [ ] Submit via App Store Connect 27 53 28 - - [ ] NSIS installer - `tauri build` default on Windows; configure install path, start menu shortcut, desktop shortcut 29 - - [ ] Optional: MSI installer via `bundle > targets` configuration 30 - - [ ] Code signing via certificate (`WINDOWS_CERTIFICATE`, `WINDOWS_CERTIFICATE_PASSWORD` secrets) 31 - - Evaluate EV vs OV certificate for SmartScreen reputation 32 - - [ ] Portable `.exe` variant (no install required) via WiX or NSIS portable config 33 - - [ ] Verify Windows Defender / SmartScreen does not flag the installer 54 + ### Windows - Direct (GitHub Release) 55 + 56 + - [ ] NSIS installer - install path, start menu shortcut, desktop shortcut 57 + - [ ] Code signing via OV certificate 58 + - [ ] Portable `.exe` variant 59 + - [ ] Verify Windows Defender / SmartScreen does not flag installer 60 + 61 + ### Windows - Microsoft Store 62 + 63 + - [ ] MSIX packaging via `winappCli` 64 + - [ ] IARC age rating questionnaire 65 + - [ ] Submit via Partner Center 66 + - [ ] Adult content toggle available in-app (Microsoft allows) 34 67 35 68 ### Linux 36 69 37 - - [ ] AppImage packaging - portable, no-install binary (primary distribution format) 38 - - [ ] `.deb` package for Debian/Ubuntu - configure dependencies (libwebkit2gtk, libssl) 39 - - [ ] `.rpm` package for Fedora/RHEL 40 - - [ ] Desktop entry file with icon, categories, and MIME type for `at://` deep links 41 - - [ ] Verify launch on Ubuntu 22.04+, Fedora 38+, and Arch (via AppImage) 70 + - [ ] AppImage (primary portable format) 71 + - [ ] `.deb` for Debian/Ubuntu (dependencies: libwebkit2gtk, libssl) 72 + - [ ] `.rpm` for Fedora/RHEL 73 + - [ ] Desktop entry with icon, categories, `at://` MIME type 74 + - [ ] Verify on Ubuntu 22.04+, Fedora 38+, Arch 42 75 43 76 ### Auto-Update - `tauri-plugin-updater` 44 77 45 - - [ ] Add `tauri-plugin-updater` to `Cargo.toml` dependencies (currently commented out) 46 - - [ ] Configure update endpoint pointing to GitHub Releases (`latest.json` / release assets) 47 - - [ ] Implement update check on app launch + periodic background check (configurable in Settings) 48 - - [ ] Update available notification with changelog summary, install-on-quit option 49 - - [ ] Differential updates where supported (Tauri v2 update mechanism) 50 - - [ ] Signing update bundles with Tauri's update keypair (`TAURI_SIGNING_PRIVATE_KEY`, `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`) 78 + - [ ] Add `tauri-plugin-updater` to `Cargo.toml` 79 + - [ ] Configure endpoint pointing to GitHub Releases (`latest.json`) 80 + - [ ] Update check on launch + periodic background check 81 + - [ ] Update notification with changelog, install-on-quit 82 + - [ ] Signing update bundles with Tauri keypair 83 + - [ ] Disabled on MAS and Microsoft Store builds (stores handle updates) 51 84 52 85 ### CI/CD - GitHub Actions 53 86 54 - - [ ] Matrix build workflow: `[macos-latest, windows-latest, ubuntu-latest]` 55 - - macOS job: build universal binary, sign, notarize, produce `.dmg` 56 - - Windows job: build NSIS installer, sign, produce `.exe` 57 - - Linux job: build AppImage, `.deb`, `.rpm` 58 - - [ ] Trigger: push to `release/*` branch or manual `workflow_dispatch` 59 - - [ ] Upload all artifacts to GitHub Release (draft) with checksums 60 - - [ ] Generate `latest.json` manifest for `tauri-plugin-updater` 61 - - [ ] Version bump automation: tag-based versioning synced to `tauri.conf.json` and `Cargo.toml` 87 + - [ ] **GitHub Release track**: matrix `[macos-latest, windows-latest, ubuntu-latest]`, triggered on `release/*` push or `workflow_dispatch` 88 + - macOS: universal `.dmg`, signed, notarized 89 + - Windows: NSIS `.exe`, signed 90 + - Linux: AppImage, `.deb`, `.rpm` 91 + - Upload artifacts + checksums to GitHub Release (draft) 92 + - Generate `latest.json` for updater 93 + - [ ] **Mac App Store track**: manual `workflow_dispatch`, builds with MAS config overlay, signs with Apple Distribution cert, uploads `.pkg` 94 + - [ ] **Microsoft Store track**: manual `workflow_dispatch`, wraps in MSIX via `winappCli`, uploads to Partner Center 95 + 96 + ### Legal 97 + 98 + - [ ] Terms of Service (published, linked in app and store listings) 99 + - [ ] Privacy Policy (published, linked in app and store listings) 100 + - [ ] In-app contact information 62 101 63 102 ### Smoke Test 64 103 65 - - [ ] Fresh install flow: download, install, first-launch welcome 66 - - [ ] OAuth login: full loopback flow on each platform 67 - - [ ] Timeline load: verify feed rendering, scroll, keyboard shortcuts 68 - - [ ] Search sync: confirm FTS5 + embedding pipeline runs post-login 69 - - [ ] Auto-update: verify update detection and installation from a prior version 70 - - [ ] Deep links: `at://` URI opens app and navigates to explorer view 71 - - [ ] Multicolumn: verify column persistence across app restart 104 + - [ ] Fresh install flow on each platform 105 + - [ ] OAuth login: loopback flow on macOS, Windows, Linux 106 + - [ ] Timeline load: feed rendering, scroll, keyboard shortcuts 107 + - [ ] NSFW moderation: labeled content blurred by default, reveal works, adult-only gated 108 + - [ ] Report + block flows complete 109 + - [ ] Search sync: FTS5 + embeddings post-login 110 + - [ ] Auto-update: detection + install from prior version (GitHub builds) 111 + - [ ] Deep links: `at://` URI opens app and navigates 112 + - [ ] Multicolumn: column persistence across restart 113 + - [ ] Store-specific: support button on GitHub, hidden on store; adult toggle on GitHub/MS, hidden on MAS 72 114 73 115 ### Parking Lot 74 116 75 - - [ ] Flathub / Snap Store submission for Linux 76 - - [ ] Windows Store (MSIX) submission 77 - - [ ] macOS App Store submission 78 - - [ ] Crash reporting integration (Sentry or similar) 117 + - [ ] Flathub / Snap Store submission 118 + - [ ] macOS: separate DMG + App Store builds in single CI run 119 + - [ ] Crash reporting (Sentry or similar) 79 120 - [ ] Analytics / telemetry (opt-in, privacy-respecting) 80 121 - [ ] Beta / nightly release channel 122 + - [ ] Differential updates (Tauri v2 update mechanism)
+22 -20
src/components/deck/AddColumnPanel.tsx
··· 1 + // TODO: there is a lot of prop drilling in this module, and could benefit from the splitProps pattern. 1 2 import { ActorSuggestionList, useActorSuggestions } from "$/components/actors/actor-search"; 2 3 import { FeedController } from "$/lib/api/feeds"; 3 4 import type { ColumnKind } from "$/lib/api/types/columns"; ··· 392 393 ); 393 394 } 394 395 395 - function PanelContent( 396 - props: { 397 - tab: PanelTab; 398 - onFeedSelect: (selection: FeedPickerSelection) => void; 399 - onExplorerSubmit: (uri: string) => void; 400 - onDiagnosticsSubmit: (did: string) => void; 401 - onMessagesSubmit: () => void; 402 - onProfileSubmit: ( 403 - selection: { actor: string; did?: string | null; displayName?: string | null; handle?: string | null }, 404 - ) => void; 405 - onSearchSubmit: (query: string, mode: SearchMode) => void; 406 - }, 407 - ) { 396 + type PanelContentProps = { 397 + tab: PanelTab; 398 + onFeedSelect: (selection: FeedPickerSelection) => void; 399 + onExplorerSubmit: (uri: string) => void; 400 + onDiagnosticsSubmit: (did: string) => void; 401 + onMessagesSubmit: () => void; 402 + onProfileSubmit: ( 403 + selection: { actor: string; did?: string | null; displayName?: string | null; handle?: string | null }, 404 + ) => void; 405 + onSearchSubmit: (query: string, mode: SearchMode) => void; 406 + }; 407 + 408 + function PanelContent(props: PanelContentProps) { 408 409 return ( 409 410 <div class="min-h-0 flex-1 overflow-y-auto px-4 pb-6"> 410 411 <Switch> ··· 454 455 ); 455 456 } 456 457 457 - function AddColumnPanelTabs( 458 - props: { 459 - activeTab: PanelTab; 460 - onTabChange: (tab: PanelTab) => void; 461 - tabs: Array<{ icon: string; id: PanelTab; label: string }>; 462 - }, 463 - ) { 458 + type AddColumnPanelTabsProps = { 459 + activeTab: PanelTab; 460 + onTabChange: (tab: PanelTab) => void; 461 + tabs: Array<{ icon: string; id: PanelTab; label: string }>; 462 + }; 463 + 464 + function AddColumnPanelTabs(props: AddColumnPanelTabsProps) { 464 465 return ( 465 466 <div class="grid shrink-0 grid-cols-2 gap-1 px-5 py-3"> 466 467 <For each={props.tabs}> ··· 561 562 props.onAdd("profile", JSON.stringify(selection)); 562 563 } 563 564 565 + // TODO: use IconKind for Icon 564 566 const tabs: Array<{ icon: string; id: PanelTab; label: string }> = [ 565 567 { icon: "i-ri-rss-line", id: "feed", label: "Feed" }, 566 568 { icon: "i-ri-compass-discover-line", id: "explorer", label: "Explorer" },