Triple-C marketplace: agents, skills, commands, hooks and plugins for all or individual containers #65

Merged
jknapp merged 48 commits from feat/marketplace into main 2026-09-27 23:43:27 +00:00
Owner

Summary

Adds a Triple-C marketplace: git repos of Claude Code agents, skills, commands, hooks and plugins that users add in Settings, browse in a new full-width Marketplace tab, and install for all projects or per project (with per-project opt-out). Installs are pinned to a commit; updates show a diff (hooks show the commands they will run) and only apply when accepted.

Also ships the Settings fix requested earlier: the shared-auth button row wraps so Check snapshot images stays inside the sidebar (ece0d74).

How it works

  • Repo format (hybrid): agents/*.md, skills/<name>/, commands/*.md, hooks/<name>/hook.json (+ scripts, ${HOOK_DIR}), and plugins/ as a standard Claude Code marketplace.
  • Accounts: named per marketplace — GitHub via host gh, or gh auth login --web inside a running container with a throwaway GH_CONFIG_DIR (container is never left logged in), or a pasted token for any host (GitHub/Gitea/GitLab who-am-I check, redirects never followed). Tokens live in the OS keychain only and never enter containers.
  • Fetching: on the host with gix into a bare cache; items are read from git objects at each pinned commit; pins protected by refs.
  • Sync: on every container start (in the background — never blocks start) and on Apply now, the host uploads a payload tar plus the app-embedded src/marketplace/sync.sh and runs it as claude once the entrypoint has finished (pgrep of its final process). No image or entrypoint.sh change, so every existing project gets it immediately. The script never overwrites or deletes files the user owns, merges/removes only its own hook entries in settings.json (kept 0600), and drives claude plugin under the update lock.
  • Settings export/import: marketplace state and account tokens travel with the encrypted export; imports are validated first and the preview warns about hooks and plugins installed for all projects.

Contract changes vs. the committed plan (from the final review fix wave)

  • Plugin marketplace slug is derived from the marketplace id only (mp-<id8>), so renames never uninstall plugins.
  • install_marketplace_item / update_marketplace_item take the reviewed commit and refuse if the head moved.
  • Payload manifest has a held list: installs the host couldn't build are kept in the container, not deleted.
  • Plugin state in the container is keyed plugin:<slug>/<key> (old records migrated).

Testing

  • cargo test --locked --lib: 842 passed; npx vitest run: 1053 passed; tsc clean; npm run build OK; clippy clean on marketplace code; secret scan clean.
  • The sync script is tested against the real script with a temp HOME and stub claude (hostile manifests, user-owned files, user hooks, symlinks, tampered state).
  • Real-container check against ghcr.io/shadowdao/triple-c-sandbox (throwaway container): readiness, install of all five kinds, idempotent re-sync, removal preserving user hooks, conflict skip, real claude plugin — all pass. It caught gh 2.101's new device-code wording, now fixed (89859b9).

Please verify on the preview build

  • Add the starter marketplace; install each kind globally and per project; opt out a global item in a project; Apply now.
  • Hook install shows its commands; update diff of a hook shows the new commands.
  • Push a change to the starter repo → update badge → diff → accept.
  • Accept an update while a refresh lands → refused with "review again".
  • Two marketplaces both shipping example-plugin.
  • Your own ~/.claude/agents/code-reviewer.md is skipped, not overwritten.
  • Private repo via token; GitHub sign-in via host gh; GitHub sign-in inside a container end to end (not yet re-run against the real image after the gh 2.101 fix).
  • First sync after upgrading reinstalls each plugin once under its new internal name (expected).

🤖 Generated with Claude Code

## Summary Adds a **Triple-C marketplace**: git repos of Claude Code **agents, skills, commands, hooks and plugins** that users add in Settings, browse in a new full-width Marketplace tab, and install **for all projects** or **per project** (with per-project opt-out). Installs are pinned to a commit; updates show a diff (hooks show the commands they will run) and only apply when accepted. Also ships the Settings fix requested earlier: the shared-auth button row wraps so **Check snapshot images** stays inside the sidebar (`ece0d74`). - Spec: `docs/superpowers/specs/2026-09-27-marketplace-design.md` - Plan: `docs/superpowers/plans/2026-09-27-marketplace.md` - Starter marketplace: https://github.com/shadowdao/triple-c-marketplace ## How it works - **Repo format (hybrid):** `agents/*.md`, `skills/<name>/`, `commands/*.md`, `hooks/<name>/hook.json` (+ scripts, `${HOOK_DIR}`), and `plugins/` as a standard Claude Code marketplace. - **Accounts:** named per marketplace — GitHub via host `gh`, or `gh auth login --web` inside a running container with a throwaway `GH_CONFIG_DIR` (container is never left logged in), or a pasted token for any host (GitHub/Gitea/GitLab who-am-I check, redirects never followed). Tokens live in the OS keychain only and **never enter containers**. - **Fetching:** on the host with `gix` into a bare cache; items are read from git objects at each pinned commit; pins protected by refs. - **Sync:** on every container start (in the background — never blocks start) and on **Apply now**, the host uploads a payload tar plus the app-embedded `src/marketplace/sync.sh` and runs it as `claude` once the entrypoint has finished (`pgrep` of its final process). No image or `entrypoint.sh` change, so every existing project gets it immediately. The script never overwrites or deletes files the user owns, merges/removes only its own hook entries in `settings.json` (kept 0600), and drives `claude plugin` under the update lock. - **Settings export/import:** marketplace state and account tokens travel with the encrypted export; imports are validated first and the preview warns about hooks and plugins installed for all projects. ## Contract changes vs. the committed plan (from the final review fix wave) - Plugin marketplace slug is derived from the marketplace **id only** (`mp-<id8>`), so renames never uninstall plugins. - `install_marketplace_item` / `update_marketplace_item` take the **reviewed commit** and refuse if the head moved. - Payload manifest has a `held` list: installs the host couldn't build are kept in the container, not deleted. - Plugin state in the container is keyed `plugin:<slug>/<key>` (old records migrated). ## Testing - `cargo test --locked --lib`: 842 passed; `npx vitest run`: 1053 passed; `tsc` clean; `npm run build` OK; clippy clean on marketplace code; secret scan clean. - The sync script is tested against the real script with a temp HOME and stub `claude` (hostile manifests, user-owned files, user hooks, symlinks, tampered state). - **Real-container check** against `ghcr.io/shadowdao/triple-c-sandbox` (throwaway container): readiness, install of all five kinds, idempotent re-sync, removal preserving user hooks, conflict skip, real `claude plugin` — all pass. It caught gh 2.101's new device-code wording, now fixed (`89859b9`). ### Please verify on the preview build - [ ] Add the starter marketplace; install each kind globally and per project; opt out a global item in a project; Apply now. - [ ] Hook install shows its commands; update diff of a hook shows the new commands. - [ ] Push a change to the starter repo → update badge → diff → accept. - [ ] Accept an update while a refresh lands → refused with "review again". - [ ] Two marketplaces both shipping `example-plugin`. - [ ] Your own `~/.claude/agents/code-reviewer.md` is skipped, not overwritten. - [ ] Private repo via token; GitHub sign-in via host `gh`; GitHub sign-in inside a container end to end (not yet re-run against the real image after the gh 2.101 fix). - [ ] First sync after upgrading reinstalls each plugin once under its new internal name (expected). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 34 commits 2026-09-27 19:44:19 +00:00
Re-authenticate, Revoke and Check snapshot images are each nowrap and
together wider than the settings sidebar, so the last one ran outside
its container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implements Task 13: BrowsePane (marketplace list, kind/search filters, item
detail), InstallControls (global/per-project install, opt-out, hook confirm
gate), HookConfirmModal, AddMarketplaceModal, and ItemDetail. Also applies
pre-flight ruling F6: a per-marketplace Remove button with a confirm dialog
(mp.remove) warning that surviving installs become "Source removed" and can
be dropped via Forget on the Installed tab, plus an inline account
reassignment select (updateMarketplace + reloadState).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix round 1 from PR review of the tree/catalog parsing:

- collect_dir now rejects an entry whose name is ".", "..", empty, or
  contains "/", "\" or NUL before it becomes part of an item's rel_path —
  a crafted git tree could otherwise walk a file outside the item's own
  folder once that path is joined against the item root downstream.
- collect_dir caps recursion at 32 directory levels and counts
  directories (not just files) toward MAX_ITEM_FILES, so a tree that is
  wide or deep rather than merely file-heavy is still bounded.
- hook.json and plugins/.claude-plugin/marketplace.json are now rejected
  unparsed above 1 MiB, rather than handed to serde_json regardless of
  size.

A pre-read size query (checking a blob's size before reading it) is
deferred per controller ruling — this round reads the blob and checks
its length before parsing, which is enough for the JSON-parsing DoS
shape being closed here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Applies preflight rulings F4, F7, F8, N5: Apply now's toast shows only
the success/info summary (the marketplace-sync-finished event listener
already toasts per-project errors/skips, so this avoids a double toast);
row removal passes the bare MarketplaceItemRef rather than the full
MarketplaceInstall; UpdateDiffModal shows a hook's rendered commands at
head above the file diff so an update is reviewed the same way an
install is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per preflight F5, Remove is disabled with a hint for an account a
marketplace uses rather than offering a confirm modal that promises a
removal the backend refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anonymous fetches of private repos map to Auth, error text drops gix
source locations and names the innermost network cause, and
valid_branch is pub(crate) for the add form (pre-flight F1, F2, F13).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shows items this project gets from "All projects" installs (with a
per-item opt-out switch saved through setGlobalItemDisabled, since
opting out doesn't require a stopped container) and this project's own
installs. Fetches the last sync report on mount and refetches it when
marketplace-sync-finished fires for this project (N7, preflight), so
Apply Now and container-start syncs don't leave it stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fix round 1 for Task 5. reqwest's default redirect policy only
strips Authorization/Cookie/Proxy-Authorization/WWW-Authenticate on a
cross-host hop, so GitLab's PRIVATE-TOKEN header (and Authorization on
an https->http same-host downgrade) would have followed a redirect to
an attacker-controlled target. The client now disables redirects
outright, and a 3xx response is treated as "not this kind of host"
rather than an error. Also adds the missing N17 test for a malformed,
credential-bearing URL, and clarifies two doc comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds diff::item_diff (similar), MarketplaceManager with snapshots,
persisted sync reports, the gh-login slot and a repo lock held across
fetches (pre-flight F11a), refresh_marketplace, load_cached_snapshot,
compute_updates, pins_by_marketplace, head_for, and the GitFixture test
helper on top of git::test_support (F3). AppState gains marketplace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Constant POSIX sh + jq script (embedded via include_str!) that applies the
payload into ~/.claude, tracks ownership in state.json, never overwrites
user-owned files, merges hook entries surgically, drives claude plugin and
prints a JSON SyncReport. Also: settings.json kept 0600 (pre-flight N11),
payloads containing symlinks are refused, slugs parsed via @tsv.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Validate manifest structure up front; malformed items are skipped with a
  reason instead of aborting extraction; an unreadable manifest changes
  nothing (no removals).
- Empty/whitespace settings.json reads as {}; non-object settings are left
  untouched; hook installs/updates/removals are reported and recorded only
  once their entries are actually merged; mv failures are checked.
- Dangling symlinks at user paths count as occupied.
- Removal paths are derived from kind+key, never taken from state.json.
- A symlinked settings.json is written through, not replaced.
- mktemp failure emits a JSON report instead of exiting silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removal only derives a path from an exact <kind>:<key> state id with a known
kind; any other record is dropped with an error and nothing is deleted
(an id like "skill" used to remove ~/.claude/skills/skill). Invalid plugin
records are dropped too, and a failed chmod 600 on settings.json is reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drives `gh auth login --web` in a running project container over an
attached pty, with GH_CONFIG_DIR/GIT_CONFIG_GLOBAL in a temp dir that is
removed on exit (also on HUP/INT/TERM), emits the one-time code and
redacted output lines, and returns the token read back between markers.
Host validation reuses auth::valid_host plus a no-port check (F13); a
cancel or timeout also pkills the in-container login (N9). Reuses the
setup-token flow's AnsiStripper, push_capped_tail and Enter delay, made
pub(crate) without behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	app/src-tauri/src/marketplace/mod.rs
Waits for the entrypoint, uploads the payload and the sync script, runs it
as claude and stores its report (payload skips merged in). The start hook
spawns the sync in the background; a per-project lock serialises syncs of
one project (pre-flight F11b).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lost stream or a failed Enter write returned without killing the
in-container gh, leaving it polling with a temp GH_CONFIG_DIR that would
receive the token. The output loop is now drive_login (generic over the
stream, writer and emitter, so it is unit-tested without Docker), and
its result goes through cleanup_on_error, so every ending except a token
read back runs the pkill. Neutral wording for the shared ANSI stripper's
overflow warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 21 marketplace commands, registered and granted; marketplace fields
kept store-owned in update_settings/update_project; a background refresh
of every marketplace at app start.

- apply_marketplace_now emits marketplace-sync-finished per project (F4).
- Settings export carries marketplace account tokens in ExportedSecrets
  (account id -> token) and import restores them; imported accounts,
  marketplaces and global installs are validated with the commands' own
  rules before anything is written. The import preview discloses the
  marketplace count, token count and global hook installs, and warns on
  the latter (F10).
- refresh_pins and cache removal hold the repo lock (F11).
- ops::validate_host/validate_branch delegate to auth::valid_host and
  git::valid_branch (F13).
- A finished gh container login frees only its own cancel slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gh 2.101.0 (the image's) prints "! One-time code (XXXX-XXXX) copied to
clipboard" and "Press Enter to open https://github.com/login/device in
your browser...". parse_device_prompt only knew "one-time code:", so no
code event went out and Enter was never pressed: gh sat at its prompt
until the 10-minute timeout. Match the label case-insensitively, accept
":" or "(" before the code, and require something after it so a code cut
by a frame boundary is not taken early.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The import preview counts global plugin installs and warns on them:
  a plugin can bring hooks and MCP servers into every container and an
  imported install skips the confirm step, like a hook.
- Item keys, hosts and branches in errors are quoted with {:?} and
  capped, since they can come from an import file.
- After an import, caches and snapshots of marketplaces the import
  dropped are removed (under the repo lock) and pins are refreshed for
  the imported installs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md gets a Marketplace subsection under Key Conventions (the sync
script is app-embedded and re-uploaded on every sync, never baked into
container/ — pre-flight F9) and the Settings export/import section now
covers marketplace account tokens traveling in ExportedSecrets and the
import preview's warning on global hook and plugin installs.
HOW-TO-USE.md gets a Marketplace section (placed after Shared Claude
Authentication) with its Table of Contents entry (pre-flight N13). The
spec doc's stale keychain service name, gh-login flags and
upload_bytes_to_container signature are amended to match the shipped
code (pre-flight N10).

Also fixes the new marketplace code's remaining build/clippy warnings:
BTreeMap/Sha256/Digest imports in tree.rs gated behind #[cfg(test)]
(their only uses are on MemTree, already test-only), the unused
`pub use marketplace::*` glob re-export dropped from models/mod.rs,
gh_login::strip_ansi marked #[cfg(test)] (production streams through
AnsiStripper instead), and four clippy lints in marketplace test code
(double_ended_iterator_last, cloned_ref_to_slice_refs x2,
single_match). Flushes the unresolved getMarketplaceSyncReport promise
in MarketplaceSection.test.tsx's "opens the Marketplace filtered to
this project" test to remove its act() warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two marketplaces shipping a plugin of the same name shared one
"plugin:<key>" state record, so every sync reinstalled one copy and
reported it updated, and removing one marketplace never uninstalled its
copy. Plugin state ids are now "plugin:<slug>/<key>"; the slug and key
for an uninstall are derived from the id and re-validated. Older
"plugin:<key>" records are migrated using their recorded slug, so
existing installs are neither reinstalled nor orphaned. Reports keep
"plugin:<key>".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Install and update pinned whatever the marketplace head was when the
click landed, so a background refresh between review and click could
pin content nobody saw (including a hook's shell commands).
install_marketplace_item and update_marketplace_item now take
expected_commit and refuse with "changed since you reviewed this item —
review it again" unless it is still the head. The UI passes the head the
selected item was read at (Browse), the head frozen with a pending hook
confirm (whose commands are frozen too), and the head of the accepted
diff (Installed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The credential callback answered every credential request. gix follows
a redirect of the initial handshake and asks for credentials for the
redirect target, so the token could be sent to another host. The
callback now answers only when the request's scheme, host and port
match the marketplace URL (gix's own URL normalisation, host compared
case-insensitively); anything else gets no credential.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The slug (plugin marketplace "triple-c-<slug>", plugin tree
"plugins/<slug>/") was built from the editable display name. After a
rename the next sync registered the new marketplace, skipped the plugin
install because the state's commit matched, then removed the old
marketplace: the plugin was gone while the report said nothing changed.

marketplace_slug now takes the id only ("mp-<id8>"). With plugin state
kept per slug (I1), containers synced with the old "<name>-<id8>" slugs
move over on their next sync: plugins are installed under the new name,
the old copies uninstalled and the old registration dropped. A sync
script test covers that migration (it fails on the pre-I1 script).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Marketplace sync: keep installs the host could not build (final review M3)
Secret Scan / scan (push) Successful in 6s
Build App (Preview) / compute-version (pull_request) Successful in 5s
Secret Scan / scan (pull_request) Successful in 6s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / build-macos (pull_request) Successful in 3m49s
Build App (Preview) / test (pull_request) Successful in 5m37s
Build App (Preview) / build-windows (pull_request) Successful in 7m20s
Build App (Preview) / build-linux (pull_request) Successful in 8m7s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
f2bb092586
An install the host skipped (pinned commit missing from the cache, cache
unreadable, item failing a tightened validation rule) never reached the
manifest, so sync.sh treated it as deselected and deleted it from the
container. The manifest now carries `held`: the state ids of such
installs ("plugin:<slug>/<key>" for plugins). The script counts them as
still selected and carries their records forward, as it already does
for items that fail inside the container. A removed marketplace is the
one skip that still removes; a malformed `held` list changes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jknapp added 13 commits 2026-09-27 20:25:45 +00:00
GitTree::read_file now takes a cap and reads the object's size from its
header first, so a blob over MAX_MANIFEST_BYTES / MAX_ITEM_BYTES is refused
without being inflated, and the blob is taken rather than cloned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Install and update now share ops::installable_at_head: the reviewed head
must still be the head and the item's catalog entry there must be valid.
The old update check (item_files) never parsed hook.json, so an upstream
hook with an unknown event could be pinned and then held by every sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ProjectsStore gains update_marketplace_fields / update_all_marketplace_fields,
which read-modify-write installs and opt-outs under the store's own lock.
Install, uninstall, update, forget and set_global_item_disabled use them
instead of writing back a whole Project read earlier, so a concurrent
start's status/container_id change is no longer overwritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plugin's update diff now includes its marketplace.json entry as a
pretty-printed "marketplace.json entry" file, so inline hooks, MCP servers
and commands are reviewed like any file. CatalogItem gains
plugin_components (entry / plugin.json runnable keys, hooks/hooks.json,
.mcp.json, commands/), and installing a plugin now goes through
PluginConfirmModal listing them. The import-preview warnings describe
that confirmation accurately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The single global repo lock becomes one lock per marketplace, and
refresh_pins takes each marketplace's lock only while setting its pins, so
a slow fetch no longer queues installs and refreshes of other marketplaces.

refresh_marketplace now takes the lock first and reads the settings store
under it (a closure, not a copy captured earlier); a marketplace removed
meanwhile gets no cache and no snapshot. Removing a marketplace deletes its
snapshot and cache under the same lock (remove_marketplace_cache).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
compute_updates reads each marketplace's head once (snapshot_head, no
snapshot clone) and opens each cache once, sharing trees across installs
through GitTree::at. refresh_marketplaces(Some(id)) re-pins only that
marketplace (mk::set_pins with only) and returns only its snapshot, which
the frontend merges by id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
changeAccount now catches a rejected updateMarketplace (e.g. an account for
another host) and toasts it instead of leaving an unhandled rejection, and
refreshes the marketplace after a successful change so the old fetch error
is replaced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InstalledPane's updateFor now requires u.pinned === i.commit, so an install
of the same item at another (unchanged) commit no longer borrows another
install's update and shows a phantom one with an empty diff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plugin's hooks / mcpServers / lspServers given as a path (or list of
paths) in its catalog entry or plugin.json is resolved inside the plugin
folder and shown as that file's contents; a path escaping the folder or
naming a missing file makes the plugin invalid. The root .lsp.json is
listed next to hooks/hooks.json and .mcp.json. Components are no longer
cut at 64 KiB: they are shown whole up to the 1 MiB manifest cap, and a
plugin whose runnable parts cannot be shown whole is refused instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ItemUpdate gains invalid_at_head: compute_updates records why the item
cannot be installed at head (catalog invalid, or gone) — the rule
update_marketplace_item applies — read once per marketplace from the
snapshot at head, else from the catalog parsed at head. The Installed row
shows that reason instead of a Review button, and the update counts in the
Marketplace tab and Settings count only applicable updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
update_project restored marketplace installs (and status, container id,
flags) from a copy read before validation, then wrote the whole record
later, so an install landing in between was lost. ProjectsStore gains
update_restoring, which runs restore_store_owned_fields against the record
as stored under the lock, and update_project writes through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md: imports skip both the hook and the plugin confirm steps (PR re-review)
Build App (Preview) / compute-version (pull_request) Successful in 1m5s
Secret Scan / scan (push) Successful in 7s
Secret Scan / scan (pull_request) Successful in 8s
Build App (Preview) / create-release (pull_request) Successful in 4s
Build App (Preview) / test (pull_request) Successful in 6m8s
Build App (Preview) / build-macos (pull_request) Successful in 4m38s
Build App (Preview) / build-linux (pull_request) Successful in 6m14s
Build App (Preview) / build-windows (pull_request) Successful in 7m10s
Build App (Preview) / prune-previews (pull_request) Successful in 3s
2ce019c470
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jknapp added 1 commit 2026-09-27 22:56:29 +00:00
Marketplace: let the kind filter wrap inside the item column
Secret Scan / scan (push) Successful in 7s
Secret Scan / scan (pull_request) Successful in 8s
Build App (Preview) / compute-version (pull_request) Successful in 7s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / build-linux (pull_request) Successful in 9m45s
Build App (Preview) / build-macos (pull_request) Successful in 4m23s
Build App (Preview) / build-windows (pull_request) Successful in 7m26s
Build App (Preview) / prune-previews (pull_request) Successful in 2s
Build App (Preview) / test (pull_request) Successful in 5m39s
f55d91a09d
All + the five kinds were wider than the fixed-width item column, so
Plugins was cut off behind a horizontal scrollbar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jknapp merged commit ee1bb691e6 into main 2026-09-27 23:43:27 +00:00
jknapp deleted branch feat/marketplace 2026-09-27 23:43:28 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: CyberCoveLLC/Triple-C#65