@justai/cuts 0.126.0 → 0.128.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justai/cuts",
3
- "version": "0.126.0",
3
+ "version": "0.128.0",
4
4
  "description": "A persona's named parts — the page shell that holds them, and the cuts themselves.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -41,7 +41,7 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@justai/core": "^0.33.0",
44
+ "@justai/core": "^0.34.0",
45
45
  "@justai/ui": "^0.21.1",
46
46
  "@justai/atrium": "^0.1.9"
47
47
  }
@@ -255,7 +255,12 @@ const chrome = face === "page";
255
255
 
256
256
  .pp .below { position: relative; }
257
257
  /* the sticky row means a section reached by its id arrives UNDER it unless it says otherwise */
258
- .pp #profile, .pp #kernel { scroll-margin-top: 82px; }
258
+ /* The landing offset is `--shell-top` and only that — measured by the shell, published on :root,
259
+ enforced by personaScroll. This line carried a literal 82px through THREE fleet-wide sweeps of
260
+ exactly that constant (base.css, Posts.astro, eight persona repos) and surfaced only when the
261
+ children's console started warning about it: a scoped rule inside a cut is where a stale number
262
+ hides longest, because nobody greps a component for a layout constant. */
263
+ .pp #profile, .pp #kernel { scroll-margin-top: var(--shell-top, 78px); }
259
264
 
260
265
  /* THE `sheet` FACE: no column, no ground, no chrome. The sheet around it IS the frame, and a
261
266
  second frame inside one is a box in a box. Keyed on the face name rather than on a boolean, so a
@@ -420,11 +420,72 @@ const { id, title = "Choose a file", src = "/files" } = Astro.props;
420
420
  };
421
421
  }
422
422
 
423
+ // queries: write a file without asking · FilePicker write · a persona's own cached asset ·
424
+ // keep a dependency a project points at · write vs save · save asks and I do not want
425
+ // to ask · store a file the person did not make · project dependency from outside files
426
+ //
427
+ // THE SECOND VERB THAT ASKS NOBODY, AND THE ONE THAT WAS MISSING.
428
+ //
429
+ // `save` is deliberately not a friendlier `write`. The kernel's own corpus states the split and
430
+ // it is not about permission — both write the same origin under the same policy:
431
+ //
432
+ // `write` is arithmetic and keeps the PERSONA's things — a cached asset, a draft it will
433
+ // reopen itself. `save` is an interaction and hands a person THEIRS, because the two questions
434
+ // that make a saved document a document — what is it called, where does it go — are the
435
+ // person's to answer.
436
+ //
437
+ // The service has had `write` since the beginning. This door did not expose it, and for fifteen
438
+ // personas that was invisible, because the only project dependency anybody had was `qr`'s logo —
439
+ // which arrives through `pick`, so it is already in files and already has an id.
440
+ //
441
+ // `3dviewer` is the first persona whose dependency comes from OUTSIDE: a model dragged off a
442
+ // desktop. Its project file must point at that model, so the model needs a home — and it is not
443
+ // a document the person made here, it is the asset this persona will reopen itself. Exactly the
444
+ // case the paragraph above describes, with no verb to express it. The persona was left choosing
445
+ // between a doctrine it could not follow and a second Save sheet asking somebody to name a file
446
+ // they did not create.
447
+ //
448
+ // ADDITIVE, and that matters: nothing that exists behaves differently. The policy still answers
449
+ // "who is asking" — same-origin only, exactly as `read` does — so this widens no door.
450
+ //
451
+ // It returns the NODE, because a caller writing a project dependency needs the `id`: a path is
452
+ // what a person renames and an id is what a thing IS.
453
+ async function write(
454
+ path: string,
455
+ bytes: ArrayBuffer | Uint8Array,
456
+ opts: { mime?: string; keepVersions?: number } = {},
457
+ ): Promise<{ id: string; name: string; path: string } | null> {
458
+ const bus = await router();
459
+ ensureFrame();
460
+ const body = bytes instanceof Uint8Array
461
+ ? (bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength
462
+ ? bytes.buffer
463
+ : bytes.slice().buffer)
464
+ : bytes;
465
+ const node = await arrived!
466
+ .then(() => bus.proxy({
467
+ service: "files",
468
+ method: "write",
469
+ args: { path, bytes: body, mime: opts.mime, keepVersions: opts.keepVersions },
470
+ // The same 30s ceiling `read` uses. A model is tens of megabytes and a phone's storage is
471
+ // not fast; the default would time out on exactly the files this verb exists for.
472
+ options: { timeout: 30000 },
473
+ }))
474
+ .catch(() => null);
475
+ // A quota that is full, or a path the kernel refused. `null` rather than a throw, for the same
476
+ // reason `read` returns null: a caller must be able to carry on and say so.
477
+ if (!node) return null;
478
+ const n = node as { id: string; name: string };
479
+ return { id: n.id, name: n.name, path };
480
+ }
481
+
423
482
  (host as any).pick = pick;
424
483
  (sheet as any).pick = pick;
425
484
  (host as any).save = save;
426
485
  (sheet as any).save = save;
427
486
  (host as any).read = read;
428
487
  (sheet as any).read = read;
488
+ (host as any).write = write;
489
+ (sheet as any).write = write;
429
490
  }
430
491
  </script>