@valbuild/core 0.127.0 → 0.129.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.
@@ -11,7 +11,7 @@ import type { ImageEncodeOption } from "./image.js";
11
11
  type MediaOptions = {
12
12
  type: "files" | "images";
13
13
  accept: string;
14
- directory: string;
14
+ dir: string;
15
15
  remote: boolean;
16
16
  altSchema?: Schema<SelectorSource>;
17
17
  /** Images only: how uploads are re-encoded in the browser. See `image.ts`. */
@@ -34,7 +34,7 @@ export type SerializedRecordSchema = {
34
34
  customValidate?: boolean;
35
35
  mediaType?: "files" | "images";
36
36
  accept?: string;
37
- directory?: string;
37
+ dir?: string;
38
38
  remote?: boolean;
39
39
  encode?: ImageEncodeOption;
40
40
  alt?: SerializedSchema;
@@ -229,7 +229,7 @@ RO extends boolean = false> extends Schema<Src> {
229
229
  * Allow the files in this gallery to be stored on Val's remote content host
230
230
  * instead of in your repository.
231
231
  *
232
- * For `s.images()` and `s.files()`, which are records of media. Remote is off
232
+ * For `s.imageset()` and `s.fileset()`, which are records of media. Remote is off
233
233
  * until this is called.
234
234
  *
235
235
  * Each entry is then keyed by its URL on the content host rather than by a
@@ -238,7 +238,7 @@ RO extends boolean = false> extends Schema<Src> {
238
238
  * upload it and rewrite the key.
239
239
  *
240
240
  * @example
241
- * const schema = s.images({ directory: "/public/val/images" }).remote();
241
+ * const schema = s.imageset({ dir: "/public/val/images" }).remote();
242
242
  * export default c.define("/content/images.val.ts", schema, {
243
243
  * "https://remote.val.build/file/p/my-project/b/01/v/1.0.0/h/8f2a1c/f/3b9d70/p/public/val/images/hero.webp":
244
244
  * {
@@ -257,7 +257,7 @@ RO extends boolean = false> extends Schema<Src> {
257
257
  * which lets the runtime, the Studio and validation work one entry at a time
258
258
  * so a record/router can scale to many thousands of entries.
259
259
  *
260
- * Not supported on image/file galleries (`s.images()` / `s.files()`).
260
+ * Not supported on image/file galleries (`s.imageset()` / `s.fileset()`).
261
261
  *
262
262
  * Only supported on a module's ROOT record/router — a `.jsonValues()` record
263
263
  * nested inside an object/array/record is rejected at startup with a module
@@ -284,7 +284,7 @@ RO extends boolean = false> extends Schema<Src> {
284
284
  * bucket — instead of in the module.
285
285
  *
286
286
  * Available on every record-derived schema, which is `s.record()`,
287
- * `s.router()`, `s.images()` and `s.files()` alike: a router is a
287
+ * `s.router()`, `s.imageset()` and `s.fileset()` alike: a router is a
288
288
  * `RecordSchema` with a `ValRouter`, a gallery one with media options, so all
289
289
  * four get external storage from this one method.
290
290
  *
@@ -141,11 +141,18 @@ export declare class SettingsSchema<Src extends {
141
141
  * context: "Val is a CMS for developers. British English, and 'Val' is never 'VAL'.",
142
142
  * tone: "Plain and direct. No exclamation marks, sentence case in headings.",
143
143
  * },
144
+ * theme: {
145
+ * accent: "#2563eb",
146
+ * radius: "tight",
147
+ * logo: { path: "/public/val/brand/mark_a1b2c.png", width: 512, height: 512, mimeType: "image/png" },
148
+ * },
144
149
  * });
145
150
  * ```
146
151
  *
147
- * The Studio edits it under the cog at the foot of the left rail, and the
148
- * assistant is told `assistant.context` and `assistant.tone` on every message.
152
+ * The Studio edits it under the cog at the foot of the left rail. The assistant
153
+ * is told `assistant.context` and `assistant.tone` on every message, and
154
+ * `theme` restyles the Studio's own chrome — see {@link ThemeSettingsSource},
155
+ * and note that it is the CMS being restyled, not the site.
149
156
  *
150
157
  * `s.settings()` takes no arguments: the shape is Val's, which is what lets the
151
158
  * Studio render a UI built for each field rather than a generic form. A
@@ -1,3 +1,4 @@
1
+ import { ImageSource } from "./media.js";
1
2
  /**
2
3
  * The source of the project's settings module — `s.settings()`.
3
4
  *
@@ -22,8 +23,97 @@
22
23
  */
23
24
  export type SettingsSource = {
24
25
  assistant?: AssistantSettingsSource;
26
+ theme?: ThemeSettingsSource;
25
27
  locales?: LocalesSettingsSource;
26
28
  };
29
+ /**
30
+ * How the Studio looks in this project.
31
+ *
32
+ * Chrome, and only chrome: nothing here reaches a visitor to the site. It is
33
+ * the CMS that is being restyled, so that a project can make the tool it edits
34
+ * in feel like its own.
35
+ *
36
+ * This is deliberately NOT the light/dark switch. That one is per-person and
37
+ * per-machine (see `ValThemeProvider`, which keeps it in `localStorage`), and it
38
+ * has to stay that way — one editor working in a dark room is not a fact about
39
+ * the project. {@link ThemeSettingsSource.mode} is the project's DEFAULT for
40
+ * someone who has not chosen, which is a different statement.
41
+ */
42
+ export type ThemeSettingsSource = {
43
+ /**
44
+ * The one colour the Studio's chrome is built from, as a hex string.
45
+ *
46
+ * Unset means Val's own green. What it replaces is not a single value but the
47
+ * whole ten-step brand ramp — `--colors-brand-green-100` through `-1000` —
48
+ * because every brand token in `index.css` points into that ramp, and light
49
+ * and dark pick different steps out of it. One accent therefore drives both
50
+ * modes, with nothing to keep in sync.
51
+ *
52
+ * Any hex is allowed rather than a list of approved ones, and that is safe
53
+ * for a measured reason rather than an optimistic one: the ramp is generated
54
+ * by reusing green's LIGHTNESS at each step and changing only the hue, and
55
+ * WCAG contrast is almost entirely a function of lightness. See
56
+ * `accentRamp` in `@valbuild/shared`, whose tests hold every
57
+ * foreground/background pair the chrome renders to AA across the hue circle.
58
+ */
59
+ accent?: string | null;
60
+ /**
61
+ * How round the Studio's corners are.
62
+ *
63
+ * Named steps rather than a length, for two reasons: a number input invites
64
+ * `7px`, which nothing in the chrome is drawn around, and the scale is Val's
65
+ * decision rather than the project's. See {@link THEME_RADIUS_LENGTHS} for
66
+ * what each one is worth.
67
+ */
68
+ radius?: ThemeRadius | null;
69
+ /**
70
+ * The mode the Studio opens in for someone who has not picked one.
71
+ *
72
+ * A default, not a setting: an editor who has flicked the switch behind the
73
+ * account button keeps their choice, and this never overrides it.
74
+ */
75
+ mode?: "dark" | "light" | null;
76
+ /**
77
+ * The project's own mark, shown where Val's is in the Studio.
78
+ *
79
+ * The top of the left rail on desktop, and beside the menu button on mobile.
80
+ * NOT the launcher that floats on the project's own site: there the mark says
81
+ * "this is Val", and a project's logo on its own page says nothing at all —
82
+ * see {@link THEME_LOGO_DIRECTORY} and `architecture/logo.md`.
83
+ *
84
+ * A square-ish mark rather than a wordmark, because the slot it goes in is
85
+ * 32px wide. A wide image is contained rather than cropped, so nothing is
86
+ * cut off — it is simply small.
87
+ */
88
+ logo?: ImageSource | null;
89
+ };
90
+ /**
91
+ * Where an uploaded logo goes.
92
+ *
93
+ * Its own directory rather than the `/public/val` default, so that the one
94
+ * image a project uploads through the settings panel does not land in the
95
+ * middle of its content's media. `val list-unused-files` reads what is
96
+ * referenced rather than where it sits, so nothing depends on this beyond
97
+ * tidiness.
98
+ */
99
+ export declare const THEME_LOGO_DIRECTORY = "/public/val/brand";
100
+ /** @see {@link ThemeSettingsSource.radius} */
101
+ export type ThemeRadius = "square" | "tight" | "default" | "soft";
102
+ export declare const THEME_RADIUS_STEPS: readonly ThemeRadius[];
103
+ /**
104
+ * What each radius step is worth, as a `--radius` value.
105
+ *
106
+ * `--radius` is the only length in the Studio's chrome that is a token:
107
+ * `rounded-sm`, `rounded-md` and `rounded-lg` are all `calc(var(--radius) …)`
108
+ * in `tailwind.config.js`, which is around three hundred call sites moving on
109
+ * one value. `default` is what `index.css` declares, so selecting it and
110
+ * clearing the setting look the same — which is what makes this list the whole
111
+ * of the feature.
112
+ *
113
+ * `rounded-full` and the hand-written `rounded-t` / `-r` / `-b` cases do not
114
+ * follow, and that is visible at `soft`: a pill stays a pill.
115
+ */
116
+ export declare const THEME_RADIUS_LENGTHS: Record<ThemeRadius, string>;
27
117
  /**
28
118
  * The languages this project publishes.
29
119
  *