chat / client / shadow-cljs.edn
  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
;; shadow-cljs builds for the sueta (chat) client — the first CLJS app on the
;; ROOMS stack (libp2p v2 + Helia + OrbitDB).
;;
;; The shared rooms core — ardegazu.rooms.js and ardegazu.rooms.lib.* — is NOT
;; in this repo any more: it is ardegazu-rooms-kit's own sources, reached over
;; the classpath (client/deps.edn adds node_modules/ardegazu-rooms-kit/src).
;; What is still under src/sueta/lib is what the kit has no counterpart for:
;; mailbox, media and selftest.
;;
;; :app — the browser build. Like game1 (and unlike the kits) an APP bundles its
;; npm deps: the libp2p/helia/@orbitdb family is plain ESM in node_modules,
;; which shadow's default :browser bundling consumes. OUR OWN kits are not in
;; that list any more — rooms-kit, id-kit and social-kit are classpath SOURCE
;; (deps.edn), compiled into this build like the app's own namespaces, so no
;; kit dist file reaches the bundler at all.
;;
;; :optimizations :advanced — reversing the earlier :simple call, which rested
;; on an argument that measurement inverted. That argument was: almost every
;; line below sueta.app is interop with untyped ESM (@orbitdb/core ships no type
;; declarations at all, helia/libp2p types never reach the CLJS side), so
;; :advanced would rest entirely on externs inference over `unchecked-get`-
;; reached objects — the one shape CLJS.md calls impossible to infer.
;;
;; The premise is backwards. Closure NEVER renames a string-keyed property
;; access: `(unchecked-get o "ts")` is `o["ts"]`, the standard escape hatch. The
;; `unchecked-get`-reached objects are precisely the ones needing NO inference —
;; and this tree reaches nearly everything that way (~1100 unchecked-get, ~470
;; unchecked-set, with ardegazu.rooms.js/ordered building every wire object by
;; sequential unchecked-set, because rooms-kit's own hygiene lint — and this
;; repo's, over what is left — forbids `#js {}` outright). The
;; entire foreign-library surface — libp2p, helia, @orbitdb/core, both kit
;; dists — is therefore renaming-IMMUNE. externs.js contains not one library
;; name. The ugliness that makes this code tedious to read is the same property
;; that makes it rename-safe.
;;
;; What DOES break is ours: the kit's lib/net and lib/log, and this repo's
;; lib/media, install prototype
;; methods string-keyed (`(unchecked-set proto "handleFrames" …)`) but CALL them
;; via dot-access, so :advanced renamed the call and not the definition. The
;; first :advanced build died at room boot with `a.net.gi is not a function`.
;; externs.js pins those 21 names — see its header. That is the whole cost:
;; no source change, no idiomatic rewrite.
;;
;; Measured, cold, prod config: main.js 236 KB gz -> 90 KB gz (first paint,
;; -62%); total 861 -> 707 KB gz. room.js barely moves (607 -> 600) because it
;; is overwhelmingly npm code, which shadow compiles separately and does not
;; rename — the win is the un-DCE'd cljs.core/Closure library in main.js, whose
;; `cljs.core` reference count drops from ~17k to 4. Two cold builds are
;; byte-identical, so the release/IPFS flow keeps its determinism.
;;
;; NOTE :optimizations must live INSIDE :compiler-options — a top-level one is
;; silently ignored and the target default is used instead.
;;
;; Three modules keep the TS build's two dynamic boundaries REAL:
;;   :main   the lobby + i18n + config + room crypto + the lean social chips
;;           (`ardegazu.social.join`, which reaches only `ardegazu.social.text`)
;;           — first paint
;;   :room   the whole p2p stack (libp2p v2, Helia, @orbitdb/core, the mailbox,
;;           calls, the UI), loaded via shadow.lazy only when entering a room —
;;           this mirrors main.ts's `await import("./room")`
;;   :social GameLb-less `ardegazu.social.app-boot/attach-social`, the ONLY
;;           module referencing the kit's engine — mirrors room.ts's
;;           `void import("ardegazu-social-kit")`. With the kit on the
;;           classpath the boundary is the require graph: shadow puts each
;;           `ardegazu.social.*` namespace in the deepest module that needs it,
;;           and test/module-split.test.mjs pins the :main closure exactly.
;; scripts/build.mjs gates all three boundaries.
;;
;; :asset-path is RELATIVE — one build must serve at https://chat.ardegazu.ro/
;; and /ipfs/<cid>/ alike.
;;
;; :js-options :resolve maps the bare `events` specifier onto the npm package's
;; browser entry. @orbitdb/core (+ lru, abstract-level) import `node:events`,
;; and the browser needs the real EventEmitter, not an empty shim — this is the
;; shadow equivalent of the TS build's `resolve.alias { events: "events/events.js" }`.
;; scripts/build.mjs greps the emitted bundle for the implementation, because a
;; wrong resolution here fails at RUNTIME inside OrbitDB, not at build time.
;;
;; Expect ZERO :infer-warning lines, and treat any as a build failure.
;;
;; This comment used to say "expect 27", which was true only before externs.js
;; existed and made the gate read BACKWARDS: pinning a name is what stops
;; shadow warning about it, so the 27 went to 0 the moment the file landed. A
;; reader told to expect 27 would see a genuinely new unpinned name — count 1 —
;; as FEWER than expected and conclude nothing was wrong. Measured 2026-08-28:
;; externs.js in place → 0 warnings; externs.js emptied → exactly 27, the same
;; 27 this line was named after.
;;
;; They come from the three hand-written app namespaces: every one is a
;; `(.method ^js (unchecked-get self "_field") …)` call, where the hint is lost
;; because it sits on a macro form (dev/docs/CLJS.md). Under :advanced they are
;; NOT inert — the names they name are exactly what externs.js has to pin, and
;; an unpinned one is a runtime TypeError at room boot, not a build failure. So
;; a warning appearing here means a missing externs entry (or a definition that
;; should move to dot-access so inference can do its job). Do not "fix" them by
;; moving the hints onto the calls: that suppresses inference silently.
;;
;; The warning list is a FLOOR, not a proof. shadow only warns for names absent
;; from its DEFAULT externs, so `.forEach`/`.get`/`.then` look clean while
;; `.getConnections` warns. Nothing here is safe because it did not warn; it is
;; safe because the browser e2e suite (e2e/mesh, smoke-v2, migrate,
;; storage-migrate) runs green against the :advanced dist.
;;
;; :parallel-build false — many namespaces plus an optimized build is exactly
;; the combination CLJS.md says the gensym normalizer cannot repair (parallel
;; namespace compilation varies gensym numbering, which changes what Closure's
;; variable coalescing decides, which is a STRUCTURAL difference). Serial
;; compilation is the only fix; only cold builds pay the time.
;;
;; :testlib — the app + lib namespaces compiled to node-consumable ESM for the
;; black-box tests in test/*.test.mjs (golden vectors from the TS canon).
{:deps {:aliases [:dev]}
 ;; BOTH dev ports serve public/ + dist-dev/ — the WATCH's own output tree,
 ;; never dist/. dist/ belongs to `ardz build` alone: it holds a RELEASE build
 ;; (goog.DEBUG=false → the PRODUCTION relay, and a service worker), and the
 ;; old `4173 {:roots ["dist"]}` arm served exactly that to a dev tab whenever
 ;; the watcher had not overwritten it yet (or the browser had cached it) —
 ;; two tabs of one dev session then sat on DIFFERENT relays and never met.
 ;; scripts/dev.mjs redirects the watch to dist-dev/assets via --config-merge;
 ;; smoke a release build with a standalone static server over dist/, never
 ;; through these ports.
 ;; :use-index-files true — without it shadow's undertow file handler gets NO
 ;; welcome files (it only sets them from this key), so a directory request
 ;; ("/", "/id/") falls through to the push-state handler, which 404s any
 ;; request whose Accept header lacks text/html (curl, fetch). With it, "/"
 ;; serves index.html from the first root that has one.
 :dev-http {5173 {:roots ["public" "dist-dev"] :push-state/index "index.html" :use-index-files true}
            4173 {:roots ["public" "dist-dev"] :push-state/index "index.html" :use-index-files true}}
 :builds
 {:app
  {:target :browser
   :output-dir "dist/assets"
   :asset-path "./assets"
   :compiler-options {:optimizations :advanced
                      :source-map false
                      :parallel-build false
                      :externs ["externs.js"]}
   :js-options {:resolve {"events" {:target :npm :require "events/events.js"}
                          ;; an npm package this app must never pull in — see the
                          ;; shim's own comment
                          "@libp2p/config" {:target :file
                                            :file "src/js/libp2p-config-shim.mjs"}}}
   :module-loader true
   :modules
   {:main {:init-fn sueta.main/init}
    :room {:entries [sueta.room]
           :depends-on #{:main}}
    :social {:entries [sueta.social]
             :depends-on #{:room}}}}
  :testlib
  {:target :esm
   :output-dir "test-dist"
   :compiler-options {:optimizations :simple
                      :source-map false
                      :parallel-build false}
   :js-options {:js-provider :import}
   :modules
   {:testlib
    {:entries [sueta.testlib]
     :exports {APP_SALT sueta.config/APP-SALT
               NS sueta.config/NS
               nsKey sueta.config/ns-key
               nsDb sueta.config/ns-db

               RoomCrypto ardegazu.rooms.lib.crypto/RoomCrypto
               Sealer ardegazu.rooms.lib.crypto/Sealer
               AtRestCipher ardegazu.rooms.lib.crypto/AtRestCipher
               toB64 ardegazu.rooms.lib.crypto/to-b64
               fromB64 ardegazu.rooms.lib.crypto/from-b64
               toB64url ardegazu.rooms.lib.crypto/to-b64url
               fromB64url ardegazu.rooms.lib.crypto/from-b64url
               randomBytes ardegazu.rooms.lib.crypto/random-bytes
               newPeerId ardegazu.rooms.lib.crypto/new-peer-id
               newRoomSecret ardegazu.rooms.lib.crypto/new-room-secret

               makeLogEncryption ardegazu.rooms.lib.encryption/make-log-encryption
               signerMatchesIdentity ardegazu.rooms.lib.access/signer-matches-identity
               HardenedIPFSAccessController ardegazu.rooms.lib.access/HardenedIPFSAccessController
               SuetaIdentityProvider ardegazu.rooms.lib.orbit-identity/SuetaIdentityProvider
               registerSuetaProvider ardegazu.rooms.lib.orbit-identity/register-sueta-provider
               resolveRelayConfig ardegazu.rooms.lib.descriptor/resolve-relay-config
               IceConfig ardegazu.rooms.lib.turn/IceConfig
               ;; the browser store-NAMES entry point, exported under the name
               ;; v1.0.0 published — exactly as rooms-kit exports its Node
               ;; open-fs-helia as `openHelia` (lib/log's own open-helia takes
               ;; already-open stores and is shared with the kit byte-for-byte)
               openHelia sueta.stores/open-idb-helia
               RoomLog ardegazu.rooms.lib.log/RoomLog
               Net ardegazu.rooms.lib.net/Net
               newSessionKey ardegazu.rooms.lib.net/new-session-key
               Media sueta.lib.media/Media
               MailboxAuth sueta.lib.mailbox/MailboxAuth
               MailboxClient sueta.lib.mailbox/MailboxClient
               MailboxSync sueta.lib.mailbox/MailboxSync
               runSelfTest sueta.lib.selftest/run-self-test

               loadHistory sueta.app.history/load-history
               trustObserve sueta.app.trust/observe
               trustSetVerified sueta.app.trust/set-verified
               trustIsVerified sueta.app.trust/verified?
               loadRooms sueta.app.rooms/load-rooms
               touchRoom sueta.app.rooms/touch-room
               renameRoom sueta.app.rooms/rename-room
               forgetRoom sueta.app.rooms/forget-room
               buildRoomSync sueta.app.rooms/build-room-sync
               mergeRooms sueta.app.rooms/merge-rooms
               setOnRoomsChanged sueta.app.rooms/set-on-rooms-changed
               migrateLegacyHistory sueta.app.migrate/migrate-legacy-history
               migrateStorageNS sueta.app.migrate-storage/migrate-storage-ns
               ChatStore sueta.app.chat/ChatStore
               CallManager sueta.app.call/CallManager

               catalogKeys sueta.testlib/catalog-keys
               catalogMsg sueta.testlib/catalog-msg
               catalogRaw sueta.testlib/catalog-raw
               t sueta.testlib/t
               tn sueta.testlib/tn}}}}

  ;; :viewlib — the VIEW layer's own node-consumable build, deliberately NOT
  ;; part of :testlib. Two structural reasons:
  ;;
  ;;  * `:testlib :exports` is a FROZEN contract (test/vectors/facade.json, and
  ;;    test/helpers/facade.mjs explains why). A UI vector must not be able to
  ;;    move it, and folding the view in would make every future screen of
  ;;    Phase 5 a façade change — which is exactly the signal that freeze is
  ;;    there to give, spent on the wrong thing.
  ;;  * this build compiles ONLY the view tree: app/view, app/lobby, app/msgs,
  ;;    app/roster, app/tiles, i18n, config, wire, app/rooms and replicant. If a
  ;;    screen ever reaches into the p2p stack, THIS build is where it shows —
  ;;    :testlib pulls in everything by design and could never tell you.
  ;;    app/msgs, app/roster and app/tiles each earn their place here by
  ;;    requiring NOTHING below the view: app/chat and CallManager, whose
  ;;    objects they project, are app/ui's business and never theirs.
  ;;
  ;; helpers/facade.mjs reads `:exports` out of the :testlib build BY NAME, so
  ;; the second map below cannot shadow the frozen one; source-hygiene proves
  ;; that with a probe rather than trusting document order.
  :viewlib
  {:target :esm
   :output-dir "view-dist"
   :compiler-options {:optimizations :simple
                      :source-map false
                      :parallel-build false}
   :js-options {:js-provider :import}
   :modules
   {:viewlib
    {:entries [sueta.viewlib]
     :exports {lobbyInit sueta.viewlib/lobby-init
               lobbyStep sueta.viewlib/lobby-step
               lobbyView sueta.viewlib/lobby-view
               lobbyHtml sueta.viewlib/lobby-html

               msgsProject sueta.viewlib/msgs-project
               msgsView sueta.viewlib/msgs-view
               msgsThreadView sueta.viewlib/msgs-thread-view
               msgsComposer sueta.viewlib/msgs-composer
               msgsHtml sueta.viewlib/msgs-html
               msgsThreadHtml sueta.viewlib/msgs-thread-html
               msgsComposerHtml sueta.viewlib/msgs-composer-html
               msgsTextRuns sueta.viewlib/msgs-text-runs
               msgsColorFor sueta.viewlib/msgs-color-for

               tilesProject sueta.viewlib/tiles-project
               tilesView sueta.viewlib/tiles-view
               tilesHtml sueta.viewlib/tiles-html
               tilesRender sueta.viewlib/tiles-render
               tilesHooks sueta.viewlib/tiles-hooks

               rosterProject sueta.viewlib/roster-project
               rosterView sueta.viewlib/roster-view
               rosterHtml sueta.viewlib/roster-html
               rosterPeersLabel sueta.viewlib/roster-peers-label

               chromeProject sueta.viewlib/chrome-project
               chromeView sueta.viewlib/chrome-view
               chromeHtml sueta.viewlib/chrome-html
               chromeRender sueta.viewlib/chrome-render

               sheetsAskName sueta.viewlib/sheets-ask-name
               sheetsPeer sueta.viewlib/sheets-peer
               sheetsIdentity sueta.viewlib/sheets-identity
               sheetsCopy sueta.viewlib/sheets-copy
               sheetsStep sueta.viewlib/sheets-step
               sheetsView sueta.viewlib/sheets-view
               sheetsHtml sueta.viewlib/sheets-html
               sheetsRender sueta.viewlib/sheets-render
               sheetsHooks sueta.viewlib/sheets-hooks

               handleHook sueta.viewlib/handle-hook
               handleRevision sueta.viewlib/handle-revision
               putHandle sueta.viewlib/put-handle
               getHandle sueta.viewlib/get-handle
               forgetHandle sueta.viewlib/forget-handle}}}}}}

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