Skip to content

Navigation Menu

Sign in
Sign up

docs(updates): design — in-app updates (Sparkle + plugin detect/hand-off) - #74

Merged
jordanrburger merged 2 commits into
main from
feat/in-app-updates
Sep 3, 2026
Merged

docs(updates): design — in-app updates (Sparkle + plugin detect/hand-off) #74
jordanrburger merged 2 commits into
main from
feat/in-app-updates

Conversation

@jordanrburger

@jordanrburger jordanrburger commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

What

Design spec for in-app updates — a single update surface in Scout.app that tracks two independent things:

  1. The Scout.app binarySparkle auto-download/install/relaunch, driven by a checked-in appcast.xml served from raw.githubusercontent.com/Raven-Scout/Scout/main/appcast.xml.
  2. The scout-plugindetect + notify + hand off (the app can't apply a plugin update — that happens inside Claude Code). Shows installed → latest and copies /scout-update to the clipboard.

Both feed one observable UpdateService, surfaced as a Settings ▸ Updates section plus a badge on the sidebar and menu-bar icon.

Why the two tracks are asymmetric

The app track can self-install (Sparkle owns detection and application). The plugin track can only surface the gap and hand you the command — there is no app→Claude-Code interface to drive a slash command.

Grounded on the real install layout

  • Installed plugin version is authoritative in ~/.claude/plugins/installed_plugins.jsonplugins["scout@scout-plugin"][0].version (currently 0.7.2; repo is 0.7.3).
  • "Latest plugin" is resolved source-aware from known_marketplaces.json: GitHub raw for end users, local directory for dev checkouts (this machine).
  • App is not sandboxed (no entitlements) → the simple Sparkle path applies. SPM is already wired (Grape), so Sparkle is added the same way.

The part with teeth: release.sh

Sparkle adds nested code (Sparkle.framework, Autoupdate, Updater.app, XPC services) that must be signed inside-out with Developer ID + hardened runtime before the outer .app, then notarized — a required change from today's flat-.app signing. Per release: sign_update the DMG, append an appcast.xml entry (EdDSA signature + the GitHub release DMG URL as the enclosure), commit + push the feed.

Decisions locked in brainstorm

Fork Choice
Update scope Both app binary + plugin, unified
App-binary mechanism Sparkle (full auto-install), not browser hand-off
Appcast hosting Checked-in appcast.xml via raw GitHub (no Pages)
UI surface Settings ▸ Updates section + sidebar/menu-bar badge

Open items for review

  1. Canonical public plugin org for the raw-URL fallback default — plugin.json homepage says jordanrburger/scout-plugin, README says Raven-Scout/scout-plugin. Runtime detection reads the marketplace source so this isn't blocking, but the fallback default should be right.
  2. sparkle:version = commit-count CURRENT_PROJECT_VERSION as the comparison key, MARKETING_VERSION as the display string — monotonic, matches release.sh today, no new state file.

Next

Per the review-first flow, the implementation plan lands on this same branch/PR next (spec + plan reviewed together before any code).

Spec: docs/superpowers/specs/2026-07-07-in-app-updates-design.md

🤖 Generated with Claude Code

jordanrburger requested a review from a team July 9, 2026 12:47
jordanrburger pushed a commit to Raven-Scout/scout-plugin that referenced this pull request Jul 12, 2026
The plugin metadata, installer, and self-update raw URL still referenced
the pre-org personal repo (jordanrburger/scout-plugin). Canonical repo is
Raven-Scout/scout-plugin. Updates all live references:
- .claude-plugin/plugin.json homepage + repository
- .claude-plugin/marketplace.json homepage + repository
- install.sh curl one-liner + MARKETPLACE slug
- engine/scout/scripts/self_update.py RAW_MARKETPLACE_URL
- engine/tests/unit/test_versioning.py fixture URL (consistency only;
 no test asserts these URLs)
Historical design docs under docs/plans/ and docs/specs/ are left
untouched as dated records (old links redirect via GitHub).
Flagged as open item 1 on Raven-Scout/Scout#74 — the in-app updates
raw-URL fallback there can now default to the org URL.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jordanrburger pushed a commit to Raven-Scout/scout-plugin that referenced this pull request Jul 12, 2026
The plugin metadata, installer, and self-update raw URL still referenced
the pre-org personal repo (jordanrburger/scout-plugin). Canonical repo is
Raven-Scout/scout-plugin. Updates all live references:
- .claude-plugin/plugin.json homepage + repository
- .claude-plugin/marketplace.json homepage + repository
- install.sh curl one-liner + MARKETPLACE slug
- engine/scout/scripts/self_update.py RAW_MARKETPLACE_URL
- engine/tests/unit/test_versioning.py fixture URL (consistency only;
 no test asserts these URLs)
Historical design docs under docs/plans/ and docs/specs/ are left
untouched as dated records (old links redirect via GitHub).
Flagged as open item 1 on Raven-Scout/Scout#74 — the in-app updates
raw-URL fallback there can now default to the org URL.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jordanrburger added a commit to Raven-Scout/scout-plugin that referenced this pull request Jul 17, 2026
...po (#188)
The plugin metadata, installer, and self-update raw URL still referenced
the pre-org personal repo (jordanrburger/scout-plugin). Canonical repo is
Raven-Scout/scout-plugin. Updates all live references:
- .claude-plugin/plugin.json homepage + repository
- .claude-plugin/marketplace.json homepage + repository
- install.sh curl one-liner + MARKETPLACE slug
- engine/scout/scripts/self_update.py RAW_MARKETPLACE_URL
- engine/tests/unit/test_versioning.py fixture URL (consistency only;
 no test asserts these URLs)
Historical design docs under docs/plans/ and docs/specs/ are left
untouched as dated records (old links redirect via GitHub).
Flagged as open item 1 on Raven-Scout/Scout#74 — the in-app updates
raw-URL fallback there can now default to the org URL.
Co-authored-by: Jordan Burger <jordan.burger@keboola.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
jordanrburger and others added 2 commits September 2, 2026 22:55
...etect/hand-off)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plan for the full approved scope — Sparkle app track, plugin detect/hand-off,
Settings ▸ Updates, sidebar/menu-bar badge, release.sh (inside-out signing,
sign_update on the stapled DMG, appcast regenerated and pushed to main).
Spec amendments are hardening only; brainstorm decisions stand: exact
Sparkle 2.9.6 pin, typed Info.plist + contract test, Debug builds never
start the updater, release.sh guards (key preflight, monotonic build number,
component existence, clean main), PRERELEASE=1 + SCOUT_APPCAST_URL rc
rehearsal before 0.12.0, one-item regenerated feed with an empty initial
appcast.xml, release-lib.sh with bash tests in CI, plugin manifest read at
the raw HEAD ref. Resolves the two open items (canonical org is
Raven-Scout/scout-plugin; sparkle:version = commit count).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Picking this back up (2026年09月02日). Rebased feat/in-app-updates onto current main (16 commits behind, no conflicts — the branch only adds the spec) and pushed two things:

1. Implementation plandocs/superpowers/plans/2026-09-02-in-app-updates.md, 14 tasks, TDD, one commit each, for the full approved scope: Sparkle app track, plugin detect + /scout-update hand-off, Settings ▸ Updates rows with chips, badge on the Settings sidebar row and menu-bar icon, release.sh (inside-out signing of Sparkle's nested code, sign_update on the stapled DMG, appcast.xml regenerated and pushed to main).

2. Spec amendments — appended as a clearly separated "Amendments (2026年09月02日)" section; none of the brainstorm decisions change. Each is individually rejectable:

  • exact Sparkle 2.9.6 pin; typed Scout/Info.plist + contract test
  • Debug builds never start the updater (dev bundle is com.scout.Scout.dev); all Sparkle code still compiles in Debug so CI catches breakage
  • release.sh guards: plist public key must match the keychain key, build number must exceed the last tag's, expected Sparkle components must exist, release only from a clean main, sign_update after stapling, xmllint on the appcast
  • PRERELEASE=1 publishes an rc with the appcast as a release asset only, and a SCOUT_APPCAST_URL (https-only) override lets an installed rc.1 update to rc.2 — the rehearsal that must pass before 0.12.0, since that's the one release a broken updater can't fix through itself
  • one-item feed regenerated per release + an empty valid appcast.xml committed up front so the URL never 404s; HTML notes rendered from the same feat/fix/other grouping; helpers in scripts/release-lib.sh with bash tests in CI
  • plugin "latest" read at the raw HEAD ref (no API call for the default branch)
  • both open items resolved: canonical org is Raven-Scout/scout-plugin (fixed in scout-plugin e0f86f5); sparkle:version = commit count confirmed

Not adopted, listed in the spec for you to opt into: 6-hour interval, SUAutomaticallyUpdate (silent download), Settings toggles, appcast-as-release-asset hosting. Daily checks + dialog-first + checked-in feed stand as decided.

Two tasks are yours unless you delegate: Task 2 (generate_keys + backup — the key is the root of trust for every future update) and Task 14 (rc.1 → rc.2 rehearsal, then scripts/release.sh → 0.12.0).

#97 was my duplicate of the app half of this, written without checking open PRs — closed as superseded.

jordanrburger merged commit cc17eeb into main Sep 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /