@pieai/swimmer-ui-kit 1.3.2 → 1.4.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
@@ -3,6 +3,47 @@
3
3
  All notable changes to `@pieai/swimmer-ui-kit`.
4
4
  Format: [Keep a Changelog](https://keepachangelog.com); versioning: semver.
5
5
 
6
+ ## 1.4.0 — 2026-08-22
7
+
8
+ The kit shipped an icon set that no consumer could see. Nothing was broken and
9
+ nothing errored, which was the problem: with no setup the kit draws lettered
10
+ placeholder squares, and a placeholder that looks deliberate gets shipped. A
11
+ product could put eight identical coloured squares in its navigation and never
12
+ file a bug, because it reads as a design choice rather than a missing step.
13
+
14
+ The sculpted PNGs were never missing — 350 files travel inside the package, in
15
+ `dist/assets`. What was missing was any route from there to a path the host
16
+ actually serves. `CLAY_ASSET_BASE_PATH` names an absolute URL on the
17
+ *consumer's* origin, and nothing copied, exported or explained it. The kit's own
18
+ Storybook worked throughout, because its assets sit in `public/`.
19
+
20
+ ### Added
21
+
22
+ - `swimmer-ui-assets [dir] [--base=…] [--force]` — a `bin` that copies the
23
+ sculpted set into a host's static root. Bundler-agnostic and explicit; no
24
+ postinstall, because a package that writes into your repo on install is worse
25
+ than the problem it solves.
26
+ - `"./assets/*"` export, so bundler-based consumers can import an individual
27
+ file and get a content-hashed URL instead of copying the whole tree.
28
+ - `setClayAssetBasePath()` / `getClayAssetBasePath()` for a CDN or a sub-path
29
+ deploy. Resolution rebases on the way out, so the variant table stays a
30
+ single source of truth.
31
+ - A one-time `console.warn` the first time a placeholder is actually drawn,
32
+ naming the two commands that fix it, and `acknowledgeClayPlaceholders()` for
33
+ anyone using them on purpose.
34
+
35
+ ### Changed
36
+
37
+ - README states plainly that the default is placeholders rather than the icon
38
+ set, and records that the sculpted family is 96px art which cannot take a
39
+ `currentColor` tint — so it is the wrong family for a dense nav rail or
40
+ toolbar however correctly it is served.
41
+
42
+ ### Compatibility
43
+
44
+ Additive only. Defaults are unchanged, so every existing consumer renders
45
+ exactly what it rendered before and gains one console line telling it why.
46
+
6
47
  ## 1.3.2 — 2026-08-17
7
48
 
8
49
  Follows 1.3.1 with the contrast work that release only half did. The new off
package/README.md CHANGED
@@ -71,10 +71,29 @@ import '@pieai/swimmer-ui-kit/tailwind.css';
71
71
  - **Official themes**: light (default) and `night`
72
72
  (`<html data-game-ui-theme="night">`). Downstream theming = overriding
73
73
  semantic tokens; see the design system guide.
74
- - **Clay assets**: inline SVG mode by default (no asset hosting needed);
75
- `setClayAssetMode('source')` switches to the sculpted PNG set, which
76
- ships in `dist/assets/` for hosts to copy under
77
- `/assets/game/ui/clay/…`.
74
+ - **Clay assets**: two lines of setup, and **skipping them is not a no-op**.
75
+ Out of the box the kit draws *placeholders* — one rounded square per icon
76
+ with a letter in it not the icon set. They exist so a fresh install
77
+ renders something instead of a broken image, and they are not shippable.
78
+ The real sculpted PNGs travel inside the package and need serving:
79
+
80
+ ```bash
81
+ npx swimmer-ui-assets public # copies dist/assets into your static root
82
+ ```
83
+ ```ts
84
+ import { setClayAssetMode } from '@pieai/swimmer-ui-kit';
85
+ setClayAssetMode('source'); // once, at your entry
86
+ ```
87
+
88
+ Serving them somewhere else — a CDN, a sub-path deploy — is
89
+ `setClayAssetBasePath('/my/path')`, and `swimmer-ui-assets public --base=/my/path`
90
+ mirrors the layout to match. If you *want* placeholders, say so with
91
+ `acknowledgeClayPlaceholders()` and the console notice goes quiet.
92
+
93
+ Note on sizing: the sculpted family is 96px art. Below roughly 24px it turns
94
+ to mud, and being PNG it cannot take a `currentColor` tint, so it is the
95
+ wrong family for a dense navigation rail or toolbar. Use the `line` style
96
+ there, or your own glyphs.
78
97
  - **Audio helper**: `playGameInteractionSound` (SSR-safe, opt-in via the
79
98
  `sound` prop on `GameButton`).
80
99
 
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ // Copy the sculpted clay asset set out of the package and into a directory the
3
+ // consuming app actually serves.
4
+ //
5
+ // The kit resolves sculpted icons to absolute URLs under CLAY_ASSET_BASE_PATH
6
+ // ('/assets/game/ui/clay/…'), which is a path on the *host's* origin. The files
7
+ // themselves ship inside the package, in dist/assets. Nothing was closing that
8
+ // gap, so every consumer had to discover it, and the default placeholder mode
9
+ // meant the failure looked like a design choice rather than a missing step.
10
+ //
11
+ // Usage: swimmer-ui-assets [targetDir] [--base=/assets/game/ui/clay/...] [--force]
12
+ // targetDir where the host serves static files from. Default "public".
13
+ // --base the URL path to mirror under targetDir. Defaults to the kit's
14
+ // CLAY_ASSET_BASE_PATH, which is what setClayAssetMode('source')
15
+ // expects with no further configuration.
16
+ // --force overwrite files that already exist (default: skip them).
17
+ //
18
+ // After running this, call setClayAssetMode('source') once at your app entry.
19
+ import { cpSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
20
+ import { dirname, join, resolve } from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+
23
+ const DEFAULT_BASE = '/assets/game/ui/clay/phase03-clay-kit';
24
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
25
+
26
+ const args = process.argv.slice(2);
27
+ const flags = new Set(args.filter((a) => a.startsWith('--')));
28
+ const positional = args.filter((a) => !a.startsWith('--'));
29
+
30
+ // Two different paths that are easy to conflate: where the files sit inside
31
+ // this package (fixed, mirrors the default URL under dist/) and where the host
32
+ // will serve them from (--base, defaults to the same thing so the common case
33
+ // needs no configuration).
34
+ const trim = (p) => p.replace(/^\/+|\/+$/g, '');
35
+ const baseFlag = args.find((a) => a.startsWith('--base='));
36
+ const servedBase = trim(baseFlag ? baseFlag.slice('--base='.length) : DEFAULT_BASE);
37
+ const targetRoot = resolve(process.cwd(), positional[0] ?? 'public');
38
+ const force = flags.has('--force');
39
+
40
+ const source = join(packageRoot, 'dist', trim(DEFAULT_BASE));
41
+ if (!existsSync(source)) {
42
+ console.error(`swimmer-ui-assets: nothing to copy — ${source} does not exist.`);
43
+ console.error('This package may have been installed without its dist/assets tree.');
44
+ process.exit(1);
45
+ }
46
+
47
+ const destination = join(targetRoot, servedBase);
48
+
49
+ function count(dir) {
50
+ let files = 0;
51
+ let bytes = 0;
52
+ for (const entry of readdirSync(dir)) {
53
+ const full = join(dir, entry);
54
+ const stat = statSync(full);
55
+ if (stat.isDirectory()) {
56
+ const inner = count(full);
57
+ files += inner.files;
58
+ bytes += inner.bytes;
59
+ } else {
60
+ files += 1;
61
+ bytes += stat.size;
62
+ }
63
+ }
64
+ return { files, bytes };
65
+ }
66
+
67
+ const { files, bytes } = count(source);
68
+
69
+ mkdirSync(dirname(destination), { recursive: true });
70
+ cpSync(source, destination, { recursive: true, force, errorOnExist: false });
71
+
72
+ const megabytes = (bytes / 1024 / 1024).toFixed(1);
73
+ console.log(`swimmer-ui-assets: copied ${files} files (${megabytes} MB)`);
74
+ console.log(` from ${source}`);
75
+ console.log(` to ${destination}`);
76
+ console.log('');
77
+ console.log('Now call this once at your app entry, or the kit keeps drawing placeholders:');
78
+ console.log(" import { setClayAssetMode } from '@pieai/swimmer-ui-kit';");
79
+ console.log(" setClayAssetMode('source');");
80
+ if (!force) {
81
+ console.log('');
82
+ console.log('Existing files were left alone. Re-run with --force to overwrite.');
83
+ }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,9 @@ import { ReactNode } from 'react';
6
6
  import { RefAttributes } from 'react';
7
7
  import { TextareaHTMLAttributes } from 'react';
8
8
 
9
+ /** Opt in to placeholders on purpose, and stop being told about it. */
10
+ export declare function acknowledgeClayPlaceholders(): void;
11
+
9
12
  export declare const CLAY_ASSET_BASE_PATH: "/assets/game/ui/clay/phase03-clay-kit";
10
13
 
11
14
  export declare const CLAY_ASSET_SIZE_TOKENS: {
@@ -1852,6 +1855,8 @@ export declare interface GameWindowPanelProps extends Omit<HTMLAttributes<HTMLEl
1852
1855
 
1853
1856
  export declare type GameWindowState = 'normal' | 'minimized' | 'maximized';
1854
1857
 
1858
+ export declare function getClayAssetBasePath(): string;
1859
+
1855
1860
  export declare function getClayAssetMode(): ClayAssetMode;
1856
1861
 
1857
1862
  export declare function getClayCatalogPaths(): typeof CLAY_ASSETS.catalog;
@@ -1867,11 +1872,23 @@ export declare function playGameInteractionSound(options?: GameInteractionSoundO
1867
1872
 
1868
1873
  export declare function playGameInteractionSoundForContext(audioContext: GameInteractionAudioContext, options?: GameInteractionSoundOptions): boolean;
1869
1874
 
1875
+ /**
1876
+ * Point the sculpted set at somewhere other than the default path — a CDN, a
1877
+ * sub-path deploy, a host whose static root is not `/`. Pass no trailing slash.
1878
+ *
1879
+ * The copy command mirrors the default layout, so a consumer that runs
1880
+ * `swimmer-ui-assets public` never needs this.
1881
+ */
1882
+ export declare function setClayAssetBasePath(basePath: string): void;
1883
+
1870
1884
  /**
1871
1885
  * Switch how clay icons resolve globally.
1872
- * - 'inline' (default): zero-dependency SVG placeholders, no assets required.
1873
- * - 'source': the real clay game-icon PNG paths under CLAY_ASSET_BASE_PATH.
1874
- * The host app must serve those assets (e.g. copy them into its public/).
1886
+ * - 'inline' (default): lettered placeholders. They are not the icon set —
1887
+ * every one is the same rounded square with a different glyph and colour.
1888
+ * They exist so a fresh install renders *something* rather than a broken
1889
+ * image, and shipping them is a bug.
1890
+ * - 'source': the real sculpted PNGs under the base path. Get the files there
1891
+ * with `npx swimmer-ui-assets <publicDir>`.
1875
1892
  */
1876
1893
  export declare function setClayAssetMode(mode: ClayAssetMode): void;
1877
1894