Skip to content

TDD: Community Registry — discover and install themes, plugins, and grammars #567

Description

@jamesbrink

TDD: Community Registry — discover and install themes, plugins, and grammars

Introduction

Claudette has a sandboxed plugin runtime and a built-in theme system, but no path for users to discover and install third-party contributions. The bundled set is small (4 env-providers, 2 SCMs, 1 grammar, ~12 themes) and grows only at Claudette's release cadence. This TDD designs the community registry — the in-app surface that browses, installs, updates, and revokes user-contributed themes, plugins, and grammars from utensils/claudette-community.

Converges: #537 — third-party plugin distribution. The discussion in #537 weighed four distribution models and the author's stated bias was "Option 4 layered on Option 3 mechanics, with a mirroring cron in the registry repo so we don't depend on upstream availability." This TDD adopts that recommendation, expands scope to themes + grammars + future kinds (not just plugins), and commits to concrete schemas and phasing.

Related: #536 (plugin runtime capabilities), #237 (Plugin Management UI redesign).

Resources

  • utensils/claudette-community — content repo (already scaffolded)
  • src/plugin_runtime/manifest.rsPluginManifest, PluginKind, LanguageContribution, GrammarContribution (schema source of truth)
  • src/plugin_runtime/seed.rs — existing .content_hash sentinel pattern (the trust anchor we extend)
  • src/ui/src/styles/themes/index.tsBuiltinThemeMeta (theme metadata shape)
  • src/ui/src/components/settings/sections/PluginsSettings.tsx and ClaudeCodePluginsSettings.tsx — existing Settings sections we sit alongside
  • reqwest, tar, sha2, serde_json — likely backend deps

Glossary

Term Definition
Contribution A theme, plugin, or grammar packaged for distribution via this registry
Kind The contribution's category: theme, plugin:scm, plugin:env-provider, plugin:language-grammar, slash-command (future), mcp-recipe (future)
Registry The registry.json index file at claudette-community/registry.json, generated by CI
In-tree contribution A contribution whose source files live inside claudette-community itself (themes, grammars, the simple plugins)
External contribution A plugin whose source lives in the author's own repo, referenced by git_url + sha + sha256, with a tarball mirror in claudette-community for resilience
Direct install Power-user install path: paste <git-url>@<sha>, marked Unverified in UI
Trust anchor The cryptographic property that "the code that runs is the code that was reviewed." Today: build-time include_str! + .content_hash. Tomorrow: registry-pinned sha256 verified at install time

Current State

  • src/plugin_runtime/seed.rs bakes bundled plugins via include_str! and reseeds them to ~/.claudette/plugins/<name>/ on startup, with a .content_hash sentinel that distinguishes "our last write" from "user edits."
  • Bundled plugins ship in-tree: plugins/scm-{github,gitlab}, plugins/env-{direnv,mise,dotenv,nix-devshell}, plugins/lang-nix.
  • Themes are CSS blocks in src/ui/src/styles/theme.css, listed in BUILTIN_THEME_META. There is no runtime theme loading path — every theme ships in the bundle.
  • Settings has two existing plugin sections: Plugins (Claudette's own Lua plugins, always visible) and Claude Code Plugins (gated by pluginManagementEnabled, the upstream Claude CLI marketplace).
  • utensils/claudette-community is scaffolded with kind directories (themes/, plugins/{scm,env-providers,language-grammars}/, …), a registry.json stub, contribution guides, and issue/PR templates. No CI, no runtime consumer.

Future State

  • Users open Settings → Community, browse contributions filtered by kind, click Install, and the contribution is hash-verified, written to disk, and immediately usable.
  • Themes installed at runtime appear in the theme picker alongside built-ins.
  • Plugins installed at runtime are picked up by the existing plugin discovery — same ~/.claudette/plugins/<name>/ path, no plugin-runtime changes.
  • Auto-update is off; updates surface as badges, applied on user click.
  • Capability changes during update (a plugin requesting a new CLI, a theme adding @import) require fresh user consent — never silent.
  • Revoked contributions (security-flagged in revocations.json) are uninstalled at app start and shown in a "Removed" history.
  • A power-user Direct install form takes <git-url>@<sha>; the result is clearly marked Unverified.
  • Plugin authors can develop in their own repos; only a small registry-PR is needed to publish a new version.

Not in Scope

  • Author signing (cosign / sigstore). Trust is registry-PR review + commit-SHA + sha256. Reserve a signature field in the schema for v2.
  • Private/org registries. Reserve a customRegistryUrls settings field but ship only the default utensils/claudette-community URL.
  • Multiple versions per contribution. v1 surfaces only the latest. Schema includes version so a future v2 can pin.
  • Auto-update. Off. Per-contribution opt-in is v2.
  • Slash-commands and MCP-recipes. Schema reserves slots; surface ships disabled. The kinds get their own follow-up TDDs once the underlying systems are runtime-loadable.
  • Bundled plugin migration. The existing seed-from-include_str! flow stays. New community-installed contributions live alongside; no consolidation.
  • Plugin runtime capability framework. Tracked in discussion(plugins): expand plugin runtime beyond CLI — HTTP/WebSockets, storage, workspace+prompt triggering #536. This TDD assumes today's required_clis allowlist is the capability surface.

Technical Design

Summary

A content-driven registry: claudette-community is the source of truth and registry.json (CI-generated) is its index. Claudette fetches the index over HTTPS, lets the user browse, then per-contribution downloads a sha256-pinned tarball, verifies it, and writes to ~/.claudette/{plugins,themes}/. Existing plugin discovery picks up installed plugins with no runtime changes. A new theme-loader subsystem injects user-installed CSS at app startup. The whole surface sits behind a communityRegistryEnabled feature flag.

The design preserves #537's invariants: hash-pinning end-to-end, capability re-consent on update, opt-in auto-update, signed revocation list, namespace policy enforced at PR time.

Architecture

┌──────────────────────────────────────┐         ┌──────────────────────────────────────┐
│   utensils/claudette-community         │         │              Claudette                  │
│                                       │         │                                       │
│   themes/<id>/                         │         │   src/community/                       │
│   plugins/{scm,env-,lang-}/<name>/     │         │     ├── registry.rs   (HTTPS fetch)    │
│   slash-commands/, mcp-recipes/        │         │     ├── install.rs    (download+verify)│
│   mirrors/<external-pkg>-<sha>.tar.gz  │ ─HTTPS─►│     ├── verify.rs     (sha256 + rev.)  │
│   registry.json    (CI-generated)      │         │     ├── revocations.rs                  │
│   revocations.json (signed, manual)    │         │     └── themes_user.rs (NEW: runtime)  │
│                                       │         │                                       │
│   .github/workflows/                   │         │   src-tauri/src/commands/community.rs  │
│     ├── validate.yml  (per PR)          │         │   src/ui/src/components/settings/      │
│     ├── regen.yml     (push to main)    │         │     sections/CommunitySettings.tsx     │
│     └── mirror.yml    (cron, hourly)    │         │                                       │
└──────────────────────────────────────┘         │   ~/.claudette/                         │
                                                  │     ├── plugins/<name>/                  │
                                                  │     ├── themes/<id>/        (NEW)        │
                                                  │     └── registry-cache/      (NEW)        │
                                                  └──────────────────────────────────────┘

Registry index schema (registry.json v1)

{
  "$schema": "https://raw.githubusercontent.com/utensils/claudette-community/main/registry.schema.json",
  "version": 1,
  "generated_at": "2026-05-01T12:00:00Z",
  "source": {
    "repo": "utensils/claudette-community",
    "ref": "main",
    "sha": "0b48c36..."
  },
  "themes": [
    {
      "id": "catppuccin-mocha",
      "name": "Catppuccin Mocha",
      "description": "Pastel dark theme",
      "color_scheme": "dark",
      "accent_preview": "#cba6f7",
      "version": "1.0.0",
      "author": "github-username",
      "license": "MIT",
      "tags": ["pastel", "popular"],
      "submitted_at": "2026-04-12",
      "path": "themes/catppuccin-mocha",
      "sha": "<commit sha last touching this directory>",
      "sha256": "<sha256 of deterministic tarball of this directory at sha>"
    }
  ],
  "plugins": {
    "scm": [
      {
        "name": "forgejo",
        "display_name": "Forgejo",
        "description": "Forgejo PR / CI status",
        "version": "1.0.0",
        "kind": "scm",
        "required_clis": ["forgejo"],
        "remote_patterns": ["codeberg.org"],
        "author": "github-username",
        "license": "MIT",
        "submitted_at": "2026-04-20",
        "source": {
          "type": "in-tree",
          "path": "plugins/scm/forgejo",
          "sha": "<commit sha>",
          "sha256": "<sha256>"
        }
      },
      {
        "name": "linear",
        "version": "1.2.3",
        "kind": "scm",
        "author": "thirdparty-username",
        "license": "Apache-2.0",
        "source": {
          "type": "external",
          "git_url": "https://github.com/thirdparty/claudette-linear.git",
          "git_ref": "v1.2.3",
          "sha": "<full commit sha at that ref>",
          "sha256": "<sha256 of mirror tarball>",
          "mirror_path": "mirrors/thirdparty-claudette-linear-<sha>.tar.gz"
        }
      }
    ],
    "env-provider": [],
    "language-grammar": []
  },
  "slash_commands": [],
  "mcp_recipes": []
}

Schema notes:

  • source.type: "in-tree" covers themes, grammars, and any plugin authored inside claudette-community. The mirror cron is unnecessary for these — they're already in the repo.
  • source.type: "external" covers plugins authored in their own repos. The mirror cron in claudette-community keeps a tarball copy at mirrors/<sha>.tar.gz, so upstream deletion does not break installation.
  • sha256 is computed against a deterministic tarball (sorted entries, fixed mtimes). The generator script is responsible for determinism. Verifying at install time means tampering with main between registry generation and download is detected.
  • version is semver, contributor-declared. Used only for display in v1.
  • author ties to a verified GitHub user/org; the validator script checks the GitHub Profile API at PR time to prevent typosquatting against unrelated users.

Distribution / fetching

Registry: https://raw.githubusercontent.com/utensils/claudette-community/main/registry.json — fetched with If-None-Match ETag, cached at ~/.claudette/registry-cache/registry.json with a 1h TTL. Stale-but-readable on network failure.

In-tree contribution: download https://codeload.github.com/utensils/claudette-community/tar.gz/<source.sha>, extract only files under <source.path>/, verify sha256 matches.

External plugin: download https://raw.githubusercontent.com/utensils/claudette-community/<registry-source-sha>/<source.mirror_path>, verify sha256. The mirror is the canonical fetch path — we never fetch directly from the upstream repo at install time. (The mirror cron does that; if upstream is gone, the mirror is what we have.)

Crucially: no git binary required. Reasoned alternative to #537's runtime-git concern: tarballs over HTTPS via reqwest, decompressed with tar, hashed with sha2. All three are pure-Rust, low-dep, cross-platform.

Backend module structure

src/community/
├── mod.rs              # public API: list, install, update, uninstall, check_updates, install_direct
├── registry.rs         # fetch + parse registry.json, cache management
├── install.rs          # download + extract + write to ~/.claudette/{plugins,themes}/<name>
├── verify.rs           # sha256 verification, revocation check, capability diff
├── revocations.rs      # fetch + cache + apply revocations.json
├── themes_user.rs      # NEW: discover and load user-installed themes
└── meta.rs             # .install_meta.json read/write helpers

Tauri command surface (src-tauri/src/commands/community.rs)

#[tauri::command]
async fn community_registry_fetch(force: bool) -> Result<Registry, String>;

#[tauri::command]
async fn community_install(
    state: State<'_, AppState>,
    kind: String,           // "theme" | "plugin:scm" | …
    name: String,
) -> Result<InstalledEntry, String>;

#[tauri::command]
async fn community_uninstall(
    state: State<'_, AppState>,
    kind: String,
    name: String,
) -> Result<(), String>;

#[tauri::command]
async fn community_list_installed(
    state: State<'_, AppState>,
) -> Result<Vec<InstalledEntry>, String>;

#[tauri::command]
async fn community_check_updates(
    state: State<'_, AppState>,
) -> Result<Vec<UpdateAvailable>, String>;

#[tauri::command]
async fn community_update(
    state: State<'_, AppState>,
    kind: String,
    name: String,
    accept_capability_changes: bool,   // must be true if capabilities expanded
) -> Result<InstalledEntry, String>;

#[tauri::command]
async fn community_install_direct(
    state: State<'_, AppState>,
    git_url: String,
    sha: String,
) -> Result<InstalledEntry, String>;

#[tauri::command]
async fn community_apply_revocations(
    state: State<'_, AppState>,
) -> Result<Vec<RevokedEntry>, String>;

Theme runtime loading (the largest new subsystem)

Today, BUILTIN_THEME_META is a static array and theme.css is a build-time CSS file. To support runtime themes:

Backend (src/community/themes_user.rs):

  • Discover ~/.claudette/themes/*/{theme.json, theme.css}.
  • Parse theme.json into UserThemeMeta { id, name, description, color_scheme, accent_preview, css_content }.
  • Expose via themes_user_list Tauri command.

Frontend (boot order in App.tsx):

  1. Before any component mounts, await invoke("themes_user_list").
  2. For each user theme, inject <style data-theme-source="user" data-theme-id="<id>">{sanitized_css}</style> into <head>.
  3. Merge BUILTIN_THEME_META with user themes for the picker (useThemes() hook).

Sanitization (defense in depth — same trust model as plugins, but CSS has its own exfiltration vectors):

  • Strip @import rules.
  • Strip any url(http://...) / url(https://...) references (background-image, font-face).
  • Allow url(data:...) only if the data URI is under 100KB.
  • Reject CSS that fails to parse cleanly (treat as install error).

Picker UX: built-in vs user-installed themes shown in two groups, with a small "Community" badge on user themes.

Storage layout

~/.claudette/
├── plugins/
│   └── <name>/
│       ├── plugin.json
│       ├── init.lua
│       ├── grammars/                  (language-grammar plugins only)
│       ├── .content_hash              # existing
│       └── .install_meta.json         # NEW
├── themes/                            # NEW directory
│   └── <id>/
│       ├── theme.json
│       ├── theme.css
│       ├── .content_hash
│       └── .install_meta.json
└── registry-cache/                    # NEW directory
    ├── registry.json
    ├── registry.etag
    ├── revocations.json
    └── revocations.etag

.install_meta.json shape:

{
  "source": "community",                 // "community" | "direct" | "bundled"
  "registry_sha": "<registry source.sha at install time>",
  "contribution_sha": "<source.sha>",
  "sha256": "<verified hash>",
  "installed_at": "2026-05-01T12:00:00Z",
  "granted_capabilities": ["forgejo"],   // for plugins: required_clis at install
  "version": "1.0.0"
}

Update flow with capability re-consent

  1. community_check_updates walks .install_meta.json files, compares contribution_sha against the current registry, returns UpdateAvailable { kind, name, current_version, new_version, capability_diff }.
  2. UI shows badges + a per-contribution "Update" button.
  3. On click, frontend calls community_update. If capability_diff is non-empty, the backend returns Err("CAPABILITY_CHANGE_REQUIRES_CONSENT") unless accept_capability_changes: true was passed.
  4. UI shows a modal with the diff ("This plugin now requests: git. Continue?"); user confirms; frontend retries with accept_capability_changes: true.

Revocation flow

revocations.json schema:

{
  "version": 1,
  "generated_at": "...",
  "signature": "<future: ed25519 sig over body>",
  "entries": [
    {
      "kind": "plugin:scm",
      "name": "linear",
      "sha": "<bad sha>",
      "reason": "Exfiltrated workspace path to remote endpoint",
      "revoked_at": "2026-05-30"
    }
  ]
}
  • Polled at app start, 24h cache, last-known-good fallback.
  • "Can't be empty" sanity check (an empty revocations file is treated as a fetch failure, not as "everything is fine") prevents an attacker from silencing the system by truncating the file.
  • Manual override: a claudette community revocations skip <name> debug command exists for self-host scenarios.

Settings UI

New section CommunitySettings.tsx, route key community, sibling to the existing Plugins and Claude Code Plugins sections.

Three tabs:

  • Browse — search/filter by kind, install button, "Installed" badge on already-installed contributions.
  • Installed — managed-by-community list, per-row update badge, uninstall button.
  • Direct install — paste <git-url>@<sha> form, large "Unverified — you are responsible for what this code does" banner.

Gated by communityRegistryEnabled experimental flag (default off until v1 is stable in nightly).

Generator + CI on claudette-community

scripts/generate-registry.ts (Bun, since the repo is content-only and Bun is the default):

  • Walks themes/, plugins/{scm,env-providers,language-grammars}/ (skipping _example/).
  • For each contribution dir, reads the manifest, runs git log -1 --format=%H -- <path> for the per-dir SHA.
  • Builds a deterministic tarball (sorted entries, fixed mtimes), sha256s it.
  • For external plugins, fetches the upstream tarball by SHA, verifies, writes to mirrors/.
  • Emits registry.json.

scripts/validate.ts:

  • JSON-schema-validates each manifest against the schema mirrored from PluginManifest / LanguageContribution / theme metadata.
  • Verifies author resolves to a real GitHub user/org.
  • Verifies license is on the allowlist (MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, MPL-2.0).
  • Lints Lua syntax (using luau-cli if available; otherwise a basic regex pre-check).

GH Actions:

  1. validate.yml (PR): runs validator + generator; asserts no diff vs committed registry.json. Fails with a "run bun scripts/generate-registry.ts and commit the result" hint.
  2. regen.yml (push to main): regenerates and commits via a bot PR if needed.
  3. mirror.yml (hourly cron): for any external plugin, refreshes its mirror tarball from the upstream git_ref if the resolved SHA changes; opens a PR with the registry diff for human review.

Schema source-of-truth synchronization

The Rust types in src/plugin_runtime/manifest.rs are canonical. Whenever they change, claudette-community/registry.schema.json must be regenerated. To prevent drift:

  • Add a Cargo build-time check that emits registry.schema.json from the Rust types via schemars.
  • A CI job in Claudette uploads the regenerated schema as an artifact; a CI job in claudette-community downloads the latest schema from Claudette's main branch and asserts it matches the committed copy.

Feature flag

communityRegistryEnabled — boolean in app settings (experimental section). Default: false. Mirrors the existing pluginManagementEnabled pattern. Flag controls visibility of the Settings → Community section. The backend module ships unconditionally; only the UI surface is gated. This lets us merge incrementally without exposing half-built UX.

Cross-platform considerations

  • Path resolution via dirs::data_dir()~/.claudette/ on macOS/Linux, %APPDATA%\claudette\ on Windows.
  • Tarball extraction normalizes path separators; rejects paths containing .. or absolute prefixes (defense against path-traversal in malicious tarballs — same protection crate::grammar_provider already enforces).
  • Symlinks in tarballs: rejected.
  • File permissions: extracted files chmod'd to 0644 regardless of tarball metadata.

Performance considerations

  • Registry fetch: ~50KB compressed in v1, under 1MB even with hundreds of contributions. Negligible.
  • Mirror tarballs: per-plugin, typically <100KB. Bounded.
  • Theme runtime loading at app start: synchronous file reads + CSS injection happen before first paint. With 10 user themes that's ~10 small files; well under the 2s cold-start target.
  • Plugin install: a single tarball download + verify + extract + write. Seconds.

ERD (storage / state)

┌──────────────────────────┐         ┌──────────────────────────┐
│   registry.json (remote)   │         │   .install_meta.json      │
├──────────────────────────┤         │   (one per installed     │
│ source.sha                 │         │    contribution)          │
│ themes[].path / .sha256    │         ├──────────────────────────┤
│ plugins.scm[].source.…    │◄────────┤ contribution_sha           │
│ plugins.env-provider[]    │  diff   │ sha256                     │
│ plugins.language-grammar[]│         │ granted_capabilities       │
└──────────────────────────┘         │ source: 'community'|'direct'│
        │                            │ installed_at, version       │
        │ verify                     └──────────────────────────┘
        ▼
┌──────────────────────────┐
│   revocations.json         │
├──────────────────────────┤
│ entries[].name + .sha     │ ──► uninstall on match at app start
│ signature                  │
└──────────────────────────┘

Release Plan

Four PRs, each independently mergeable behind the feature flag. Each phase ends in something user-visible.

  1. PR feat: scaffold Rust + Iced boilerplate project #1: claudette-community generator + validator + CI. No Claudette code touched. End state: every PR to claudette-community validates manifests, regenerates registry.json, and the file is committed. Mirror cron stubbed for external plugins (none yet exist). Adds registry.schema.json (hand-written for v1; schemars-generated in PR ci: add GitHub Actions for Rust CI, conventional commits, and release please #2).
  2. PR ci: add GitHub Actions for Rust CI, conventional commits, and release please #2: Claudette plugins MVP. New src/community/ module (registry fetch + install + verify), community_* Tauri commands, Settings → Community tab (Browse + Installed + Direct install). Themes excluded — install only writes to ~/.claudette/plugins/. communityRegistryEnabled flag added. Existing plugin discovery picks up installed plugins; no plugin runtime changes.
  3. PR Terminal emulation library #3: Theme runtime loading. src/community/themes_user.rs, frontend theme loader + CSS sanitizer, theme picker integration, "Community" badge, install path extended to ~/.claudette/themes/.
  4. PR chore(main): release claudette 0.2.0 #4: Updates + revocations + capability re-consent. community_check_updates, community_update, capability-diff modal, revocations fetcher + apply-at-startup. Completes the supply-chain story.

After PR #4, the feature flag flips to default-on in nightly and ships in the next release.

Slash-commands and MCP-recipes are deliberately deferred — the underlying runtime systems aren't fully content-loadable today. Each gets its own follow-up TDD.

Monitoring / Telemetry

Mandatory

None — Claudette has no telemetry without opt-in. All observability is local logs.

Nice to have

  • tracing events at install / update / uninstall / revocation-applied, written to ~/.claudette/logs/community.log.
  • A debug command community status that dumps the current cached registry, list of installed contributions, pending updates, and last revocation poll. Hooks into the existing /claudette-debug skill.
  • Cold-start budget: include theme runtime loading in the existing startup-timing trace.

Testing

Use cases

# Use case Expected outcome
1 User opens Settings → Community for the first time Registry fetched, browse list populated, no installs yet
2 User installs a theme Tarball downloaded, sha256 verified, files written to ~/.claudette/themes/<id>/, theme appears in picker without restart
3 User installs a plugin Files written to ~/.claudette/plugins/<name>/, existing plugin discovery picks it up, plugin appears in Plugins settings
4 User installs an external plugin Mirror tarball fetched (not the upstream), sha256 verified
5 sha256 mismatch on download Install rejected with a clear error; nothing written to disk
6 Plugin update with same capabilities Update applied silently on user click
7 Plugin update with new required_clis Capability-diff modal shown; user must confirm
8 App start with revoked plugin installed Plugin uninstalled, "Removed" entry shown in UI
9 App start with stale registry cache + offline Stale cache used; UI shows "Last updated "
10 Direct install of <git-url>@<sha> Tarball fetched from codeload.github.com/<repo>/tar.gz/<sha>, verified, marked Unverified
11 Theme CSS containing url(https://evil.com/track.png) URL stripped during sanitization; install succeeds; CSS applied without the leak
12 Tarball with a .. path entry Install rejected; tampered package warning
13 User uninstalls a community theme Files removed from ~/.claudette/themes/<id>/, theme picker reverts to previous active theme if it was active
14 Empty revocations.json returned Treated as a fetch failure; previous cache retained

Testing notes

  • Backend unit tests for verify.rs (sha256 mismatches, path traversal, symlink rejection), revocations.rs (signature, empty-file sanity check), themes_user.rs (sanitizer).
  • Integration tests with a local mock registry server (wiremock crate) covering install/update/uninstall + capability re-consent.
  • claudette-community tests: validator red/green snapshots, generator determinism (same input → byte-identical output), schema-mirror check.
  • CI matrix: macOS / Linux / Windows for path normalization and extraction.
  • Manual UAT via /claudette-debug: install all bundled-equivalent contributions through the registry, verify behavior matches build-time bundling.

Steps to Completion

Claudette repo

  1. Add src/community/ module with registry.rs, install.rs, verify.rs, revocations.rs, meta.rs.
  2. Add src/community/themes_user.rs (PR Terminal emulation library #3).
  3. Add src-tauri/src/commands/community.rs Tauri command wrappers.
  4. Add experimental.communityRegistryEnabled to settings.
  5. Add new deps: reqwest, tar, flate2, sha2, schemars (build-only).
  6. Frontend: src/ui/src/components/settings/sections/CommunitySettings.tsx + Browse / Installed / Direct-install tabs.
  7. Frontend: theme loader + sanitizer in src/ui/src/utils/userThemes.ts, integration in App.tsx.
  8. Frontend: capability-diff modal in src/ui/src/components/settings/CapabilityDiffDialog.tsx.
  9. Build-time schemars job that emits registry.schema.json and uploads it as a CI artifact.

claudette-community repo

  1. scripts/generate-registry.ts — deterministic tarball + sha256 + per-dir git SHA + JSON emission.
  2. scripts/validate.ts — manifest schema validation + author/license checks + Lua syntax pre-check.
  3. .github/workflows/validate.yml — per-PR validator + generator-diff check.
  4. .github/workflows/regen.yml — regen on push to main, bot-PR if needed.
  5. .github/workflows/mirror.yml — hourly cron for external plugin mirrors.
  6. registry.schema.json — initial hand-written schema; replaced by Claudette-generated artifact in PR ci: add GitHub Actions for Rust CI, conventional commits, and release please #2.
  7. CONTRIBUTING.md updates — license allowlist, namespace policy, version bumping rules.

Other

Open Questions

  1. color_scheme: "auto" for themes — should themes be able to declare they follow OS dark/light mode? Recommend: defer; today's dark/light is sufficient.
  2. Multiple versions per contribution — schema supports version but v1 surfaces only the latest. Is "pin to specific version" worth shipping in v1, or v2? Recommend: v2.
  3. Auto-update granularity — once we ship opt-in auto-update, is it per-contribution or one global toggle? Recommend: per-contribution, defaulting off.
  4. Tarball mirror size cap — should mirror.yml reject upstream tarballs over a threshold (e.g. 5MB) to keep claudette-community lean? Recommend: yes, hard cap of 5MB for plugins, 1MB for grammars.
  5. Direct-install discovery — should there be an in-app way to share a <git-url>@<sha> link (deep link), or is paste-only fine? Recommend: paste-only for v1; revisit if users ask.
  6. Should bundled plugins migrate to community-installed? — they currently ship via include_str! and reseed on every app start. Migrating them would consolidate the install path but breaks "works offline on first launch." Recommend: keep bundled plugins bundled; community contributions live alongside.
  7. Author signing in v2 — when we add it, should it use an existing standard (cosign / minisign / SSH signed tags) or a Claudette-specific scheme? Recommend: SSH signed tags (least new infrastructure, GitHub already supports it).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority: mediumShould be resolved in a reasonable timeframetddTechnical Design Document

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions