@cloudcrane/app-spec 0.5.0 → 0.7.1
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/dist/src/blueprint.d.ts +14 -0
- package/dist/src/blueprint.js +56 -3
- package/dist/src/design.d.ts +16 -0
- package/dist/src/design.js +137 -0
- package/dist/src/fidelity.d.ts +17 -0
- package/dist/src/fidelity.js +36 -0
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.js +5 -1
- package/dist/src/leak.js +9 -0
- package/dist/src/logic.js +14 -1
- package/dist/src/memory.d.ts +7 -0
- package/dist/src/memory.js +81 -0
- package/dist/src/reach.d.ts +7 -0
- package/dist/src/reach.js +102 -0
- package/dist/src/schema.d.ts +205 -9
- package/dist/src/schema.js +52 -2
- package/dist/src/validate.d.ts +1 -0
- package/dist/src/validate.js +12 -5
- package/package.json +1 -1
- package/schema/app-spec.v1.json +125 -1
package/dist/src/schema.d.ts
CHANGED
|
@@ -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,15 +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
|
}>>>;
|
|
380
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). */
|
|
381
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">>;
|
|
382
490
|
/** What a remix may change and what stays as the partner made it. */
|
|
383
491
|
mods: z.ZodDefault<z.ZodObject<{
|
|
384
492
|
open: z.ZodArray<z.ZodString, "many">;
|
|
@@ -397,6 +505,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
397
505
|
kit: "game" | "page";
|
|
398
506
|
inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
|
|
399
507
|
events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
|
|
508
|
+
replaces?: string | undefined;
|
|
400
509
|
}>;
|
|
401
510
|
surface: {
|
|
402
511
|
catalog: string;
|
|
@@ -434,6 +543,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
434
543
|
cel: string;
|
|
435
544
|
}[];
|
|
436
545
|
initial?: unknown;
|
|
546
|
+
personal?: string[] | undefined;
|
|
437
547
|
};
|
|
438
548
|
logic: {
|
|
439
549
|
consts: Record<string, string | number | boolean>;
|
|
@@ -443,7 +553,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
443
553
|
}>;
|
|
444
554
|
computed: Record<string, string>;
|
|
445
555
|
vectors: {
|
|
446
|
-
from: "recorded" | "hint";
|
|
556
|
+
from: "recorded" | "app" | "hint";
|
|
447
557
|
fn: string;
|
|
448
558
|
in: unknown[];
|
|
449
559
|
out?: unknown;
|
|
@@ -452,6 +562,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
452
562
|
network: {
|
|
453
563
|
site: string;
|
|
454
564
|
why: string;
|
|
565
|
+
sends: string;
|
|
455
566
|
}[];
|
|
456
567
|
uses: {
|
|
457
568
|
why: string;
|
|
@@ -476,6 +587,25 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
476
587
|
locked: string[];
|
|
477
588
|
};
|
|
478
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;
|
|
479
609
|
}, {
|
|
480
610
|
surface: {
|
|
481
611
|
catalog: string;
|
|
@@ -513,6 +643,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
513
643
|
path: string;
|
|
514
644
|
source: string;
|
|
515
645
|
}[] | undefined;
|
|
646
|
+
personal?: string[] | undefined;
|
|
516
647
|
};
|
|
517
648
|
logic: {
|
|
518
649
|
consts?: Record<string, string | number | boolean> | undefined;
|
|
@@ -522,7 +653,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
522
653
|
}> | undefined;
|
|
523
654
|
computed?: Record<string, string> | undefined;
|
|
524
655
|
vectors?: {
|
|
525
|
-
from: "recorded" | "hint";
|
|
656
|
+
from: "recorded" | "app" | "hint";
|
|
526
657
|
fn: string;
|
|
527
658
|
in: unknown[];
|
|
528
659
|
out?: unknown;
|
|
@@ -540,6 +671,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
540
671
|
code: string;
|
|
541
672
|
kind: "coded";
|
|
542
673
|
kit: "game" | "page";
|
|
674
|
+
replaces?: string | undefined;
|
|
543
675
|
inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
|
|
544
676
|
events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
|
|
545
677
|
}> | undefined;
|
|
@@ -547,17 +679,37 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
547
679
|
network?: {
|
|
548
680
|
site: string;
|
|
549
681
|
why: string;
|
|
682
|
+
sends: string;
|
|
550
683
|
}[] | undefined;
|
|
551
684
|
uses?: {
|
|
552
685
|
why: string;
|
|
553
686
|
what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
|
|
554
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;
|
|
555
702
|
promises?: {
|
|
556
703
|
text: string;
|
|
557
704
|
id: string;
|
|
558
705
|
kind?: "data" | undefined;
|
|
559
706
|
}[] | undefined;
|
|
560
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;
|
|
561
713
|
mods?: {
|
|
562
714
|
open: string[];
|
|
563
715
|
locked: string[];
|
|
@@ -569,6 +721,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
569
721
|
kit: "game" | "page";
|
|
570
722
|
inputs: Record<string, "string" | "number" | "boolean" | "map" | "list">;
|
|
571
723
|
events: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">>;
|
|
724
|
+
replaces?: string | undefined;
|
|
572
725
|
}>;
|
|
573
726
|
surface: {
|
|
574
727
|
catalog: string;
|
|
@@ -606,6 +759,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
606
759
|
cel: string;
|
|
607
760
|
}[];
|
|
608
761
|
initial?: unknown;
|
|
762
|
+
personal?: string[] | undefined;
|
|
609
763
|
};
|
|
610
764
|
logic: {
|
|
611
765
|
consts: Record<string, string | number | boolean>;
|
|
@@ -615,7 +769,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
615
769
|
}>;
|
|
616
770
|
computed: Record<string, string>;
|
|
617
771
|
vectors: {
|
|
618
|
-
from: "recorded" | "hint";
|
|
772
|
+
from: "recorded" | "app" | "hint";
|
|
619
773
|
fn: string;
|
|
620
774
|
in: unknown[];
|
|
621
775
|
out?: unknown;
|
|
@@ -624,6 +778,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
624
778
|
network: {
|
|
625
779
|
site: string;
|
|
626
780
|
why: string;
|
|
781
|
+
sends: string;
|
|
627
782
|
}[];
|
|
628
783
|
uses: {
|
|
629
784
|
why: string;
|
|
@@ -648,6 +803,25 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
648
803
|
locked: string[];
|
|
649
804
|
};
|
|
650
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;
|
|
651
825
|
}, {
|
|
652
826
|
surface: {
|
|
653
827
|
catalog: string;
|
|
@@ -685,6 +859,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
685
859
|
path: string;
|
|
686
860
|
source: string;
|
|
687
861
|
}[] | undefined;
|
|
862
|
+
personal?: string[] | undefined;
|
|
688
863
|
};
|
|
689
864
|
logic: {
|
|
690
865
|
consts?: Record<string, string | number | boolean> | undefined;
|
|
@@ -694,7 +869,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
694
869
|
}> | undefined;
|
|
695
870
|
computed?: Record<string, string> | undefined;
|
|
696
871
|
vectors?: {
|
|
697
|
-
from: "recorded" | "hint";
|
|
872
|
+
from: "recorded" | "app" | "hint";
|
|
698
873
|
fn: string;
|
|
699
874
|
in: unknown[];
|
|
700
875
|
out?: unknown;
|
|
@@ -712,6 +887,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
712
887
|
code: string;
|
|
713
888
|
kind: "coded";
|
|
714
889
|
kit: "game" | "page";
|
|
890
|
+
replaces?: string | undefined;
|
|
715
891
|
inputs?: Record<string, "string" | "number" | "boolean" | "map" | "list"> | undefined;
|
|
716
892
|
events?: Record<string, Record<string, "string" | "number" | "boolean" | "map" | "list">> | undefined;
|
|
717
893
|
}> | undefined;
|
|
@@ -719,17 +895,37 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
|
|
|
719
895
|
network?: {
|
|
720
896
|
site: string;
|
|
721
897
|
why: string;
|
|
898
|
+
sends: string;
|
|
722
899
|
}[] | undefined;
|
|
723
900
|
uses?: {
|
|
724
901
|
why: string;
|
|
725
902
|
what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
|
|
726
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;
|
|
727
918
|
promises?: {
|
|
728
919
|
text: string;
|
|
729
920
|
id: string;
|
|
730
921
|
kind?: "data" | undefined;
|
|
731
922
|
}[] | undefined;
|
|
732
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;
|
|
733
929
|
mods?: {
|
|
734
930
|
open: string[];
|
|
735
931
|
locked: string[];
|
package/dist/src/schema.js
CHANGED
|
@@ -61,6 +61,8 @@ export const CodedComponent = z
|
|
|
61
61
|
kind: z.literal("coded"),
|
|
62
62
|
/** 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). */
|
|
63
63
|
kit: z.enum(["game", "page"]),
|
|
64
|
+
/** Optional: the native widget or screen it stands in for, like "CupertinoSlidingSegmentedControl", so `blackbox coverage` can say which the catalog lacks. */
|
|
65
|
+
replaces: Text(120).optional(),
|
|
64
66
|
inputs: z.record(CelName, CodedType).default({}),
|
|
65
67
|
events: z.record(CelName, z.record(CelName, CodedType)).default({}),
|
|
66
68
|
code: z
|
|
@@ -80,6 +82,8 @@ export const CodedComponent = z
|
|
|
80
82
|
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["code"], message: "a game kit's code is at most 64 KB" });
|
|
81
83
|
})
|
|
82
84
|
.refine((c) => Object.keys(c.inputs).length <= 10 && Object.keys(c.events).length <= 10, "at most 10 inputs and 10 events");
|
|
85
|
+
/** The kinds of thing a ported app may not do that its original did. */
|
|
86
|
+
export const GAP_KINDS = ["background", "widget", "notifications", "purchases", "accounts", "sync", "device", "other"];
|
|
83
87
|
export const SpecSchema = z
|
|
84
88
|
.object({
|
|
85
89
|
v: z.literal(1),
|
|
@@ -138,6 +142,11 @@ export const SpecSchema = z
|
|
|
138
142
|
.default([]),
|
|
139
143
|
/** Each step turns version `from` into `to`, as a CEL expression over `saved`. */
|
|
140
144
|
migrations: z.array(z.object({ from: z.number().int().min(1), to: z.number().int().min(2), cel: Cel }).strict()),
|
|
145
|
+
/**
|
|
146
|
+
* What it keeps: the saved fields that are personal (health, money, people's names), as pointers into the saved
|
|
147
|
+
* data, like /days or /people. They stay on the phone; CloudCrane never puts them in a remix, screenshot or report.
|
|
148
|
+
*/
|
|
149
|
+
personal: z.array(Pointer).max(20).optional(),
|
|
141
150
|
})
|
|
142
151
|
.strict(),
|
|
143
152
|
logic: z
|
|
@@ -147,11 +156,21 @@ export const SpecSchema = z
|
|
|
147
156
|
/** Values the screens read, kept up to date: a data-model pointer and the CEL that computes it. */
|
|
148
157
|
computed: z.record(Pointer, Cel).default({}),
|
|
149
158
|
/** Examples each function must reproduce: recorded from the source's own tests, or given. */
|
|
150
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Examples each function must reproduce: `recorded` from the module's own tests, `app` from calls recorded while
|
|
161
|
+
* the original app ran, `hint` written by hand.
|
|
162
|
+
*/
|
|
163
|
+
vectors: z
|
|
164
|
+
.array(z.object({ fn: CelName, in: z.array(z.unknown()).max(8), out: z.unknown(), from: z.enum(["recorded", "app", "hint"]) }).strict())
|
|
165
|
+
.default([]),
|
|
151
166
|
})
|
|
152
167
|
.strict(),
|
|
168
|
+
/**
|
|
169
|
+
* What it can reach: each site, why, and what it's sent, in the person's words ("the city you typed"). Shown to the
|
|
170
|
+
* person before the app opens; a site the code names but this doesn't list is refused.
|
|
171
|
+
*/
|
|
153
172
|
network: z
|
|
154
|
-
.array(z.object({ site: z.string().regex(/^[a-z0-9.-]+\.[a-z]{2,}$/), why: Text(300) }).strict())
|
|
173
|
+
.array(z.object({ site: z.string().regex(/^[a-z0-9.-]+\.[a-z]{2,}$/), why: Text(300), sends: Text(200) }).strict())
|
|
155
174
|
.max(5)
|
|
156
175
|
.default([]),
|
|
157
176
|
/** The device features the app uses, by CloudCrane's names, each once with why; anything not listed is blocked. */
|
|
@@ -159,6 +178,29 @@ export const SpecSchema = z
|
|
|
159
178
|
.array(z.object({ what: z.enum(USE_NAMES), why: Text(300) }).strict())
|
|
160
179
|
.max(USE_NAMES.length)
|
|
161
180
|
.default([]),
|
|
181
|
+
/**
|
|
182
|
+
* What it does: each screen in words, and what it shows when there's nothing yet, while it loads, offline, and when
|
|
183
|
+
* something fails. Optional; without it the blueprint describes the one surface from its words.
|
|
184
|
+
*/
|
|
185
|
+
screens: z
|
|
186
|
+
.array(z
|
|
187
|
+
.object({
|
|
188
|
+
id: Id,
|
|
189
|
+
says: Text(300),
|
|
190
|
+
states: z
|
|
191
|
+
.object({ empty: Text(200).optional(), loading: Text(200).optional(), offline: Text(200).optional(), error: Text(200).optional() })
|
|
192
|
+
.strict()
|
|
193
|
+
.optional(),
|
|
194
|
+
})
|
|
195
|
+
.strict())
|
|
196
|
+
.min(1)
|
|
197
|
+
.max(20)
|
|
198
|
+
.optional(),
|
|
199
|
+
/** How it looks on the phone: the orientation it holds and whether it fills the screen (Android honours both). */
|
|
200
|
+
screen: z
|
|
201
|
+
.object({ orientation: z.enum(["portrait", "landscape", "any"]).optional(), fullscreen: z.boolean().optional() })
|
|
202
|
+
.strict()
|
|
203
|
+
.optional(),
|
|
162
204
|
journeys: z
|
|
163
205
|
.array(z
|
|
164
206
|
.object({
|
|
@@ -178,6 +220,14 @@ export const SpecSchema = z
|
|
|
178
220
|
.default({}),
|
|
179
221
|
/** The page-kit component that is the whole app: the spec is then that page, compiled to its HTML as written (see page.ts). */
|
|
180
222
|
page: Id.optional(),
|
|
223
|
+
/**
|
|
224
|
+
* What the original app does that this one doesn't, said plainly so no one is promised it: a native feature with
|
|
225
|
+
* no web equivalent yet (background jobs, home-screen widgets, push notifications, in-app purchases...).
|
|
226
|
+
*/
|
|
227
|
+
gaps: z
|
|
228
|
+
.array(z.object({ kind: z.enum(GAP_KINDS), what: Text(120), why: Text(300).optional() }).strict())
|
|
229
|
+
.max(20)
|
|
230
|
+
.optional(),
|
|
181
231
|
/** What a remix may change and what stays as the partner made it. */
|
|
182
232
|
mods: z
|
|
183
233
|
.object({ open: z.array(PointerPattern), locked: z.array(PointerPattern) })
|
package/dist/src/validate.d.ts
CHANGED
package/dist/src/validate.js
CHANGED
|
@@ -4,8 +4,14 @@ import { leakProblems } from "./leak.js";
|
|
|
4
4
|
import { logicProblems } from "./logic.js";
|
|
5
5
|
import { SpecSchema } from "./schema.js";
|
|
6
6
|
import { pageProblems } from "./page.js";
|
|
7
|
+
import { designWarnings, statesWarnings } from "./design.js";
|
|
8
|
+
import { memoryProblems } from "./memory.js";
|
|
9
|
+
import { reachProblems } from "./reach.js";
|
|
10
|
+
import { rulesWarnings } from "./fidelity.js";
|
|
7
11
|
// Everything wrong with a spec, collected rather than stopping at the first: the
|
|
8
|
-
// envelope's shape, then the A2UI surface, then references between parts.
|
|
12
|
+
// envelope's shape, then the A2UI surface, then references between parts, what it keeps (memory.ts) and what its code
|
|
13
|
+
// reaches (reach.ts). Problems refuse it. Warnings (how it looks and what each screen shows when empty, offline or
|
|
14
|
+
// failing: design.ts) don't: they say what would make it better.
|
|
9
15
|
const pathOf = (path) => path
|
|
10
16
|
.map((p) => (typeof p === "number" ? `[${p}]` : `.${p}`))
|
|
11
17
|
.join("")
|
|
@@ -13,12 +19,12 @@ const pathOf = (path) => path
|
|
|
13
19
|
export function validateSpec(input, catalog) {
|
|
14
20
|
const parsed = SpecSchema.safeParse(input);
|
|
15
21
|
if (!parsed.success)
|
|
16
|
-
return { spec: null, problems: parsed.error.issues.map((i) => ({ at: pathOf(i.path) || "spec", problem: i.message })) };
|
|
22
|
+
return { spec: null, problems: parsed.error.issues.map((i) => ({ at: pathOf(i.path) || "spec", problem: i.message })), warnings: [] };
|
|
17
23
|
const spec = parsed.data;
|
|
18
24
|
// The surface is checked against the catalog it names (CloudCrane's, or A2UI's basic one).
|
|
19
25
|
const named = catalog ?? catalogFor(spec.surface.catalog);
|
|
20
26
|
if (!named)
|
|
21
|
-
return { spec: null, problems: [{ at: "surface.catalog", problem: "not a catalog this runtime has" }] };
|
|
27
|
+
return { spec: null, problems: [{ at: "surface.catalog", problem: "not a catalog this runtime has" }], warnings: [] };
|
|
22
28
|
const problems = [...surfaceProblems(spec.surface.messages, named)];
|
|
23
29
|
const catalogId = spec.surface.messages[0].createSurface?.catalogId;
|
|
24
30
|
if (catalogId && catalogId !== spec.surface.catalog)
|
|
@@ -47,11 +53,12 @@ export function validateSpec(input, catalog) {
|
|
|
47
53
|
for (const m of spec.data.migrations)
|
|
48
54
|
if (m.to !== m.from + 1 || m.to > spec.data.version)
|
|
49
55
|
problems.push({ at: `data.migrations ${m.from}`, problem: "steps go one version at a time, up to data.version" });
|
|
50
|
-
problems.push(...codedProblems(spec), ...pageProblems(spec), ...logicProblems(spec), ...leakProblems(spec));
|
|
56
|
+
problems.push(...codedProblems(spec), ...pageProblems(spec), ...logicProblems(spec), ...leakProblems(spec), ...memoryProblems(spec), ...reachProblems(spec));
|
|
51
57
|
for (const locked of spec.mods.locked)
|
|
52
58
|
if (spec.mods.open.includes(locked))
|
|
53
59
|
problems.push({ at: `mods ${locked}`, problem: "both open and locked" });
|
|
54
|
-
|
|
60
|
+
const warnings = [...designWarnings(spec), ...statesWarnings(spec), ...rulesWarnings(spec)];
|
|
61
|
+
return { spec: problems.length ? null : spec, problems, warnings };
|
|
55
62
|
}
|
|
56
63
|
/** Each Coded placement names a component the spec defines, and only its declared inputs and events; each component is placed. */
|
|
57
64
|
function codedProblems(spec) {
|