@mulmoclaude/core 0.30.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -208,11 +208,42 @@ if (res.ok) {
208
208
  } // non-ok: leave the placeholder — 404 = not an image-field value / unresolvable
209
209
  ```
210
210
 
211
+ **Never fire one fetch per image in parallel** (`Promise.all` over every
212
+ record) — the host caps in-flight `/image` + `/query` requests at **4 per
213
+ collection** and answers the rest **429**, so a first paint of a dozen
214
+ images half-fails. Resolve through a small worker pool instead:
215
+
216
+ ```js
217
+ // Throttled resolver: N paths, at most 3 in flight, one retry on 429.
218
+ async function resolveImages(paths, onResolved, workers = 3) {
219
+ const queue = [...paths];
220
+ const work = async () => {
221
+ for (let path = queue.shift(); path !== undefined; path = queue.shift()) {
222
+ let res = await fetch(dataUrl + "/image?path=" + encodeURIComponent(path) + "&maxEdge=256", {
223
+ headers: { Authorization: "Bearer " + token },
224
+ });
225
+ if (res.status === 429) {
226
+ await new Promise((r) => setTimeout(r, 500));
227
+ res = await fetch(dataUrl + "/image?path=" + encodeURIComponent(path) + "&maxEdge=256", {
228
+ headers: { Authorization: "Bearer " + token },
229
+ });
230
+ }
231
+ if (res.ok) onResolved(path, (await res.json()).dataUrl);
232
+ }
233
+ };
234
+ await Promise.all(Array.from({ length: workers }, work));
235
+ }
236
+ ```
237
+
211
238
  - **Only current image-field values resolve.** The host checks `path`
212
239
  against the collection's records: it must be the CURRENT value of a
213
240
  schema `image`-type field — the token cannot read arbitrary workspace
214
241
  files. A stale or hand-built path answers 404.
215
242
  - `maxEdge` clamps to 64–1024 (default 512) — request the size you render.
243
+ - **429 = concurrency/rate limit, not a bad path.** Both a shared in-flight
244
+ cap (4 per collection, shared with `/query`) and a per-minute budget guard
245
+ this endpoint; a 429'd path resolves fine on retry — never mark it failed
246
+ without one.
216
247
  - Cache the resolved `data:` URLs per path in your view (a simple `Map`)
217
248
  and re-resolve inside your `onChange` callback only for paths you haven't
218
249
  seen — each request re-scans the records server-side.
@@ -463,3 +463,30 @@ Two ways the link can be minted, and the tool picks automatically:
463
463
  - **Drive shows nothing / "I can't find the user's file"** — not an error. The app holds the
464
464
  `drive.file` scope, so it can only ever see files IT created; the user's wider Drive is
465
465
  invisible by design. Say so plainly instead of implying an empty Drive.
466
+
467
+ ## Custom view — some images 429 / only a few thumbnails render
468
+
469
+ ### Symptoms
470
+
471
+ - A collection's custom view renders records fine, but only a handful of its
472
+ `image`-type field thumbnails appear; the rest stay placeholders.
473
+ - The view's error UI (or console) shows **HTTP 429** from
474
+ `<dataUrl>/image` with **"too many concurrent queries for this collection —
475
+ retry shortly"**.
476
+
477
+ ### Cause
478
+
479
+ The view resolves every image at once — typically a `Promise.all` over all
480
+ records firing one `GET <dataUrl>/image` each. The host caps in-flight
481
+ `/image` + `/query` requests at **4 per collection** (each request re-scans
482
+ the records for authorization), and answers the overflow 429. The paths are
483
+ valid; only the burst is.
484
+
485
+ ### Fix
486
+
487
+ Edit the view's image-resolution code (`views/*.html` under the collection's
488
+ skill directory) to throttle: a small worker pool (≤ 3 concurrent) draining a
489
+ queue of paths, with one short-delay retry on 429. See the throttled-resolver
490
+ example in `custom-view.md` ("Displaying images"). Do NOT widen the server
491
+ cap, switch to base64-embedding images in the HTML, or treat the 429'd paths
492
+ as bad values.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulmoclaude/core",
3
- "version": "0.30.0",
3
+ "version": "1.0.0",
4
4
  "description": "Shared server-side core for MulmoClaude and MulmoTerminal — the always-shipped-together subsystems consolidated behind subpath exports so the two hosts can't drift. Server-only except the browser-safe ./whisper/client, ./workspace-setup/slug, ./translation/client, ./remote-view and ./remote-host entries. All host specifics are injected.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -182,7 +182,7 @@
182
182
  "peerDependencies": {
183
183
  "@receptron/task-scheduler": "*",
184
184
  "firebase": "^12.0.0",
185
- "gui-chat-protocol": "^0.4.0"
185
+ "gui-chat-protocol": "^1.1.0"
186
186
  },
187
187
  "peerDependenciesMeta": {
188
188
  "firebase": {
@@ -192,7 +192,7 @@
192
192
  "devDependencies": {
193
193
  "@receptron/task-scheduler": "*",
194
194
  "@types/node": "^26.1.1",
195
- "gui-chat-protocol": "^0.4.0",
195
+ "gui-chat-protocol": "^1.1.0",
196
196
  "tsx": "^4.23.1",
197
197
  "typescript": "^6.0.3",
198
198
  "vite": "^8.1.5",