@valbuild/core 0.128.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,99 @@
1
1
  # @valbuild/core
2
2
 
3
+ ## 0.129.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#655](https://github.com/valbuild/val/pull/655) [`7d34ecc`](https://github.com/valbuild/val/commit/7d34ecce787a0709025ae7b4764cb6c3ad1f766b) Thanks [@freekh](https://github.com/freekh)! - A project can now make Val Studio look like its own.
8
+
9
+ `s.settings()` has a new `theme` section, edited under **Settings → Appearance**
10
+ in the Studio. It is content like everything else: edited as a draft, shown in
11
+ the publish diff, and the same for everyone working on the project.
12
+
13
+ ```ts
14
+ export default c.define("/settings.val.ts", s.settings(), {
15
+ theme: {
16
+ accent: "#2563eb",
17
+ radius: "tight",
18
+ mode: "light",
19
+ },
20
+ });
21
+ ```
22
+
23
+ **`accent` is one hex, and it restyles the whole chrome** — Publish, the active
24
+ rail item, focus rings, switches, the caret in a rich text field, and the
25
+ outlines the canvas draws around editable elements on your own page. Any colour
26
+ is allowed, not a list of approved ones, because what the accent replaces is a
27
+ ten-step ramp that is _generated_ from it: each step keeps the lightness of the
28
+ step it replaces and changes only the hue. WCAG contrast is almost entirely a
29
+ function of lightness, so every foreground/background pair the chrome renders
30
+ stays at AA — which is asserted across the hue circle, pure black and a
31
+ saturated yellow included, rather than argued for. One value drives both light
32
+ and dark mode, since the semantic tokens pick different steps of the ramp in
33
+ each.
34
+
35
+ **`radius`** is `square`, `tight`, `default` or `soft`, and moves every corner
36
+ in the Studio.
37
+
38
+ The accent also moves the outlines the canvas draws around editable elements on
39
+ your own page. Those are drawn inside your document, which has none of Val's
40
+ stylesheet, so the colour is sent to the page over the canvas protocol — which
41
+ means a project on an older `@valbuild/next` or `@valbuild/tanstack` than its
42
+ Studio keeps Val's green there until it upgrades, rather than breaking.
43
+
44
+ **`mode`** is the mode the Studio opens in for an editor who has never picked
45
+ one. It never overrides an editor who has — that choice stays theirs, per
46
+ person and per browser, behind the account button.
47
+
48
+ Every field is optional, and unset means Val's own look, so an existing
49
+ settings module needs no change.
50
+
51
+ Two smaller fixes that came with it:
52
+
53
+ - **A settings change is its own entry in the publish diff.** Editing the
54
+ assistant's tone used to collapse the entire settings module into one change
55
+ card labelled "Settings", because the patch that writes a settings field is
56
+ an `add`, and `PatchSets` had no case for a settings section — so it gave up
57
+ and grouped the whole module. Two unrelated settings edits now show as two
58
+ changes, each under the name the panel gives it.
59
+ - **The Val mark keeps its green.** It named a step of the brand ramp, so it
60
+ would have recoloured along with a project's accent. It has its own token now.
61
+
62
+ - [#657](https://github.com/valbuild/val/pull/657) [`0c351c4`](https://github.com/valbuild/val/commit/0c351c4f97ae7f09772eab0e856ae69821b803b7) Thanks [@freekh](https://github.com/freekh)! - A project can put its own logo in Val Studio.
63
+
64
+ `s.settings()`'s `theme` section takes a `logo`, uploaded from **Settings →
65
+ Appearance**. It replaces Val's mark at the top of the left rail, and beside the
66
+ menu button below the desktop breakpoint — the two slots that say which
67
+ workspace you are in.
68
+
69
+ ```ts
70
+ export default c.define("/settings.val.ts", s.settings(), {
71
+ theme: {
72
+ logo: {
73
+ path: "/public/val/brand/mark_a1b2c.png",
74
+ width: 512,
75
+ height: 512,
76
+ mimeType: "image/png",
77
+ },
78
+ },
79
+ });
80
+ ```
81
+
82
+ It is an ordinary image field, so it comes with the upload, the alt text and
83
+ everything else `s.image()` has, and the file lands in `/public/val/brand` so it
84
+ does not sit in the middle of your content's media. A draft logo shows
85
+ immediately and publishes with the rest of your changes.
86
+
87
+ Two things are deliberate:
88
+
89
+ - **A square-ish mark, not a wordmark.** The slot is 32px wide. A wide image is
90
+ fitted into it rather than cropped, so all of it is there and none of it is
91
+ large.
92
+ - **Val's mark stays on the launcher that floats on your own site.** In the
93
+ Studio the mark labels the workspace, so your logo belongs there. On your own
94
+ page it labels the tool — it is the button that opens Val — and your logo
95
+ floating over your own website says nothing.
96
+
3
97
  ## 0.128.0
4
98
 
5
99
  ### Minor Changes
@@ -54,7 +54,7 @@ export { type SerializedImageSchema, ImageSchema } from "./schema/image.js";
54
54
  export { type SerializedFileSchema, FileSchema } from "./schema/file.js";
55
55
  export { type SerializedDateSchema, DateSchema } from "./schema/date.js";
56
56
  export { type SerializedSettingsSchema, SettingsSchema, } from "./schema/settings.js";
57
- export { type SettingsSource, type AssistantSettingsSource, type LocalesSettingsSource, type AssistantAvailability, ASSISTANT_SETTINGS_MAX_LENGTH, assistantAvailability, } from "./source/settings.js";
57
+ export { type SettingsSource, type AssistantSettingsSource, type LocalesSettingsSource, type AssistantAvailability, type ThemeSettingsSource, type ThemeRadius, ASSISTANT_SETTINGS_MAX_LENGTH, THEME_LOGO_DIRECTORY, THEME_RADIUS_STEPS, THEME_RADIUS_LENGTHS, assistantAvailability, } from "./source/settings.js";
58
58
  export { type ResolvedSettingsModule, SETTINGS_MODULE_CONVENTION, isRootModuleFilePath, resolveSettingsModule, } from "./settingsModule.js";
59
59
  export { type Locale, localeTagError, localeOfValue } from "./locale.js";
60
60
  export { type SerializedLocaleSchema, LocaleSchema } from "./schema/locale.js";
@@ -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
  *