- JavaScript 55.4%
- Svelte 43.4%
- CSS 0.4%
- Nix 0.3%
- Dockerfile 0.2%
- Other 0.3%
Adds a read-only audit (scripts/audit-concepts) that diffs canonical SKOS sources against published kind 39738 events under AUTHOR. Matches by source URI via the on-relay `i`-tag bridge so renamed concepts do not surface as missing/extras. Adds a one-off fix (scripts/fix-hcrt) for the two hcrt concepts that collided with ekw-lrt under the same author: republishes them under `hcrt-audio` / `hcrt-video` and rewrites the hcrt scheme's hasTopConcept refs to point at the new addresses. Re-adds the two cross-scheme exactMatch rows in vocab-mappings/data.mjs (now meaningful because the addresses differ). |
||
|---|---|---|
| .forgejo/workflows | ||
| docs/superpowers/plans | ||
| scripts | ||
| src | ||
| static | ||
| .dockerignore | ||
| .env.example | ||
| .envrc | ||
| .gitignore | ||
| .mcp.json | ||
| Caddyfile | ||
| CLAUDE.md | ||
| create-page.png | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| explore.png | ||
| flake.lock | ||
| flake.nix | ||
| footer-check.png | ||
| homepage.png | ||
| import-page.png | ||
| package-lock.json | ||
| package.json | ||
| pnpm-lock.yaml | ||
| README.md | ||
| settings-page.png | ||
| signin-modal.png | ||
| svelte.config.js | ||
| tree-3-levels.png | ||
| tree-verification.png | ||
| vite.config.js | ||
NOCABS
Vocabs but on Nostr — a Svelte 5 app for authoring, browsing, and sharing controlled vocabularies as Nostr events, using the NIP-VOCAB six-kind split (ConceptSchemes, Concepts, Collections — published and draft variants).
NOCABS is a pure client-side SPA: all Nostr work happens in the browser (applesauce signers, localStorage-backed drafts, direct relay subscriptions). There is no backend, no server-side data, no cookies.
Develop
Requirements: Node 22+, pnpm (via corepack enable).
pnpm install
pnpm dev # http://localhost:5173
pnpm test
The two libs (nostr-vocab-core, nostr-vocab-skos-import) are pinned to
public tarball URLs on the Forgejo npm registry directly in package.json.
No .npmrc, no SSH keys, no registry routing needed — pnpm install fetches
them by URL and locks the integrity hash in pnpm-lock.yaml.
Local development against sibling repos
If you're iterating on the libs at the same time, you can override the registry
versions with a file path without touching the committed package.json:
pnpm link ../nostr-vocab-core
pnpm link ../nostr-vocab-skos-import
Undo with pnpm unlink or pnpm install to restore registry versions.
Build
pnpm build
Output is a fully static site in build/. Serve it with any static host — Caddy,
nginx, GitHub Pages, Netlify, Cloudflare Pages, npx serve build, etc.
Dynamic routes like /vocab/[pubkey]/[d] depend on client-side routing. Your static
host must fall back unknown paths to index.html (the bundled Caddyfile does
this out of the box).
Run anywhere (Docker)
A production image is published to the Forgejo container registry on every push to
main:
docker run --rm -p 8080:80 git.edufeed.org/edufeed/nocabs:latest
# open http://localhost:8080
The image is caddy:2-alpine serving the built static assets on port 80. For
TLS, put it behind any reverse proxy (Traefik, Caddy, nginx, Cloudflare)
terminating HTTPS upstream.
Runtime configuration
The entrypoint renders /srv/config.json from environment variables at
container start (no image rebuild required). Currently supported:
BLOCKED_NPUBS— comma-separated npubs and/or 64-char hex pubkeys whose content is hidden from this deployment. Events from these authors never enter the EventStore; direct URLs such as/vocab/<blocked-npub>[/<d>]return a 404. Unset or empty means no operator-level blocking.
Change the value and restart the container to take effect.
Deploy behind your own reverse proxy
Example Caddy block on the host:
nocabs.example.com {
reverse_proxy nocabs:80
}
Example nginx server block:
server {
server_name nocabs.example.com;
listen 443 ssl http2;
# ... tls config ...
location / {
proxy_pass http://nocabs:80;
}
}
Homelab deploy (edufeed)
The homelab Ansible repo ships a role and playbook mirroring the edufeed-app
pattern (docker-compose + Traefik labels on the proxy network):
ansible-playbook playbooks/deploy_nocabs.yml
Default domain: nocabs.edufeed.org. Change via nocabs_domain in
roles/nocabs/defaults/main.yml or the playbook vars.
CI / Release
.forgejo/workflows/build.ymlruns tests on every push/PR and builds+pushes the image onmainand onv*tags.- Required repo secret:
REGISTRY_TOKEN— a token withwrite:packageonedufeed/nocabscontainer registry.
To cut a release: bump package.json version, commit, tag v0.2.0 (or whatever),
push the tag. The image will be published with :latest, :0.2.0, and the commit
SHA.
Bumping lib versions
When a new nostr-vocab-core or nostr-vocab-skos-import is published to
the Forgejo npm registry, update the tarball URLs in package.json (the
version appears twice per URL), then pnpm install to refresh the lockfile
and commit both files.
One-time preflight (first bring-up)
- In the Forgejo UI for the
edufeedorg: generate an API token withwrite:packagescope. Add it as secretFORGEJO_NPM_TOKENon both lib repos (nostr-vocab-core,nostr-vocab-skos-import). - In
nostr-vocab-core: commit the publish workflow, tagv0.2.3, push the tag. The Forgejo Action publishesnostr-vocab-core@0.2.3. Mark the package public in the Forgejo packages UI. - In
nostr-vocab-skos-import: same — tagv0.2.4, push, mark public. - In
nocabs: runpnpm installlocally to regeneratepnpm-lock.yamlagainst the public tarball URLs, commit the updated lockfile, push. From this commit onwardpnpm install --frozen-lockfileworks in CI and on any clean machine — no credentials needed for install. - Create the
REGISTRY_TOKENsecret onnocabs(package:write on the container registry) and push tomain— the first image will build.
Tech
- SvelteKit +
@sveltejs/adapter-static - Tailwind CSS + daisyUI
- applesauce — Nostr client libraries
- Forgejo for source, CI (Forgejo Actions), npm + container registries