Files
toolbox-portal/README.md
2026-07-22 11:49:49 +02:00

238 lines
15 KiB
Markdown

# add·ideas Toolbox Portal
`toolbox-portal` is the static launcher and release assembler for the add·ideas
browser toolbox. Version `0.1.0` reads one same-origin catalogue, shows each app
as a full-page launch card, and keeps personal pins, ordering, visibility, and
appearance in the current browser.
The v1 boundary is intentionally small:
- apps remain separately versioned and released repositories;
- `@add-ideas/toolbox-contract` owns manifest, catalogue, and URL validation;
- the portal navigates to apps—there are no iframes, plugins, remote modules, or
runtime-loaded application code;
- the assembler copies already-built, exact ZIP artifacts and never builds app
source or resolves a `latest` release.
## Development
Requirements: Node.js 22 or newer and npm 11 or newer. The committed `.npmrc`
selects the public, token-free package scope on Gitea; registry credentials are
not required to install the Toolbox SDK packages. Install and verify the portal:
```sh
npm ci
npm test
npm run lint
npm run build
npm run dev
```
The package lock resolves the `^0.1.0` SDK range to the published `0.1.1`
packages. A sibling SDK checkout is only needed when intentionally developing
the SDK and portal together.
Vite uses `base: './'`, so the built portal works at `/` or a nested static path.
The development catalogue at `public/toolbox.catalog.json` points to assembled
paths (`./apps/pdf/` and `./apps/xslt/`). Those manifests will correctly appear
as unavailable until an assembled release is being served.
## Catalogue and launches
The portal fetches `./toolbox.catalog.json`, validates it with the shared
contract, and then validates each enabled manifest. A bad individual manifest is
reported without hiding valid apps; a bad or unreachable catalogue gets a retry
state. An empty valid catalogue gets its own empty state.
Internal launch links stay same-origin and receive the exact catalogue URL in a
`toolbox` query parameter. For example:
```text
/apps/pdf/?toolbox=https%3A%2F%2Ftoolbox.example%2Ftoolbox.catalog.json
```
Apps discover that context through the shared shell/contract and retain their
standalone fallback when no context is present. External catalogue entries open
according to their declared launch mode and do not receive toolbox context.
Personal settings use the namespaced local-storage key
`@add-ideas/toolbox-portal:v1:preferences`. The preferences panel can reset,
export, and import the versioned JSON document. The portal itself does not
transmit preferences. Because Toolbox apps share an origin, code explicitly
trusted in an app can access same-origin browser storage; review each app's
privacy and executable-code warning. Light, dark, and system modes are supported.
## Exact release assembly
Every app release ZIP must put these items at its archive root:
```text
index.html (or the entry declared by toolbox-app.json)
toolbox-app.json
CHANGELOG.md
LICENSES/
SOURCE.md
...built static assets
```
The app release should also publish a matching `.sha256` sidecar. The manifest
must declare the pinned reverse-DNS id and version. Application and portal
versions are independent.
`release/toolbox.lock.json` is the reviewed v0.1.0 lock. Its URLs and checksums
pin the published PDF Tools 0.3.4 and XSLT Tools 0.3.2 release bytes. Use
`release/toolbox.lock.example.json` as the template for a future release and
verify every downloaded asset before committing updated values. Artifacts may be:
- a path relative to the lock file;
- a `file:` URL; or
- a credential-free HTTPS URL to an immutable release asset.
For a private Gitea release, download the exact asset first and put its local
path in the lock. Do not put credentials in a lock URL.
Build the portal and assemble the distribution:
```sh
npm ci
npm test
npm run build
npm run assemble -- \
--lock release/toolbox.lock.json \
--portal-dist dist \
--output build/toolbox \
--archive build/add-ideas-toolbox-0.1.0.zip
```
Existing output is refused. Pass `--force` only when replacing those exact
paths is intended. A successful run produces:
```text
build/toolbox/
├── index.html
├── toolbox.catalog.json generated from the lock
├── toolbox.release.json ids, versions, targets, checksums
├── LICENSE.txt
├── SOURCE.md
├── THIRD_PARTY_LICENSES.txt
├── THIRD_PARTY_NOTICES.md
└── apps/
├── pdf/
└── xslt/
build/add-ideas-toolbox-0.1.0.zip
build/add-ideas-toolbox-0.1.0.zip.sha256
```
The assembler verifies SHA-256 before opening an artifact, validates every app
manifest with `@add-ideas/toolbox-contract`, requires its id/version to equal the
lock, and smoke-checks each declared entry, icon, and packaged manifest asset
after extraction. It rejects insecure or credential-bearing redirects, HTTP,
`latest` aliases, ZIP traversal, absolute paths, symlinks, special files,
duplicate paths, oversized entries, and unsafe compression ratios. Canonical
path checks protect source and output trees even through symlink aliases, and
publication rolls all outputs back if a staged rename fails. Artifacts are
unpacked only under `apps/<locked-target>`. The assembler never runs artifact
content.
The archive writer sorts file names and fixes ZIP timestamps and modes so the
same assembled directory produces stable bytes. To package an already assembled
directory separately:
```sh
npm run package:static -- \
--input build/toolbox \
--output artifacts/add-ideas-toolbox-0.1.0.zip
```
This also emits a `.sha256` sidecar.
## Container image
`Containerfile` serves only the assembled files with unprivileged nginx on port 8080. It adds restrictive browser headers, same-origin isolation, immutable
caching only for Vite-hashed assets, and revalidation for all other files.
Assemble first, then:
The packaged policy intentionally does not grant CSP `unsafe-eval`. SaxonJS's
`ixsl:eval()` extension is therefore unsupported in this deployment profile;
normal local XML/XSLT transformations do not require that permission.
```sh
podman build -f Containerfile \
-t git.add-ideas.de/zemion/toolbox:0.1.0 .
podman run --rm -p 8080:8080 \
git.add-ideas.de/zemion/toolbox:0.1.0
```
For a local deployment, copy `compose.example.yaml` to `compose.yaml` and run
`docker compose up --build -d` (or the Podman Compose equivalent).
Publish both common architectures from a buildx-enabled workstation or Gitea
runner:
```sh
docker login git.add-ideas.de
docker buildx build \
--platform linux/amd64,linux/arm64 \
--file Containerfile \
--tag git.add-ideas.de/zemion/toolbox:0.1.0 \
--tag git.add-ideas.de/zemion/toolbox:0.1 \
--push .
```
The default unprivileged nginx image is the security-fixed `1.31.3-alpine`
baseline pinned to its reviewed multi-architecture digest. Re-resolve and review
that digest whenever the base image is upgraded, then record it in the release
notes.
## Manual Gitea publication checklist
No credentials belong in this repository. Before the first release, create the
`zemion/toolbox-portal` Gitea repository and grant the workstation or runner
permission to push code, releases, and container packages.
1. Run `npm ci`, `npm test`, `npm run lint`, and `npm run build` from a clean
checkout of the intended portal tag.
2. Publish each app's versioned ZIP, `.sha256`, `toolbox-app.json`, changelog,
and licence notices in its own Gitea release.
3. Verify downloaded app assets with `sha256sum -c <asset>.sha256`; copy those
exact values into a reviewed toolbox lock.
4. Run the assembler and serve `build/toolbox` from a nested test path. Open
both apps, switch between them, and confirm the encoded `toolbox` context.
5. Verify the distribution with
`(cd build && sha256sum -c add-ideas-toolbox-0.1.0.zip.sha256)`.
6. Commit the reviewed lock, tag the portal source (for example `v0.1.0`), and
push the branch and tag to Gitea.
7. In Gitea, open **Releases → New release**, select the tag, and upload the
static ZIP plus its `.sha256` file. Do not use a mutable “latest” URL in a
future lock.
8. Build and push the AMD64/ARM64 OCI image as shown above. Record the resulting
multi-architecture manifest digest in the release notes.
For Gitea Actions, store registry credentials as repository secrets, check out
the exact tag, install with `npm ci`, run the same verification/assembly commands,
and upload only the already-created ZIP/checksum and OCI image. The workflow
must not re-resolve app versions or substitute a newer release.
## Planned / existing tools
| Tool | State and boundary | Principal workflows | Important concrete scope | Critical considerations and integrations |
| ------------------- | --------------------------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`pdf-tools`** | Existing; dedicated repository | Merge, split, reorder, rotate and export PDF pages | Thumbnail workspace; multi-document operations; ZIP and PDF output; saved local workspace | Workers and IndexedDB; large-document memory control; future signature inspection through `crypto-tools`; metadata and safe-sharing through `privacy-tools` |
| **`xslt-tools`** | Existing; dedicated repository | Develop, test and run XSLT transformations | XML/XSLT editors; transformation results; local files; saved projects; validation and diagnostics | Lazy SaxonJS loading; relocatable assets; useful handoffs to `data-tools`, `schema-tools` and `diff-tools` |
| **`svg-tools`** | Planned; dedicated repository | Source and visual SVG editing | Synchronized DOM tree, code and canvas; optimization; path simplification; symbols; accessibility; animation; sanitization | SVG is potentially active content; previews require sanitization or sandboxing; integrate with `colour-tools`, `token-tools`, `image-tools` and `label-tools` |
| **`regex-tools`** | Planned; dedicated repository | Develop, test and apply regular expressions | Corpus mode; replacement preview; capture table; flavour comparison; performance visualization; generated test cases; failing-input minimization | Never claim exact equivalence across regex engines without qualification; bound execution; integrate with `text-tools`, `log-tools` and `minimize-tools` |
| **`colour-tools`** | Planned; small-to-medium dedicated repository | Colour conversion, calculation and palette work | sRGB, Display P3, Lab, LCH, OKLab and OKLCH; gamut mapping; contrast; colour-vision simulation; gradients; palette extraction; design-token export | Make colour-space assumptions explicit; distinguish mathematical conversion from display simulation; integrate with SVG, image and token tools |
| **`rand-tools`** | Planned; small dedicated repository | Generate random numbers, strings, identifiers and samples | Cryptographically secure generation; deterministic seeded generation; UUIDs, ULIDs, passphrases, dice notation, distributions, weighted selection and shuffling | Clearly separate secure randomness from reproducible pseudorandomness; no misleading “strength” claims; natural foundation for `fixture-tools` |
| **`barcode-tools`** | Planned; dedicated repository | Generate, inspect and decode QR and barcodes | QR, common one- and two-dimensional codes; camera/image decoding; GS1 assistance; CSV batch generation; quiet-zone and print-size checks; structured payload builders | Treat decoded payloads as untrusted; never open links automatically; camera permission must be optional; integrate with `label-tools`, `contact-tools` and `crypto-tools` |
| **`av-tools`** | Planned; large dedicated repository | Light audio and video editing and transcoding | Trim, split, concatenate, crop, resize, normalize, fades, waveform, metadata, chapters, subtitles, thumbnail/contact-sheet generation and export presets | Heavy WASM and worker workloads; temporary-file storage; configurable memory limits; possibly special cross-origin-isolation headers; integrate with `subtitle-tools` and `image-tools` |
| **`crypto-tools`** | Planned; dedicated repository | Inspect keys, certificates and signatures; validate chains | X.509 PEM/DER, CSR, CRL, PKCS #8, encrypted PKCS #8, PKCS #12/PFX, JWK/JWKS; key/certificate matching; path construction; hostname, purpose and time checks; CMS/JWS later | Inspection-first; explicit trust anchors; no implication that browser/OS trust stores are used; no private-key persistence; offline revocation first; network checks opt-in; restrictive CSP and worker isolation |
| **`onenote-tools`** | Immediate candidate; dedicated repository | Open, browse, search and extract OneNote notebooks | `.onepkg` and `.one`; section/page tree; positioned page rendering; rich text, lists, tables, links, images, timestamps and attachments; diagnostics and HTML export later | `.onepkg` is CAB; parser must run in a worker/WASM; untrusted content must not execute; bounded decompression; safe attachment names; no editing in the first release |
## Licensing
Portal source is `AGPL-3.0-only`; see `LICENSE`. The contract is Apache-2.0, and
each assembled application retains its own licence and notices. An assembled
ZIP/container is an aggregate of those independently licensed components; see
`THIRD_PARTY_NOTICES.md` and each `apps/<slug>/LICENSES/` directory.