@botiverse/porthole 0.1.5 → 0.1.7

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.
@@ -86,9 +86,11 @@ export interface ThemeConfig {
86
86
  * Whether a whole document (an artifact page) takes the person's light or dark scheme:
87
87
  * `declared` (default): only a page that declares it supports both
88
88
  * (`<meta name="color-scheme" content="light dark">`); any other page shows as its author
89
- * styled it, in light. `always`: every page. The variables are there either way.
89
+ * styled it, in light. `always`: every page. `never` (pages only, P065): the host has no
90
+ * theme for it: it always gets the light values, its frame says light, and no theme change
91
+ * reaches it. The variables are there in every case.
90
92
  */
91
- readonly documents?: "declared" | "always";
93
+ readonly documents?: "declared" | "always" | "never";
92
94
  }
93
95
 
94
96
  export interface Limits {
@@ -143,6 +145,13 @@ export interface LayoutConfig {
143
145
  readonly narrowWidthPx: number;
144
146
  /** The widest the frame is (a desktop column). */
145
147
  readonly wideWidthPx: number;
148
+ /**
149
+ * How the frame sits in the host (P065). `flow` (default): a card in a column, as tall as its
150
+ * content (up to `maxHeightPx`). `fill`: it fills the viewer (the page itself): as tall as its
151
+ * container, the content scrolling itself, `position: fixed` and `sticky` working. Every mount
152
+ * then fills (`MountOptions.fill`), and the manual says so.
153
+ */
154
+ readonly kind?: "flow" | "fill";
146
155
  }
147
156
 
148
157
  export interface GuideConfig {
@@ -193,9 +202,23 @@ export interface PortholeConfig {
193
202
  * "conversation" }`), so the manual says storage is shared across it and keys need a prefix.
194
203
  */
195
204
  readonly storage?: StorageConfig;
205
+ /**
206
+ * Where the content's own errors go, for the manual (P065): `agent`, back to the agent that
207
+ * wrote it; `host`, to the app only (the agent never sees them); `none`, nowhere (the config
208
+ * declares no `reportError`). Absent: the manual says they are reported to the app.
209
+ */
210
+ readonly errors?: "agent" | "host" | "none";
196
211
  }
197
212
 
198
213
  export interface StorageConfig {
199
- /** What the displays sharing one origin have in common, as a noun ("conversation"). */
214
+ /**
215
+ * What the displays sharing one origin at once have in common, as a noun ("conversation"):
216
+ * the manual tells authors to prefix their keys.
217
+ */
200
218
  readonly shared?: string;
219
+ /**
220
+ * What one display's storage is kept for, when only one display uses its origin at a time, in
221
+ * words ("this artifact, across its versions", P065): the manual says so, with no prefix advice.
222
+ */
223
+ readonly scope?: string;
201
224
  }
@@ -292,6 +292,7 @@ export function validateConfig(input: unknown): PortholeConfig {
292
292
  "sandbox",
293
293
  "permissions",
294
294
  "storage",
295
+ "errors",
295
296
  ],
296
297
  "config",
297
298
  );
@@ -360,9 +361,16 @@ export function validateConfig(input: unknown): PortholeConfig {
360
361
  if (
361
362
  theme.documents !== undefined &&
362
363
  theme.documents !== "declared" &&
363
- theme.documents !== "always"
364
+ theme.documents !== "always" &&
365
+ theme.documents !== "never"
364
366
  ) {
365
- fail("config.theme.documents", 'must be "declared" or "always"');
367
+ fail("config.theme.documents", 'must be "declared", "always" or "never"');
368
+ }
369
+ if (theme.documents === "never" && config.mode !== "artifact") {
370
+ fail(
371
+ "config.theme.documents",
372
+ '"never" is for whole pages: a fragment takes the theme it is drawn in',
373
+ );
366
374
  }
367
375
  const variables = object(theme.variables, "config.theme.variables");
368
376
  for (const [name, value] of Object.entries(variables)) {
@@ -414,7 +422,10 @@ export function validateConfig(input: unknown): PortholeConfig {
414
422
  }
415
423
 
416
424
  const layout = object(config.layout, "config.layout");
417
- only(layout, ["minWidthPx", "narrowWidthPx", "wideWidthPx"], "config.layout");
425
+ only(layout, ["minWidthPx", "narrowWidthPx", "wideWidthPx", "kind"], "config.layout");
426
+ if (layout.kind !== undefined && layout.kind !== "flow" && layout.kind !== "fill") {
427
+ fail("config.layout.kind", 'must be "flow" or "fill"');
428
+ }
418
429
  for (const name of ["minWidthPx", "narrowWidthPx", "wideWidthPx"]) {
419
430
  positive(layout[name], `config.layout.${name}`, 10_000);
420
431
  }
@@ -494,7 +505,22 @@ export function validateConfig(input: unknown): PortholeConfig {
494
505
 
495
506
  if (config.storage !== undefined) {
496
507
  const storage = object(config.storage, "config.storage");
497
- only(storage, ["shared"], "config.storage");
508
+ only(storage, ["shared", "scope"], "config.storage");
509
+ if (storage.shared !== undefined && storage.scope !== undefined) {
510
+ fail(
511
+ "config.storage",
512
+ "shared (several displays at once) or scope (one at a time), not both",
513
+ );
514
+ }
515
+ if (
516
+ storage.scope !== undefined &&
517
+ (typeof storage.scope !== "string" || !/^[a-z][a-z ,'-]{0,59}$/u.test(storage.scope))
518
+ ) {
519
+ fail(
520
+ "config.storage.scope",
521
+ 'must be short lowercase words ("this artifact, across its versions")',
522
+ );
523
+ }
498
524
  if (config.isolation !== "domain") {
499
525
  fail("config.storage", "only domain isolation keeps storage across displays");
500
526
  }
@@ -506,6 +532,20 @@ export function validateConfig(input: unknown): PortholeConfig {
506
532
  }
507
533
  }
508
534
 
535
+ if (config.errors !== undefined) {
536
+ if (config.errors !== "agent" && config.errors !== "host" && config.errors !== "none") {
537
+ fail("config.errors", 'must be "agent", "host" or "none"');
538
+ }
539
+ if ((config.errors === "none") !== (capabilities.reportError === undefined)) {
540
+ fail(
541
+ "config.errors",
542
+ config.errors === "none"
543
+ ? '"none" means no reportError: drop the capability'
544
+ : `${JSON.stringify(config.errors)} needs the reportError capability, which carries them`,
545
+ );
546
+ }
547
+ }
548
+
509
549
  if (config.permissions !== undefined) {
510
550
  if (config.mode !== "url") {
511
551
  fail("config.permissions", "only url mode may be delegated features");
@@ -40,6 +40,8 @@ export interface LaunchSpec {
40
40
  readonly scheme?: ColorScheme;
41
41
  /** The frame's width, px. */
42
42
  readonly widthPx?: number;
43
+ /** The height of the box the frame sits in, px: for a layout that fills it (`layout.kind`). */
44
+ readonly heightPx?: number;
43
45
  /** The host's live values for theme variables, per scheme (as `HostOptions.theme` answers). */
44
46
  readonly hostTheme?: {
45
47
  readonly light?: Readonly<Record<string, string>>;
@@ -34,7 +34,8 @@ export interface Probe {
34
34
  readonly id: string;
35
35
  /**
36
36
  * The DESIGN.md egress-table item it proves; 0 for the shell's own wiring and interop, 11 for
37
- * behavior (streaming, sizing, theming), 12 for host-function gates, 13 for the clipboard.
37
+ * behavior (streaming, sizing, theming), 12 for host-function gates, 13 for the clipboard, 14
38
+ * for the manual's claims about the sandbox (`LIMIT_CHECKS`).
38
39
  */
39
40
  readonly item: number;
40
41
  readonly title: string;