@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.
|
|
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": "^
|
|
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": "^
|
|
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",
|