Release & Distribution#
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.
Distribution Channels#
| Channel | Format | Support Button | NSFW Toggle |
|---|---|---|---|
| GitHub Release | DMG, NSIS .exe, AppImage |
Yes | In-app |
| Mac App Store | .app (sandboxed) |
No | Web-only |
| Microsoft Store | MSIX | No | In-app |
| Linux (future) | Flatpak, Snap | Yes | In-app |
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.
Content Moderation#
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).
Architecture#
┌─ Rust Backend ──────────────────────────────┐
│ ModerationService │
│ ├─ Fetch labeler policies (cached in DB) │
│ ├─ Evaluate labels → ModerationDecision │
│ ├─ Send atproto-accept-labelers header │
│ └─ Store preferences per account │
├─────────────────────────────────────────────┤
│ Tauri Commands │
│ ├─ get_moderation_prefs() │
│ ├─ set_adult_content_enabled(bool) │
│ ├─ set_label_preference(labeler, label, │
│ │ visibility) │
│ ├─ subscribe_labeler(did) / unsubscribe │
│ ├─ create_report(subject, reason_type, │
│ │ reason) │
│ └─ moderate_content(subject, labels, │
│ context) → ModerationUI │
├─────────────────────────────────────────────┤
│ SolidJS Frontend │
│ ├─ ModeratedBlurOverlay │
│ ├─ ModeratedAvatar │
│ ├─ ModerationBadgeRow │
│ ├─ ReportDialog │
│ └─ Moderation Settings section │
└─────────────────────────────────────────────┘
Label Evaluation#
Each piece of content (post, profile, avatar, media embed) is evaluated against the user's moderation preferences to produce a ModerationDecision:
- Context-aware:
contentList,contentView,contentMedia,avatar,profileList,profileView- different contexts can produce different UI outcomes for the same label. - Visibility levels:
ignore(no action),warn(badge + interstitial),hide(blur, removable for non-restricted content). - Adult content gate: labels marked
adultOnly: truerequire the adult content master toggle to be on. If off, content is hidden with no reveal option.
Moderation Preferences Storage#
-- Per-account moderation preferences (JSON blob)
-- Key format: moderation_preferences::{accountDid}
-- Stored in app_settings table
Labeler Cache#
New table:
CREATE TABLE IF NOT EXISTS labeler_cache (
labeler_did TEXT PRIMARY KEY,
policies_json TEXT NOT NULL,
fetched_at INTEGER NOT NULL
);
Policies are fetched on login and periodically refreshed. The cache enables offline moderation.
UI Components#
ModeratedBlurOverlay - wraps any content that may need blurring.
- 14px Gaussian blur + semi-transparent overlay
i-ri-eye-off-lineicon + label name- "Show content" button (only if content is revealable, not restricted)
- Used on: post bodies, image embeds, video embeds, quoted posts
ModeratedAvatar - wraps avatar images.
- Shows
i-ri-shield-lineicon when avatar is hidden - Falls through to normal avatar when no moderation applies
ModerationBadgeRow - inline badges on posts/profiles.
- Alert tone (red): content warnings, blocks
- Inform tone (blue): informational labels
- Shows label source (labeler name)
ReportDialog - modal for reporting content.
- Subject: post URI or profile DID
- Reason type: spam, violation, misleading, sexual, rude, other
- Optional free-text reason
- Calls
com.atproto.moderation.createReportvia backend
Store-Specific Behavior#
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.
GitHub / Microsoft Store: Adult content toggle is available in-app within the moderation settings section.
Settings Keys#
| Key | Type | Default | Description |
|---|---|---|---|
moderation_preferences |
JSON | {} |
Per-account labeler + label prefs (keyed) |
The moderation preferences JSON contains: adultContentEnabled (bool), subscribedLabelers (array of DIDs), and per-labeler label visibility overrides.
Moderation Settings Section#
New section in Settings panel, between "Notifications" and "Search & Embeddings":
- Adult content: toggle (hidden on MAS builds; shows link to web settings instead)
- Subscribed labelers: list with add/remove. Built-in Bluesky labeler shown but not removable. Max 20 custom labelers.
- Label preferences: expandable per labeler, three-way control per label (ignore / warn / hide). Labels marked
adultOnlyare gated behind the adult content toggle.
Conditional Support Button#
A "Support Lazurite" link in the About section of Settings. Gated by distribution channel:
// Frontend check
const channel = await invoke<string>("get_distribution_channel");
const showSupport = channel === "github";
On GitHub builds: shows a heart icon link to the sponsorship/support page. On store builds: omitted entirely (Apple and Microsoft have their own monetization rules).
App Identity#
| Field | Value |
|---|---|
| Product name | Lazurite |
| Identifier | org.stormlightlabs.lazurite-desktop |
| Category | Social Networking |
| Age rating | 17+ (user-generated content) |
| Copyright | Copyright 2026 Owais |
macOS Distribution#
Direct (GitHub Release)#
- Developer ID certificate for signing
- Notarization via
notarytool - Universal binary (
x86_64 + aarch64) - Output:
.dmg
Mac App Store#
- Apple Distribution certificate + Mac App Store provisioning profile
- Separate entitlements file (
Entitlements.mac-app-store.plist):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key> <true/>
<key>com.apple.security.network.client</key> <true/>
<key>com.apple.security.files.downloads.read-write</key> <true/>
</dict>
</plist>
- Separate Tauri config overlay (
tauri.mac-app-store.conf.json) merged at build time codesign --force --options runtime --entitlementsbeforeproductbuild- No notarization needed (Apple handles during review)
Windows Distribution#
Direct (GitHub Release)#
- NSIS installer with start menu + desktop shortcuts
- Optional code signing (OV certificate)
- Output:
.exe
Microsoft Store#
- MSIX package via
winappCliwrapping the Tauri build - No self-signing needed (Microsoft signs on upload)
- IARC age rating questionnaire completed in Partner Center
- Published Terms of Service and Privacy Policy required
Linux Distribution#
- AppImage (primary),
.deb,.rpm - Desktop entry with
at://MIME type - GitHub Release only (store submissions are parking lot)
Auto-Update (tauri-plugin-updater)#
- Update endpoint: GitHub Releases (
latest.json) - Check on app launch + configurable periodic check
- Update notification with changelog, install-on-quit option
- Update bundles signed with Tauri keypair
- Disabled on Mac App Store and Microsoft Store builds (stores handle updates)
CI/CD - GitHub Actions#
Matrix build with three tracks:
GitHub Release Track#
Trigger: push to release/* or workflow_dispatch.
| Job | OS | Output |
|---|---|---|
| macOS | macos-latest |
Universal .dmg (signed, notarized) |
| Windows | windows-latest |
NSIS .exe (signed) |
| Linux | ubuntu-latest |
AppImage, .deb, .rpm |
Artifacts uploaded to GitHub Release (draft) with SHA256 checksums. Generates latest.json for updater.
Mac App Store Track#
Trigger: manual workflow_dispatch with target: mas.
- Builds with
tauri.mac-app-store.conf.jsonoverlay - Signs with Apple Distribution certificate
- Packages with
productbuild - Uploads
.pkgto App Store Connect viaxcrun altoolor Transporter
Microsoft Store Track#
Trigger: manual workflow_dispatch with target: msstore.
- Builds standard Tauri NSIS output
- Wraps in MSIX via
winappCli - Uploads to Partner Center (manual or via Store API)
Required Secrets#
| Secret | Used by |
|---|---|
APPLE_CERTIFICATE |
macOS signing |
APPLE_CERTIFICATE_PASSWORD |
macOS signing |
APPLE_ID |
Notarization |
APPLE_PASSWORD |
Notarization |
APPLE_TEAM_ID |
Notarization |
APPLE_DISTRIBUTION_CERTIFICATE |
MAS signing |
APPLE_PROVISIONING_PROFILE |
MAS builds |
WINDOWS_CERTIFICATE |
Windows signing |
WINDOWS_CERTIFICATE_PASSWORD |
Windows signing |
TAURI_SIGNING_PRIVATE_KEY |
Update signing |
TAURI_SIGNING_PRIVATE_KEY_PASSWORD |
Update signing |
Legal Requirements (Both Stores)#
- Published Terms of Service
- Published Privacy Policy
- In-app contact information
- Content reporting mechanism (24-hour response for Apple)
- User blocking functionality
- Proactive content moderation via label system
Smoke Test#
- Fresh install: download → install → first-launch welcome
- OAuth login: loopback flow on each platform
- Timeline: feed rendering, scroll, keyboard shortcuts
- NSFW: labeled content is blurred by default, reveal works, adult-only hidden when disabled
- Reporting: submit report flow completes
- Search sync: FTS5 + embedding pipeline post-login
- Auto-update: detection + install from prior version (GitHub builds only)
- Deep links:
at://URI opens app and navigates - Multicolumn: column persistence across restart
- Store-specific: support button visible on GitHub, hidden on store builds