dev / docs / NEW-APP-PROMPT.md
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# PROMPT: build & publish a serverless p2p app on ardegazu.ro

You are an agentic developer with access to the **ird MCP tools** (IPFS pins,
IPNS names, gateway domains, DNS) and the workspace's `ardz` CLI. Build a
browser app exactly in the house style described below, then deploy it and
publish its repo. Everything you need is public; follow the flow in order.
Verify each stage before moving on.

This is the app-flavored sibling of `game1/docs/NEW-GAME-PROMPT.md` (games)
and `dev/templates/bot/docs/NEW-BOT-PROMPT.md` (bots). Games have their own
prompt; if the thing you're building is a game, use that one.

---

## 1. APP SPEC (filled in by the requester)

```
APP NAME:     <e.g. "ledger">
SUBDOMAIN:    <name>.ardegazu.ro       (app at <name>., source at git.ardegazu.ro/<name>)
VARIANT:      live | rooms             (see the decision rule below)
WHAT IT DOES: <the app in two sentences>
DATA MODEL:   <rooms: the LogOp union — what gets appended, who may append;
               live: the wire-frame union — what peers broadcast while present>
UI/AESTHETIC: <palette, mood, layout>
EXTRAS:       <anything else: media, exports, spectators, ...>
```

**Variant decision rule.** Ask one question: must state survive both tabs
closing, or reach members who were never online together? If yes → `rooms`
(durable encrypted log, mailbox offline delivery, multi-room lobby, versioned
PWA). If everything can evaporate when the last tab closes → `live` (the
games' stack minus the game: presence + sealed broadcast only, far less
machinery). When in doubt, start `live` — you can rebuild on `rooms` later;
the reverse never happens.

Everything below is fixed house style — do not ask the requester about it.

## 2. House architecture (non-negotiable)

Common to both variants:

- **Static client only. No servers, no accounts, no database.**
  **ClojureScript on shadow-cljs** — both variants, no exceptions: the suite
  publishes only Clojure/ClojureScript, and no TypeScript canon survives
  anywhere to vendor from. `dev/docs/CLJS.md` is the build canon; read it
  before touching `deps.edn`, `shadow-cljs.edn` or the vendored
  `client/scripts/`. The production build is plain static files, served both
  at the apex domain and at `/ipfs/<cid>/` paths — hence
  `:asset-path "./assets"` and relative URLs in the hand-written
  `public/index.html`, never absolute asset paths. The app name must be
  `[a-z0-9]` only: it becomes the CLJS namespace root.
- **Relay & TURN are shared and already running** at `signal.ardegazu.ro`
  (descriptor at `/.well-known/ap2p`, TURN creds at `/turn-credentials`,
  compiled-in relay fallback in the config). **Do NOT create a new p2p app**
  on the relay. The dev server MUST run on port 4173 or 5173 — that is what
  `:dev-http` in `shadow-cljs.edn` binds, and the relay's origin allowlist has
  no other entries. Browser verification serialises across the whole suite for
  the same reason (CLJS.md).
- **Room = URL fragment.** A `#<base64url secret>` that never leaves the
  browser; membership is proved cryptographically (sealed hello), not by
  discovery. The invite link IS the room key.
- **`APP-SALT` namespaces everything** — salts, storage, protocol ids. It is
  `<name>.ardegazu.ro/v1` (rooms) or `/v2` (live, matching the games' current
  hello version). Never reuse another app's salt.
- **Suite identity (id-kit)** via the hidden iframe bridge at
  `https://ardegazu.ro/id/`; **social layer (social-kit)** riding your
  node's pubsub so friends see presence and get invites. Both are sha-pinned
  git deps (`git+https://git.ardegazu.ro/<kit>.git#<sha>`) — bump ONLY with
  the explicit `npm install <pkg>@git+…#<sha>` form (npm silently reuses
  stale lockfile resolutions otherwise).
- **i18n en/ro/hu** — `i18n/runtime.cljs` is byte-identical across the CLJS
  apps modulo the namespace rename (game1 canonical; the scaffolder vendors
  it). English baked into `public/index.html` behind `data-i18n` where the
  markup is static; all code-side text through `t`/`tn` (plurals NEVER via
  string concatenation — Intl.PluralRules, so Romanian's few/other split and
  Hungarian's uninflected numerals fall out of the catalogs). Canon:
  `game1/docs/I18N.md`.
- **PWA + iOS home-screen quality bar**: `viewport-fit=cover` +
  `env(safe-area-inset-*)` padding on fixed elements, `black-translucent`
  status bar, no pinch/double-tap zoom or rubber-banding.

Variant **live** (game stack, generalized) — canon repo `game1` (neon-grid):

- `client/src/<name>/net/`, `id/boot.cljs` and `i18n/runtime.cljs` are
  vendored from game1 at scaffold time and are **byte-identical to the canon
  modulo the namespace rename** (`game1.` → `<name>.`, `src/game1/` →
  `src/<name>/`) **except one line** — the protocol prefix in `net/peers.cljs`
  (the scaffolder already set yours). Never edit anything else in there; fixes
  happen in game1 and replicate. `client/scripts/` and the package files are
  vendored the same way.
- Every frame E2E-encrypted by noise; define your wire protocol as `mk-*`
  builders plus a guard in `src/<name>/game/frames.cljs` (quoted string keys —
  never renamed by `:advanced`; build anything wider than eight fields with
  sequential `unchecked-set`) and validate on receipt with the JS forms
  (`js/Array.isArray`, `=== null`). Keep frames well under 64 KiB; design for a
  full mesh (practical ceiling ~12 simultaneous peers).
- If your app needs an authority (one peer refereeing shared state), copy
  neon-grid's proven host model (`game1/client/src/game1/game/game.cljs`: host
  claim, lowest-id takeover, seat-indexed rounds, snapshots for late joiners)
  rather than reinventing.
- Build: `:optimizations :advanced` + `:infer-externs :auto` (the surface is
  small and typed enough to audit). PWA: workbox `generateSW` from
  `scripts/build.mjs` with `skipWaiting: true` — no version.json, not
  `versioned` in apps.tsv.

Variant **rooms** — the shared core is a KIT (`ardegazu-rooms-kit`); `chat`
(sueta) is the canon for the app-local remainder:

- **The durable core is not in your repo, and you do not vendor it.** Net, the
  encrypted OrbitDB log, the hardened access controller, the OrbitDB identity
  provider, the crypto/encryption layers, TURN, the descriptor fetch and the
  `js` helpers (`truthy?`, `nn`, `ordered`) are `ardegazu-rooms-kit`'s own
  ClojureScript sources, and your app **compiles them off its classpath**:
  `client/package.json` sha-pins the kit, `client/deps.edn` puts
  `node_modules/ardegazu-rooms-kit/src` on `:paths`, and you require them under
  their real names — `ardegazu.rooms.js`, `ardegazu.rooms.lib.*`. No rename, no
  copy, one place to fix. The npm sha pin stays the ONE cross-repo dependency
  mechanism (`dev/docs/CLJS.md`); the classpath entry only points at what the
  pin already installed. *Never* edit anything under `node_modules/` — fix it
  in rooms-kit, `ardz kit-release rooms-kit`, then bump your pin.
- **What IS yours, in `client/src/<name>/`:** `lib/protocol.cljs` (your `LogOp`
  union — the scaffold's demo rides chat's `{t:"chat"}` op, and replacing it is
  your step 1); `lib/mailbox.cljs` and `lib/selftest.cljs` (scaffold-time copies
  from chat, which the kit has no counterpart for — board's mailbox already
  diverges, at a `canon.tsv`-tracked delta of 15); and `stores.cljs`, the
  browser IndexedDB blockstore/datastore opener that `lib.log`'s `open-helia`
  deliberately does not name, so the same kit file serves a tab and a Node bot.
  `lib/media.cljs` — chat's call plumbing — is **not** scaffolded; copy it from
  chat the day you add calls.
- **No protocol-prefix delta on this line.** The libp2p protocol ids
  (`/sueta/2/msg/1.0`), the OrbitDB provider type `"sueta"` and the crypto
  domain tags stay as deployed — that is what makes your app wire-compatible
  with the rooms core — and **`APP-SALT` is what makes your rooms unreachable
  from chat's**.
- **`onEntry` is wired BEFORE replication starts** in `room.cljs` — this
  ordering is load-bearing (`rlog/open` starts sync and marks hashes seen, so a
  handler attached afterwards loses those entries permanently). Keep the
  scaffold's structure, including the `pending` buffer.
- Storage is namespaced through `NS`/`ns-key`/`ns-db` from `config.cljs` —
  never raw localStorage keys or db names.
- Identity: rooms apps **never auto-adopt** on identity conflict (logs and
  grants are bound to the old key), so `room.cljs` uses `IdBridge` directly and
  deliberately does NOT vendor game1's `id/boot.cljs`. Keep that; if you
  surface the conflict, make it an explicit two-tap choice (chat's identity
  sheet is the worked example).
- Build: the scaffold ships `:optimizations :simple`, the safe default and
  where board still is. chat has since moved this same stack to `:advanced`
  (main.js 236 KB gz → 91 KB gz) on the observation that Closure never renames a
  string-keyed property access, so the whole untyped-ESM surface is
  renaming-immune; what breaks is our own prototype installs, and it breaks at
  room boot, not at build time. If you follow chat, read its `shadow-cljs.edn`
  header and `externs.js` first.
  Three shadow modules keep the two dynamic boundaries real
  (`:main` lobby / `:room` p2p stack / `:social` full kit) and
  `scripts/build.mjs` gates all three, plus the `events` resolution, the
  relative-URL rule and the SW semantics.
- **Versioning discipline**: the app is `versioned=y` in apps.tsv.
  `ardz release <name>` runs `npm run release`, which bumps
  `client/version.json` and builds; **commit the bump after every release**
  or installed PWAs never see the update banner. Never flip the worker to
  auto-update — the prompt+banner combination is the suite's update contract
  (workbox `skipWaiting: false` + `clientsClaim: true`, and `globPatterns`
  must omit `.json` so `version.json` is never precached).

## 3. Project layout

`ardz new-app <name> <host> <live|rooms>` scaffolds all of this, appends the
apps.tsv row, and sets the anonymous git identity. Files marked VENDORED are
copied from the canon repos at scaffold time — do not edit them, fix the canon
and replicate. Files marked KIT are not in your repo at all. (`live-cljs` and
`rooms-cljs` are still accepted as aliases; the `-cljs` suffix is redundant now
that both stacks are ClojureScript.)

```
<name>/
  README.md  LICENSE(MIT)  .gitignore     # node_modules/ client/dist/ deploy/.site/
                                          # + .shadow-cljs/ .cpcache/ *.js.map (+ assistant dirs)
  <agentcfg>/launch.json                  # dev server config, port 5173 (scaffolded to the live dotfile)
  site/index.html                         # landing page for the published repo (§6)
  deploy/publish-repo.sh                  # anonymity-gated mirror builder (new-style
                                          # gate: reads ~/.config/cod-sursa/idpat)
  client/
    package.json  package-lock.json       # VENDORED (canon deps, renamed to yours)
    deps.edn  shadow-cljs.edn             # YOURS (exact-pinned compiler pair; see CLJS.md)
                                          # rooms: deps.edn's :paths also carries
                                          # node_modules/ardegazu-rooms-kit/src
    scripts/                              # VENDORED (dev.mjs, build.mjs + the build gates)
    test/source-hygiene.test.mjs          # TEMPLATE'S OWN app-agnostic copy of chat's
                                          # CLJS lint (chat's carries chat-only façade
                                          # checks a fresh app cannot pass)
    test/README.md  .cljfmt.edn           # what `npm test` runs, and what to add
    public/index.html  public/style.css   # YOURS — hand-written shell, RELATIVE urls only
    public/manifest.webmanifest
    public/icons/                         # VENDORED placeholder art — REPLACE IT
    src/<name>/i18n/runtime.cljs          # VENDORED (game1 canon, byte-identical)
    src/<name>/i18n/{en,ro,hu}.cljs  src/<name>/i18n.cljs   # YOURS
    src/<name>/config.cljs  src/<name>/main.cljs            # YOURS
    # live variant:
    src/<name>/net/                       # VENDORED (game1; peers.cljs prefix pre-set)
    src/<name>/id/boot.cljs               # VENDORED (game1, verbatim)
    src/<name>/social.cljs                # YOURS — the lazy social-kit module
    src/<name>/game/{app,frames,ui}.cljs  # YOURS — the demo compiles; gut it
    # rooms variant:
    version.json                          # {"version":1} — the release bump target
    ardegazu.rooms.js / .lib.*            # KIT — ardegazu-rooms-kit's sources, compiled
                                          # off the deps.edn classpath. Not in this repo.
    src/<name>/lib/protocol.cljs          # YOURS — the LogOp union, your only wire surface
    src/<name>/lib/{mailbox,selftest}.cljs  # VENDORED (chat; the kit has no counterpart)
    src/<name>/stores.cljs                # VENDORED (chat; the browser IDB store opener)
    src/js/                               # VENDORED (chat; the @libp2p/config shim)
    src/<name>/room.cljs  src/<name>/social.cljs  # YOURS (scaffolded skeleton)
    src/<name>/app/{rooms,store,ui}.cljs  # YOURS — the demo compiles; gut it
```

## 4. Anonymity — already set up; verify BEFORE the first commit

The scaffolder ran `git init` and set the repo-local identity
`<name> <name@noreply.local>`. Verify it (`git config user.name`), and keep
every commit anonymous: no real names, emails, usernames, machine hostnames
or `$HOME` paths anywhere in tracked files. The publish gate (§6) decompresses
every mirror object and aborts on identity strings — it reads private
patterns from `~/.config/cod-sursa/idpat`. Prefer targeted `git add <files>`;
never `git add -A`.

## 5. Build, test, deploy

1. Develop with two browser tabs on the same `#room` URL: `ardz dev <sub>`
   (port 5173; `<sub>` is the first label of your host — the apps.tsv key the
   scaffolder printed). Rooms variant: also run `ardz relay` (:9090) for the
   local relay + mailbox pair, and test offline delivery by closing one tab,
   posting in the other, reopening.
2. `npm test` in `client/` — out of the scaffold this is `cljfmt check src`,
   a compile of the `:app` build, plus `test/source-hygiene.test.mjs`, the only
   gates a day-zero app can run honestly, and all of them pass on a fresh
   scaffold (the demo UI builds DOM nodes via `app/dom.cljs`, so the lint's
   raw-HTML allowlist ships empty). It deliberately does NOT run
   a `:testlib` build, because you have no black-box tests yet;
   `client/test/README.md` says what to add, in what order, and how to put the
   `:testlib` link back into the script. Add golden vectors before you have two
   appenders, not after.
3. `ardz build <sub>` (shadow-cljs release + workbox → `client/dist/`).
4. Create the site once, when ready: `ipns_create(hostname="<host>",
   serve=true)` — IPNS name + gateway domain + DNS in one call.
5. `ardz release <sub>` — builds (rooms: bumps version.json first), adds to
   IPFS, re-pins as `site:<host>`, publishes IPNS, unpins the previous site
   pin only. **Rooms variant: commit the version.json bump it printed.**
6. Verify live: poll `https://<host>/` until 200, then exercise the app in
   two tabs ON THE LIVE URL.

## 6. Publish the repo

Same "this page is also the git repo" pattern as every app:
`https://git.ardegazu.ro/<sub>.git` (clone) and `https://git.ardegazu.ro/<sub>/`
(landing page).

1. Rewrite `site/index.html` in the app's aesthetic: what it is, how it
   works, the clone command, a link to the live app, honest guarantees.
2. `deploy/publish-repo.sh` is pre-placed with `APP`/`IDENT` set and the
   new-style anonymity gate. It needs `~/.config/cod-sursa/idpat` to exist.
   It also generates the in-browser source browser (`gen-browse.py` →
   `/browse/`), linked from the landing page.
3. Register in `../git/assemble.sh` (a `repo_for` case + the default list)
   and add a card to `../git/index.html`, then `ardz publish-src <sub>`.
4. Verify: `git clone https://git.ardegazu.ro/<sub>.git` into a temp dir;
   log 100% anonymous, tree complete.

## 7. Registration follow-ups (manual, all of them)

- `home/client/catalog.json`: append an apps[] entry (schema v1:
  id/name/host/kind:"app"/tagline/description/tags/accent/glyph/status,
  plus the additive `i18n: {ro:{…}, hu:{…}}` map — never bump `v` for
  additive fields). Bespoke glyph art goes in `home/client/src/home/glyphs.cljs`
  keyed by the `glyph` field. Then release home (versioned — commit its
  version bump).
- `social-kit`'s `SUITE_APPS` list.
- The git-site card (`../git/index.html`) if not done in §6.

## 8. Definition of done

Common:
- [ ] Two-tab session end-to-end on `https://<host>` (not just localhost)
- [ ] All three languages render; Romanian plurals right at n = 1 / 2 / 20
- [ ] `npm run build` twice from a clean clone produces an identical `dist`
- [ ] `npm test` green (and it checks something real: at minimum the source
      lint plus your own golden vectors — see `client/test/README.md`)
- [ ] Mobile viewport: safe areas respected, PWA installable
- [ ] `git clone https://git.ardegazu.ro/<sub>.git` succeeds; log is 100% anonymous
- [ ] README documents dev/build/release; no `node_modules`/`dist`/`.site` tracked
- [ ] Hub card live (catalog.json + glyph), SUITE_APPS updated
- [ ] Report the two URLs and total pinned size

Live variant:
- [ ] Late joiner sees the current presence roster immediately
- [ ] If the app has an authority: host tab killed → survivor takes over

Rooms variant:
- [ ] Two devices that were NEVER online together converge via the mailbox
- [ ] Offline reload replays the local log (airplane-mode test)
- [ ] Lobby room list syncs across two same-identity devices; forget/tombstone propagates
- [ ] After a release bump, an installed PWA shows the update banner and
      reloads into the new version
- [ ] Nothing under `node_modules/ardegazu-rooms-kit/` was edited; a fix to the
      shared core landed in rooms-kit and arrived here as a sha bump

## 9. Cost summary to include in your report

No tunnels. IPFS storage hourly per MB (typically ~1 MB), TURN traffic per MB
only when peers can't connect directly; the mailbox rides the existing managed
relay. The shared relay app (`signal.ardegazu.ro`) already exists — do NOT
create a new p2p app.

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/dev.git