One platform.
Many modules.
No landlord.
A modularization plan for Plebeian — the market, the CMS, the meeting place and the makers' agent. Built on TypeScript packages and web components, distributed over Nostr and Blossom, forked and extended with agent assistance. This page is the top-level map; the technicalities are on purpose.
src/ todayValue stays where it's made.
Three apps on one sovereign stack, each self-hosted on your own box under your own keys: Plebeian — the market and the circular-economic CMS; Torii Quest — the meeting place in 3D; Continuum — the makers' agent with encrypted memory. GPL-3.0. Non-custodial. Nostr-native. Lightning and Cashu. Farmers' markets as the beachhead, community co-ops as the shape.
Plebeian
Publish your site, list goods, run auctions, chat with buyers. Visual site and store builder, NIP-99 marketplace, onchain · Lightning · Cashu · zaps, neighbourhood directory and pickup listings.
Torii Quest
Browser-native 3D, mobile-first PWA, portable characters, gateway hops between community worlds. A market square that never closes.
Continuum
Personal AI on your box or self-routed. Encrypted memory keyed to you. Scheduled agents, long-running tasks. It runs the site, greets buyers, and writes new corners of it while you sleep.
The decision ledger.
Every call below was made in this review, with the evidence behind it. Nothing here is a promise — each line is a rule the code will obey.
The base is packages, not applets
TypeScript packages plus a reusable web-component library, with one spec sheet per module. Napplets are a projection, not the foundation — their draft NIP is unmerged and their dependency model inlines every package into every artifact.
Modules are the abstraction
A module has a spec, a pure core, bindings and components. How it runs — in-process, sandboxed, agent-authored — is a property of the module, not a different architecture.
The trust boundary moves, it doesn't vanish
Shared CMS modules are declared dependencies reviewed by the operator, their agent, or a third-party service. Auth and wallet stay behind a strict boundary that module code cannot import.
Forking is the feature
The point of self-hosting is that you can fork the code, the theme, the CMS and the modules — with Continuum doing the rewriting. That makes spec sheets the agent's input format, not documentation.
From one app to a graph of modules.
Plebeian today is a working monolith: routes, components, stores, query and publish layers, all wired through a global signer and relay pool. It already contains the seams of the modules — they're just not separated. Modularization is the act of naming them, giving each a spec, and cutting the wires that shouldn't exist.
One process, shared everything
- 615 files, ~147,000 lines in
src/, plus ~15,000 of e2e - The cart store alone is 1,911 lines and mixes state, queries, persistence and shipping matching
- Validation schemas exist but are imported by nothing on the browse path — reads cast raw tags
- 19 ADRs, with two different ADR-0006, a gap at 0017, and one unnumbered
- Every surface can reach every other surface, so changes ripple
Modules with declared edges
- A module owns one job and publishes one interface
- Pure logic lives in a package with no ambient authority — no fetch, no storage, no signer
- Anything privileged arrives as an injected capability, never as an import
- State is either a published event (durable, portable) or scoped ephemeral cache
- Cross-module talk is a contract: an event, or an intent — not a shared store handle
What a module actually is
- Identity — one job, owner, maturity, spec version
- Spec sources — which NIP or spec it implements, and what we define ourselves
- Data contract — kinds, tags, validation, and the gating rule per surface
- Capability interface — what it needs from its host, each mapped or marked runtime-only
- State ownership — what survives a rebuild, and what must never be persisted
- Platform projections — in-process, sandboxed, mobile, self-hosted
- Composition — which components and artifacts it ships
- Permissions & consent — grants, rebuild impact, fund-safety rules
- Tests & conformance — unit, conformance, e2e, cross-adapter invariants
- Versioning — spec version vs package version vs build hash
// the interface a module declares — capability names, never imports interface ModuleHost { events: { query(f: Filter): Promise<Event[]>; publish(t: Template): Promise<Event> } identity: { current(): Pubkey } storage?: { get(k: string): string | null; set(k: string, v: string): void } payments?: { request(i: Invoice): Promise<PayReq>; status(id: string): Promise<Settled> } theme?: TokenMap } // no signer. no wallet URI. no network. those are host-owned, always.
Cart & browsing
Cart: kind 30078, d=plebeian-market-cart, version 1. The cart of record is a published event, never sandbox storage — so a rebuild can't eat a buyer's basket. Browsing: read-only, requests no write capability at all.
Checkout & orders
Checkout splits three ways: wizard and address form applet-side; invoice generation, paying, and NIP-17 wrapping host-side. Orders read and render anywhere; the transport is privileged.
CMS & the circle
The CMS is the headline feature, not a side quest: visual site builder, themes, published sites as content-addressed nsites. The profit-share circle becomes its own module — the split at payment, the statements, the pot.
Nodes that trade with nodes.
A citadel is not an island. Every node is a relay plus a gateway plus a blob store, and the links between them are protocols, not integrations. Identity travels, content travels, orders travel — the software stays where it is.
Nostr relays — Sniper
Each node runs its own relay; nodes follow each other. A seller's listing on Citadel B is readable from Citadel A because the buyer's client queries both. The relay is the node's public presence — and its git-over-nostr home for the forkable source.
Blossom — the shared blobs
Images, themes, world assets, published sites and module bundles are content-addressed and served by any node. Same hash, same bytes, anywhere. A fork doesn't need a re-upload; it needs the hash.
Torii gateways
The node's public face: domain, TLS, deploys, and the hops that carry a character between community worlds. Gateways are how two circles meet without merging their infrastructure.
Role mapping to confirm: Sniper = the relay/node layer, Torii = the gateway and suite host layer. If the split is different in your head, say so — the diagram is one line to redraw.
The same abstraction, two runtime kinds.
Your framing is the one I'd adopt: a shared CMS module is a declared dependency, reviewed before install — by the operator, their agent, or a third-party service. Napplets remain supported through that exact system, as a stricter runtime kind, not as the main system.
Auth and wallet never become importable
A module gets a facade, not the module graph. The signer, the wallet, the NWC connection and the private keys live behind a host-owned capability that requires an explicit, visible approval per action. That is what keeps "we never touch your money" true even with third-party code installed.
// module manifest — the declared surface is the reviewed surface { "name": "stall-gallery", "version": "1.2.0", "hash": "sha256:9f2c…", "reviewed_by": "node-operator", "requires": ["events.read", "theme.tokens", "media.read"], "never": ["signer", "wallet", "orders.decrypt"], "slots": ["storefront.after-hero", "cms.page.section"] }
Customize it like it's yours — because it is.
This is the part that makes the architecture pay off. Self-hosting is only freedom if changing the thing is cheap, and that's what Continuum is for: it reads a module's spec, rewrites the module, and proves the rewrite still holds.
Spec → change → verify → publish
- The spec is the agent's input format — not documentation for humans only
- Invariants and tests are the acceptance gate: the change must still pass
- The operator reviews the diff, or delegates that review to their own agent
- The fork is published content-addressed, so other nodes can install it by hash
What the agent may touch
- Yes: themes and tokens, page layouts, CMS sections, module config, component variants, copy, pricing rules
- Yes: new modules built against an existing spec, and new slots in a page
- Never: the signer, the wallet, NWC connections, private keys, escrow rules
- Never: silently widening a module's declared capabilities
- Always: a visible, per-action approval when money would move
What gets built, in order.
Fix the ground
Resolve the ADR numbering collision, freeze the spec-sheet template, and adopt the capability interface as the house rule.
Cart & browsing packages
Extract the pure cores, write the in-process bindings, and make the components take props instead of reading a global store. First real proof of the module shape.
The CMS and the theme contract
Visual site builder, token-based theming with a three-colour floor, and published sites as content-addressed nsites over Blossom.
The circle
Profit-share as its own module: the split at payment, the statements, the community pot, the co-op configuration.
Checkout & orders
Define the payments capability — receive, status, balance — and keep paying and wrapping host-side. Close the order-flow defects found in review.
Module manifest & review gate
Declared capabilities, content hashes, page slots, and the review path: operator, their agent, or a third-party service.
The strict runtime kind
Add the sandboxed projection as one more adapter, for the day a third party ships UI nobody wants to review. The interface is already there.
Honesty in the UI
Loading states that admit they're loading, validation on the read path, and named failures instead of silently shorter baskets.
Same freedom, less scaffolding.
The napplet model asks you to rebuild an operating system before you can ship a button. The module model asks you to write down what each piece needs — and then lets the pieces be shared, forked, reviewed and rewritten. Keep the capability interface, keep the content addressing, keep the strict boundary around keys and money. Drop the second runtime until someone actually needs it.