@cloudcrane/app-spec 0.4.0 → 0.7.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.
@@ -0,0 +1,102 @@
1
+ import { USE_NAMES } from "./uses.js";
2
+ // What it can reach, checked against the code: a coded or page component's code may name only the sites in `network`
3
+ // and use only the device features in `uses`, so what the person is shown before the app opens is everything it does.
4
+ // Refusals, not warnings. The same reading CloudCrane's checks give an app's code (its apps.rules.ts and
5
+ // blueprints/capabilities.ts), so a spec that passes here passes there.
6
+ /** Example domains and schema.org name no site an app talks to. */
7
+ const EXAMPLE_DOMAIN = /^(?:[a-z0-9-]+\.)*example\.(?:com|org|net)$|^(?:[a-z0-9-]+\.)+(?:example|test|invalid|localhost)$/i;
8
+ const notASite = (host) => EXAMPLE_DOMAIN.test(host.replace(/:\d+$/, "")) || /^(?:[a-z0-9-]+\.)*schema\.org(?::\d+)?$/.test(host);
9
+ /** Links and the page's own tags carry nothing with them: their addresses aren't sites the code talks to. */
10
+ const OWN_TAG = /<(a|script|link|iframe|embed|object|form)\b[^>]*>/gi;
11
+ const ADDRESS_ATTR = /(?<=\s)(href|src|data|action)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;
12
+ /** Every host an https:// or wss:// address in the code names, lower-case, once each. */
13
+ export function hostsNamedIn(code) {
14
+ const text = code.replace(OWN_TAG, (tag) => tag.replace(ADDRESS_ATTR, '$1=""'));
15
+ const hosts = new Set();
16
+ for (const m of text.matchAll(/\b(?:https|wss):\/\/([^\s/?#"'`<>\\()[\]{},;$]+)/gi)) {
17
+ const authority = m[1];
18
+ const host = authority
19
+ .slice(authority.lastIndexOf("@") + 1)
20
+ .toLowerCase()
21
+ .replace(/\.$/, "")
22
+ .replace(/:443$/, "");
23
+ if (host && !notASite(host))
24
+ hosts.add(host);
25
+ }
26
+ return [...hosts];
27
+ }
28
+ /** Calls that reach the network, and where their address starts. */
29
+ const REQUESTS = [
30
+ /(?<![\w$])fetch\s*\(\s*/g,
31
+ /\bnew\s+(?:WebSocket|EventSource)\s*\(\s*/g,
32
+ /\.sendBeacon\s*\(\s*/g,
33
+ /\.open\s*\(\s*(["'`])[A-Za-z]+\1\s*,\s*/g,
34
+ ];
35
+ /** Whether a request's address is put together while the app runs, so no one can read which site it reaches. */
36
+ export function buildsAddressAtRuntime(code) {
37
+ for (const pattern of REQUESTS) {
38
+ if (pattern.source.startsWith("\\.open") && !/XMLHttpRequest/.test(code))
39
+ continue;
40
+ for (const m of code.matchAll(pattern)) {
41
+ const rest = code.slice(m.index + m[0].length, m.index + m[0].length + 300);
42
+ const quote = rest[0];
43
+ if (quote !== '"' && quote !== "'" && quote !== "`")
44
+ return true;
45
+ const end = rest.indexOf(quote, 1);
46
+ let start = rest.slice(1, end === -1 ? undefined : end);
47
+ if (quote === "`" && start.includes("${"))
48
+ start = start.slice(0, start.indexOf("${"));
49
+ if (!start)
50
+ return true;
51
+ const absolute = /^([a-z][a-z0-9+.-]*:|\/\/)/i.test(start);
52
+ if (absolute && !/^(?:[a-z][a-z0-9+.-]*:)?\/\/[a-z0-9-]+(\.[a-z0-9-]+)*/i.test(start))
53
+ return true;
54
+ }
55
+ }
56
+ return false;
57
+ }
58
+ /** How code uses each device feature a spec can declare. */
59
+ const USED_BY = {
60
+ camera: [/\bgetUserMedia\b/],
61
+ microphone: [/\bgetUserMedia\b/],
62
+ location: [/\bgeolocation\b/],
63
+ motion: [/\bDevice(Motion|Orientation)Event\b/, /\bdevice(motion|orientation)\b/],
64
+ "screen-awake": [/\bwakeLock\b/],
65
+ gamepad: [/\bgetGamepads\b/, /\bgamepad(dis)?connected\b/],
66
+ voice: [/\b(webkit)?SpeechRecognition\b/],
67
+ contacts: [/\bnavigator\.contacts\b/, /\bContactsManager\b/],
68
+ nfc: [/\bNDEF(Reader|Message|Record)\b/],
69
+ };
70
+ /** Features a spec can't declare, so code that uses them is refused. */
71
+ const NOT_FOR_SPECS = [
72
+ ["Bluetooth", /\bnavigator\.bluetooth\b/],
73
+ ["direct connections to other phones (RTCPeerConnection)", /\b(webkit)?RTCPeerConnection\b/],
74
+ ["USB, serial or HID devices", /\bnavigator\.(usb|serial|hid)\b/],
75
+ ["payments", /\bPaymentRequest\b/],
76
+ ];
77
+ export function reachProblems(spec) {
78
+ const problems = [];
79
+ const sites = new Set(spec.network.map((n) => n.site));
80
+ const declared = new Set(spec.uses.map((u) => u.what));
81
+ for (const [name, component] of Object.entries(spec.components)) {
82
+ const at = `components.${name}.code`;
83
+ const code = component.code;
84
+ for (const host of hostsNamedIn(code))
85
+ if (!sites.has(host))
86
+ problems.push({ at, problem: `talks to ${host}, which network doesn't list: add it with why and what it's sent, or take it out` });
87
+ if (buildsAddressAtRuntime(code))
88
+ problems.push({ at, problem: "builds a request's address while it runs, so no one can tell which site it reaches: write each address out in full" });
89
+ // camera and microphone share getUserMedia: either one declared covers it.
90
+ for (const what of USE_NAMES) {
91
+ if (declared.has(what) || !USED_BY[what].some((p) => p.test(code)))
92
+ continue;
93
+ if ((what === "camera" || what === "microphone") && (declared.has("camera") || declared.has("microphone")))
94
+ continue;
95
+ problems.push({ at, problem: `uses ${what} but uses doesn't declare it: add it with why, or take it out` });
96
+ }
97
+ for (const [what, pattern] of NOT_FOR_SPECS)
98
+ if (pattern.test(code))
99
+ problems.push({ at, problem: `uses ${what}, which a spec can't declare` });
100
+ }
101
+ return problems;
102
+ }
@@ -28,6 +28,8 @@ export declare const CodedComponent: z.ZodEffects<z.ZodEffects<z.ZodObject<{
28
28
  kind: z.ZodLiteral<"coded">;
29
29
  /** game: a canvas, a loop and touch, the code a script; page: a whole HTML page, drawn full screen (what an app written as HTML becomes). */
30
30
  kit: z.ZodEnum<["game", "page"]>;
31
+ /** Optional: the native widget or screen it stands in for, like "CupertinoSlidingSegmentedControl", so `blackbox coverage` can say which the catalog lacks. */
32
+ replaces: z.ZodOptional<z.ZodString>;
31
33
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<["number", "string", "boolean", "list", "map"]>>>;
32
34
  events: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodEnum<["number", "string", "boolean", "list", "map"]>>>>;
33
35
  code: z.ZodString;
@@ -37,10 +39,12 @@ export declare const CodedComponent: z.ZodEffects<z.ZodEffects<z.ZodObject<{
37
39
  kit: "game" | "page";
38
40
  inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
39
41
  events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
42
+ replaces?: string | undefined;
40
43
  }, {
41
44
  code: string;
42
45
  kind: "coded";
43
46
  kit: "game" | "page";
47
+ replaces?: string | undefined;
44
48
  inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
45
49
  events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
46
50
  }>, {
@@ -49,10 +53,12 @@ export declare const CodedComponent: z.ZodEffects<z.ZodEffects<z.ZodObject<{
49
53
  kit: "game" | "page";
50
54
  inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
51
55
  events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
56
+ replaces?: string | undefined;
52
57
  }, {
53
58
  code: string;
54
59
  kind: "coded";
55
60
  kit: "game" | "page";
61
+ replaces?: string | undefined;
56
62
  inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
57
63
  events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
58
64
  }>, {
@@ -61,13 +67,17 @@ export declare const CodedComponent: z.ZodEffects<z.ZodEffects<z.ZodObject<{
61
67
  kit: "game" | "page";
62
68
  inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
63
69
  events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
70
+ replaces?: string | undefined;
64
71
  }, {
65
72
  code: string;
66
73
  kind: "coded";
67
74
  kit: "game" | "page";
75
+ replaces?: string | undefined;
68
76
  inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
69
77
  events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
70
78
  }>;
79
+ /** The kinds of thing a ported app may not do that its original did. */
80
+ export declare const GAP_KINDS: readonly ["background", "widget", "notifications", "purchases", "accounts", "sync", "device", "other"];
71
81
  export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
72
82
  v: z.ZodLiteral<1>;
73
83
  manifest: z.ZodObject<{
@@ -167,6 +177,11 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
167
177
  to: number;
168
178
  cel: string;
169
179
  }>, "many">;
180
+ /**
181
+ * What it keeps: the saved fields that are personal (health, money, people's names), as pointers into the saved
182
+ * data, like /days or /people. They stay on the phone; CloudCrane never puts them in a remix, screenshot or report.
183
+ */
184
+ personal: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
170
185
  }, "strict", z.ZodTypeAny, {
171
186
  version: number;
172
187
  key: string;
@@ -182,6 +197,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
182
197
  cel: string;
183
198
  }[];
184
199
  initial?: unknown;
200
+ personal?: string[] | undefined;
185
201
  }, {
186
202
  version: number;
187
203
  key: string;
@@ -197,6 +213,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
197
213
  path: string;
198
214
  source: string;
199
215
  }[] | undefined;
216
+ personal?: string[] | undefined;
200
217
  }>;
201
218
  logic: z.ZodObject<{
202
219
  consts: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodNumber, z.ZodString, z.ZodBoolean]>>>;
@@ -213,18 +230,22 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
213
230
  /** Values the screens read, kept up to date: a data-model pointer and the CEL that computes it. */
214
231
  computed: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
215
232
  /** Examples each function must reproduce: recorded from the source's own tests, or given. */
233
+ /**
234
+ * Examples each function must reproduce: `recorded` from the module's own tests, `app` from calls recorded while
235
+ * the original app ran, `hint` written by hand.
236
+ */
216
237
  vectors: z.ZodDefault<z.ZodArray<z.ZodObject<{
217
238
  fn: z.ZodString;
218
239
  in: z.ZodArray<z.ZodUnknown, "many">;
219
240
  out: z.ZodUnknown;
220
- from: z.ZodEnum<["recorded", "hint"]>;
241
+ from: z.ZodEnum<["recorded", "app", "hint"]>;
221
242
  }, "strict", z.ZodTypeAny, {
222
- from: "recorded" | "hint";
243
+ from: "recorded" | "app" | "hint";
223
244
  fn: string;
224
245
  in: unknown[];
225
246
  out?: unknown;
226
247
  }, {
227
- from: "recorded" | "hint";
248
+ from: "recorded" | "app" | "hint";
228
249
  fn: string;
229
250
  in: unknown[];
230
251
  out?: unknown;
@@ -237,7 +258,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
237
258
  }>;
238
259
  computed: Record<string, string>;
239
260
  vectors: {
240
- from: "recorded" | "hint";
261
+ from: "recorded" | "app" | "hint";
241
262
  fn: string;
242
263
  in: unknown[];
243
264
  out?: unknown;
@@ -250,21 +271,28 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
250
271
  }> | undefined;
251
272
  computed?: Record<string, string> | undefined;
252
273
  vectors?: {
253
- from: "recorded" | "hint";
274
+ from: "recorded" | "app" | "hint";
254
275
  fn: string;
255
276
  in: unknown[];
256
277
  out?: unknown;
257
278
  }[] | undefined;
258
279
  }>;
280
+ /**
281
+ * What it can reach: each site, why, and what it's sent, in the person's words ("the city you typed"). Shown to the
282
+ * person before the app opens; a site the code names but this doesn't list is refused.
283
+ */
259
284
  network: z.ZodDefault<z.ZodArray<z.ZodObject<{
260
285
  site: z.ZodString;
261
286
  why: z.ZodString;
287
+ sends: z.ZodString;
262
288
  }, "strict", z.ZodTypeAny, {
263
289
  site: string;
264
290
  why: string;
291
+ sends: string;
265
292
  }, {
266
293
  site: string;
267
294
  why: string;
295
+ sends: string;
268
296
  }>, "many">>;
269
297
  /** The device features the app uses, by CloudCrane's names, each once with why; anything not listed is blocked. */
270
298
  uses: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -277,6 +305,59 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
277
305
  why: string;
278
306
  what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
279
307
  }>, "many">>;
308
+ /**
309
+ * What it does: each screen in words, and what it shows when there's nothing yet, while it loads, offline, and when
310
+ * something fails. Optional; without it the blueprint describes the one surface from its words.
311
+ */
312
+ screens: z.ZodOptional<z.ZodArray<z.ZodObject<{
313
+ id: z.ZodString;
314
+ says: z.ZodString;
315
+ states: z.ZodOptional<z.ZodObject<{
316
+ empty: z.ZodOptional<z.ZodString>;
317
+ loading: z.ZodOptional<z.ZodString>;
318
+ offline: z.ZodOptional<z.ZodString>;
319
+ error: z.ZodOptional<z.ZodString>;
320
+ }, "strict", z.ZodTypeAny, {
321
+ error?: string | undefined;
322
+ empty?: string | undefined;
323
+ loading?: string | undefined;
324
+ offline?: string | undefined;
325
+ }, {
326
+ error?: string | undefined;
327
+ empty?: string | undefined;
328
+ loading?: string | undefined;
329
+ offline?: string | undefined;
330
+ }>>;
331
+ }, "strict", z.ZodTypeAny, {
332
+ id: string;
333
+ says: string;
334
+ states?: {
335
+ error?: string | undefined;
336
+ empty?: string | undefined;
337
+ loading?: string | undefined;
338
+ offline?: string | undefined;
339
+ } | undefined;
340
+ }, {
341
+ id: string;
342
+ says: string;
343
+ states?: {
344
+ error?: string | undefined;
345
+ empty?: string | undefined;
346
+ loading?: string | undefined;
347
+ offline?: string | undefined;
348
+ } | undefined;
349
+ }>, "many">>;
350
+ /** How it looks on the phone: the orientation it holds and whether it fills the screen (Android honours both). */
351
+ screen: z.ZodOptional<z.ZodObject<{
352
+ orientation: z.ZodOptional<z.ZodEnum<["portrait", "landscape", "any"]>>;
353
+ fullscreen: z.ZodOptional<z.ZodBoolean>;
354
+ }, "strict", z.ZodTypeAny, {
355
+ orientation?: "any" | "portrait" | "landscape" | undefined;
356
+ fullscreen?: boolean | undefined;
357
+ }, {
358
+ orientation?: "any" | "portrait" | "landscape" | undefined;
359
+ fullscreen?: boolean | undefined;
360
+ }>>;
280
361
  journeys: z.ZodArray<z.ZodObject<{
281
362
  id: z.ZodString;
282
363
  title: z.ZodString;
@@ -325,6 +406,8 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
325
406
  kind: z.ZodLiteral<"coded">;
326
407
  /** game: a canvas, a loop and touch, the code a script; page: a whole HTML page, drawn full screen (what an app written as HTML becomes). */
327
408
  kit: z.ZodEnum<["game", "page"]>;
409
+ /** Optional: the native widget or screen it stands in for, like "CupertinoSlidingSegmentedControl", so `blackbox coverage` can say which the catalog lacks. */
410
+ replaces: z.ZodOptional<z.ZodString>;
328
411
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<["number", "string", "boolean", "list", "map"]>>>;
329
412
  events: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodEnum<["number", "string", "boolean", "list", "map"]>>>>;
330
413
  code: z.ZodString;
@@ -334,10 +417,12 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
334
417
  kit: "game" | "page";
335
418
  inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
336
419
  events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
420
+ replaces?: string | undefined;
337
421
  }, {
338
422
  code: string;
339
423
  kind: "coded";
340
424
  kit: "game" | "page";
425
+ replaces?: string | undefined;
341
426
  inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
342
427
  events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
343
428
  }>, {
@@ -346,10 +431,12 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
346
431
  kit: "game" | "page";
347
432
  inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
348
433
  events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
434
+ replaces?: string | undefined;
349
435
  }, {
350
436
  code: string;
351
437
  kind: "coded";
352
438
  kit: "game" | "page";
439
+ replaces?: string | undefined;
353
440
  inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
354
441
  events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
355
442
  }>, {
@@ -358,10 +445,12 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
358
445
  kit: "game" | "page";
359
446
  inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
360
447
  events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
448
+ replaces?: string | undefined;
361
449
  }, {
362
450
  code: string;
363
451
  kind: "coded";
364
452
  kit: "game" | "page";
453
+ replaces?: string | undefined;
365
454
  inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
366
455
  events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
367
456
  }>>, Record<string, {
@@ -370,13 +459,34 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
370
459
  kit: "game" | "page";
371
460
  inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
372
461
  events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
462
+ replaces?: string | undefined;
373
463
  }>, Record<string, {
374
464
  code: string;
375
465
  kind: "coded";
376
466
  kit: "game" | "page";
467
+ replaces?: string | undefined;
377
468
  inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
378
469
  events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
379
470
  }>>>;
471
+ /** The page-kit component that is the whole app: the spec is then that page, compiled to its HTML as written (see page.ts). */
472
+ page: z.ZodOptional<z.ZodString>;
473
+ /**
474
+ * What the original app does that this one doesn't, said plainly so no one is promised it: a native feature with
475
+ * no web equivalent yet (background jobs, home-screen widgets, push notifications, in-app purchases...).
476
+ */
477
+ gaps: z.ZodOptional<z.ZodArray<z.ZodObject<{
478
+ kind: z.ZodEnum<["background", "widget", "notifications", "purchases", "accounts", "sync", "device", "other"]>;
479
+ what: z.ZodString;
480
+ why: z.ZodOptional<z.ZodString>;
481
+ }, "strict", z.ZodTypeAny, {
482
+ kind: "notifications" | "other" | "background" | "widget" | "purchases" | "accounts" | "sync" | "device";
483
+ what: string;
484
+ why?: string | undefined;
485
+ }, {
486
+ kind: "notifications" | "other" | "background" | "widget" | "purchases" | "accounts" | "sync" | "device";
487
+ what: string;
488
+ why?: string | undefined;
489
+ }>, "many">>;
380
490
  /** What a remix may change and what stays as the partner made it. */
381
491
  mods: z.ZodDefault<z.ZodObject<{
382
492
  open: z.ZodArray<z.ZodString, "many">;
@@ -395,6 +505,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
395
505
  kit: "game" | "page";
396
506
  inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
397
507
  events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
508
+ replaces?: string | undefined;
398
509
  }>;
399
510
  surface: {
400
511
  catalog: string;
@@ -432,6 +543,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
432
543
  cel: string;
433
544
  }[];
434
545
  initial?: unknown;
546
+ personal?: string[] | undefined;
435
547
  };
436
548
  logic: {
437
549
  consts: Record<string, string | number | boolean>;
@@ -441,7 +553,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
441
553
  }>;
442
554
  computed: Record<string, string>;
443
555
  vectors: {
444
- from: "recorded" | "hint";
556
+ from: "recorded" | "app" | "hint";
445
557
  fn: string;
446
558
  in: unknown[];
447
559
  out?: unknown;
@@ -450,6 +562,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
450
562
  network: {
451
563
  site: string;
452
564
  why: string;
565
+ sends: string;
453
566
  }[];
454
567
  uses: {
455
568
  why: string;
@@ -473,6 +586,26 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
473
586
  open: string[];
474
587
  locked: string[];
475
588
  };
589
+ page?: string | undefined;
590
+ screens?: {
591
+ id: string;
592
+ says: string;
593
+ states?: {
594
+ error?: string | undefined;
595
+ empty?: string | undefined;
596
+ loading?: string | undefined;
597
+ offline?: string | undefined;
598
+ } | undefined;
599
+ }[] | undefined;
600
+ screen?: {
601
+ orientation?: "any" | "portrait" | "landscape" | undefined;
602
+ fullscreen?: boolean | undefined;
603
+ } | undefined;
604
+ gaps?: {
605
+ kind: "notifications" | "other" | "background" | "widget" | "purchases" | "accounts" | "sync" | "device";
606
+ what: string;
607
+ why?: string | undefined;
608
+ }[] | undefined;
476
609
  }, {
477
610
  surface: {
478
611
  catalog: string;
@@ -510,6 +643,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
510
643
  path: string;
511
644
  source: string;
512
645
  }[] | undefined;
646
+ personal?: string[] | undefined;
513
647
  };
514
648
  logic: {
515
649
  consts?: Record<string, string | number | boolean> | undefined;
@@ -519,7 +653,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
519
653
  }> | undefined;
520
654
  computed?: Record<string, string> | undefined;
521
655
  vectors?: {
522
- from: "recorded" | "hint";
656
+ from: "recorded" | "app" | "hint";
523
657
  fn: string;
524
658
  in: unknown[];
525
659
  out?: unknown;
@@ -537,23 +671,45 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
537
671
  code: string;
538
672
  kind: "coded";
539
673
  kit: "game" | "page";
674
+ replaces?: string | undefined;
540
675
  inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
541
676
  events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
542
677
  }> | undefined;
678
+ page?: string | undefined;
543
679
  network?: {
544
680
  site: string;
545
681
  why: string;
682
+ sends: string;
546
683
  }[] | undefined;
547
684
  uses?: {
548
685
  why: string;
549
686
  what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
550
687
  }[] | undefined;
688
+ screens?: {
689
+ id: string;
690
+ says: string;
691
+ states?: {
692
+ error?: string | undefined;
693
+ empty?: string | undefined;
694
+ loading?: string | undefined;
695
+ offline?: string | undefined;
696
+ } | undefined;
697
+ }[] | undefined;
698
+ screen?: {
699
+ orientation?: "any" | "portrait" | "landscape" | undefined;
700
+ fullscreen?: boolean | undefined;
701
+ } | undefined;
551
702
  promises?: {
552
703
  text: string;
553
704
  id: string;
554
705
  kind?: "data" | undefined;
555
706
  }[] | undefined;
556
707
  tests?: Record<string, unknown>[] | undefined;
708
+ gaps?: {
709
+ kind: "notifications" | "other" | "background" | "widget" | "purchases" | "accounts" | "sync" | "device";
710
+ what: string;
711
+ why?: string | undefined;
712
+ }[] | undefined;
557
713
  mods?: {
558
714
  open: string[];
559
715
  locked: string[];
@@ -565,6 +721,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
565
721
  kit: "game" | "page";
566
722
  inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
567
723
  events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
724
+ replaces?: string | undefined;
568
725
  }>;
569
726
  surface: {
570
727
  catalog: string;
@@ -602,6 +759,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
602
759
  cel: string;
603
760
  }[];
604
761
  initial?: unknown;
762
+ personal?: string[] | undefined;
605
763
  };
606
764
  logic: {
607
765
  consts: Record<string, string | number | boolean>;
@@ -611,7 +769,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
611
769
  }>;
612
770
  computed: Record<string, string>;
613
771
  vectors: {
614
- from: "recorded" | "hint";
772
+ from: "recorded" | "app" | "hint";
615
773
  fn: string;
616
774
  in: unknown[];
617
775
  out?: unknown;
@@ -620,6 +778,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
620
778
  network: {
621
779
  site: string;
622
780
  why: string;
781
+ sends: string;
623
782
  }[];
624
783
  uses: {
625
784
  why: string;
@@ -643,6 +802,26 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
643
802
  open: string[];
644
803
  locked: string[];
645
804
  };
805
+ page?: string | undefined;
806
+ screens?: {
807
+ id: string;
808
+ says: string;
809
+ states?: {
810
+ error?: string | undefined;
811
+ empty?: string | undefined;
812
+ loading?: string | undefined;
813
+ offline?: string | undefined;
814
+ } | undefined;
815
+ }[] | undefined;
816
+ screen?: {
817
+ orientation?: "any" | "portrait" | "landscape" | undefined;
818
+ fullscreen?: boolean | undefined;
819
+ } | undefined;
820
+ gaps?: {
821
+ kind: "notifications" | "other" | "background" | "widget" | "purchases" | "accounts" | "sync" | "device";
822
+ what: string;
823
+ why?: string | undefined;
824
+ }[] | undefined;
646
825
  }, {
647
826
  surface: {
648
827
  catalog: string;
@@ -680,6 +859,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
680
859
  path: string;
681
860
  source: string;
682
861
  }[] | undefined;
862
+ personal?: string[] | undefined;
683
863
  };
684
864
  logic: {
685
865
  consts?: Record<string, string | number | boolean> | undefined;
@@ -689,7 +869,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
689
869
  }> | undefined;
690
870
  computed?: Record<string, string> | undefined;
691
871
  vectors?: {
692
- from: "recorded" | "hint";
872
+ from: "recorded" | "app" | "hint";
693
873
  fn: string;
694
874
  in: unknown[];
695
875
  out?: unknown;
@@ -707,23 +887,45 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
707
887
  code: string;
708
888
  kind: "coded";
709
889
  kit: "game" | "page";
890
+ replaces?: string | undefined;
710
891
  inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
711
892
  events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
712
893
  }> | undefined;
894
+ page?: string | undefined;
713
895
  network?: {
714
896
  site: string;
715
897
  why: string;
898
+ sends: string;
716
899
  }[] | undefined;
717
900
  uses?: {
718
901
  why: string;
719
902
  what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
720
903
  }[] | undefined;
904
+ screens?: {
905
+ id: string;
906
+ says: string;
907
+ states?: {
908
+ error?: string | undefined;
909
+ empty?: string | undefined;
910
+ loading?: string | undefined;
911
+ offline?: string | undefined;
912
+ } | undefined;
913
+ }[] | undefined;
914
+ screen?: {
915
+ orientation?: "any" | "portrait" | "landscape" | undefined;
916
+ fullscreen?: boolean | undefined;
917
+ } | undefined;
721
918
  promises?: {
722
919
  text: string;
723
920
  id: string;
724
921
  kind?: "data" | undefined;
725
922
  }[] | undefined;
726
923
  tests?: Record<string, unknown>[] | undefined;
924
+ gaps?: {
925
+ kind: "notifications" | "other" | "background" | "widget" | "purchases" | "accounts" | "sync" | "device";
926
+ what: string;
927
+ why?: string | undefined;
928
+ }[] | undefined;
727
929
  mods?: {
728
930
  open: string[];
729
931
  locked: string[];