@pieai/swimmer-ui-kit 1.1.0 → 1.3.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,99 @@
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.3.0 — 2026-07-25
7
+
8
+ Gives `<GameCallout>` its class name back. Since the preview split in 0.6 the
9
+ demo-table speech bubble had been left behind in `styles.css` under the same
10
+ `.game-ui-callout` class the component uses, so every product's callout
11
+ silently inherited scenery layout. Four products had each patched it locally.
12
+ Also stops `.game-ui-badge` from wrapping mid-token as a flex item, and lets
13
+ `<GameProgress>` label progress in domain units (counts) instead of only as a
14
+ rounded percentage.
15
+
16
+ ### Fixed
17
+
18
+ - `.game-ui-callout` no longer inherits `position: absolute` and
19
+ `white-space: nowrap`. `styles.css` declared the class twice — first as the
20
+ absolutely-positioned bubble pinned to the demo table in `<GameUiPreview />`,
21
+ then as the real component. The second rule never re-declared `position` or
22
+ `white-space`, so the cascade kept the scenery values and every
23
+ `<GameCallout>` rendered out of flow: it overlapped whatever sat behind it
24
+ and swallowed those elements' clicks. The bubble now lives in `preview.css`
25
+ as `.game-ui-seat-callout`, next to the `.game-ui-seat` scenery it belongs
26
+ to, and `.game-ui-callout` is owned by `<GameCallout>` alone.
27
+ - `.game-ui-badge` no longer wraps mid-label when placed as a flex item next to
28
+ a growing text block. Flex items default to `min-width: auto` (min-content);
29
+ for CJK that is one character, so `flex-shrink` was splitting badges like
30
+ "课程已发布" across two lines. A badge is a single short token — the base
31
+ rule now sets `white-space: nowrap`.
32
+
33
+ ### Added
34
+
35
+ - Internal `cssOwnership` analyzer plus `src/cssOwnership.test.ts`, enforcing
36
+ one structural owner per class: a bare single-class selector may declare
37
+ layout properties (`position`, `display`, `float`, `white-space`, inset,
38
+ grid placement, `flex-direction`) in exactly one unconditional rule.
39
+ Cosmetic and motion rules may still be split across sections, and `@media` /
40
+ `@supports` overrides stay exempt, so the existing "base rule here, motion
41
+ rule in the motion section" pattern is untouched. `styles.css` and
42
+ `preview.css` are both asserted clean, which makes this class of name
43
+ collision a build failure rather than four downstream workarounds.
44
+ - `<GameProgress valueLabel="3 / 21">` — optional domain-unit text shown next
45
+ to the bar in place of the percentage. Passing a non-empty `valueLabel` is
46
+ enough (no need for `showValue`); absent `valueLabel` keeps the existing
47
+ `showValue` percentage behaviour. When set, `aria-valuetext` mirrors the
48
+ label so assistive tech does not announce a percentage while the screen
49
+ shows a count.
50
+
51
+ ### Removed
52
+
53
+ - `.game-ui-callout.is-a` / `.game-ui-callout.is-b` from `styles.css`. These
54
+ were never public API — undocumented preview scenery that leaked out of the
55
+ 0.6 preview split. A grep across the whole PieAI portfolio found no consumer
56
+ outside `GameUiPreview.tsx`, which moved to `.game-ui-seat-callout` in the
57
+ same commit. Products that render `<GameUiPreview />` must already be
58
+ loading `preview.css`, where the bubble now lives.
59
+
60
+ ### Migration notes
61
+
62
+ - No product action is required; callouts start laying out correctly on
63
+ upgrade. Products may now delete their local re-anchoring patches:
64
+ SupaLuv `apps/web/src/styles/meta.css` (`.settings-panel .game-ui-callout`),
65
+ Sea `sea-viewer.css` / `sea-console.css` (`position: static !important`),
66
+ and Anvil `AiConnectionCenter.module.css`. Those overrides are harmless if
67
+ left in place — they now merely restate the kit default.
68
+
69
+ ## 1.2.0 — 2026-07-18
70
+
71
+ Purely additive: an official "overlay glass" HUD surface tone plus a compact
72
+ density, for products that float kit chrome over a live scene (TuringPact 3D
73
+ tavern, SupaLuv cinematic stage). No exported component, prop, class, or token
74
+ was removed or renamed.
75
+
76
+ ### Added
77
+
78
+ - Overlay glass scope: `data-game-ui-tone="glass"` (alias class
79
+ `.game-ui-overlay-scope`) re-scopes semantic surface/text/elevation tokens on
80
+ a subtree to dark translucent glass — fill `rgba(12,14,20,0.72)`, 1px light
81
+ border, no clay box-shadow, warm accent hover/primary, WCAG-visible warm
82
+ focus ring (`--game-ui-overlay-glass-focus-ring`). Nests inside light or
83
+ `night` page themes.
84
+ - Compact density: `data-game-ui-density="compact"` (orthogonal to tone) —
85
+ 34px control floor, tighter panel/chip padding for HUD clusters.
86
+ - Primitive tokens `--game-ui-overlay-glass-*` in `theme.css`; TS mirrors
87
+ `CLAY_OVERLAY_GLASS_TOKENS` and `GAME_UI_OVERLAY` attr/class constants.
88
+ - Clay `GameAssetIcon` gets a soft dark drop-shadow inside glass scope so
89
+ sculpted icons read on translucent dark chips.
90
+ - Stories/preview compare: same HUD cluster in default clay vs overlay glass
91
+ over a busy scene background, light and night contexts.
92
+
93
+ ### Migration notes
94
+
95
+ - Consumers opt in per subtree; nothing changes without the attribute.
96
+ - SupaLuv can delete its `.vn-stage .game-ui-button` `!important` overrides
97
+ (stage.css ~217-360) after wrapping its HUD in the glass scope.
98
+
6
99
  ## 1.1.0 — 2026-07-13
7
100
 
8
101
  A design-system quality pass: fixes real bugs in the existing surface
@@ -6,10 +6,11 @@
6
6
  // "token-only" bar the design-system-guide asks for.
7
7
  //
8
8
  // Raw colors are expected (and fine) inside token-defining blocks —
9
- // :root { ... } or an attribute-selector theme block like
10
- // [data-game-ui-theme='night'] / [data-theme='dark'] — since that is
11
- // exactly how the design-system-guide tells consumers to re-theme. Only
12
- // raw colors inside *other* selectors (component rules) are flagged.
9
+ // :root { ... } or an attribute-selector theme/tone block like
10
+ // [data-game-ui-theme='night'] / [data-game-ui-tone='glass'] /
11
+ // [data-theme='dark'] — since that is how the design-system-guide tells
12
+ // consumers to re-theme or re-scope surface tones. Only raw colors inside
13
+ // *other* selectors (component rules) are flagged.
13
14
  //
14
15
  // Usage: swimmer-ui-check [dir] [--ext=css,tsx]
15
16
  // dir directory to scan, default "src"
@@ -18,8 +19,15 @@ import { readFileSync, readdirSync, statSync } from 'node:fs';
18
19
  import { extname, join, relative } from 'node:path';
19
20
 
20
21
  const RAW_COLOR = /#[0-9a-fA-F]{3,8}\b|\brgba?\(|\bhsla?\(|\boklch\(/g;
21
- const TOKEN_BLOCK_SELECTOR = /:root\b|\[data-[\w-]*theme[\w-]*\s*=/i;
22
- const IGNORE_DIRS = new Set(['node_modules', 'dist', '.git', 'storybook-static', 'site-dist', 'build']);
22
+ const TOKEN_BLOCK_SELECTOR = /:root\b|\[data-[\w-]*(?:theme|tone)[\w-]*\s*=/i;
23
+ const IGNORE_DIRS = new Set([
24
+ 'node_modules',
25
+ 'dist',
26
+ '.git',
27
+ 'storybook-static',
28
+ 'site-dist',
29
+ 'build',
30
+ ]);
23
31
 
24
32
  function walk(dir, exts, out) {
25
33
  for (const entry of readdirSync(dir)) {
@@ -90,7 +98,9 @@ const args = process.argv.slice(2);
90
98
  const target = args.find((a) => !a.startsWith('--')) ?? 'src';
91
99
  const extArg = args.find((a) => a.startsWith('--ext='));
92
100
  const exts = new Set(
93
- (extArg ? extArg.slice('--ext='.length).split(',') : ['css']).map((e) => (e.startsWith('.') ? e : `.${e}`)),
101
+ (extArg ? extArg.slice('--ext='.length).split(',') : ['css']).map((e) =>
102
+ e.startsWith('.') ? e : `.${e}`,
103
+ ),
94
104
  );
95
105
 
96
106
  const files = [];
@@ -105,7 +115,9 @@ let violationCount = 0;
105
115
  for (const file of files) {
106
116
  const text = readFileSync(file, 'utf8');
107
117
  for (const violation of findViolations(text)) {
108
- console.log(`${relative(process.cwd(), file)}:${violation.line}: raw color literal "${violation.text}" — use var(--game-ui-*) instead`);
118
+ console.log(
119
+ `${relative(process.cwd(), file)}:${violation.line}: raw color literal "${violation.text}" — use var(--game-ui-*) instead`,
120
+ );
109
121
  violationCount += 1;
110
122
  }
111
123
  }
@@ -113,9 +125,12 @@ for (const file of files) {
113
125
  if (violationCount > 0) {
114
126
  console.error(
115
127
  `\nswimmer-ui-check: ${violationCount} raw color literal(s) in ${files.length} file(s) under "${target}". ` +
116
- 'Raw colors are expected inside :root / [data-*theme*=...] token blocks (that is how you re-theme the kit) ' +
117
- 'but not inside component rules — see the design-system-guide "主题化配方" section.',
128
+ 'Raw colors are expected inside :root / [data-*theme*=...] / [data-*tone*=...] token blocks ' +
129
+ '(that is how you re-theme or re-scope the kit) but not inside component rules — see the ' +
130
+ 'design-system-guide "主题化配方" section.',
118
131
  );
119
132
  process.exit(1);
120
133
  }
121
- console.log(`swimmer-ui-check: 0 raw color literals in component rules across ${files.length} file(s) under "${target}". Clean.`);
134
+ console.log(
135
+ `swimmer-ui-check: 0 raw color literals in component rules across ${files.length} file(s) under "${target}". Clean.`,
136
+ );
package/dist/index.d.ts CHANGED
@@ -72,6 +72,9 @@ export declare const CLAY_COLOR_TOKENS: {
72
72
  readonly shadow: "rgba(76, 52, 28, 0.24)";
73
73
  readonly glass: "rgba(255, 248, 236, 0.82)";
74
74
  readonly nightGlass: "rgba(31, 24, 18, 0.76)";
75
+ /** Official HUD-on-scene glass fill (matches SupaLuv stage chrome). */
76
+ readonly overlayGlass: "rgba(12, 14, 20, 0.72)";
77
+ readonly overlayGlassText: "#fff6ee";
75
78
  };
76
79
 
77
80
  export declare const CLAY_ELEVATION_TOKENS: {
@@ -268,6 +271,26 @@ export declare const CLAY_MOTION_TOKENS: {
268
271
  readonly reducedMotion: "var(--game-ui-reduced-motion-policy)";
269
272
  };
270
273
 
274
+ /**
275
+ * Semantic CSS vars for the official overlay-glass HUD tone
276
+ * (`data-game-ui-tone="glass"` / `.game-ui-overlay-scope`).
277
+ */
278
+ declare const CLAY_OVERLAY_GLASS_TOKENS: {
279
+ readonly bg: "var(--game-ui-overlay-glass-bg)";
280
+ readonly bgHover: "var(--game-ui-overlay-glass-bg-hover)";
281
+ readonly bgStrong: "var(--game-ui-overlay-glass-bg-strong)";
282
+ readonly bgPanel: "var(--game-ui-overlay-glass-bg-panel)";
283
+ readonly border: "var(--game-ui-overlay-glass-border)";
284
+ readonly borderHover: "var(--game-ui-overlay-glass-border-hover)";
285
+ readonly text: "var(--game-ui-overlay-glass-text)";
286
+ readonly textMuted: "var(--game-ui-overlay-glass-text-muted)";
287
+ readonly blur: "var(--game-ui-overlay-glass-blur)";
288
+ readonly focusRing: "var(--game-ui-overlay-glass-focus-ring)";
289
+ readonly primaryFill: "var(--game-ui-overlay-glass-primary-fill)";
290
+ };
291
+ export { CLAY_OVERLAY_GLASS_TOKENS }
292
+ export { CLAY_OVERLAY_GLASS_TOKENS as GAME_UI_OVERLAY_GLASS_TOKENS }
293
+
271
294
  export declare const CLAY_RADIUS_TOKENS: {
272
295
  readonly bead: "var(--game-ui-radius-bead)";
273
296
  readonly control: "var(--game-ui-radius-control)";
@@ -363,6 +386,9 @@ export declare const CLAY_UI_TOKENS: {
363
386
  readonly shadow: "rgba(76, 52, 28, 0.24)";
364
387
  readonly glass: "rgba(255, 248, 236, 0.82)";
365
388
  readonly nightGlass: "rgba(31, 24, 18, 0.76)";
389
+ /** Official HUD-on-scene glass fill (matches SupaLuv stage chrome). */
390
+ readonly overlayGlass: "rgba(12, 14, 20, 0.72)";
391
+ readonly overlayGlassText: "#fff6ee";
366
392
  };
367
393
  readonly semantic: {
368
394
  readonly background: "var(--game-ui-bg)";
@@ -470,6 +496,19 @@ export declare const CLAY_UI_TOKENS: {
470
496
  readonly buildRailCardSize: "var(--game-ui-build-rail-card-size)";
471
497
  readonly brushNumberWidth: "var(--game-ui-brush-number-width)";
472
498
  };
499
+ readonly overlayGlass: {
500
+ readonly bg: "var(--game-ui-overlay-glass-bg)";
501
+ readonly bgHover: "var(--game-ui-overlay-glass-bg-hover)";
502
+ readonly bgStrong: "var(--game-ui-overlay-glass-bg-strong)";
503
+ readonly bgPanel: "var(--game-ui-overlay-glass-bg-panel)";
504
+ readonly border: "var(--game-ui-overlay-glass-border)";
505
+ readonly borderHover: "var(--game-ui-overlay-glass-border-hover)";
506
+ readonly text: "var(--game-ui-overlay-glass-text)";
507
+ readonly textMuted: "var(--game-ui-overlay-glass-text-muted)";
508
+ readonly blur: "var(--game-ui-overlay-glass-blur)";
509
+ readonly focusRing: "var(--game-ui-overlay-glass-focus-ring)";
510
+ readonly primaryFill: "var(--game-ui-overlay-glass-primary-fill)";
511
+ };
473
512
  };
474
513
 
475
514
  export declare type ClayAssetGroup = keyof typeof CLAY_ASSETS;
@@ -507,7 +546,7 @@ export declare type ClayIconStyle = 'game' | 'line';
507
546
 
508
547
  export declare type ClayTokenCategory = keyof typeof CLAY_UI_TOKENS;
509
548
 
510
- export declare function FirstSessionHud({ authenticated, batteryCount, className, dailyStreak, iconSlots, labels, onHistory, onSettings, onWardrobe, playerName }: FirstSessionHudProps): ReactNode;
549
+ export declare function FirstSessionHud({ authenticated, batteryCount, className, dailyStreak, iconSlots, labels, onHistory, onSettings, onWardrobe, playerName, }: FirstSessionHudProps): ReactNode;
511
550
 
512
551
  export declare interface FirstSessionHudIconSlots {
513
552
  history?: ReactNode;
@@ -556,7 +595,7 @@ export declare interface FirstSessionHudProps {
556
595
  playerName: string;
557
596
  }
558
597
 
559
- export declare function FirstSessionOnboarding({ labels, onDismiss, open }: FirstSessionOnboardingProps): ReactNode;
598
+ export declare function FirstSessionOnboarding({ labels, onDismiss, open, }: FirstSessionOnboardingProps): ReactNode;
560
599
 
561
600
  export declare interface FirstSessionOnboardingLabels {
562
601
  badge: string;
@@ -578,6 +617,15 @@ export declare interface FirstSessionOnboardingStep {
578
617
  title: string;
579
618
  }
580
619
 
620
+ /** Opt-in attribute values for the official overlay-glass HUD scope. */
621
+ export declare const GAME_UI_OVERLAY: {
622
+ readonly toneAttr: "data-game-ui-tone";
623
+ readonly toneGlass: "glass";
624
+ readonly densityAttr: "data-game-ui-density";
625
+ readonly densityCompact: "compact";
626
+ readonly scopeClass: "game-ui-overlay-scope";
627
+ };
628
+
581
629
  export declare const GAME_UI_PREVIEW_MESSAGES: readonly GameUiHistoryEntry[];
582
630
 
583
631
  export declare const GAME_UI_TARGETS: {
@@ -681,7 +729,7 @@ export declare const GAME_UI_TOKENS: {
681
729
  readonly disabled: "var(--game-ui-disabled)";
682
730
  };
683
731
 
684
- export declare function GameActionGrid({ actions, className, density, iconLabelMode, label, style }: GameActionGridProps): ReactNode;
732
+ export declare function GameActionGrid({ actions, className, density, iconLabelMode, label, style, }: GameActionGridProps): ReactNode;
685
733
 
686
734
  export declare interface GameActionGridProps {
687
735
  actions: readonly GameUiAction[];
@@ -736,7 +784,7 @@ export declare interface GameAssetGroup {
736
784
  source?: GameAssetSource;
737
785
  }
738
786
 
739
- export declare function GameAssetIcon({ className, icon, label, size, style, useSourceAsset }: GameAssetIconProps): ReactNode;
787
+ export declare function GameAssetIcon({ className, icon, label, size, style, useSourceAsset, }: GameAssetIconProps): ReactNode;
740
788
 
741
789
  export declare interface GameAssetIconProps {
742
790
  className?: string;
@@ -773,7 +821,7 @@ export declare type GameAssetSource = 'starter' | 'generated' | 'imported';
773
821
 
774
822
  export declare type GameAssetStatus = 'ready' | 'generating' | 'missing' | 'placed' | 'selected' | 'error';
775
823
 
776
- export declare function GameAvatar({ className, name, size, src, status }: GameAvatarProps): ReactNode;
824
+ export declare function GameAvatar({ className, name, size, src, status, }: GameAvatarProps): ReactNode;
777
825
 
778
826
  export declare interface GameAvatarProps {
779
827
  /** Display name — used as image alt text and for the initials fallback. */
@@ -1155,7 +1203,7 @@ export declare interface GameDialogProps {
1155
1203
  title: string;
1156
1204
  }
1157
1205
 
1158
- export declare function GameEmptyState({ action, className, description, icon, title }: GameEmptyStateProps): ReactNode;
1206
+ export declare function GameEmptyState({ action, className, description, icon, title, }: GameEmptyStateProps): ReactNode;
1159
1207
 
1160
1208
  export declare interface GameEmptyStateProps {
1161
1209
  /** Optional clay icon shown above the title. */
@@ -1176,7 +1224,7 @@ export declare interface GameFactItem {
1176
1224
  value: ReactNode;
1177
1225
  }
1178
1226
 
1179
- export declare function GameFactList({ className, density, facts, label, variant }: GameFactListProps): ReactNode;
1227
+ export declare function GameFactList({ className, density, facts, label, variant, }: GameFactListProps): ReactNode;
1180
1228
 
1181
1229
  export declare interface GameFactListProps {
1182
1230
  className?: string;
@@ -1186,7 +1234,7 @@ export declare interface GameFactListProps {
1186
1234
  variant?: 'facts' | 'stats';
1187
1235
  }
1188
1236
 
1189
- export declare function GameField({ children, className, error, hint, label, required }: GameFieldProps): ReactNode;
1237
+ export declare function GameField({ children, className, error, hint, label, required, }: GameFieldProps): ReactNode;
1190
1238
 
1191
1239
  export declare interface GameFieldProps {
1192
1240
  /** The input/textarea this field labels. Rendered inside the <label>. */
@@ -1286,7 +1334,7 @@ export declare interface GameInteractionSoundOptions {
1286
1334
  sfxVolume?: number;
1287
1335
  }
1288
1336
 
1289
- export declare function GameLanguageMenu({ className, currentLabel, label, options, value, defaultValue, onSelect }: GameLanguageMenuProps): ReactNode;
1337
+ export declare function GameLanguageMenu({ className, currentLabel, label, options, value, defaultValue, onSelect, }: GameLanguageMenuProps): ReactNode;
1290
1338
 
1291
1339
  export declare interface GameLanguageMenuProps {
1292
1340
  className?: string;
@@ -1377,7 +1425,7 @@ export declare const GameObjectToolbar: typeof GamePlacementToolbar;
1377
1425
 
1378
1426
  export declare type GameObjectToolbarProps = GamePlacementToolbarProps;
1379
1427
 
1380
- export declare function GameOrientationGate({ body, cta, badgeLabel, manualHint, preview, title }: GameOrientationGateProps): ReactNode;
1428
+ export declare function GameOrientationGate({ body, cta, badgeLabel, manualHint, preview, title, }: GameOrientationGateProps): ReactNode;
1381
1429
 
1382
1430
  export declare interface GameOrientationGateProps {
1383
1431
  body: string;
@@ -1417,7 +1465,7 @@ export declare interface GamePlacementToolbarProps {
1417
1465
  title: string;
1418
1466
  }
1419
1467
 
1420
- export declare function GameProgress({ className, label, max, showValue, tone, value }: GameProgressProps): ReactNode;
1468
+ export declare function GameProgress({ className, label, max, showValue, tone, value, valueLabel, }: GameProgressProps): ReactNode;
1421
1469
 
1422
1470
  export declare interface GameProgressProps {
1423
1471
  /** Current value, between 0 and `max`. */
@@ -1428,6 +1476,11 @@ export declare interface GameProgressProps {
1428
1476
  tone?: 'accent' | 'success' | 'danger' | 'warning';
1429
1477
  /** Show the rounded percentage next to the bar. */
1430
1478
  showValue?: boolean;
1479
+ /**
1480
+ * Text shown next to the bar in place of the percentage, for progress that
1481
+ * is more meaningful as a count than a ratio (e.g. "3 / 21").
1482
+ */
1483
+ valueLabel?: string;
1431
1484
  className?: string;
1432
1485
  }
1433
1486
 
@@ -1480,7 +1533,7 @@ export declare const GameSceneHudLayout: typeof GameShell;
1480
1533
 
1481
1534
  export declare type GameSceneHudLayoutProps = GameShellProps;
1482
1535
 
1483
- export declare function GameSegmentedControl({ activeId, label, onSelect, options }: GameSegmentedControlProps): ReactNode;
1536
+ export declare function GameSegmentedControl({ activeId, label, onSelect, options, }: GameSegmentedControlProps): ReactNode;
1484
1537
 
1485
1538
  export declare interface GameSegmentedControlProps {
1486
1539
  activeId: string;