4.1 KiB
Archipelago app starter
This is a small standalone Vue/Vite app that demonstrates Archipelago's initial
design contract: semantic --archy-* tokens, dark native controls, responsive
cards, bottom actions, visible focus, 44px targets, and loading/empty/error/
success states. It intentionally has no dashboard dependency or host handshake.
Generate a separate source project from the repository root:
python3 scripts/create-app-starter.py my-notes /existing/parent/my-notes
The parent must exist and the output directory must not. Generation never
overwrites existing work, installs dependencies, builds or activates an app.
Use a clean template: symlinks, Git/build/cache state and an old GENERATION.json
receipt are refused before output creation. The receipt binds each input and
generated file hash; it is source provenance, not review or runtime acceptance.
Keep source and output directories under your control during generation.
The manifest ID stays quoted so YAML keywords such as null and off remain
the exact requested string. Generator regression tests require Python's PyYAML
package, also used by the contributor manifest preflight; generation itself uses
only Python's standard library.
The example saves items in this browser's local storage, not node storage or a
server API. Reload retains them; unreadable, invalid or full storage reports an
error without resetting existing data. This is not a multi-tab transaction store
or a wallet/secret store. npm test runs dependency-free storage regressions.
Append refusals (capacity, serialized-size limit or storage quota) leave readable
items visible and report the refusal separately. The size limit counts serialized
JavaScript string characters, not filesystem bytes or a browser quota guarantee.
From the repository root, source-only component checks can use an existing, trusted frontend dependency directory without installing or building anything:
node --max-old-space-size=192 scripts/test-app-starter-component.cjs /path/to/neode-ui/node_modules
This compiles the actual Vue component and exercises it in jsdom. It requires Vue/compiler-sfc, TypeScript and jsdom already available in that directory. It does not establish browser/layout, TypeScript typechecking, packaged runtime, embedding or managed-app acceptance; qualify those separately before shipping.
When dependency installation is separately authorized, use npm ci --ignore-scripts
from the generated directory, then npm run dev. The checked-in package and lock
use matching exact versions; do not replace the lock with a mutable install.
Generation requires the package/lock, Dockerfile, nginx config, manifest, Vite
config, HTML entry and starter source files. Missing inputs or mismatched
package/lock identity, version or direct dependencies are refused before output.
The generator validates captured input bytes and preserves all lock resolution
and integrity entries while substituting the app ID in package and lock names.
This source check does not validate the complete transitive dependency graph or
replace npm ci and subsequent artifact qualification.
The Dockerfile uses digest-pinned Node/nginx bases, a locked npm ci build and
nginx directly as UID/GID 101:101, without upstream configuration-mutating startup
scripts. Its config serves port 8080; the gated loopback manifest interface is
8180, with an HTTP health probe on 8080. The read-only root has only a bounded
16 MiB /tmp tmpfs for nginx scratch; there is no /data bind or node-persistence
claim. Browser-local items still belong to this browser/origin, not the container.
These are packaging source inputs, not a qualified reproducible image. Before shipping, independently review the exact source/output hashes, qualify the locked build and actual image's own entrypoint/config/assets under appropriate admission, and validate the manifest using the repository app developer guide. Real-browser, design/accessibility, embedded/Companion and managed lifecycle checks remain separate. If your app requires server persistence, supply and qualify its API and data-preserving lifecycle separately; this browser-local example does not do so.