2026-07-20 18:40:51 +02:00
2026-07-20 18:40:51 +02:00
2026-07-20 18:40:51 +02:00
2026-07-20 18:40:51 +02:00
2026-07-20 18:40:51 +02:00
2026-07-20 18:40:51 +02:00
2026-07-20 18:40:51 +02:00
2026-07-20 18:40:51 +02:00

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:

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:

/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:

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:

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:

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:

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.

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:

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.

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.

Description
No description provided
Readme AGPL-3.0 1.2 MiB
2026-07-28 00:33:36 +02:00
Languages
TypeScript 62.3%
JavaScript 26.1%
CSS 10.9%
HTML 0.4%
Dockerfile 0.3%