shelving 1.233.0 → 1.234.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.233.0",
3
+ "version": "1.234.1",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,14 +9,14 @@
9
9
  "main": "./index.js",
10
10
  "module": "./index.js",
11
11
  "devDependencies": {
12
- "@biomejs/biome": "^2.4.15",
12
+ "@biomejs/biome": "^2.4.16",
13
13
  "@google-cloud/firestore": "^8.6.0",
14
14
  "@heroicons/react": "^2.2.0",
15
15
  "@types/bun": "^1.3.14",
16
16
  "@types/react": "^19.2.15",
17
17
  "@types/react-dom": "^19.2.3",
18
- "@typescript/native-preview": "^7.0.0-dev.20260523.1",
19
- "firebase": "^12.13.0",
18
+ "@typescript/native-preview": "^7.0.0-dev.20260527.2",
19
+ "firebase": "^12.14.0",
20
20
  "react": "^19.3.0-canary-fef12a01-20260413",
21
21
  "react-dom": "^19.3.0-canary-fef12a01-20260413",
22
22
  "typescript": "^5.9.3"
package/ui/README.md CHANGED
@@ -155,6 +155,37 @@ Other tokens (`--*-padding`, `--*-spacing`, `--*-radius`, `--*-font`, `--*-size`
155
155
 
156
156
  This split is deliberate. The rebind is the right tool when an identity needs to propagate; for everything else, plain CSS inheritance (or no inheritance at all) is the right tool.
157
157
 
158
+ ### Retheming via the global scale
159
+
160
+ The rebind pattern has a powerful consequence: because every surface component rebinds the scale from `inherit`, the page-level `:root` scale is the **cascade root they all fall back to**. Retinting a step at `:root` repaints every surface component at once — and *identically*, so a standalone `<Preformatted>` matches one nested in a `<Card>`, and both match the `<Card>` itself. This is almost always preferable to overriding each component's own hook (`--card-color-light`, `--preformatted-color-light`, …) one by one, which only themes that single component and leaves its siblings on the grey defaults.
161
+
162
+ **But retint one step at a time, and know what else reads it.** The global scale isn't surfaces-only — the page baseline reads from it too. In `base.css`:
163
+
164
+ ```css
165
+ body { color: var(--color-dark); background: var(--color-white); }
166
+ ```
167
+
168
+ All body copy (Titles, Headings, Paragraphs, lists) has no `color` of its own; it inherits this baseline. So moving `--color-dark` at `:root` recolours **every word on the page**, not just text sitting on a card. Likewise `--color-vivid` tints borders and accents app-wide. Retint only the step whose reach you actually want:
169
+
170
+ - `--color-light` — **surfaces** (Card / Preformatted / Tag / Code backgrounds). Safe to retint broadly; nothing paints page text or the page background from it.
171
+ - `--color-vivid` — borders and accents everywhere. Retint only if you want app-wide accent recolouring.
172
+ - `--color-dark` — **the page text colour**, via the `body` baseline above. Retinting this is a whole-page text recolour; usually not what a "themed surfaces" look wants.
173
+ - `--color-black` / `--color-white` — the page extremes (max-contrast text, page background). Leave unless inverting (e.g. dark mode).
174
+
175
+ The docs theme wants peach surfaces with normal near-black text, so it retints **only** `--color-light`:
176
+
177
+ ```css
178
+ :root {
179
+ /* Surfaces go peach; text and the page background stay the library defaults. */
180
+ --color-light: color-mix(in srgb, #ff7a1a 14%, white);
181
+ }
182
+ ```
183
+
184
+ Two more rules keep a theme clean:
185
+
186
+ - **If you do move a whole hue, move the anchor.** The `--light-<hue>` / `--dark-<hue>` tokens are defined in `base.css` as expressions over `--vivid-<hue>`, resolved lazily at use-time. Overriding `--vivid-orange` at `:root` re-tints the whole orange family for free, so `var(--light-orange)` / `var(--dark-orange)` stay coherent.
187
+ - **Pin the exceptions back — and pin *every* step the component paints.** A component that should resist a global retint sets its own hooks. But a component rebinds the *whole* scale, and any step you leave unpinned still inherits the page colour. The docs site keeps Buttons purple by pinning both steps the default variant paints — `--button-color-light: var(--light-purple)` (background) and `--button-color-vivid: var(--vivid-purple)` (border/label) — plus `--button-color-white` for the `strong` label. Pinning only `vivid` would leave the default button's `bg=light` background inheriting the page peach: a purple-bordered peach button. Check which steps the variant in use actually paints (default = `light`+`vivid`, `strong` = `vivid`+`white`) and pin all of them.
188
+
158
189
  ### How `:first-child` / `:last-child` margin overrides work
159
190
 
160
191
  Every block-level component zeros its outer margins when it's the first or last child of its container — otherwise a Heading at the top of a Card would leave a strip of unwanted space. These rules live in `@layer overrides`, which beats every other layer including `variants`, so a `<Card space-large>` still collapses its abutting edges correctly.
@@ -6,9 +6,10 @@ import { Preformatted } from "../block/Preformatted.js";
6
6
  import { Subheading } from "../block/Subheading.js";
7
7
  import { Code } from "../inline/Code.js";
8
8
  import { Flex } from "../style/Flex.js";
9
- import { DocumentationKind } from "./DocumentationKind.js";
9
+ import { DocumentationKind, getDocumentationKindColor } from "./DocumentationKind.js";
10
10
  /** Card renderer for a `tree-documentation` element — a summary card showing the heading, signatures, and description. */
11
11
  export function DocumentationCard({ path, title, name, kind, description, signatures }) {
12
12
  const href = joinPath(path, name);
13
- return (_jsxs(Card, { href: href, children: [_jsx(Subheading, { children: _jsxs(Flex, { left: true, wrap: true, children: [_jsx(Code, { children: title ?? name }), kind && _jsx(DocumentationKind, { kind: kind })] }) }), signatures?.map(sig => (_jsx(Preformatted, { children: sig }, sig))), description && _jsx(Paragraph, { children: description })] }));
13
+ const color = kind ? getDocumentationKindColor(kind) : undefined;
14
+ return (_jsxs(Card, { href: href, ...(color ? { [color]: true } : {}), children: [_jsx(Subheading, { children: _jsxs(Flex, { left: true, wrap: true, children: [_jsx(Code, { children: title ?? name }), kind && _jsx(DocumentationKind, { kind: kind })] }) }), signatures?.map(sig => (_jsx(Preformatted, { children: sig }, sig))), description && _jsx(Paragraph, { children: description })] }));
14
15
  }
@@ -7,7 +7,7 @@ import { Preformatted } from "../block/Preformatted.js";
7
7
  import { Subheading } from "../block/Subheading.js";
8
8
  import { Code } from "../inline/Code.js";
9
9
  import { Flex } from "../style/Flex.js";
10
- import { DocumentationKind } from "./DocumentationKind.js";
10
+ import { DocumentationKind, getDocumentationKindColor } from "./DocumentationKind.js";
11
11
 
12
12
  interface DocumentationCardProps extends DocumentationElementProps {
13
13
  path: AbsolutePath;
@@ -16,8 +16,9 @@ interface DocumentationCardProps extends DocumentationElementProps {
16
16
  /** Card renderer for a `tree-documentation` element — a summary card showing the heading, signatures, and description. */
17
17
  export function DocumentationCard({ path, title, name, kind, description, signatures }: DocumentationCardProps): ReactNode {
18
18
  const href = joinPath(path, name);
19
+ const color = kind ? getDocumentationKindColor(kind) : undefined;
19
20
  return (
20
- <Card href={href}>
21
+ <Card href={href} {...(color ? { [color]: true } : {})}>
21
22
  <Subheading>
22
23
  <Flex left wrap>
23
24
  <Code>{title ?? name}</Code>
@@ -1,9 +1,15 @@
1
1
  import type { ReactElement } from "react";
2
+ import type { Color } from "../style/Color.js";
2
3
  /** Props for `DocumentationKind`. */
3
4
  export interface DocumentationKindProps {
4
5
  /** The documentation kind (e.g. `"function"`, `"class"`, `"interface"`, `"type"`, `"constant"`, `"method"`, `"property"`). */
5
6
  readonly kind: string;
6
7
  }
8
+ /**
9
+ * Get the raw colour variant for a documented symbol's `kind`, or `undefined` for an unknown kind.
10
+ * - Shared source of truth so the kind tag and its surrounding card pick the same hue.
11
+ */
12
+ export declare function getDocumentationKindColor(kind: string): Color | undefined;
7
13
  /**
8
14
  * Colour-coded tag for a documented symbol's kind.
9
15
  * - Thin wrapper over `<Tag>` that maps the kind string to a raw colour variant.
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Tag } from "../misc/Tag.js";
3
- /** Mapping from a documented symbol's `kind` to its tag colour. */
3
+ /** Mapping from a documented symbol's `kind` to its raw colour variant. */
4
4
  const KIND_COLOR = {
5
5
  module: "red",
6
6
  function: "blue",
@@ -11,6 +11,13 @@ const KIND_COLOR = {
11
11
  method: "orange",
12
12
  property: "yellow",
13
13
  };
14
+ /**
15
+ * Get the raw colour variant for a documented symbol's `kind`, or `undefined` for an unknown kind.
16
+ * - Shared source of truth so the kind tag and its surrounding card pick the same hue.
17
+ */
18
+ export function getDocumentationKindColor(kind) {
19
+ return KIND_COLOR[kind];
20
+ }
14
21
  /**
15
22
  * Colour-coded tag for a documented symbol's kind.
16
23
  * - Thin wrapper over `<Tag>` that maps the kind string to a raw colour variant.
@@ -18,6 +25,6 @@ const KIND_COLOR = {
18
25
  * @example <DocumentationKind kind="function" />
19
26
  */
20
27
  export function DocumentationKind({ kind }) {
21
- const color = KIND_COLOR[kind];
28
+ const color = getDocumentationKindColor(kind);
22
29
  return _jsx(Tag, { ...(color ? { [color]: true } : {}), children: kind });
23
30
  }
@@ -8,7 +8,7 @@ export interface DocumentationKindProps {
8
8
  readonly kind: string;
9
9
  }
10
10
 
11
- /** Mapping from a documented symbol's `kind` to its tag colour. */
11
+ /** Mapping from a documented symbol's `kind` to its raw colour variant. */
12
12
  const KIND_COLOR: { readonly [K in string]?: Color } = {
13
13
  module: "red",
14
14
  function: "blue",
@@ -20,6 +20,14 @@ const KIND_COLOR: { readonly [K in string]?: Color } = {
20
20
  property: "yellow",
21
21
  };
22
22
 
23
+ /**
24
+ * Get the raw colour variant for a documented symbol's `kind`, or `undefined` for an unknown kind.
25
+ * - Shared source of truth so the kind tag and its surrounding card pick the same hue.
26
+ */
27
+ export function getDocumentationKindColor(kind: string): Color | undefined {
28
+ return KIND_COLOR[kind];
29
+ }
30
+
23
31
  /**
24
32
  * Colour-coded tag for a documented symbol's kind.
25
33
  * - Thin wrapper over `<Tag>` that maps the kind string to a raw colour variant.
@@ -27,6 +35,6 @@ const KIND_COLOR: { readonly [K in string]?: Color } = {
27
35
  * @example <DocumentationKind kind="function" />
28
36
  */
29
37
  export function DocumentationKind({ kind }: DocumentationKindProps): ReactElement {
30
- const color = KIND_COLOR[kind];
38
+ const color = getDocumentationKindColor(kind);
31
39
  return <Tag {...(color ? { [color]: true } : {})}>{kind}</Tag>;
32
40
  }
package/util/ansi.d.ts CHANGED
@@ -14,12 +14,24 @@ export declare const ANSI_UNDERLINE: "\u001B[4m";
14
14
  export declare const ANSI_STRIKE: "\u001B[9m";
15
15
  export declare const ANSI_INVERSE: "\u001B[7m";
16
16
  export declare const ANSI_RESET = "\u001B[0m";
17
- /** Wrap a string in `ANSI_RED` or the ANSI color/style codes (at the start), and `ANSI_RESET` at the end. */
17
+ /**
18
+ * Wrap a string in the ANSI color/style codes (at the start), and `ANSI_RESET` at the end.
19
+ *
20
+ * - The `NO_COLOR` environment variable is read live on every call, so runtimes that populate `process.env` late (e.g. Cloudflare Workers, where `[vars]` bindings are only reliably available within the request scope) are honoured rather than baking in whatever `NO_COLOR` was at module-load time.
21
+ */
18
22
  export declare function ansiWrap(input: string, ...wrappers: ImmutableArray<string>): string;
19
- export declare const ANSI_WAITING: string;
20
- export declare const ANSI_SUCCESS: string;
21
- export declare const ANSI_FAILURE: string;
22
- export declare const ANSI_UP: string;
23
- export declare const ANSI_DOWN: string;
24
- export declare const ANSI_RIGHT: string;
25
- export declare const ANSI_LEFT: string;
23
+ /**
24
+ * A lazily-coloured icon that re-evaluates its ANSI colouring against the live `NO_COLOR` environment variable every time it is converted to a string.
25
+ *
26
+ * - Used directly inside template literals (`${ANSI_SUCCESS}`), where JavaScript invokes `toString()` automatically, so the icon is coloured at use-time, not at module-load time.
27
+ */
28
+ export type AnsiIcon = {
29
+ toString(): string;
30
+ };
31
+ export declare const ANSI_WAITING: AnsiIcon;
32
+ export declare const ANSI_SUCCESS: AnsiIcon;
33
+ export declare const ANSI_FAILURE: AnsiIcon;
34
+ export declare const ANSI_UP: AnsiIcon;
35
+ export declare const ANSI_DOWN: AnsiIcon;
36
+ export declare const ANSI_RIGHT: AnsiIcon;
37
+ export declare const ANSI_LEFT: AnsiIcon;
package/util/ansi.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DOWN, FAILURE, LEFT, RIGHT, SUCCESS, UP, WAITING } from "./constants.js";
2
- import { NO_COLOR } from "./env.js";
2
+ import { getEnvBoolean } from "./env.js";
3
3
  // Colors.
4
4
  export const ANSI_DEFAULT = "\x1b[39m";
5
5
  export const ANSI_BLACK = "\x1b[30m";
@@ -18,17 +18,29 @@ export const ANSI_STRIKE = "\x1b[9m";
18
18
  export const ANSI_INVERSE = "\x1b[7m";
19
19
  // Reset.
20
20
  export const ANSI_RESET = "\x1b[0m";
21
- /** Wrap a string in `ANSI_RED` or the ANSI color/style codes (at the start), and `ANSI_RESET` at the end. */
21
+ /**
22
+ * Wrap a string in the ANSI color/style codes (at the start), and `ANSI_RESET` at the end.
23
+ *
24
+ * - The `NO_COLOR` environment variable is read live on every call, so runtimes that populate `process.env` late (e.g. Cloudflare Workers, where `[vars]` bindings are only reliably available within the request scope) are honoured rather than baking in whatever `NO_COLOR` was at module-load time.
25
+ */
22
26
  export function ansiWrap(input, ...wrappers) {
23
- if (NO_COLOR)
27
+ if (getEnvBoolean("NO_COLOR"))
24
28
  return input;
25
29
  return `${wrappers.join("")}${input}${ANSI_RESET}`;
26
30
  }
31
+ /** Create a lazily-coloured {@link AnsiIcon} that wraps `icon` in `wrappers` on each `toString()`. */
32
+ function _createAnsiIcon(icon, ...wrappers) {
33
+ return {
34
+ toString() {
35
+ return ansiWrap(icon, ...wrappers);
36
+ },
37
+ };
38
+ }
27
39
  // Coloured icons.
28
- export const ANSI_WAITING = ansiWrap(WAITING, ANSI_BLUE);
29
- export const ANSI_SUCCESS = ansiWrap(SUCCESS, ANSI_GREEN);
30
- export const ANSI_FAILURE = ansiWrap(FAILURE, ANSI_RED);
31
- export const ANSI_UP = ansiWrap(UP, ANSI_BLUE);
32
- export const ANSI_DOWN = ansiWrap(DOWN, ANSI_BLUE);
33
- export const ANSI_RIGHT = ansiWrap(RIGHT, ANSI_BLUE);
34
- export const ANSI_LEFT = ansiWrap(LEFT, ANSI_BLUE);
40
+ export const ANSI_WAITING = _createAnsiIcon(WAITING, ANSI_BLUE);
41
+ export const ANSI_SUCCESS = _createAnsiIcon(SUCCESS, ANSI_GREEN);
42
+ export const ANSI_FAILURE = _createAnsiIcon(FAILURE, ANSI_RED);
43
+ export const ANSI_UP = _createAnsiIcon(UP, ANSI_BLUE);
44
+ export const ANSI_DOWN = _createAnsiIcon(DOWN, ANSI_BLUE);
45
+ export const ANSI_RIGHT = _createAnsiIcon(RIGHT, ANSI_BLUE);
46
+ export const ANSI_LEFT = _createAnsiIcon(LEFT, ANSI_BLUE);
package/util/env.d.ts CHANGED
@@ -19,5 +19,3 @@ export declare function getEnvBoolean(name: string): boolean | undefined;
19
19
  * @throws `RequiredError` if the env variable is any other value.
20
20
  */
21
21
  export declare function requireEnvBoolean(name: string, caller?: AnyCaller): boolean;
22
- /** The `NO_COLOR` environment variable is commonly used to indicate that ANSI output shouldn't have color. */
23
- export declare const NO_COLOR: boolean;
package/util/env.js CHANGED
@@ -40,5 +40,3 @@ export function requireEnvBoolean(name, caller = requireEnvBoolean) {
40
40
  throw new RequiredError(`Environment variable "${name}" must be boolean`, { caller });
41
41
  return env;
42
42
  }
43
- /** The `NO_COLOR` environment variable is commonly used to indicate that ANSI output shouldn't have color. */
44
- export const NO_COLOR = getEnvBoolean("NO_COLOR") ?? false;