@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.
- 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 +6 -1
- package/dist/src/index.js +6 -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 +78 -0
- package/dist/src/page.d.ts +35 -0
- package/dist/src/page.js +72 -0
- package/dist/src/reach.d.ts +7 -0
- package/dist/src/reach.js +102 -0
- package/dist/src/schema.d.ts +211 -9
- package/dist/src/schema.js +54 -2
- package/dist/src/validate.d.ts +1 -0
- package/dist/src/validate.js +13 -5
- package/package.json +1 -1
- package/schema/app-spec.v1.json +129 -1
|
@@ -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
|
+
}
|
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,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[];
|