Locara

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-modeld v0 + v1.0 (foundation + Chat streaming via Stub)
  • locara-modeld v1.1 (real LlamaBackend adapter ready to plug in)
  • locara-models::fetch_to_cache (HTTP model fetch with SHA verification)
  • ModelDownloader component (UI surface, never mounted in a real app)
  • 52-component library + 24 live demos
  • 81 components in registry.json (every locara 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:

  1. Visit a website
  2. Click “Download”
  3. Get a .dmg or .app
  4. Double-click
  5. 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

LayerStatusOwner
locara build exists (Tauri build delegated)🚧 partialframework
locara build produces a .app bundle✅ (via tauri build)framework
Bundle includes the manifest + capability declarationsframework
Bundle includes the runtime + IPC plumbingframework
Bundle can include models (bundled mode)🚧 needs a tauri.conf.json resources field per appframework, ~1 day
Bundle excludes models + uses fetcher (picker mode)🚧 needs the v1.2 transcribe wiringframework, 1-2 days + Mac validation
Code signing with Locara Developer IDUSER ACTION (cert + p12 export)
Notarization via xcrun notarytoolunblocks once signing works
GitHub Actions workflow that signs + notarizes on push to release tagsframework, ~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 existsframework
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:

  1. https://developer.apple.com/programs/enroll/ — Individual or Organization.
  2. Apple ID verification (2-factor + photo ID).
  3. $99 annual fee (Stripe, recurring).
  4. Wait 1–2 days for Apple to approve.
  5. Generate a “Developer ID Application” certificate in Certificates, Identifiers & Profiles.
  6. Export the cert + private key as a .p12 (Keychain Access → right-click cert → Export).
  7. 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-sign produces a runnable .app (works today).
  • locara build --sign invokes codesign with a configured identity, embeds the entitlements derived from locara.json’s capabilities, and produces a signed .app. Surfaces a useful error if the identity isn’t found in Keychain.
  • locara build --bundle dmg wraps the .app in a .dmg via create-dmg or hdiutil. Output is one file ready to host.
  • locara build runs locara verify --strict first; 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 .dmg opens, 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.2 produces a Transcribe-0.0.2.dmg attached 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-updater is the integration. Apps poll a JSON manifest at a known URL, download + apply updates in place.
  • Registry server: the /apps listing 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 manual locara publish flow exists.

Critical-path summary

Minimum viable “user can download and run”:

  1. You enroll in Apple Developer Program + add secrets to GitHub (~1 week wall-clock, ~2 hours active).
  2. P1: locara build --sign --bundle dmg (~2 days).
  3. P2: Bundle transcribe (~0.5 day).
  4. Host the .dmg somewhere (GH Releases is free; ~30 minutes).
  5. 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 build drive cargo directly inside an app’s src-tauri/, or rely on the existing bun tauri build flow? The current cmd/build.rs is 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 on app.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)

  1. Day 1: P1 — extend locara build with --sign + --bundle dmg. Manifest-driven entitlements; helpful errors when the identity is missing; output one .dmg per app.
  2. Day 2: P2 — bundle transcribe, smoke-test the .dmg on a fresh user account.
  3. Day 3: P3 — GitHub Actions workflow that runs P1/P2 on tag push; attaches to a release.
  4. 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.