@cloudcrane/app-spec 0.7.1 → 0.7.2

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.
@@ -8,6 +8,7 @@ export { leakProblems } from "./leak.js";
8
8
  export { CLOUDCRANE_CATALOG, CLOUDCRANE_CATALOG_ID, catalogFor, cloudcraneCatalog } from "./catalog.js";
9
9
  export { deriveBlueprint, type BlueprintV1 } from "./blueprint.js";
10
10
  export { USES, USE_NAMES, NEEDS_ORIGIN, featuresOf, type Use } from "./uses.js";
11
+ export { DECLARABLE, PAGE_ONLY, type Declarable, type PageOnlyUse } from "./uses.page.js";
11
12
  export { pageOf, pageProblems, wrapPage, type PageApp } from "./page.js";
12
13
  export { designWarnings, statesWarnings, contrast, rgbOf, CORE_ROLES, TEXT_PAIRS, TEXT_CONTRAST } from "./design.js";
13
14
  export { memoryProblems, keptShape, type KeptType } from "./memory.js";
package/dist/src/index.js CHANGED
@@ -10,6 +10,7 @@ export { leakProblems } from "./leak.js";
10
10
  export { CLOUDCRANE_CATALOG, CLOUDCRANE_CATALOG_ID, catalogFor, cloudcraneCatalog } from "./catalog.js";
11
11
  export { deriveBlueprint } from "./blueprint.js";
12
12
  export { USES, USE_NAMES, NEEDS_ORIGIN, featuresOf } from "./uses.js";
13
+ export { DECLARABLE, PAGE_ONLY } from "./uses.page.js";
13
14
  export { pageOf, pageProblems, wrapPage } from "./page.js";
14
15
  export { designWarnings, statesWarnings, contrast, rgbOf, CORE_ROLES, TEXT_PAIRS, TEXT_CONTRAST } from "./design.js";
15
16
  export { memoryProblems, keptShape } from "./memory.js";
package/dist/src/reach.js CHANGED
@@ -1,4 +1,4 @@
1
- import { USE_NAMES } from "./uses.js";
1
+ import { DECLARABLE } from "./uses.page.js";
2
2
  // What it can reach, checked against the code: a coded or page component's code may name only the sites in `network`
3
3
  // and use only the device features in `uses`, so what the person is shown before the app opens is everything it does.
4
4
  // Refusals, not warnings. The same reading CloudCrane's checks give an app's code (its apps.rules.ts and
@@ -66,6 +66,7 @@ const USED_BY = {
66
66
  voice: [/\b(webkit)?SpeechRecognition\b/],
67
67
  contacts: [/\bnavigator\.contacts\b/, /\bContactsManager\b/],
68
68
  nfc: [/\bNDEF(Reader|Message|Record)\b/],
69
+ notifications: [/\bccRemind\b/, /\bNotification\b/, /\bPushManager\b/],
69
70
  };
70
71
  /** Features a spec can't declare, so code that uses them is refused. */
71
72
  const NOT_FOR_SPECS = [
@@ -87,7 +88,7 @@ export function reachProblems(spec) {
87
88
  if (buildsAddressAtRuntime(code))
88
89
  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
90
  // camera and microphone share getUserMedia: either one declared covers it.
90
- for (const what of USE_NAMES) {
91
+ for (const what of DECLARABLE) {
91
92
  if (declared.has(what) || !USED_BY[what].some((p) => p.test(code)))
92
93
  continue;
93
94
  if ((what === "camera" || what === "microphone") && (declared.has("camera") || declared.has("microphone")))
@@ -296,14 +296,14 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
296
296
  }>, "many">>;
297
297
  /** The device features the app uses, by CloudCrane's names, each once with why; anything not listed is blocked. */
298
298
  uses: z.ZodDefault<z.ZodArray<z.ZodObject<{
299
- what: z.ZodEnum<["camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc", ...("camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc")[]]>;
299
+ what: z.ZodEnum<["camera" | "notifications" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc", ...("camera" | "notifications" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc")[]]>;
300
300
  why: z.ZodString;
301
301
  }, "strict", z.ZodTypeAny, {
302
302
  why: string;
303
- what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
303
+ what: "camera" | "notifications" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
304
304
  }, {
305
305
  why: string;
306
- what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
306
+ what: "camera" | "notifications" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
307
307
  }>, "many">>;
308
308
  /**
309
309
  * What it does: each screen in words, and what it shows when there's nothing yet, while it loads, offline, and when
@@ -566,7 +566,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
566
566
  }[];
567
567
  uses: {
568
568
  why: string;
569
- what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
569
+ what: "camera" | "notifications" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
570
570
  }[];
571
571
  journeys: {
572
572
  title: string;
@@ -683,7 +683,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
683
683
  }[] | undefined;
684
684
  uses?: {
685
685
  why: string;
686
- what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
686
+ what: "camera" | "notifications" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
687
687
  }[] | undefined;
688
688
  screens?: {
689
689
  id: string;
@@ -782,7 +782,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
782
782
  }[];
783
783
  uses: {
784
784
  why: string;
785
- what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
785
+ what: "camera" | "notifications" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
786
786
  }[];
787
787
  journeys: {
788
788
  title: string;
@@ -899,7 +899,7 @@ export declare const SpecSchema: z.ZodEffects<z.ZodObject<{
899
899
  }[] | undefined;
900
900
  uses?: {
901
901
  why: string;
902
- what: "camera" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
902
+ what: "camera" | "notifications" | "microphone" | "location" | "motion" | "screen-awake" | "gamepad" | "voice" | "contacts" | "nfc";
903
903
  }[] | undefined;
904
904
  screens?: {
905
905
  id: string;
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
- import { NEEDS_ORIGIN, USE_NAMES } from "./uses.js";
2
+ import { NEEDS_ORIGIN } from "./uses.js";
3
+ import { DECLARABLE, PAGE_ONLY } from "./uses.page.js";
3
4
  // The CloudCrane app spec: an A2UI surface (the screens) in a small envelope for
4
5
  // what A2UI doesn't cover. Nothing in it is code from the app it came from:
5
6
  // screens are A2UI components, the look is DTCG design tokens, rules are CEL
@@ -175,8 +176,8 @@ export const SpecSchema = z
175
176
  .default([]),
176
177
  /** The device features the app uses, by CloudCrane's names, each once with why; anything not listed is blocked. */
177
178
  uses: z
178
- .array(z.object({ what: z.enum(USE_NAMES), why: Text(300) }).strict())
179
- .max(USE_NAMES.length)
179
+ .array(z.object({ what: z.enum(DECLARABLE), why: Text(300) }).strict())
180
+ .max(DECLARABLE.length)
180
181
  .default([]),
181
182
  /**
182
183
  * What it does: each screen in words, and what it shows when there's nothing yet, while it loads, offline, and when
@@ -245,4 +246,11 @@ export const SpecSchema = z
245
246
  const needsOrigin = s.uses.some((u) => NEEDS_ORIGIN.includes(u.what));
246
247
  if (needsOrigin && !Object.values(s.components).some((c) => c.kit === "page"))
247
248
  ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["uses"], message: "the camera and microphone work only in a page-kit component" });
249
+ for (const u of s.uses)
250
+ if (PAGE_ONLY.includes(u.what) && !s.page)
251
+ ctx.addIssue({
252
+ code: z.ZodIssueCode.custom,
253
+ path: ["uses"],
254
+ message: `${u.what} works only in a page spec (an HTML app as a spec, with page set): CloudCrane gives it to the app's page, not to its screens or a coded component`,
255
+ });
248
256
  });
@@ -0,0 +1,6 @@
1
+ import { type Use } from "./uses.js";
2
+ export declare const PAGE_ONLY: readonly ["notifications"];
3
+ export type PageOnlyUse = (typeof PAGE_ONLY)[number];
4
+ /** Every name `uses` can declare: the device features, then what only a page spec can. */
5
+ export declare const DECLARABLE: [Use | PageOnlyUse, ...(Use | PageOnlyUse)[]];
6
+ export type Declarable = (typeof DECLARABLE)[number];
@@ -0,0 +1,8 @@
1
+ import { USE_NAMES } from "./uses.js";
2
+ // What only a page spec (an HTML app as a spec) can declare: CloudCrane gives it to the app's page, never to its
3
+ // screens or a frame. Kept out of uses.ts, which the runtime bundles (and locks by hash): none of these needs a
4
+ // browser feature, so the runtime has nothing to do with them.
5
+ // - notifications: reminders at a time, through CloudCrane's ccRemind.
6
+ export const PAGE_ONLY = ["notifications"];
7
+ /** Every name `uses` can declare: the device features, then what only a page spec can. */
8
+ export const DECLARABLE = [...USE_NAMES, ...PAGE_ONLY];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudcrane/app-spec",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "The CloudCrane app spec: its schema, the expression language its rules are written in, and the checks that keep partner code out of it.",
5
5
  "keywords": [
6
6
  "cloudcrane",
@@ -364,7 +364,8 @@
364
364
  "gamepad",
365
365
  "voice",
366
366
  "contacts",
367
- "nfc"
367
+ "nfc",
368
+ "notifications"
368
369
  ]
369
370
  },
370
371
  "why": {
@@ -379,7 +380,7 @@
379
380
  ],
380
381
  "additionalProperties": false
381
382
  },
382
- "maxItems": 9,
383
+ "maxItems": 10,
383
384
  "default": []
384
385
  },
385
386
  "screens": {