Skip to content

Publish the extension host API: services, ui and testing entries, version policy #3315

Closed

Description

@mstomar125

Summary

Complete the host API that extension code imports from core (ADR-082 §2.6, proposed). Add a compatibility policy, and a check that makes every change to the API deliberate and versioned.

The entries:

  • @nodespace/extension-api:
    • the registration types and EXTENSION_API_VERSION;
    • databases, including refreshDatabaseSettings();
    • nodes;
    • collections.reload() and schemas.reload();
    • onDaemonReconnect;
    • createLogger and error helpers.
  • @nodespace/extension-api/ui: Button, Card, Dialog, Input, Badge and focusTrap.
  • @nodespace/extension-api/testing: mockTauriCore and the testing-library helpers. Core's Vitest setup applies core's rune mocks.

The checks:

  • a surface snapshot tied to EXTENSION_API_VERSION;
  • a guard that no core module imports the host API.

Core code never imports the host API. Once this merges, the API is at 1.0, and the policy applies to every later change.

Context

What already exists. #3311 created:

  • src/lib/extension-api/index.ts, holding the registration types;
  • the @nodespace/extension-api alias, in svelte.config.js and in the Vitest configs.

What today's Pro UI imports from core. This is the surface the Pro frontend needs once it moves out:

  • From databaseStore: activeDatabaseId, activeDatabase, databases, error, create, switchTo and refreshDatabaseSettings. Used by add-synced-database-dialog.svelte, account-settings.svelte, first-pro-consent-slot.svelte and pro-sync.svelte.ts.
  • collectionsData.loadCollections. Used by add-synced-database-dialog.svelte, and by the pro:tier-detected reload that Move the Pro sync and collaboration UI out of core #3333 moves.
  • schemasData.loadSchemas. Used by the same reload.
  • backendAdapter.getNode and backendAdapter.createNode.
  • SharedNodeStore.getNode, for the variant machine's read of the settings node.
  • createLogger, toError, focusTrap, and components/ui/{button,card,badge,dialog,input}.
  • onDaemonReconnect, from services/daemon-status.ts. The Pro stores need it for their own reconnect handling.

How Pro tests run.

What later plans rely on:

Scope

1. src/lib/extension-api/index.ts

Use explicit export lists only, with no export *.

Registration. EXTENSION_API_VERSION, plus every type from plugins/ui-extensions.ts that extensions use:

  • NodespaceExtension;
  • Contribution;
  • ChromeSlot and ChromeContribution;
  • ViewerTabContribution;
  • SettingsSectionContribution, SettingsSlot and SettingsSlotContribution;
  • ExtensionLifecycle.

databases (type ExtensionDatabases). An object of read-through getters and delegating methods over databaseStore:

  • getters: activeDatabaseId, activeDatabase, list and error;
  • methods: create(name, path?), switchTo(id) and refreshDatabaseSettings();
  • also export the DatabaseInfo type.

The getters read the store's $state, so they are reactive inside a derivation.

nodes:

  • getNode(id): the reactive read from the shared node store;
  • fetchNode(id): delegates to backendAdapter.getNode;
  • createNode(input): delegates to backendAdapter.createNode.

collections: { reload(): Promise<void> }, over collectionsData.loadCollections().

schemas: { reload() }, over schemasData.loadSchemas().

Re-exports: onDaemonReconnect, createLogger, toError, isCommandError and the CommandError type.

DATABASE_SETTINGS_NODE_ID, provisionally, until ADR-083 decides how extensions read database settings.

2. src/lib/extension-api/ui.ts

  • Button, Badge and Input;
  • Card, CardHeader, CardContent, CardTitle, CardDescription, CardFooter and CardAction;
  • Dialog, as a namespace, which is how add-synced-database-dialog.svelte uses it;
  • focusTrap.

3. src/lib/extension-api/testing.ts

  • mockTauriCore, re-exported from src/tests/helpers/mock-tauri-core.ts.
  • render, screen, fireEvent, waitFor and cleanup from @testing-library/svelte. They are re-exported here because extension code may import only the host API, svelte and @tauri-apps/api (ADR-082 §2.6).
  • Module doc:
    • this entry is test-only, and app code never imports it;
    • the rune mocks and setup files come from core's vitest.config.ts, which runs the injected entry's tests.

4. Compatibility policy

Write this as the module doc of index.ts, following ADR-082's compatibility section.

What is versioned:

  • this module and its /ui and /testing entries;
  • the virtual:nodespace-extensions contract and NODESPACE_EXTENSIONS;
  • slot ids, host props and mount semantics;
  • contribution failure behaviour (§2.4);
  • hook timing, as documented in extension-lifecycle.ts.

Which bump each change needs:

  • Major: removing, renaming or retyping an export; changing a slot's location, props or mount semantics; changing a hook's timing.
  • Minor: a new optional slot, hook, service or field.
  • None: anything not reachable through the API.

Process:

  • A change that alters the API bumps EXTENSION_API_VERSION in the same PR.
  • The PR description says so, and so do core's release notes, under an "Extension API" heading.
  • Core modules never import the host API.

5. Surface snapshot

The snapshot file. src/tests/extension-api/extension-api-surface.json holds:

  • version: { major, minor };
  • entries: { index, ui, testing }, each a sorted list of export names;
  • typesHash: a SHA-256 over the exported interface and type declarations of plugins/ui-extensions.ts and src/lib/extension-api/*.ts, with comments stripped and whitespace normalized.

The test. src/tests/extension-api/extension-api-surface.test.ts reads each entry's export lists from source, recomputes the hash and compares both with the snapshot. It fails outright if an entry uses export *. Otherwise:

Situation Result
The surface changed, the version did not Fail: bump EXTENSION_API_VERSION (minor for additions; major for removals, renames or type changes), then re-record
A name was removed, but only the minor version changed Fail
The version changed, but the snapshot was not re-recorded Fail, printing the re-record command

To re-record: UPDATE_EXTENSION_API_SURFACE=1 bun run --cwd packages/desktop-app test src/tests/extension-api.

Type shape. The fixture extension in src/tests/fixtures/test-extension/ imports its types only through @nodespace/extension-api, and uses every kind and hook. svelte-check therefore fails on an incompatible type change.

6. Boundary guard

New src/tests/extension-api/extension-api-boundary.test.ts:

  • No .ts or .svelte file under src/lib (outside src/lib/extension-api/) or under src/routes imports @nodespace/extension-api or $lib/extension-api. Check import specifiers with comments stripped, as shared-node-store-pin-guard.test.ts does.
  • Nothing in src/lib/extension-api/* imports a Pro module: the variant machine, the Pro stores, the membership store or service, or the Pro extension.
  • Build those needles from fragments, per Add the Pro-boundary ratchet check and the CLAUDE.md Pro/sync placement rule #3299's absence-test convention, so the test adds no ratchet markers.

7. Docs

  • src/lib/plugins/index.ts: add one doc line pointing out-of-tree extensions to @nodespace/extension-api. The exports are unchanged.
  • CLAUDE.md: add one sentence. Changing the host API, the registry types, slot hosts or hook timing needs an EXTENSION_API_VERSION bump and a re-recorded surface snapshot (ADR-082).

8. Pro-boundary ratchet

Paste the printed BASELINES. No increase is expected.

Out of scope

Steps

  1. Start up as core's CLAUDE.md describes: a worktree named issue-<N>-extension-host-api, and self-assign at pickup.
  2. Write the facades and the entries.
  3. Write the policy doc.
  4. Write the snapshot, guard and facade tests, and record the snapshot at 1.0.
  5. Update the docs and the ratchet, then run the verification.

Acceptance criteria

Surface:

  • The three entries export exactly the surface above.
  • EXTENSION_API_VERSION is { major: 1, minor: 0 }, and the snapshot is recorded at 1.0.
  • Facades delegate to the stores, and reads through them are reactive.

Checks that must fail when they should:

  • The snapshot test fails when an export is added, removed or renamed, or a registration type changes, without a matching version bump. Check this once by mutation, and note it in the PR.
  • The boundary guard fails if a core module imports the host API. Check this once by mutation.

Everything else is unchanged:

  • No core module imports the host API.
  • bun run test:changed and test:free-users pass, and quality:fix is clean.
  • bun run scripts/check-pro-boundary.ts exits 0, and BASELINES equal the printed counts.

Tests and verification

New src/tests/extension-api/extension-api-facades.test.ts:

  • Setting databaseStore.activeDatabaseId, databases and error is visible through the facade.
  • create, switchTo and refreshDatabaseSettings delegate to the store (checked with spies).
  • collections.reload and schemas.reload call the store loaders.
  • nodes.getNode reads the shared store.
  • nodes.fetchNode and nodes.createNode call backendAdapter.
  • onDaemonReconnect and mockTauriCore are core's own functions.
  • Mock @tauri-apps/api/core with mockTauriCore, as the store tests do.

Also: the snapshot test and the boundary test described above.

bun run test:changed
bun run --cwd packages/desktop-app test src/tests/extension-api
bun run --cwd packages/desktop-app quality:fix
bun run scripts/check-pro-boundary.ts

Dependencies

Risks and rollback


Plan key: C3.6 (pro-boundary DAG).

🤖 Generated with Claude Code

Activity

  1. self-assigned this
    on Sep 30, 2026
  2. added
    pro-boundaryPro/core boundary: Pro code moves to nodespace-sync (ADR-081..085)
    on Sep 30, 2026
  3. mstomar125 commented on Oct 1, 2026

    @mstomar125
    ContributorAuthor

    Frontend baseline on origin/main 066fac4: 308 files, 5606 tests passed

  4. added a commit that references this issue on Oct 1, 2026
    fbd77f6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

pro-boundaryPro/core boundary: Pro code moves to nodespace-sync (ADR-081..085)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions