No description
  • TypeScript 49.4%
  • Rust 49.2%
  • Dockerfile 0.5%
  • SCSS 0.3%
  • JavaScript 0.2%
  • Other 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Aiden McClelland 68dd390782
Some checks failed
Tag and Release / tag (push) Failing after 1m2s
Sync next / sync (push) Successful in 52s
ready for primetime
2026-10-22 15:28:32 -06:00
.forgejo/workflows ready for primetime 2026-10-22 15:28:32 -06:00
.github/workflows ready for primetime 2026-10-22 15:28:32 -06:00
assets ready for primetime 2026-10-22 15:28:32 -06:00
backend ready for primetime 2026-10-22 15:28:32 -06:00
scripts ready for primetime 2026-10-22 15:28:32 -06:00
startos ready for primetime 2026-10-22 15:28:32 -06:00
web ready for primetime 2026-10-22 15:28:32 -06:00
.dockerignore ready for primetime 2026-10-22 15:28:32 -06:00
.gitignore ready for primetime 2026-10-22 15:28:32 -06:00
.prettierrc ready for primetime 2026-10-22 15:28:32 -06:00
AGENTS.md ready for primetime 2026-10-22 15:28:32 -06:00
CLAUDE.md ready for primetime 2026-10-22 15:28:32 -06:00
Dockerfile ready for primetime 2026-10-22 15:28:32 -06:00
icon.svg ready for primetime 2026-10-22 15:28:32 -06:00
instructions.md ready for primetime 2026-10-22 15:28:32 -06:00
LICENSE ready for primetime 2026-10-22 15:28:32 -06:00
Makefile ready for primetime 2026-10-22 15:28:32 -06:00
package-lock.json ready for primetime 2026-10-22 15:28:32 -06:00
package.json ready for primetime 2026-10-22 15:28:32 -06:00
README.md ready for primetime 2026-10-22 15:28:32 -06:00
tsconfig.json ready for primetime 2026-10-22 15:28:32 -06:00
UPDATING.md ready for primetime 2026-10-22 15:28:32 -06:00

Wish List Logo

Wish List on StartOS

Wish List has no upstream project. The application is developed in this repository and built for StartOS, so this document describes the whole service rather than differences from an upstream release.

Wish List lets someone make gift wish lists, share each one by link, and lets the people buying for them claim gifts, split a gift's cost, add gifts that aren't on the list, and discuss every item, while the list's owners never see any of it. A list can have several owners. Source: https://forge.thebarsonists.run/helix-test/wish-list-startos.


Table of Contents


Image and Container Runtime

The image is built from the Dockerfile in this repository, for x86_64 and aarch64. It holds one binary, wish-list, and the compiled web app in /app/web.

Subcontainer Runs Purpose
wish-list wish-list serve The daemon: web app and JSON-RPC API on port 80
wish-list-admin wish-list <command> Temporary, per action: writes links, reads and deletes users in the database

wish-list serve takes WISH_LIST_PUBLIC_URL from the package on every start. Run without serve, the same binary is the admin CLI (signup-link, reset-link --user-id <id>, delete-user --user-id <id>, users), which works on the database directly whether or not the service is running.

Volume and Data Layout

Two volumes. Everything users create lives in one SQLite database.

Volume Mount point Contents
main /data wish-list.db (SQLite, WAL mode): accounts, passkeys, sessions, lists, items, claims, comments, avatars, push subscriptions, the VAPID key
startos not mounted store.json

The server applies database migrations itself at start. Each user's view of the data is derived from the database and held only in memory, rebuilt at every start.

File Models

One file model, and no configuration file inside the container.

  • store.json on the startos volume holds primaryUrl, the address chosen with Set Primary URL. Only that action writes it. Hand edits survive, but an address that is not one of the interface's addresses is ignored in favour of the preferred one.

The primary URL reaches the app as the WISH_LIST_PUBLIC_URL environment variable, re-read on every start; changing it restarts the service.

Dependencies

None.

Network Access and Interfaces

One HTTP interface serves both the web app and the API the web app calls.

Interface Type Port Serves
ui ui 80 The web app, POST /rpc/v1 (JSON-RPC), GET /avatars/<id> (signed-in users only), GET /ws/db/<guid> (WebSocket of live updates)

Share, sign-up and reset links are built on the primary URL, so the people a list is shared with need to reach that address. Sessions are an HttpOnly, SameSite=Strict cookie, Secure on HTTPS origins, valid for a year.

A passkey belongs to the host name it was created on. It signs in on any port of that host name, never on another address. Passkeys need a domain name, so they are unavailable on an IP address.

Pages update live. A signed-in browser calls db.subscribe for a snapshot of its user's view and a single-use WebSocket that streams patches to it. Views are computed on the server from what each user may see, so nothing hidden from the owner ever reaches the owner's browser. A browser that loses the WebSocket reconnects with a fresh snapshot.

Push notifications, sent to every owner of a list when someone asks a question and to the asker when an owner answers, are the service's only outbound traffic. They are standard Web Push (RFC 8030), encrypted (RFC 8291, aes128gcm) and signed with a VAPID key (RFC 8292) the server generates on first start and keeps in the database. They go to whatever push service the subscribing browser names: Google's for Chrome, Mozilla's for Firefox, Apple's for Safari, and the user's UnifiedPush distributor for browsers that use one (Fennec, IronFox). A subscription belongs to one device's session and ends when that session does; an endpoint the push service reports gone (404 or 410) is deleted. Delivery failures are logged and never retried.

Push endpoints must be https and on the public internet. An endpoint whose host is, or resolves to, a loopback, private (LAN), link-local, CGNAT or other non-public address is refused when the browser subscribes and skipped at delivery. Connections go only to public addresses, without proxies or redirects. A UnifiedPush distributor on the LAN therefore cannot receive notifications.

Installation and First-Run Flow

The app has no open registration and no setup wizard: an account can only be created through a link the package mints, so whoever reaches the address first cannot claim the instance.

  1. Install raises two tasks, one for Set Primary URL and one for Create Sign-up Link.
  2. Create Sign-up Link returns a single-use link; opening it creates the first account.
  3. From then on, people join by the share links that list owners send, or by further sign-up links.

The service runs before either task is done. Until a primary URL is chosen, links use the preferred address (a public domain, else the .local address).

Actions

Four actions. All run whether the service is running or stopped.

  • Create Sign-up Link: run whenever someone needs an account but has no share link. It adds a single-use invitation to the database and returns the link. The link expires after 7 days. Each run makes a new, independent link; earlier ones stay valid until used or expired. Takes a few seconds and does not interrupt the service.
  • Create Password Reset Link: run when a user is locked out. Pick the user; it returns a single-use link, valid 7 days, that sets a new password. Using the link signs that user out of every other session. Their passkeys are kept. Repeatable; each run makes a new link.
  • Delete User: pick the user. Deletes their account, sessions, passkeys, avatar, claims, comments and questions, the gifts they added themselves, and every list they are the only owner of. Lists they co-own stay with the other owners, and their answers there stay, credited to a former owner. Restarts a running service, whose live views are in memory and would miss the change.
  • Set Primary URL: run to choose the address links point at, typically a public domain so that people outside the network can open share links. Writes store.json and restarts the service. Changing it does not invalidate existing share links, which keep their token on the new address, but links already sent still name the old address.

Tasks

Two tasks, both important; neither blocks the service.

  • Set Primary URL is raised while no primary URL is stored, or while the stored URL's host name is not one of the interface's addresses. Choosing an address clears it. It comes back if that address later disappears (a removed domain, or an IP address whose network link is down).
  • Create Sign-up Link is raised once, at install. Running the action clears it, and it does not return.

Health Checks

One check, Web Interface, passes once the server is listening on port 80. It goes green within a few seconds of start. A failure that persists means the server exited: read the logs. The usual cause is a database the server cannot open or migrate.

Backups and Restore

Both volumes are copied wholesale (ofVolumes('main', 'startos')). The SQLite database is copied as files, including its write-ahead log.

A restored instance is complete: accounts, passkeys, sessions, lists, claims and comments, push subscriptions, and the chosen primary URL all come back. If the interface's port changed, links follow the new port automatically. Passkeys keep working because they are bound to the host name, not the port.

Limitations and Differences

  1. No email: sign-up, reset and share links are copied and sent by hand, and nobody is notified of claims or comments.
  2. Owners see questions asked about their items, and who asked unless they asked anonymously (the default), and no other comments. They never see gifts participants added themselves. A participant made an owner loses their claims on that list, and the gifts they added go with them.
  3. Names are unique per server, compared case-insensitively, and are what people sign in with.
  4. A passkey works only on the host name it was made on; a user who switches to a different address signs in with their password or adds a passkey there.
  5. Removing someone from a list, as a participant or an owner, does not invalidate the share link they used. The owner who removes them is prompted to revoke it, which issues a new link to everyone the owner sends it to afterwards.
  6. The web app is in English only.
  7. Push notifications need a browser with the Push API. On iPhone and iPad that means Wish List added to the Home Screen; on Android without Google services, a UnifiedPush-capable browser. Private and incognito windows cannot subscribe, and push services on a private network are refused.

Quick Reference for AI Consumers

package_id: wish-list
image: built from Dockerfile
architectures: [x86_64, aarch64]
subcontainers: [wish-list, wish-list-admin]
volumes:
  main: /data
  startos: unmounted
file_models:
  - store.json
startos_managed_env_vars:
  - WISH_LIST_PUBLIC_URL
dependencies: none
interfaces:
  ui: { type: ui, port: 80 }
actions:
  - create-signup-link
  - reset-password-link
  - delete-user
  - set-primary-url
tasks:
  - { action: set-primary-url, severity: important }
  - { action: create-signup-link, severity: important }
health_checks:
  - primary