8.8 KiB
Toolbox SDK
Toolbox SDK defines a small, versioned contract for independently deployed web tools. An app always works by itself. When it receives a trusted same-origin catalog URL, the same app gains a toolbox home link and an app switcher without becoming coupled to a portal or client-side router.
Version 0.2.1 contains three publish-ready packages:
@add-ideas/toolbox-contract— types, strict v1 runtime parsing, context discovery/loading, resolved URLs, and contextual link helpers.@add-ideas/toolbox-shell-react—AppShellplus the v1 CSS theme.@add-ideas/toolbox-testkit— thetoolbox-check <dist>build validation and nested-path smoke-test CLI.
The project is licensed under Apache-2.0. That permissive license was chosen in particular for its explicit patent grant.
Install
The repository includes a token-free .npmrc pointing the @add-ideas scope at
the ADD Ideas package registry. Supply registry credentials through your normal
user/CI npm configuration; never add them to this repository.
npm install @add-ideas/toolbox-contract @add-ideas/toolbox-shell-react
npm install --save-dev @add-ideas/toolbox-testkit
React and ReactDOM are peer dependencies of the shell (>=18 <20).
Application manifest v1
Deploy toolbox-app.json beside the application entry. The canonical schema is
schemas/toolbox-app.v1.schema.json.
{
"$schema": "https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json",
"schemaVersion": 1,
"id": "de.add-ideas.pdf-tools",
"name": "PDF Workbench",
"version": "0.3.4",
"description": "Page-level PDF operations performed locally in the browser.",
"entry": "./",
"icon": "./favicon.svg",
"categories": ["documents", "pdf"],
"tags": ["merge", "split", "rotate"],
"integration": {
"contextVersion": 1,
"launchModes": ["navigate", "new-tab"],
"embedding": "unsupported"
},
"requirements": {
"secureContext": true,
"workers": true,
"indexedDb": true,
"crossOriginIsolated": false
},
"privacy": {
"processing": "local",
"fileUploads": false,
"telemetry": false
},
"source": {
"repository": "https://git.add-ideas.de/zemion/pdf-tools",
"license": "AGPL-3.0-only"
}
}
source, privacy.label, privacy.url, requirements.topLevelContext,
actions, and assets are optional v1 additions. toolbox-check verifies the
entry, icon, and every declared asset. Runtime parsers validate every known
field, require schemaVersion: 1, and deliberately discard unknown fields so
future optional additions do not break v1 consumers.
For typed source definitions, use the literal-preserving identity helper:
import { defineToolboxApp } from "@add-ideas/toolbox-contract";
export const manifest = defineToolboxApp({
// The same fields as toolbox-app.json.
});
Use parseToolboxApp(unknownValue) at trust boundaries; defineToolboxApp() is
compile-time only and does not replace runtime parsing.
Catalog v1
The canonical schema is
schemas/toolbox-catalog.v1.schema.json.
A catalog owns its home and theme. Manifest references resolve relative to the
catalog. An external entry may be declared inline when no toolbox manifest is
available.
{
"schemaVersion": 1,
"id": "de.add-ideas.toolbox",
"name": "add·ideas Toolbox",
"home": "./",
"theme": { "mode": "system", "brand": "add·ideas" },
"apps": [
{ "manifest": "./apps/pdf/toolbox-app.json", "enabled": true },
{
"name": "Documentation",
"entry": "https://docs.example.org/",
"launch": "new-tab",
"enabled": true
}
]
}
Disabled entries remain in the parsed catalog but are not fetched or exposed in
the resolved app list. loadToolboxCatalog() fetches enabled manifests and
returns resolved manifest, entry, icon, asset, action, privacy, home, and
external-entry URLs.
Context discovery and security
An app discovers context in this order:
?toolbox=/toolbox.catalog.json<meta name="toolbox" content="/toolbox.catalog.json">- standalone mode when neither exists
The query parameter takes precedence. Discovered catalog URLs and their manifest documents must share the current page origin; cross-origin values are rejected before fetch. Credential-bearing and non-HTTP(S) URLs are rejected. Unknown, invalid, or unavailable context is returned as a discriminated error, not an uncaught rejection:
import { loadToolboxContext } from "@add-ideas/toolbox-contract";
const result = await loadToolboxContext();
if (result.status === "ready") {
console.log(result.context.catalog.apps);
} else if (result.status === "standalone") {
console.log("No toolbox requested");
} else {
console.warn(result.error.code, result.error.message);
}
contextualizeToolboxLink(target, catalog, { location }) (also exported as
createContextualLink) adds the context parameter only to same-origin targets.
External targets are resolved but never decorated with the context URL.
React shell
import { AppShell } from "@add-ideas/toolbox-shell-react";
import "@add-ideas/toolbox-shell-react/styles.css";
<AppShell
app={manifest}
manifestUrl="/toolbox-app.json"
helpAction={{ onClick: () => setHelpOpen(true) }}
onContextError={(error) => console.warn(error)}
>
<Application />
</AppShell>;
The shell's fixed top row renders Personalize, Apps, source, and Help controls in that order, a geometrically centered text identity, and the Toolbox brand/home at the far right. Personalize and Apps have labeled desktop controls and retain their accessible names when compact. The mutually exclusive popovers close on Escape, outside interaction, and link selection.
AppShell moves version, derived privacy facts, manifest actions, and legacy
appActions into its standard footer, keeping the top row stable in connected
and standalone modes. With valid context it supplies the catalog home, favicon,
and available app destinations. Destinations are ordinary links, so switching
performs full-page navigation. On missing, cross-origin, invalid, or unavailable
context the shell quietly remains usable in standalone mode; onContextError is
optional observability.
For a custom portal-style header, ToolboxHeader accepts personalizeContent
for content hosted inside the shared controlled popover and brandIconUrl for
the Toolbox mark. onPersonalize remains available for callback-only
integrations. The deprecated iconUrl, leadingActions, and metadata props
are retained for source compatibility; the centered app icon is intentionally no
longer rendered.
CSS custom properties prefixed with --toolbox- are the v1 theme surface. The
user's light, dark, or system preference is shared with same-origin apps
under TOOLBOX_PREFERENCES_KEY; the catalog mode is used until a preference is
saved. ToolboxHeader is also exported for portal-like pages that do not need
the complete application shell.
Validate a build
{
"scripts": {
"toolbox:check": "toolbox-check dist"
}
}
toolbox-check strictly parses dist/toolbox-app.json, checks its id and
SemVer version, rejects absolute/traversing/escaping local asset paths, verifies
the entry/icon/assets, serves the build from a deep nested prefix, and
smoke-fetches both standalone and ?toolbox=/toolbox.catalog.json modes. It
also follows local script, stylesheet, image, icon, and web-manifest references
in the entry HTML (including web-manifest icons), rejecting root-absolute
references that would break a nested deployment. It uses Node fetch and a
temporary in-process HTTP server; browser behavior is covered by the shell's
jsdom tests.
Contract API
The main exports are:
- Definitions:
ToolboxAppManifest,ToolboxCatalog, their nested and resolved types,defineToolboxApp(), anddefineToolboxCatalog(). - Validation:
parseToolboxApp(),parseToolboxCatalog(),ToolboxValidationError, andToolboxError. - Discovery/loading:
discoverToolboxCatalog(),fetchToolboxAppManifest(),fetchToolboxCatalog(),loadToolboxCatalog(), andloadToolboxContext(). - URLs:
resolveWebUrl(),resolveToolboxApp(),requireSameOrigin(), andcontextualizeToolboxLink(). - Preferences:
parseToolboxPreferences(),readToolboxPreferences(),writeToolboxPreferences(), andTOOLBOX_PREFERENCES_KEY.
See each emitted .d.ts file for the complete signatures.
Develop
Requires Node 20 or newer.
npm install
npm run check
npm run check runs strict TypeScript checks, ESLint, Prettier verification,
Vitest/jsdom tests, and all package builds. The workspace uses current versions
aligned with the consuming apps: React 19.2, TypeScript 6, Vitest 4, and
ESLint 10.
Package publication is intentionally separate from the build. Authenticate in the caller environment, then use npm workspace publishing commands after review.