# 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.3` 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` — `AppShell` plus the v1 CSS theme. - `@add-ideas/toolbox-testkit` — the `toolbox-check ` 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. ```sh 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`](./schemas/toolbox-app.v1.schema.json). ```json { "$schema": "https://git.add-ideas.de/lotobo/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/lotobo/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: ```ts 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`](./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. ```json { "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: 1. `?toolbox=/toolbox.catalog.json` 2. `` 3. 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: ```ts 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 ```tsx import { AppShell } from "@add-ideas/toolbox-shell-react"; import "@add-ideas/toolbox-shell-react/styles.css"; setHelpOpen(true) }} onContextError={(error) => console.warn(error)} > ; ``` The shell's fixed top row renders the Toolbox brand/home at the far left, a geometrically centered text identity, and Help, source, Apps, and Personalize controls at the far right in that order. 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. `ToolboxPersonalizePanel` provides the shared portal-style heading, storage warning, and appearance picker, with a child slot for portal-specific controls. `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 ```json { "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()`, and `defineToolboxCatalog()`. - Validation: `parseToolboxApp()`, `parseToolboxCatalog()`, `ToolboxValidationError`, and `ToolboxError`. - Discovery/loading: `discoverToolboxCatalog()`, `fetchToolboxAppManifest()`, `fetchToolboxCatalog()`, `loadToolboxCatalog()`, and `loadToolboxContext()`. - URLs: `resolveWebUrl()`, `resolveToolboxApp()`, `requireSameOrigin()`, and `contextualizeToolboxLink()`. - Preferences: `parseToolboxPreferences()`, `readToolboxPreferences()`, `writeToolboxPreferences()`, and `TOOLBOX_PREFERENCES_KEY`. See each emitted `.d.ts` file for the complete signatures. ## Develop Requires Node 20 or newer. ```sh 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.