@brydio/manifest 0.1.0-alpha.6 → 0.1.0-alpha.61

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,7 +8,7 @@ import { z } from 'zod';
8
8
  */
9
9
  export const FIELD_LIMITS = {
10
10
  /** Collections one app may keep. */
11
- collections: 20,
11
+ collections: 40,
12
12
  /** Fields one collection may have. */
13
13
  fields: 40,
14
14
  /** Values one choice may allow. */
@@ -65,10 +65,12 @@ const STRUCTURED = new Set([
65
65
  'number',
66
66
  'boolean',
67
67
  'token',
68
+ // Another of the app's records, by id (DW06): an id, never words, so a list can name it.
69
+ 'ref',
68
70
  ]);
69
71
  /** Only the words in these may one day be searched (A3-F01-S02). */
70
72
  const SEARCHABLE = new Set(['string', 'text', 'string[]']);
71
- const SCALARS = new Set(['string', 'text', 'member', 'project', 'date', 'number', 'boolean', 'token']);
73
+ const SCALARS = new Set(['string', 'text', 'member', 'project', 'date', 'number', 'boolean', 'token', 'canvas', 'ref']);
72
74
  /** A field type the manifest wrote that Brydio does not have, or a bad list. */
73
75
  export class FieldTypeInvalid extends Error {
74
76
  code;
@@ -78,6 +80,10 @@ export class FieldTypeInvalid extends Error {
78
80
  this.name = 'FieldTypeInvalid';
79
81
  }
80
82
  }
83
+ /** The longest record id a `ref` field holds. */
84
+ export const REF_CHARS = 128;
85
+ /** What a record id looks like: letters, digits, `_` and `-`; never words. */
86
+ export const REF_FORMAT = /^[A-Za-z0-9_-]+$/;
81
87
  /**
82
88
  * One field's manifest spelling, as a type.
83
89
  *
@@ -99,18 +105,24 @@ export function parseFieldType(raw) {
99
105
  return { kind: 'string[]', optional };
100
106
  if (name === 'token')
101
107
  return { kind: 'token', optional, values: COLOUR_TOKENS };
108
+ // A drawing is always co-edited and never required: it starts empty (WB01).
109
+ if (name === 'canvas')
110
+ return { kind: 'canvas', optional: true, coedit: true };
102
111
  if (SCALARS.has(name))
103
112
  return { kind: name, optional };
104
113
  throw new FieldTypeInvalid('data_field_type_unknown', `"${raw}" is not a field type.`);
105
114
  }
106
- const FIELD_KEYS = new Set(['type', 'optional', 'default', 'labels']);
115
+ const FIELD_KEYS = new Set(['type', 'optional', 'default', 'labels', 'coedit']);
107
116
  /** How long a choice value's label may be. */
108
117
  export const LABEL_CHARS = 60;
109
118
  /** The object form: the short form under `type`, and what it may add. */
110
119
  function parseFieldObject(raw) {
111
120
  const unknown = Object.keys(raw).find(key => !FIELD_KEYS.has(key));
112
121
  if (unknown) {
113
- throw new FieldTypeInvalid('data_field_key_unknown', `"${unknown}" is not something a field may say; use type, optional, default and labels.`);
122
+ throw new FieldTypeInvalid('data_field_key_unknown', `"${unknown}" is not something a field may say; use type, optional, default, labels and coedit.`);
123
+ }
124
+ if (raw.coedit !== undefined && typeof raw.coedit !== 'boolean') {
125
+ throw new FieldTypeInvalid('data_field_key_unknown', '"coedit" is true or false.');
114
126
  }
115
127
  if (typeof raw.type !== 'string' && !Array.isArray(raw.type)) {
116
128
  throw new FieldTypeInvalid('data_field_type_unknown', 'A field written as an object names its type under "type".');
@@ -119,7 +131,12 @@ function parseFieldObject(raw) {
119
131
  throw new FieldTypeInvalid('data_field_key_unknown', '"optional" is true or false.');
120
132
  }
121
133
  const inner = parseFieldType(raw.type);
122
- const type = { ...inner, optional: inner.optional || raw.optional === true, ...labelsOf(inner, raw) };
134
+ const type = {
135
+ ...inner,
136
+ optional: inner.optional || raw.optional === true,
137
+ ...labelsOf(inner, raw),
138
+ ...coeditOf(inner, raw),
139
+ };
123
140
  if (!('default' in raw))
124
141
  return type;
125
142
  const value = raw.default;
@@ -157,6 +174,27 @@ function labelsOf(type, raw) {
157
174
  }
158
175
  return { labels: { ...labels } };
159
176
  }
177
+ /** `coedit`, on long text only: nothing else is typed into by several people at once. */
178
+ function coeditOf(type, raw) {
179
+ if (raw.coedit !== true)
180
+ return {};
181
+ if (type.kind !== 'text' && type.kind !== 'canvas') {
182
+ throw new FieldTypeInvalid('data_coedit_not_text', `Only a text or canvas field may be co-edited, not a ${type.kind}.`);
183
+ }
184
+ return { coedit: true };
185
+ }
186
+ /** True for a field several people write at once, through the co-editing layer. */
187
+ export const isCoedited = (type) => type.kind === 'canvas' || (type.kind === 'text' && type.coedit === true);
188
+ /**
189
+ * What a drawing's field holds in the record (WB01): a summary the host writes
190
+ * from the live board, so a list, search and the assistant can say what is on
191
+ * it. The drawing itself lives only in the co-edited document.
192
+ */
193
+ export const CANVAS_SUMMARY_CHARS = 10_000;
194
+ /** A collection's co-edited fields, by name. */
195
+ export const coeditedFields = (fields) => Object.entries(fields)
196
+ .filter(([, type]) => isCoedited(type))
197
+ .map(([field]) => field);
160
198
  /** How a choice's value reads to a person: its label, or the value itself. */
161
199
  export const labelOfValue = (type, value) => type.labels?.[value] ?? value;
162
200
  function parseEnumeration(values) {
@@ -177,9 +215,9 @@ function parseEnumeration(values) {
177
215
  return { kind: 'enum', optional: false, values: values };
178
216
  }
179
217
  /** True for a type whose value lands in the plain `fields` column. */
180
- export const isStructured = (type) => STRUCTURED.has(type.kind);
218
+ export const isStructured = (type) => STRUCTURED.has(type.kind) || type.plain === true;
181
219
  /** True for a type a list can be ordered by: structured, and one value. */
182
- export const isSortable = (type) => STRUCTURED.has(type.kind);
220
+ export const isSortable = (type) => STRUCTURED.has(type.kind) || (type.plain === true && type.kind !== 'string[]');
183
221
  export const isSearchable = (type) => SEARCHABLE.has(type.kind);
184
222
  /**
185
223
  * The names of a schema's structured fields: the ones kept in plain beside the
@@ -227,7 +265,11 @@ export function valueSchema(type) {
227
265
  case 'boolean':
228
266
  return z.boolean();
229
267
  case 'string[]':
230
- return z.array(z.string().max(FIELD_LIMITS.stringChars)).max(FIELD_LIMITS.listEntries);
268
+ return z.array(z.string().max(FIELD_LIMITS.stringChars)).max(type.maxEntries ?? FIELD_LIMITS.listEntries);
269
+ case 'canvas':
270
+ return z.object({ elements: z.number().int().min(0), text: z.string().max(CANVAS_SUMMARY_CHARS) }).strict();
271
+ case 'ref':
272
+ return z.string().min(1).max(REF_CHARS).regex(REF_FORMAT);
231
273
  }
232
274
  }
233
275
  /**
@@ -260,9 +302,13 @@ export function valueProblem(field, type, value) {
260
302
  case 'boolean':
261
303
  return `${field} must be true or false.`;
262
304
  case 'string[]':
263
- return Array.isArray(value) && value.length > FIELD_LIMITS.listEntries
264
- ? `${field} may hold at most ${FIELD_LIMITS.listEntries} entries.`
305
+ return Array.isArray(value) && value.length > (type.maxEntries ?? FIELD_LIMITS.listEntries)
306
+ ? `${field} may hold at most ${type.maxEntries ?? FIELD_LIMITS.listEntries} entries.`
265
307
  : `${field} must be a list of short texts, each at most ${FIELD_LIMITS.stringChars.toLocaleString('en-GB')} characters.`;
308
+ case 'canvas':
309
+ return `${field} is a drawing: it is changed on its board, never written.`;
310
+ case 'ref':
311
+ return `${field} must be the id of one of this app's records.`;
266
312
  }
267
313
  }
268
314
  /** A type in a few words, for a tool's description: every allowed value named. */
@@ -288,6 +334,10 @@ export function describeType(type) {
288
334
  return 'true or false';
289
335
  case 'string[]':
290
336
  return 'a list of short texts';
337
+ case 'canvas':
338
+ return 'a drawing, read only: how many shapes it has and the words on it';
339
+ case 'ref':
340
+ return "the id of another of this app's records";
291
341
  }
292
342
  }
293
343
  /** ", read as \"todo\" is To do, \"done\" is Done", for a choice with labels. */
@@ -0,0 +1,13 @@
1
+ import type { BundleFiles } from './bundle.js';
2
+ /**
3
+ * What a publish warns about in a frame screen's own files (PB27), and never
4
+ * refuses: a password field, and a font of its own. A copy of Brydio's
5
+ * `frameWarnings` (`apps/api/src/apps/publishing/frame-checks.ts`), in its
6
+ * words, so `brydio validate` says what the registry will.
7
+ */
8
+ export interface FrameWarning {
9
+ code: 'frame_password_field' | 'frame_font';
10
+ message: string;
11
+ at: string;
12
+ }
13
+ export declare function frameWarnings(manifest: Record<string, unknown>, files: BundleFiles): FrameWarning[];
@@ -0,0 +1,35 @@
1
+ import { framePagesOf } from "./screen-kinds.js";
2
+ const text = (bytes) => (bytes ? new TextDecoder().decode(bytes) : '');
3
+ const PASSWORD_IN_PAGE = /<input\b[^>]*\btype\s*=\s*["']?password\b/i;
4
+ const PASSWORD_IN_SCRIPT = /\btype\s*[:=]\s*["']password["']|setAttribute\(\s*["']type["']\s*,\s*["']password["']\)/;
5
+ const FONT_FACE = /@font-face\b/i;
6
+ export function frameWarnings(manifest, files) {
7
+ const pages = framePagesOf(manifest);
8
+ const screens = (manifest.screens ?? {});
9
+ const warnings = [];
10
+ for (const [screen, declared] of Object.entries(screens)) {
11
+ const entry = declared?.entry;
12
+ if (typeof entry !== 'string' || !pages.has(entry))
13
+ continue;
14
+ const folder = entry.includes('/') ? entry.slice(0, entry.lastIndexOf('/') + 1) : '';
15
+ const scripts = [...files.keys()].filter(path => path !== entry && path.startsWith(folder) && !path.slice(folder.length).includes('/') && /\.m?js$/.test(path));
16
+ const password = PASSWORD_IN_PAGE.test(text(files.get(entry))) ? entry : scripts.find(path => PASSWORD_IN_SCRIPT.test(text(files.get(path))));
17
+ if (password) {
18
+ warnings.push({
19
+ code: 'frame_password_field',
20
+ at: password,
21
+ message: `The "${screen}" frame screen has a password field (${password}). An app never asks for a Brydio password or sign-in; a reviewer will ask why it is there.`,
22
+ });
23
+ }
24
+ }
25
+ for (const path of [...files.keys()].filter(one => one.endsWith('.css') || pages.has(one))) {
26
+ if (FONT_FACE.test(text(files.get(path)))) {
27
+ warnings.push({
28
+ code: 'frame_font',
29
+ at: path,
30
+ message: `${path} declares a font of its own. A frame screen loads no fonts; every app uses Brydio's own, so this one is never shown.`,
31
+ });
32
+ }
33
+ }
34
+ return warnings;
35
+ }
package/src/grants.d.ts CHANGED
@@ -6,8 +6,20 @@
6
6
  * `apps/api/src/apps/manifest/grants.ts`, and the `grant_unknown` sentence in
7
7
  * `apps/api/src/apps/publishing/app-publish.service.ts`.
8
8
  */
9
- /** The host grants with a name of their own. A connection's is `connection:<name>`. */
10
- export declare const HOST_CAPABILITIES: readonly ["navigate", "message", "members", "projects", "model"];
9
+ /**
10
+ * The host grants with a name of their own. A connection's is `connection:<name>`.
11
+ *
12
+ * `chats` and `chat` differ: `chats` is the typed Brydio API's reach into the
13
+ * person's assistant conversations; `chat` is a handler's `chat.post`, the
14
+ * app posting one of its cards into a workspace room (FO03). `directory` is
15
+ * a handler's `directory.*` and a screen's `useProfile` (FO02); `webhooks`
16
+ * is a handler's `webhooks.send` (FO07); `timers` is a handler's
17
+ * `timers.*`, running one of the app's own tools later (PJ01); `calendar`
18
+ * is a handler's `calendar.*`, the caller's events on the event layer (CA02).
19
+ * `people` is a handler's native HR operations as the current caller; it does
20
+ * not broaden `directory` access or authorize private records by itself.
21
+ */
22
+ export declare const HOST_CAPABILITIES: readonly ["navigate", "message", "members", "projects", "files", "chats", "model", "secrets", "notify", "approvals", "chat", "directory", "webhooks", "timers", "calendar", "metrics", "sign", "finance", "people"];
11
23
  /** The older name for `HOST_CAPABILITIES`, kept for what already imports it. */
12
24
  export declare const KNOWN_HOST_GRANTS: readonly string[];
13
25
  /** True for a host grant Brydio knows how to honour: a named capability, or one connection. */
package/src/grants.js CHANGED
@@ -7,8 +7,40 @@
7
7
  * `apps/api/src/apps/publishing/app-publish.service.ts`.
8
8
  */
9
9
  import { BUNDLE_MANIFEST } from "./bundle.js";
10
- /** The host grants with a name of their own. A connection's is `connection:<name>`. */
11
- export const HOST_CAPABILITIES = ['navigate', 'message', 'members', 'projects', 'model'];
10
+ /**
11
+ * The host grants with a name of their own. A connection's is `connection:<name>`.
12
+ *
13
+ * `chats` and `chat` differ: `chats` is the typed Brydio API's reach into the
14
+ * person's assistant conversations; `chat` is a handler's `chat.post`, the
15
+ * app posting one of its cards into a workspace room (FO03). `directory` is
16
+ * a handler's `directory.*` and a screen's `useProfile` (FO02); `webhooks`
17
+ * is a handler's `webhooks.send` (FO07); `timers` is a handler's
18
+ * `timers.*`, running one of the app's own tools later (PJ01); `calendar`
19
+ * is a handler's `calendar.*`, the caller's events on the event layer (CA02).
20
+ * `people` is a handler's native HR operations as the current caller; it does
21
+ * not broaden `directory` access or authorize private records by itself.
22
+ */
23
+ export const HOST_CAPABILITIES = [
24
+ 'navigate',
25
+ 'message',
26
+ 'members',
27
+ 'projects',
28
+ 'files',
29
+ 'chats',
30
+ 'model',
31
+ 'secrets',
32
+ 'notify',
33
+ 'approvals',
34
+ 'chat',
35
+ 'directory',
36
+ 'webhooks',
37
+ 'timers',
38
+ 'calendar',
39
+ 'metrics',
40
+ 'sign',
41
+ 'finance',
42
+ 'people',
43
+ ];
12
44
  /** The older name for `HOST_CAPABILITIES`, kept for what already imports it. */
13
45
  export const KNOWN_HOST_GRANTS = HOST_CAPABILITIES;
14
46
  const CONNECTION = /^connection:[a-z0-9][a-z0-9_-]{0,59}$/;
package/src/index.d.ts CHANGED
@@ -3,15 +3,23 @@
3
3
  * types, the tools Brydio generates from them, and the bundle rules. Each is
4
4
  * a copy of the server's own, so an app that passes here passes there.
5
5
  */
6
- export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema } from './base.js';
7
- export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, bundleBytes, bundleHash, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, type BundleFiles, type BundleProblem, type BundleProblemCode, } from './bundle.js';
8
- export { COLOUR_TOKENS, FIELD_LIMITS, RESERVED_FIELDS, describeType, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, type DocumentOf, type FieldKind, type FieldType, type FieldValue, type FieldsOf, } from './field-types.js';
9
- export { appManifestSchema, collectionOf, collectionsOf, dataProblems, labelOf, manifestExtensionsSchema, storedExtensionsSchema, type AppManifestWithData, type CollectionSpec, type DataProblem, type DataProblemCode, type ManifestExtensions, } from './schema.js';
6
+ export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema, brandImagesSchema } from './base.js';
7
+ export { BRAND_FIELDS, BRAND_LIMITS, brandFilePath, brandPathsOf, brandProblems, brandShapeOf, isBrandPath, pngVerdict, svgColours, toolsProblem, type BrandField, type RequirementCode, type RequirementFiles, type RequirementProblem, } from './requirements.js';
8
+ export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, FRAME_ASSETS_MAX_BYTES, FRAME_ASSET_TYPES, bundleBytes, contentTypeOf, frameAssetBytes, isFrameAsset, bundleHash, brandOf, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, type BundleFiles, type BundleProblem, type BundleProblemCode, } from './bundle.js';
9
+ export { COLOUR_TOKENS, coeditedFields, FIELD_LIMITS, RESERVED_FIELDS, describeType, isCoedited, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, type DocumentOf, type FieldKind, type FieldType, type FieldValue, type FieldsOf, } from './field-types.js';
10
+ export { appManifestSchema, appToolPrefix, collectionOf, crossAppTool, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, type AppManifestWithData, type CollectionSpec, type DataProblem, type DataProblemCode, type DataProblemOptions, type HomeCardSize, type ManifestExtensions, type SecretSpec, } from './schema.js';
11
+ export { OPEN_LIMITS, OPEN_TYPES, choicesProblem, definitionOf, definitionTypeProblem, describeField, fieldTypeOf, hasChoices, isPlainOpenType, keyFromName, keyProblem, missingRequired, moveValue, openSchemaProblems, openSpec, openValueProblem, type Moved, type OpenField, type OpenFieldChange, type OpenFieldValue, type OpenProblem, type OpenProblemCode, type OpenSchemaFlag, type OpenType, } from './open-schema.js';
12
+ export { CONFIRM_LIMITS, confirmEmailProblems, confirmEmailSpec, type ConfirmEmailDeclared, type ConfirmEmailSpec, type ConfirmProblemCode, } from './confirm-email.js';
13
+ export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, type AnonymousDeclared, type AnonymousProblemCode, type AnonymousSpec, } from './anonymous.js';
14
+ export { editorsOf, editorsProblems, READERS_LIMIT, readersOf, readersProblems, type EditorsProblemCode, type ReadersProblemCode } from './readers.js';
10
15
  export { DOCUMENT_LIMITS } from './document-limits.js';
11
16
  export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from './grants.js';
12
17
  export { diffSchemas, migrationProblems, migrationSchema, migrationStepSchema, migrationsSchema, publishedMigrationProblems, schemaOf, type Migration, type MigrationCode, type MigrationProblem, type MigrationStep, type SchemaChange, } from './migrations.js';
13
- export { SECRET_MESSAGE, findSecrets, secretsInJson, type SecretFound } from './secrets.js';
18
+ export { DEVELOPER_KEY_MESSAGE, SECRET_MESSAGE, developerKeyIn, findSecrets, holdsDeveloperKey, secretsInJson, type SecretFound, } from './secrets.js';
14
19
  export { compareVersions, sdkRefusal, type SdkSupport } from './sdk.js';
15
20
  export { TOOL_WRITES, generatedToolsOf, toolNames, type GeneratedTool, type ToolVerb } from './tools.js';
16
21
  export { validateManifest, validateManifestText, type ManifestProblem, type ManifestProblemCode, type ManifestValidation, } from './validate.js';
17
22
  export { defineManifest, type ManifestShape, type ScreenNameOf, type WithDeclaredScreens } from './define.js';
23
+ export { themeProblems, themeWarnings, themeOf, styleRefusal, colourRefusal, type AppTheme, type ColourPair, type LookProblem, type LookProblemCode, type LookWarningCode, } from './look.js';
24
+ export { SCREEN_KINDS, framePagesOf, screenKindOf, screensOfKind, type ScreenKind } from './screen-kinds.js';
25
+ export { frameWarnings, type FrameWarning } from './frame-checks.js';
package/src/index.js CHANGED
@@ -3,15 +3,25 @@
3
3
  * types, the tools Brydio generates from them, and the bundle rules. Each is
4
4
  * a copy of the server's own, so an app that passes here passes there.
5
5
  */
6
- export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema } from "./base.js";
7
- export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, bundleBytes, bundleHash, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, } from "./bundle.js";
8
- export { COLOUR_TOKENS, FIELD_LIMITS, RESERVED_FIELDS, describeType, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, } from "./field-types.js";
9
- export { appManifestSchema, collectionOf, collectionsOf, dataProblems, labelOf, manifestExtensionsSchema, storedExtensionsSchema, } from "./schema.js";
6
+ export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema, brandImagesSchema } from "./base.js";
7
+ export { BRAND_FIELDS, BRAND_LIMITS, brandFilePath, brandPathsOf, brandProblems, brandShapeOf, isBrandPath, pngVerdict, svgColours, toolsProblem, } from "./requirements.js";
8
+ export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, FRAME_ASSETS_MAX_BYTES, FRAME_ASSET_TYPES, bundleBytes, contentTypeOf, frameAssetBytes, isFrameAsset, bundleHash, brandOf, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, } from "./bundle.js";
9
+ export { COLOUR_TOKENS, coeditedFields, FIELD_LIMITS, RESERVED_FIELDS, describeType, isCoedited, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, } from "./field-types.js";
10
+ export { appManifestSchema, appToolPrefix, collectionOf, crossAppTool, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, } from "./schema.js";
11
+ export { OPEN_LIMITS, OPEN_TYPES, choicesProblem, definitionOf, definitionTypeProblem, describeField, fieldTypeOf, hasChoices, isPlainOpenType, keyFromName, keyProblem, missingRequired, moveValue, openSchemaProblems, openSpec, openValueProblem, } from "./open-schema.js";
12
+ export { CONFIRM_LIMITS, confirmEmailProblems, confirmEmailSpec, } from "./confirm-email.js";
13
+ export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, } from "./anonymous.js";
14
+ export { editorsOf, editorsProblems, READERS_LIMIT, readersOf, readersProblems } from "./readers.js";
10
15
  export { DOCUMENT_LIMITS } from "./document-limits.js";
11
16
  export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from "./grants.js";
12
17
  export { diffSchemas, migrationProblems, migrationSchema, migrationStepSchema, migrationsSchema, publishedMigrationProblems, schemaOf, } from "./migrations.js";
13
- export { SECRET_MESSAGE, findSecrets, secretsInJson } from "./secrets.js";
18
+ export { DEVELOPER_KEY_MESSAGE, SECRET_MESSAGE, developerKeyIn, findSecrets, holdsDeveloperKey, secretsInJson, } from "./secrets.js";
14
19
  export { compareVersions, sdkRefusal } from "./sdk.js";
15
20
  export { TOOL_WRITES, generatedToolsOf, toolNames } from "./tools.js";
16
21
  export { validateManifest, validateManifestText, } from "./validate.js";
17
22
  export { defineManifest } from "./define.js";
23
+ // PB21, PB22: the look rules, Brydio's one rule set, copied.
24
+ export { themeProblems, themeWarnings, themeOf, styleRefusal, colourRefusal, } from "./look.js";
25
+ // PB27: tree screens and frame screens.
26
+ export { SCREEN_KINDS, framePagesOf, screenKindOf, screensOfKind } from "./screen-kinds.js";
27
+ export { frameWarnings } from "./frame-checks.js";
package/src/look.d.ts ADDED
@@ -0,0 +1,196 @@
1
+ /**
2
+ * How an app may look: its theme (`tasks/app-publishing` PB21) and the
3
+ * `style` any element takes (PB22).
4
+ *
5
+ * A copy of Brydio's rule set, `apps/api/src/apps/manifest/app-look.ts`, which
6
+ * is the one source: a publish and the registry read it there, the host
7
+ * checks every node it draws with its own copy, and `brydio validate`, the
8
+ * catalogue's checks (`@brydio/ui/validate`) and the fake host read this
9
+ * one. `test/look.test.ts` holds
10
+ * this copy to Brydio's answers, sentence for sentence, whenever a Brydio
11
+ * checkout sits beside this repository, and to the recorded answers in
12
+ * `contracts/brydio.json` otherwise. It imports nothing.
13
+ */
14
+ /** A name an app gives one of its colours: `ocean`, read as `app.ocean`. */
15
+ export declare const APP_COLOUR_NAME: RegExp;
16
+ /** The most named colours a theme may hold. */
17
+ export declare const MAX_APP_COLOURS = 24;
18
+ /** A plain colour: `#rgb` or `#rrggbb`. */
19
+ export declare const HEX_COLOUR: RegExp;
20
+ /** WCAG AA for body text. */
21
+ export declare const TEXT_CONTRAST = 4.5;
22
+ /**
23
+ * Brydio's own colours an app may name in a style, each to the token it
24
+ * reads. They follow light and dark by themselves.
25
+ */
26
+ export declare const BRYDIO_COLOURS: Readonly<Record<string, string>>;
27
+ /**
28
+ * What a check reads a Brydio colour as, to judge contrast on a screenshot.
29
+ * Near enough to the tokens to judge by; never drawn.
30
+ */
31
+ export declare const BRYDIO_COLOUR_CHECK_VALUES: Readonly<Record<'light' | 'dark', Readonly<Record<string, string>>>>;
32
+ /** Where Brydio draws an app: the ground a screenshot check reads text against. */
33
+ export declare const GROUND: {
34
+ readonly light: "#ffffff";
35
+ readonly dark: "#212121";
36
+ };
37
+ /** A colour with a value for each theme. */
38
+ export interface ColourPair {
39
+ light: string;
40
+ dark: string;
41
+ }
42
+ export declare const THEME_DENSITIES: readonly ["compact", "normal", "roomy"];
43
+ export declare const THEME_ROUNDINGS: readonly ["none", "small", "normal", "large", "round"];
44
+ export type ThemeDensity = (typeof THEME_DENSITIES)[number];
45
+ export type ThemeRounding = (typeof THEME_ROUNDINGS)[number];
46
+ /** How much each density scales Brydio's spacing and control heights. */
47
+ export declare const DENSITY_SCALE: Readonly<Record<ThemeDensity, number>>;
48
+ /** How much each rounding scales Brydio's corner radii. */
49
+ export declare const ROUNDING_SCALE: Readonly<Record<ThemeRounding, number>>;
50
+ /** An app's theme, as the manifest gives it (PB21). */
51
+ export interface AppTheme {
52
+ /** What the `brand` tone becomes inside the app. */
53
+ accent?: ColourPair;
54
+ /** Named colours, read as `app.<name>`. */
55
+ colors?: Record<string, ColourPair>;
56
+ rounding?: ThemeRounding;
57
+ density?: ThemeDensity;
58
+ }
59
+ export type LookProblemCode = 'theme_invalid' | 'theme_font' | 'theme_colour_pair' | 'theme_colour_format' | 'theme_colour_name' | 'theme_colours_too_many' | 'theme_density' | 'theme_rounding';
60
+ export type LookWarningCode = 'theme_contrast_low';
61
+ /** One thing wrong with a theme, named down to the field. */
62
+ export interface LookProblem {
63
+ code: LookProblemCode | LookWarningCode;
64
+ message: string;
65
+ /** Inside the manifest: `theme.colors.ocean.dark`. */
66
+ path: string;
67
+ }
68
+ /** Everything a publish refuses about a theme. Empty when it is fine. */
69
+ export declare function themeProblems(theme: unknown): LookProblem[];
70
+ /**
71
+ * What a publish warns about a theme, and never refuses: a colour whose best
72
+ * text, white or near-black, falls below WCAG AA on it.
73
+ */
74
+ export declare function themeWarnings(theme: unknown): LookProblem[];
75
+ /** The theme as code reads it after `themeProblems` found nothing. */
76
+ export declare function themeOf(manifest: unknown): AppTheme | null;
77
+ /** `#abc` → `[170, 187, 204]`, or null. */
78
+ export declare function rgbOf(hex: string): [number, number, number] | null;
79
+ /** WCAG contrast between two plain colours; 1 when either isn't one. */
80
+ export declare function contrastOf(a: string, b: string): number;
81
+ /** The text colour that reads best on a fill: white or near-black. */
82
+ export declare function onColour(fill: string): string;
83
+ /** How long a style may be, so a node can't carry a stylesheet. */
84
+ export declare const STYLE_MAX_KEYS = 48;
85
+ export declare const STYLE_MAX_BYTES: number;
86
+ /** The furthest a length may reach, in pixels. */
87
+ export declare const LENGTH_MAX = 10000;
88
+ /** Brydio's spacing steps an app may name: `space.4`. */
89
+ export declare const SPACE_MAX = 16;
90
+ /** The highest layer inside an app's area. The area starts its own, so this is all an app needs. */
91
+ export declare const Z_INDEX_MAX = 10;
92
+ export declare const BRYDIO_RADII: readonly ["xs", "sm", "md", "lg", "xl", "2xl", "3xl"];
93
+ export declare const BRYDIO_SHADOWS: readonly ["xs", "sm", "md", "lg"];
94
+ export declare const TRANSITIONS: readonly ["none", "fast", "base", "slow"];
95
+ type Rule = {
96
+ kind: 'size';
97
+ } | {
98
+ kind: 'space';
99
+ negative: boolean;
100
+ sides: boolean;
101
+ } | {
102
+ kind: 'offset';
103
+ } | {
104
+ kind: 'colour';
105
+ } | {
106
+ kind: 'enum';
107
+ values: readonly string[];
108
+ } | {
109
+ kind: 'number';
110
+ min: number;
111
+ max: number;
112
+ integer?: boolean;
113
+ } | {
114
+ kind: 'border-width';
115
+ } | {
116
+ kind: 'radius';
117
+ } | {
118
+ kind: 'shadow';
119
+ } | {
120
+ kind: 'font-size';
121
+ } | {
122
+ kind: 'font-weight';
123
+ } | {
124
+ kind: 'line-height';
125
+ } | {
126
+ kind: 'flex';
127
+ } | {
128
+ kind: 'tracks';
129
+ } | {
130
+ kind: 'grid-line';
131
+ } | {
132
+ kind: 'position';
133
+ } | {
134
+ kind: 'z-index';
135
+ } | {
136
+ kind: 'image';
137
+ } | {
138
+ kind: 'transition';
139
+ };
140
+ /** Every property a style may set (PB22), and what it takes. Nothing else is drawn. */
141
+ export declare const STYLE_PROPERTIES: Readonly<Record<string, Rule>>;
142
+ /** Properties people reach for that are refused on purpose, with the rule that refuses them. */
143
+ export declare const STYLE_REFUSED: Readonly<Record<string, string>>;
144
+ /**
145
+ * Why a colour can't be drawn, or null. A colour is one of Brydio's
146
+ * (`brydio.text`), one of the app's (`app.ocean`), `transparent`,
147
+ * `currentColor`, or a plain pair `{ light, dark }`. `colours`, when given,
148
+ * are the app's own names, so a name it never declared is refused too.
149
+ */
150
+ export declare function colourRefusal(value: unknown, colours?: ReadonlySet<string>): string | null;
151
+ /**
152
+ * Why a node's `style` can't be drawn, or null when it can (PB22). The
153
+ * sentence names the property and the rule: "style position can’t be fixed
154
+ * or sticky: …".
155
+ */
156
+ export declare function styleRefusal(style: unknown, colours?: ReadonlySet<string>): string | null;
157
+ /** The app colour names a theme declares, for `colourRefusal`. */
158
+ export declare function colourNamesOf(theme: AppTheme | null | undefined): Set<string>;
159
+ /** The app colour a tone names (`app.ocean` → `ocean`), or null when it names a Brydio tone. */
160
+ export declare const appColourOfTone: (tone: unknown) => string | null;
161
+ export type Mode = 'light' | 'dark';
162
+ /**
163
+ * The plain colour a value is in this mode, for a check: an app colour or a
164
+ * pair to its hex, a Brydio colour to the value checks read it as, otherwise
165
+ * null.
166
+ */
167
+ export declare function checkColourOf(value: unknown, mode: Mode, theme: AppTheme | null | undefined): string | null;
168
+ /** The CSS a colour is drawn with in this mode. */
169
+ export declare function cssColourOf(value: unknown, mode: Mode): string | null;
170
+ /**
171
+ * A checked style as CSS declarations, for this mode. Only what passed
172
+ * `styleRefusal` is ever given here; anything it can't read is left out.
173
+ * `backgroundImage` is the host's to draw, since only it can read the file.
174
+ */
175
+ export declare function styleToCss(style: Record<string, unknown>, mode: Mode): Record<string, string>;
176
+ /**
177
+ * The CSS variables an app's area carries for its theme in this mode: its
178
+ * colours as `--app-color-<name>`, the accent as Brydio's brand tokens (so
179
+ * the `brand` tone is the app's), and the rounding and density as scales of
180
+ * Brydio's own radii and spacing.
181
+ */
182
+ export declare function themeVariables(theme: AppTheme | null | undefined, mode: Mode): Record<string, string>;
183
+ /**
184
+ * Whether a setting may name one of the app's colours as well as its own
185
+ * values (PB21): a `tone` that offers Brydio's `brand`.
186
+ */
187
+ export declare const takesAppColour: (name: string, values: readonly string[]) => boolean;
188
+ /** What a tone that names an app colour is told when it isn't one. */
189
+ export declare const appColourSaid = "or one of the app\u2019s colours (app.<name>)";
190
+ /**
191
+ * Why a `bry-canvas`'s shapes can't be drawn, or null (PB24). Their shape is
192
+ * the catalogue's to check; this is their colours, which are names only —
193
+ * Brydio's or the app's — so a canvas follows light and dark.
194
+ */
195
+ export declare function canvasRefusal(ops: unknown, colours?: ReadonlySet<string>): string | null;
196
+ export {};