Packaging scope — what exists, what’s missing, what to do next
Living scoping note, not a spec. The canonical spec is
spec/16-build.md; the load-bearing decision is ADR-0004. This note ties them together for the developer who’s about to start the packaging work, and surfaces what’s blocked on user-action vs. what’s blocked on framework work.
Why now
We’ve shipped a lot of infrastructure that’s invisible to end users:
locara-modeldv0 + v1.0 (foundation + Chat streaming via Stub)locara-modeldv1.1 (realLlamaBackendadapter ready to plug in)locara-models::fetch_to_cache(HTTP model fetch with SHA verification)ModelDownloadercomponent (UI surface, never mounted in a real app)- 52-component library + 24 live demos
- 81 components in
registry.json(everylocara add <id>works) - 8 reference apps that build clean and pass
locara verify
None of this is reachable by an end user today because there is no shippable artifact. A user can’t:
- Visit a website
- Click “Download”
- Get a
.dmgor.app - Double-click
- Use the app
Until that flow exists, every other piece of work amplifies an artifact nobody can install. Packaging is the bridge.
The end-user journey we’re building toward
website (locara.app/apps)
│
▼
"Download Transcribe (1.2 MB)" — link to a signed .dmg on a CDN
│
▼
.dmg opens → user drags Transcribe.app to /Applications
│
▼
First launch:
• Bundled-mode app: app starts immediately, models already in
Contents/Resources/models/. ~2 GB .app bundle, no first-run wait.
• Picker-mode app: app starts, renders the first-run sheet
(ModelDownloader), fetches models via locara-models::fetch_to_cache,
daemon serves them. ~10 MB .app bundle, ~30s first-run wait.
Both modes are real per the BACKLOG entry “Model + app distribution: bundle vs picker”. Most reference apps will probably ship picker by default (small download, shared models via the daemon, fresh models without re-shipping).
Two-axis state of play
| Layer | Status | Owner |
|---|---|---|
locara build exists (Tauri build delegated) | 🚧 partial | framework |
locara build produces a .app bundle | ✅ (via tauri build) | framework |
| Bundle includes the manifest + capability declarations | ✅ | framework |
| Bundle includes the runtime + IPC plumbing | ✅ | framework |
| Bundle can include models (bundled mode) | 🚧 needs a tauri.conf.json resources field per app | framework, ~1 day |
| Bundle excludes models + uses fetcher (picker mode) | 🚧 needs the v1.2 transcribe wiring | framework, 1-2 days + Mac validation |
| Code signing with Locara Developer ID | ❌ | USER ACTION (cert + p12 export) |
Notarization via xcrun notarytool | ❌ | unblocks once signing works |
| GitHub Actions workflow that signs + notarizes on push to release tags | ❌ | framework, ~2 days once secrets exist |
Hosted .dmg distribution (S3 / R2 + a download URL) | ❌ | framework + hosting cost |
App listing page on locara.app/apps/<publisher>/<name> | ✅ already exists | framework |
Updater (Sparkle / tauri-plugin-updater) | ❌ | framework, ~1 week |
| Registry server (per ADR-0015 mention) | ❌ | weeks; deferable until we have ≥5 apps to list |
User-action blockers
These cannot be done by code alone; they need someone to act on the user’s behalf (you):
1. Apple Developer Program enrollment ($99/year)
Per ADR-0004, the Locara project owns ONE Apple Developer Program enrollment that signs every app in the registry. Enrolling involves:
- https://developer.apple.com/programs/enroll/ — Individual or Organization.
- Apple ID verification (2-factor + photo ID).
- $99 annual fee (Stripe, recurring).
- Wait 1–2 days for Apple to approve.
- Generate a “Developer ID Application” certificate in Certificates, Identifiers & Profiles.
- Export the cert + private key as a
.p12(Keychain Access → right-click cert → Export). - Add to GitHub Actions secrets:
APPLE_DEV_ID_CERT_P12(base64-encoded.p12)APPLE_DEV_ID_CERT_PASSWORD(the export password)APPLE_ID(developer email)APPLE_ID_PASSWORD(an app-specific password from appleid.apple.com)APPLE_TEAM_ID(10-character team id from the membership page)
Once those secrets exist, the rest is automation.
2. Hosting for the .dmg
Options ordered by cost:
- GitHub Releases ($0, but cap at 2GB per asset and rate-limited for popular projects).
- Cloudflare R2 (~$0.015 / GB-month + $0 egress; if a release hits 100K downloads of a 2 GB bundle, that’s $0 — egress is free on R2, this is the big advantage over S3).
- AWS S3 + CloudFront (~$0.085 / GB egress; gets expensive on popular releases).
Recommended: GitHub Releases for v0 (free, no infra to manage); R2 when an app crosses the 2 GB asset limit or for the eventual registry server.
3. A domain to serve from
Already covered by locara.app (per BACKLOG operational items, if
purchased). Otherwise GitHub Pages or Cloudflare Pages on a free
subdomain.
Framework work (no user-action required)
In rough dependency order:
Phase P1 — Make locara build produce a complete .app (~2 days)
Currently locara build shells to tauri build. The output is a
runnable .app but it isn’t ready for distribution: no code sign,
no notarization, no compressed .dmg, no manifest validation of
the final bundle.
Done when:
locara build --no-signproduces a runnable.app(works today).locara build --signinvokescodesignwith a configured identity, embeds the entitlements derived fromlocara.json’s capabilities, and produces a signed.app. Surfaces a useful error if the identity isn’t found in Keychain.locara build --bundle dmgwraps the.appin a.dmgviacreate-dmgorhdiutil. Output is one file ready to host.locara buildrunslocara verify --strictfirst; refuses to build a manifest with unresolved(open)decisions.- For bundled-mode apps: copies declared models into
Contents/Resources/models/and rewrites the runtime’s resolution path accordingly.
Phase P2 — Wire one reference app’s bundled mode (~0.5 day)
Pick transcribe (smallest model set: Qwen 1.5B + Whisper Base.en
= ~1.2 GB). Add bundle: true to its manifest, run
locara build --bundle dmg, verify the resulting .dmg is
runnable on a fresh user’s Mac.
Done when:
- The
.dmgopens, drag-to-Applications works, app launches without any env vars or downloads. - Activity Monitor shows the model loaded from the .app bundle’s Resources, not from a user cache.
Phase P3 — GitHub Actions workflow (~1 day)
Builds, signs, notarizes, attests provenance, and attaches the
.dmg to a GitHub Release on push to app-<name>-v<version> tags.
The Locara monorepo already has a .github/workflows/publish-app.yml
that’s a reusable workflow stub — fill it out.
Done when:
- Pushing
app-transcribe-v0.0.2produces aTranscribe-0.0.2.dmgattached to a GH Release, signed and notarized, with a Sigstore attestation. - README on
apps/transcribe/links to the download.
Phase P4 — Picker-mode end-to-end (~2 days, depends on v1.2)
For apps that don’t want to bundle 1+ GB of weights. Needs the v1.2 transcribe wiring (the React first-run UI + the runtime IPC commands). Once that’s in, the .app bundle stays small (~50 MB) and models fetch on first launch.
Phase P5 — Updater + multi-app distribution (~1 week each)
- Updater: Sparkle is the standard;
tauri-plugin-updateris the integration. Apps poll a JSON manifest at a known URL, download + apply updates in place. - Registry server: the
/appslisting on the website is static today (one Astro page per content entry). Eventually replace with a real backend that does discovery, search, and version metadata. Deferred until the v0 manuallocara publishflow exists.
Critical-path summary
Minimum viable “user can download and run”:
- You enroll in Apple Developer Program + add secrets to GitHub (~1 week wall-clock, ~2 hours active).
- P1:
locara build --sign --bundle dmg(~2 days). - P2: Bundle
transcribe(~0.5 day). - Host the
.dmgsomewhere (GH Releases is free; ~30 minutes). - Add a download link to
/apps/kingtongchoo/transcribe(~30 minutes).
That’s the watershed: anyone with a Mac can install + run a Locara app, no terminal. P3 (CI) and P4 (picker mode) follow.
Total framework work for the critical path: ~3 days of focused sessions + 1 week of Apple’s approval queue.
Open questions to think about before P1 starts
- Apps as crates outside the workspace — should
locara builddrivecargodirectly inside an app’ssrc-tauri/, or rely on the existingbun tauri buildflow? The currentcmd/build.rsis a thin shell; we’ll need to make it smarter for sign/notarize. - Per-app icons — every app has a placeholder icon today
(
apps/*/src-tauri/icons/). Real Locara-branded but per-app icons are a design task before any v1 ship. - Bundle ID — Apple needs a unique reverse-DNS bundle id per
app. Today they’re set to placeholder values
(
com.kingtongchoo.<name>). Either standardise onapp.locara.<name>or let publishers choose; either way ADR territory. - Translocation — macOS Gatekeeper “translocates” downloaded
apps to a read-only mount on first run, which breaks any app
that expects to write to its own
Contents/Resources/. Bundled models are read-only so probably fine, but tested early. - Cert rotation — Apple Developer certs expire annually. The Locara process needs a documented rotation runbook so a forgotten cert doesn’t break every published app simultaneously.
What I’d actually do this week (if Apple enrollment were a
button you’d already pressed)
- Day 1: P1 — extend
locara buildwith--sign+--bundle dmg. Manifest-driven entitlements; helpful errors when the identity is missing; output one.dmgper app. - Day 2: P2 — bundle
transcribe, smoke-test the.dmgon a fresh user account. - Day 3: P3 — GitHub Actions workflow that runs P1/P2 on tag push; attaches to a release.
- Day 4: P4 (picker mode) for one app that’s too big to bundle.
End state: two distribution patterns proven end-to-end, the
workflow that automates them, and the website’s /apps page
hosting real downloadable artifacts.
When NOT to do this
If we’re not ready for end users yet — e.g. if the daemon isn’t
yet wired into any app, if there’s no marketing surface, if
there’s no support story for crashes / refunds / takedowns — then
shipping a .dmg invites support load we can’t handle. The
trigger condition is: “I am willing to handle the first 10 angry
emails from end users.” If that’s not true yet, finish v1.2
(transcribe daemon wiring) and dog-food it personally for a week
first.