@dreamboard-games/cli 0.1.30-alpha.1 → 0.1.30-alpha.2

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.
Files changed (114) hide show
  1. package/README.md +179 -22
  2. package/dist/{chunk-C6UAT6EH.js → chunk-N7XPNNUI.js} +9 -12
  3. package/dist/chunk-N7XPNNUI.js.map +1 -0
  4. package/dist/chunk-SEGVTWSK.js +44 -0
  5. package/dist/{chunk-RS7UXJZV.js → chunk-TAQKH67O.js} +21300 -35881
  6. package/dist/chunk-TAQKH67O.js.map +1 -0
  7. package/dist/{global-config-AGFBDFYD.js → global-config-S4ZIPECE.js} +3 -3
  8. package/dist/index.js +415 -37
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal.js +3 -4
  11. package/dist/{agent-verifier/keychain-backend-TNOPQV3Z.mjs → keychain-backend-HDF4TZDL.js} +2 -1
  12. package/dist/{agent-verifier/prompt-3BAINGAQ.mjs → prompt-NDV3AE5L.js} +2 -1
  13. package/package.json +6 -6
  14. package/skills/dreamboard/references/building-your-first-game.md +510 -0
  15. package/skills/dreamboard/references/cli.md +104 -0
  16. package/skills/dreamboard/references/game-interface.md +548 -0
  17. package/skills/dreamboard/references/manifest-authoring.md +597 -0
  18. package/skills/dreamboard/references/quickstart.md +66 -0
  19. package/skills/dreamboard/references/reducer.md +864 -0
  20. package/skills/dreamboard/references/rule-authoring.md +147 -0
  21. package/skills/dreamboard/references/testing.md +249 -0
  22. package/skills/dreamboard/scripts/events-extract.mjs +218 -0
  23. package/dist/agent-verifier/agent-workspace-verifier.mjs +0 -227
  24. package/dist/agent-verifier/chunk-2E5P5NWG.mjs +0 -835
  25. package/dist/agent-verifier/chunk-2GBBP27W.mjs +0 -301
  26. package/dist/agent-verifier/chunk-2QMNAVV4.mjs +0 -14522
  27. package/dist/agent-verifier/chunk-2SZHMP6F.mjs +0 -264
  28. package/dist/agent-verifier/chunk-4WD3YU2E.mjs +0 -166
  29. package/dist/agent-verifier/chunk-54TAYXUD.mjs +0 -12
  30. package/dist/agent-verifier/chunk-6A5HRJMQ.mjs +0 -3174
  31. package/dist/agent-verifier/chunk-6UUJEYDV.mjs +0 -213
  32. package/dist/agent-verifier/chunk-7653FPGJ.mjs +0 -381
  33. package/dist/agent-verifier/chunk-7E65UQLY.mjs +0 -38
  34. package/dist/agent-verifier/chunk-BVVNBJM4.mjs +0 -221
  35. package/dist/agent-verifier/chunk-CEDUHGNH.mjs +0 -74
  36. package/dist/agent-verifier/chunk-CEQ2VJWN.mjs +0 -149
  37. package/dist/agent-verifier/chunk-CFU5EWIC.mjs +0 -69
  38. package/dist/agent-verifier/chunk-CJEEA6NJ.mjs +0 -730
  39. package/dist/agent-verifier/chunk-EIQWDQWJ.mjs +0 -186
  40. package/dist/agent-verifier/chunk-EOQIV6PS.mjs +0 -649
  41. package/dist/agent-verifier/chunk-HBNDKQT5.mjs +0 -8381
  42. package/dist/agent-verifier/chunk-HJFQDSTU.mjs +0 -225
  43. package/dist/agent-verifier/chunk-JH22JNYD.mjs +0 -1681
  44. package/dist/agent-verifier/chunk-LI3ZR3BI.mjs +0 -41
  45. package/dist/agent-verifier/chunk-LM3OZLZG.mjs +0 -48
  46. package/dist/agent-verifier/chunk-MINCYHXN.mjs +0 -106
  47. package/dist/agent-verifier/chunk-MRCUP5SW.mjs +0 -128
  48. package/dist/agent-verifier/chunk-RBDDIIPM.mjs +0 -19
  49. package/dist/agent-verifier/chunk-SHUMAVAP.mjs +0 -59
  50. package/dist/agent-verifier/chunk-SYPLYRGB.mjs +0 -2812
  51. package/dist/agent-verifier/chunk-U6OJN7XS.mjs +0 -8092
  52. package/dist/agent-verifier/chunk-VYJTHSYR.mjs +0 -44
  53. package/dist/agent-verifier/chunk-XYDL7GY6.mjs +0 -10
  54. package/dist/agent-verifier/compile-5QSPIOUT.mjs +0 -313
  55. package/dist/agent-verifier/global-config-WX3ZZIVU.mjs +0 -17
  56. package/dist/agent-verifier/local-files-MTPLP62S.mjs +0 -46
  57. package/dist/agent-verifier/local-typecheck-QFYYAZOK.mjs +0 -9
  58. package/dist/agent-verifier/materialize-workspace-FKALAE2T.mjs +0 -90
  59. package/dist/agent-verifier/project-state-7GR6BQTQ.mjs +0 -32
  60. package/dist/agent-verifier/reducer-bundle-preflight-C73LEXI2.mjs +0 -23
  61. package/dist/agent-verifier/reducer-contract-preflight-22X7DSZW.mjs +0 -10
  62. package/dist/agent-verifier/reducer-native-test-harness-GMWBUISX.mjs +0 -53
  63. package/dist/agent-verifier/static-scaffold-AJMZZQWS.mjs +0 -28
  64. package/dist/agent-verifier/sync-3DUQH32H.mjs +0 -594
  65. package/dist/agent-verifier/test-P4U5INTD.mjs +0 -356
  66. package/dist/agent-verifier/testing-5K2BJYF2.mjs +0 -674
  67. package/dist/agent-verifier/workspace-codegen-JDZJRSDV.mjs +0 -11
  68. package/dist/agent-verifier/workspace-dependencies-HZ6VVS4G.mjs +0 -14
  69. package/dist/chunk-2H7UOFLK.js +0 -11
  70. package/dist/chunk-7FOO4AJI.js +0 -50
  71. package/dist/chunk-7FOO4AJI.js.map +0 -1
  72. package/dist/chunk-C6UAT6EH.js.map +0 -1
  73. package/dist/chunk-RS7UXJZV.js.map +0 -1
  74. package/dist/internal.d.ts +0 -311
  75. package/dist/keychain-backend-JHTXAKWC.js +0 -135
  76. package/dist/prompt-GMZABCJC.js +0 -756
  77. package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +0 -71
  78. package/dist/runtime-packages/ui-host-runtime/src/browser-interaction.ts +0 -139
  79. package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +0 -374
  80. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +0 -266
  81. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +0 -212
  82. package/dist/runtime-packages/ui-host-runtime/src/components/host-primitives.tsx +0 -271
  83. package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +0 -135
  84. package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +0 -5
  85. package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +0 -194
  86. package/dist/runtime-packages/ui-host-runtime/src/gameplay-authority-transport.ts +0 -626
  87. package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +0 -1
  88. package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +0 -1
  89. package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +0 -294
  90. package/dist/runtime-packages/ui-host-runtime/src/index.ts +0 -3
  91. package/dist/runtime-packages/ui-host-runtime/src/logger.ts +0 -11
  92. package/dist/runtime-packages/ui-host-runtime/src/perf.ts +0 -324
  93. package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +0 -195
  94. package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +0 -138
  95. package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +0 -159
  96. package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +0 -551
  97. package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +0 -13
  98. package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +0 -122
  99. package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +0 -26
  100. package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +0 -583
  101. package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +0 -219
  102. package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +0 -117
  103. package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +0 -431
  104. package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +0 -211
  105. package/dist/runtime-packages/ui-host-runtime/src/session-recovery.ts +0 -80
  106. package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +0 -1034
  107. package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +0 -416
  108. package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +0 -184
  109. package/dist/testing-KLSV6CPJ.js +0 -674
  110. package/dist/testing-KLSV6CPJ.js.map +0 -1
  111. /package/dist/{chunk-2H7UOFLK.js.map → chunk-SEGVTWSK.js.map} +0 -0
  112. /package/dist/{global-config-AGFBDFYD.js.map → global-config-S4ZIPECE.js.map} +0 -0
  113. /package/dist/{keychain-backend-JHTXAKWC.js.map → keychain-backend-HDF4TZDL.js.map} +0 -0
  114. /package/dist/{prompt-GMZABCJC.js.map → prompt-NDV3AE5L.js.map} +0 -0
@@ -1,271 +0,0 @@
1
- import * as React from "react";
2
- import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
3
- import * as PopoverPrimitive from "@radix-ui/react-popover";
4
- import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
5
- import { Slot } from "@radix-ui/react-slot";
6
- import { cva, type VariantProps } from "class-variance-authority";
7
- import { CircleIcon } from "lucide-react";
8
- import { clsx, type ClassValue } from "clsx";
9
- import { twMerge } from "tailwind-merge";
10
-
11
- export function cn(...inputs: ClassValue[]) {
12
- return twMerge(clsx(inputs));
13
- }
14
-
15
- const buttonVariants = cva(
16
- "inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:border-destructive border-[3px] wobbly-border-md",
17
- {
18
- variants: {
19
- variant: {
20
- default:
21
- "border-border hard-shadow hard-shadow-hover hard-shadow-active bg-white text-foreground hover:bg-primary hover:text-white",
22
- outline:
23
- "border-border hard-shadow hard-shadow-hover hard-shadow-active bg-transparent hover:bg-muted",
24
- secondary:
25
- "border-border hard-shadow hard-shadow-hover hard-shadow-active bg-secondary text-secondary-foreground hover:bg-ring hover:text-white",
26
- },
27
- size: {
28
- default: "h-12 px-6 py-2 has-[>svg]:px-4 text-lg",
29
- sm: "h-10 gap-1.5 px-4 has-[>svg]:px-3 text-base",
30
- },
31
- },
32
- defaultVariants: {
33
- variant: "default",
34
- size: "default",
35
- },
36
- },
37
- );
38
-
39
- export function Button({
40
- className,
41
- variant,
42
- size,
43
- asChild = false,
44
- ...props
45
- }: React.ComponentProps<"button"> &
46
- VariantProps<typeof buttonVariants> & {
47
- asChild?: boolean;
48
- }) {
49
- const Comp = asChild ? Slot : "button";
50
-
51
- return (
52
- <Comp
53
- data-slot="button"
54
- className={cn(buttonVariants({ variant, size, className }))}
55
- {...props}
56
- />
57
- );
58
- }
59
-
60
- const badgeVariants = cva(
61
- "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
62
- {
63
- variants: {
64
- variant: {
65
- default:
66
- "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
67
- secondary:
68
- "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
69
- },
70
- },
71
- defaultVariants: {
72
- variant: "default",
73
- },
74
- },
75
- );
76
-
77
- export function Badge({
78
- className,
79
- variant,
80
- asChild = false,
81
- ...props
82
- }: React.ComponentProps<"span"> &
83
- VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
84
- const Comp = asChild ? Slot : "span";
85
-
86
- return (
87
- <Comp
88
- data-slot="badge"
89
- className={cn(badgeVariants({ variant }), className)}
90
- {...props}
91
- />
92
- );
93
- }
94
-
95
- export function DropdownMenu({
96
- ...props
97
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
98
- return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
99
- }
100
-
101
- export function DropdownMenuTrigger({
102
- ...props
103
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
104
- return (
105
- <DropdownMenuPrimitive.Trigger
106
- data-slot="dropdown-menu-trigger"
107
- {...props}
108
- />
109
- );
110
- }
111
-
112
- export function DropdownMenuContent({
113
- className,
114
- sideOffset = 4,
115
- ...props
116
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
117
- return (
118
- <DropdownMenuPrimitive.Portal>
119
- <DropdownMenuPrimitive.Content
120
- data-slot="dropdown-menu-content"
121
- sideOffset={sideOffset}
122
- className={cn(
123
- "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
124
- className,
125
- )}
126
- {...props}
127
- />
128
- </DropdownMenuPrimitive.Portal>
129
- );
130
- }
131
-
132
- export function DropdownMenuLabel({
133
- className,
134
- inset,
135
- ...props
136
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
137
- inset?: boolean;
138
- }) {
139
- return (
140
- <DropdownMenuPrimitive.Label
141
- data-slot="dropdown-menu-label"
142
- data-inset={inset}
143
- className={cn(
144
- "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
145
- className,
146
- )}
147
- {...props}
148
- />
149
- );
150
- }
151
-
152
- export function DropdownMenuRadioGroup({
153
- ...props
154
- }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
155
- return (
156
- <DropdownMenuPrimitive.RadioGroup
157
- data-slot="dropdown-menu-radio-group"
158
- {...props}
159
- />
160
- );
161
- }
162
-
163
- export function DropdownMenuRadioItem({
164
- className,
165
- children,
166
- ...props
167
- }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
168
- return (
169
- <DropdownMenuPrimitive.RadioItem
170
- data-slot="dropdown-menu-radio-item"
171
- className={cn(
172
- "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
173
- className,
174
- )}
175
- {...props}
176
- >
177
- <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
178
- <DropdownMenuPrimitive.ItemIndicator>
179
- <CircleIcon className="size-2 fill-current" />
180
- </DropdownMenuPrimitive.ItemIndicator>
181
- </span>
182
- {children}
183
- </DropdownMenuPrimitive.RadioItem>
184
- );
185
- }
186
-
187
- export function DropdownMenuSeparator({
188
- className,
189
- ...props
190
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
191
- return (
192
- <DropdownMenuPrimitive.Separator
193
- data-slot="dropdown-menu-separator"
194
- className={cn("bg-border -mx-1 my-1 h-px", className)}
195
- {...props}
196
- />
197
- );
198
- }
199
-
200
- export function Popover({
201
- ...props
202
- }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
203
- return <PopoverPrimitive.Root data-slot="popover" {...props} />;
204
- }
205
-
206
- export function PopoverTrigger({
207
- ...props
208
- }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
209
- return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
210
- }
211
-
212
- type PopoverContentProps = React.ComponentProps<
213
- typeof PopoverPrimitive.Content
214
- > & {
215
- container?: HTMLElement | null;
216
- };
217
-
218
- export function PopoverContent({
219
- className,
220
- align = "center",
221
- sideOffset = 4,
222
- container,
223
- ...props
224
- }: PopoverContentProps) {
225
- return (
226
- <PopoverPrimitive.Portal container={container ?? undefined}>
227
- <PopoverPrimitive.Content
228
- data-slot="popover-content"
229
- align={align}
230
- sideOffset={sideOffset}
231
- className={cn(
232
- "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
233
- className,
234
- )}
235
- {...props}
236
- />
237
- </PopoverPrimitive.Portal>
238
- );
239
- }
240
-
241
- export function ScrollArea({
242
- className,
243
- children,
244
- ...props
245
- }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
246
- return (
247
- <ScrollAreaPrimitive.Root
248
- data-slot="scroll-area"
249
- className={cn("relative", className)}
250
- {...props}
251
- >
252
- <ScrollAreaPrimitive.Viewport
253
- data-slot="scroll-area-viewport"
254
- className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
255
- >
256
- {children}
257
- </ScrollAreaPrimitive.Viewport>
258
- <ScrollAreaPrimitive.ScrollAreaScrollbar
259
- data-slot="scroll-area-scrollbar"
260
- orientation="vertical"
261
- className="flex h-full w-2.5 touch-none border-l border-l-transparent p-px transition-colors select-none"
262
- >
263
- <ScrollAreaPrimitive.ScrollAreaThumb
264
- data-slot="scroll-area-thumb"
265
- className="bg-border relative flex-1 rounded-full"
266
- />
267
- </ScrollAreaPrimitive.ScrollAreaScrollbar>
268
- <ScrollAreaPrimitive.Corner />
269
- </ScrollAreaPrimitive.Root>
270
- );
271
- }
@@ -1,135 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { Check, Copy } from "lucide-react";
3
-
4
- type HostMetaField = "gameId" | "sessionId" | "shortCode";
5
-
6
- export interface HostSessionMetadataProps {
7
- gameId: string;
8
- sessionId: string;
9
- shortCode: string;
10
- className?: string;
11
- }
12
-
13
- export function HostSessionMetadata({
14
- gameId,
15
- sessionId,
16
- shortCode,
17
- className,
18
- }: HostSessionMetadataProps) {
19
- const [copiedField, setCopiedField] = useState<HostMetaField | null>(null);
20
-
21
- useEffect(() => {
22
- if (!copiedField) {
23
- return;
24
- }
25
-
26
- const timeoutId = window.setTimeout(() => {
27
- setCopiedField(null);
28
- }, 1600);
29
-
30
- return () => {
31
- window.clearTimeout(timeoutId);
32
- };
33
- }, [copiedField]);
34
-
35
- const handleCopyMeta = async (field: HostMetaField, value: string) => {
36
- if (!value || !navigator.clipboard?.writeText) {
37
- return;
38
- }
39
-
40
- try {
41
- await navigator.clipboard.writeText(value);
42
- setCopiedField(field);
43
- } catch {
44
- // Ignore clipboard failures; the button remains readable.
45
- }
46
- };
47
-
48
- return (
49
- <dl className={className ?? "space-y-3 text-sm"}>
50
- <HostSessionMetaRow
51
- label="Game ID"
52
- value={gameId}
53
- copied={copiedField === "gameId"}
54
- onCopy={() => void handleCopyMeta("gameId", gameId)}
55
- truncate
56
- />
57
- <HostSessionMetaRow
58
- label="Session ID"
59
- value={sessionId}
60
- copied={copiedField === "sessionId"}
61
- onCopy={() => void handleCopyMeta("sessionId", sessionId)}
62
- truncate
63
- />
64
- <HostSessionMetaRow
65
- label="Short code"
66
- value={shortCode}
67
- copied={copiedField === "shortCode"}
68
- onCopy={() => void handleCopyMeta("shortCode", shortCode)}
69
- />
70
- </dl>
71
- );
72
- }
73
-
74
- interface HostSessionMetaRowProps {
75
- label: string;
76
- value: string;
77
- copied: boolean;
78
- onCopy: () => void;
79
- truncate?: boolean;
80
- }
81
-
82
- function HostSessionMetaRow({
83
- label,
84
- value,
85
- copied,
86
- onCopy,
87
- truncate = false,
88
- }: HostSessionMetaRowProps) {
89
- const displayValue = truncate ? truncateMiddle(value) : value;
90
-
91
- return (
92
- <div className="grid grid-cols-[88px_minmax(0,1fr)] items-start gap-3">
93
- <dt className="pt-0.5 text-[11px] font-bold uppercase tracking-[0.14em] text-muted-foreground">
94
- {label}
95
- </dt>
96
- <dd className="min-w-0">
97
- <button
98
- type="button"
99
- onClick={onCopy}
100
- className={`flex w-full items-center justify-end gap-2 border-[2px] px-2.5 py-2 text-right transition-all ${
101
- copied
102
- ? "border-[#2d5da1] bg-[#2d5da1] text-white shadow-[2px_2px_0px_0px_#1d3f72]"
103
- : "border-border bg-white/85 text-foreground shadow-[2px_2px_0px_0px_#2d2d2d] hover:-translate-y-[1px] hover:bg-[#fff9c4]"
104
- } wobbly-border-md`}
105
- title={`Copy ${label}`}
106
- >
107
- <span className="min-w-0 flex-1 truncate font-mono text-[12px] font-bold sm:text-[13px]">
108
- {displayValue}
109
- </span>
110
- <span className="inline-flex shrink-0 items-center gap-1 text-[10px] font-bold uppercase tracking-[0.14em]">
111
- {copied ? (
112
- <>
113
- <Check className="h-3.5 w-3.5" />
114
- Copied
115
- </>
116
- ) : (
117
- <>
118
- <Copy className="h-3.5 w-3.5" />
119
- Copy
120
- </>
121
- )}
122
- </span>
123
- </button>
124
- </dd>
125
- </div>
126
- );
127
- }
128
-
129
- function truncateMiddle(value: string, leading = 12, trailing = 8): string {
130
- if (value.length <= leading + trailing + 3) {
131
- return value;
132
- }
133
-
134
- return `${value.slice(0, leading)}...${value.slice(-trailing)}`;
135
- }
@@ -1,5 +0,0 @@
1
- export * from "./host-controls.js";
2
- export * from "./host-feedback.js";
3
- export * from "./host-feedback-toaster.js";
4
- export * from "./host-session-metadata.js";
5
- export * from "./perf-overlay.js";
@@ -1,194 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import {
3
- PERF_MARK_NAMES,
4
- clearPerfEntries,
5
- getPerfEntries,
6
- isPerfEnabled,
7
- type PerfEntry,
8
- } from "../perf.js";
9
-
10
- /**
11
- * Tier-0 input-latency HUD.
12
- *
13
- * Enabled when {@link isPerfEnabled} returns true (dev build or
14
- * `localStorage.dreamboard.perf = 1`) AND the URL contains `?perf=1`.
15
- * The explicit query flag keeps the overlay off in the normal dev
16
- * experience; flip it on for a targeted benchmarking session.
17
- *
18
- * Shared by `apps/web` (real game sessions) and `apps/dreamboard-cli`
19
- * (`dreamboard dev` local host) so both surfaces expose the same HUD
20
- * against the same `clientActionId`-keyed ring buffer.
21
- */
22
- const REFRESH_INTERVAL_MS = 500;
23
- const MAX_ROWS = 10;
24
-
25
- interface RowStats {
26
- clientActionId: string;
27
- totalMs: number | null;
28
- serverMs: number | null;
29
- postAckTailMs: number | null;
30
- sseMs: number | null;
31
- renderMs: number | null;
32
- version: number | undefined;
33
- syncId: number | undefined;
34
- }
35
-
36
- function firstMarkMs(entry: PerfEntry, name: string): number | null {
37
- const mark = entry.marks.find((m) => m.name === name);
38
- return mark ? mark.timestampMs : null;
39
- }
40
-
41
- function deltaMs(a: number | null, b: number | null): number | null {
42
- if (a === null || b === null) return null;
43
- return Math.round((b - a) * 10) / 10;
44
- }
45
-
46
- function toRowStats(entry: PerfEntry): RowStats {
47
- const t0 = firstMarkMs(entry, PERF_MARK_NAMES.T0_CLICK);
48
- const t2 = firstMarkMs(entry, PERF_MARK_NAMES.T2_HTTP_SENT);
49
- const t3 = firstMarkMs(entry, PERF_MARK_NAMES.T3_HTTP_RESPONSE);
50
- const t4 = firstMarkMs(entry, PERF_MARK_NAMES.T4_SSE_RECEIVED);
51
- const t7 = firstMarkMs(entry, PERF_MARK_NAMES.T7_STATE_SYNC_RECEIVED);
52
- const t8 = firstMarkMs(entry, PERF_MARK_NAMES.T8_RENDER_COMMIT);
53
- return {
54
- clientActionId: entry.clientActionId,
55
- totalMs: deltaMs(t0, t8),
56
- serverMs: deltaMs(t2, t3),
57
- postAckTailMs: deltaMs(t3, t8),
58
- sseMs: deltaMs(t3, t4),
59
- renderMs: deltaMs(t7, t8),
60
- version: entry.version,
61
- syncId: entry.syncId,
62
- };
63
- }
64
-
65
- function formatMs(value: number | null): string {
66
- if (value === null) return "—";
67
- return `${value.toFixed(1)}ms`;
68
- }
69
-
70
- function formatActionId(actionId: string): string {
71
- return actionId.length > 8 ? `${actionId.slice(0, 8)}…` : actionId;
72
- }
73
-
74
- function usePerfOverlayEnabled(): boolean {
75
- const [enabled, setEnabled] = useState(false);
76
- useEffect(() => {
77
- if (typeof window === "undefined") return;
78
- if (!isPerfEnabled()) {
79
- setEnabled(false);
80
- return;
81
- }
82
- const check = () => {
83
- const params = new URLSearchParams(window.location.search);
84
- setEnabled(params.get("perf") === "1");
85
- };
86
- check();
87
- window.addEventListener("popstate", check);
88
- return () => window.removeEventListener("popstate", check);
89
- }, []);
90
- return enabled;
91
- }
92
-
93
- export const PerfOverlay: React.FC = () => {
94
- const enabled = usePerfOverlayEnabled();
95
- const [rows, setRows] = useState<RowStats[]>([]);
96
-
97
- useEffect(() => {
98
- if (!enabled) return;
99
- const refresh = () => {
100
- const entries = getPerfEntries();
101
- const sliced = entries.slice(-MAX_ROWS).reverse();
102
- setRows(sliced.map(toRowStats));
103
- };
104
- refresh();
105
- const handle = window.setInterval(refresh, REFRESH_INTERVAL_MS);
106
- return () => window.clearInterval(handle);
107
- }, [enabled]);
108
-
109
- if (!enabled) return null;
110
-
111
- return (
112
- <div
113
- className="fixed bottom-4 right-4 z-[9999] max-w-[640px] rounded-md border border-slate-700 bg-slate-900/90 p-3 font-mono text-[11px] text-slate-100 shadow-lg backdrop-blur"
114
- data-testid="perf-overlay"
115
- >
116
- <div className="mb-2 flex items-center justify-between gap-3">
117
- <span className="font-bold uppercase tracking-wider text-emerald-300">
118
- Tier-0 Input Perf
119
- </span>
120
- <div className="flex items-center gap-2">
121
- <button
122
- type="button"
123
- className="rounded border border-slate-600 px-2 py-0.5 text-[10px] uppercase hover:bg-slate-700"
124
- onClick={() => {
125
- clearPerfEntries();
126
- setRows([]);
127
- }}
128
- >
129
- Clear
130
- </button>
131
- <button
132
- type="button"
133
- className="rounded border border-slate-600 px-2 py-0.5 text-[10px] uppercase hover:bg-slate-700"
134
- onClick={() => {
135
- // eslint-disable-next-line no-console
136
- console.table(getPerfEntries());
137
- }}
138
- >
139
- Dump
140
- </button>
141
- </div>
142
- </div>
143
- {rows.length === 0 ? (
144
- <div className="py-2 text-slate-400">
145
- No samples yet. Submit an action to populate the HUD.
146
- </div>
147
- ) : (
148
- <table className="w-full border-collapse">
149
- <thead className="text-slate-400">
150
- <tr>
151
- <th className="pr-2 text-left font-semibold">action</th>
152
- <th className="pr-2 text-right font-semibold">v</th>
153
- <th className="pr-2 text-right font-semibold">total</th>
154
- <th className="pr-2 text-right font-semibold">server</th>
155
- <th className="pr-2 text-right font-semibold">post-ack</th>
156
- <th className="pr-2 text-right font-semibold">sse</th>
157
- <th className="text-right font-semibold">render</th>
158
- </tr>
159
- </thead>
160
- <tbody>
161
- {rows.map((row) => (
162
- <tr
163
- key={row.clientActionId}
164
- className="border-t border-slate-800 align-top"
165
- >
166
- <td className="pr-2 text-slate-300">
167
- {formatActionId(row.clientActionId)}
168
- </td>
169
- <td className="pr-2 text-right text-slate-400">
170
- {row.version ?? "—"}
171
- </td>
172
- <td className="pr-2 text-right text-emerald-200">
173
- {formatMs(row.totalMs)}
174
- </td>
175
- <td className="pr-2 text-right text-slate-200">
176
- {formatMs(row.serverMs)}
177
- </td>
178
- <td className="pr-2 text-right text-amber-200">
179
- {formatMs(row.postAckTailMs)}
180
- </td>
181
- <td className="pr-2 text-right text-slate-200">
182
- {formatMs(row.sseMs)}
183
- </td>
184
- <td className="text-right text-slate-200">
185
- {formatMs(row.renderMs)}
186
- </td>
187
- </tr>
188
- ))}
189
- </tbody>
190
- </table>
191
- )}
192
- </div>
193
- );
194
- };