Repository navigation
Publish the extension host API: services, ui and testing entries, version policy #3315
Description
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, includingrefreshDatabaseSettings();nodes;collections.reload()andschemas.reload();onDaemonReconnect;createLoggerand error helpers.
- the registration types and
@nodespace/extension-api/ui:Button,Card,Dialog,Input,BadgeandfocusTrap.@nodespace/extension-api/testing:mockTauriCoreand 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-apialias, insvelte.config.jsand 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,switchToandrefreshDatabaseSettings. Used byadd-synced-database-dialog.svelte,account-settings.svelte,first-pro-consent-slot.svelteandpro-sync.svelte.ts. collectionsData.loadCollections. Used byadd-synced-database-dialog.svelte, and by thepro:tier-detectedreload that Move the Pro sync and collaboration UI out of core #3333 moves.schemasData.loadSchemas. Used by the same reload.backendAdapter.getNodeandbackendAdapter.createNode.SharedNodeStore.getNode, for the variant machine's read of the settings node.createLogger,toError,focusTrap, andcomponents/ui/{button,card,badge,dialog,input}.onDaemonReconnect, fromservices/daemon-status.ts. The Pro stores need it for their own reconnect handling.
How Pro tests run.
- They use
mockTauriCore(fromsrc/tests/helpers/mock-tauri-core.ts) and@testing-library/svelte. - Core's
vitest.config.tsapplies the rune mocks (injectRuneMocks,setup-svelte-mocks.ts) to every test it includes. - Add the virtual:nodespace-extensions build entry and the extension-api import alias #3311 makes that config include the injected entry's tests.
What later plans rely on:
- Delete the Add synced database dialog and tenant badge from Database settings #3326's Pro replacement uses
databases.create,databases.switchTo,databases.error,nodes.createNodeandcollections.reload(). - Stop driving Pro sync lifecycle from the app shell, database store and debug channel #3329's uses
databases.refreshDatabaseSettings(),collections.reload()andschemas.reload().
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;ChromeSlotandChromeContribution;ViewerTabContribution;SettingsSectionContribution,SettingsSlotandSettingsSlotContribution;ExtensionLifecycle.
databases (type ExtensionDatabases). An object of read-through getters and delegating methods over databaseStore:
- getters:
activeDatabaseId,activeDatabase,listanderror; - methods:
create(name, path?),switchTo(id)andrefreshDatabaseSettings(); - also export the
DatabaseInfotype.
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 tobackendAdapter.getNode;createNode(input): delegates tobackendAdapter.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,BadgeandInput;Card,CardHeader,CardContent,CardTitle,CardDescription,CardFooterandCardAction;Dialog, as a namespace, which is howadd-synced-database-dialog.svelteuses it;focusTrap.
3. src/lib/extension-api/testing.ts
mockTauriCore, re-exported fromsrc/tests/helpers/mock-tauri-core.ts.render,screen,fireEvent,waitForandcleanupfrom@testing-library/svelte. They are re-exported here because extension code may import only the host API,svelteand@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
/uiand/testingentries; - the
virtual:nodespace-extensionscontract andNODESPACE_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_VERSIONin 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 exportedinterfaceandtypedeclarations ofplugins/ui-extensions.tsandsrc/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
.tsor.sveltefile undersrc/lib(outsidesrc/lib/extension-api/) or undersrc/routesimports@nodespace/extension-apior$lib/extension-api. Check import specifiers with comments stripped, asshared-node-store-pin-guard.test.tsdoes. - 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 anEXTENSION_API_VERSIONbump and a re-recorded surface snapshot (ADR-082).
8. Pro-boundary ratchet
Paste the printed BASELINES. No increase is expected.
Out of scope
- Moving core's Pro modules onto host API imports. They stay on
$libpaths until Move the Pro account, admission and synced-database settings out of core #3327 and Move the Pro sync and collaboration UI out of core #3333 move them. - The test that the TypeScript and Rust versions are equal. It lands with the Rust constant, in Split the desktop Tauri crate and add ADR-082's Tauri extension points #3322.
- The ESLint import rule for extension code. It belongs to nodespace-sync's gate.
- The nodespace-docs reference. It is part of the ADR-082 docs follow-up.
Steps
- Start up as core's
CLAUDE.mddescribes: a worktree namedissue-<N>-extension-host-api, and self-assign at pickup. - Write the facades and the entries.
- Write the policy doc.
- Write the snapshot, guard and facade tests, and record the snapshot at
1.0. - Update the docs and the ratchet, then run the verification.
Acceptance criteria
Surface:
- The three entries export exactly the surface above.
-
EXTENSION_API_VERSIONis{ major: 1, minor: 0 }, and the snapshot is recorded at1.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:changedandtest:free-userspass, andquality:fixis clean. -
bun run scripts/check-pro-boundary.tsexits 0, andBASELINESequal the printed counts.
Tests and verification
New src/tests/extension-api/extension-api-facades.test.ts:
- Setting
databaseStore.activeDatabaseId,databasesanderroris visible through the facade. create,switchToandrefreshDatabaseSettingsdelegate to the store (checked with spies).collections.reloadandschemas.reloadcall the store loaders.nodes.getNodereads the shared store.nodes.fetchNodeandnodes.createNodecallbackendAdapter.onDaemonReconnectandmockTauriCoreare core's own functions.- Mock
@tauri-apps/api/corewithmockTauriCore, 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
- Add the virtual:nodespace-extensions build entry and the extension-api import alias #3311: the alias and the minimal barrel.
- Add settings-section and database settings-slot extension points #3312, Add start, onDatabaseActivated and debugDump extension lifecycle hooks #3313, Add the collectionTreeRoots extension point to the sidebar collection tree #3314: the settings, lifecycle and tree-root types that the snapshot records.
Risks and rollback
- The surface may be too narrow. Additions are minor bumps, so it can grow as Move the Pro account, admission and synced-database settings out of core #3327 and Move the Pro sync and collaboration UI out of core #3333 need more.
testing.tsimports test helpers fromsrc/lib. Nothing in the app importstesting.ts, so it never reaches a bundle. The boundary guard keeps it that way.- Rollback. Revert the PR. Nothing in core depends on it.
Plan key: C3.6 (pro-boundary DAG).
🤖 Generated with Claude Code
Activity
mstomar125 commentedon Oct 1, 2026
Frontend baseline on origin/main 066fac4: 308 files, 5606 tests passed
Publish the extension host API: services, ui and testing entries, ver…