dreamboard 0.1.21 → 0.1.22

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 (186) hide show
  1. package/dist/{chunk-EYYWGWTO.js → chunk-2RCUHMGL.js} +343 -1911
  2. package/dist/chunk-2RCUHMGL.js.map +1 -0
  3. package/dist/{chunk-MOVHYB6E.js → chunk-AQ5UUNJS.js} +1018 -692
  4. package/dist/chunk-AQ5UUNJS.js.map +1 -0
  5. package/dist/{chunk-BMYC6772.js → chunk-H3O43F5P.js} +9765 -3068
  6. package/dist/chunk-H3O43F5P.js.map +1 -0
  7. package/dist/dev-host/dev-api-proxy-plugin.ts +330 -0
  8. package/dist/dev-host/dev-diagnostics.ts +62 -0
  9. package/dist/dev-host/dev-fallback-stylesheet.ts +50 -0
  10. package/dist/dev-host/dev-host-controller.ts +686 -0
  11. package/dist/dev-host/dev-host-player-query.ts +17 -0
  12. package/dist/dev-host/dev-host-session-transport.ts +52 -0
  13. package/dist/dev-host/dev-host-storage.ts +56 -0
  14. package/dist/dev-host/dev-log-relay-plugin.ts +469 -0
  15. package/dist/dev-host/dev-runtime-config.ts +14 -0
  16. package/dist/dev-host/dev-runtime-platform.ts +419 -0
  17. package/dist/dev-host/dev-virtual-modules-plugin.ts +63 -0
  18. package/dist/dev-host/host-main.css +182 -0
  19. package/dist/dev-host/host-main.tsx +754 -0
  20. package/dist/dev-host/index.html +56 -0
  21. package/dist/dev-host/plugin-main.ts +55 -0
  22. package/dist/dev-host/plugin.html +24 -0
  23. package/dist/dev-host/start-dev-server.ts +138 -0
  24. package/dist/dev-host/virtual-modules.d.ts +27 -0
  25. package/dist/dist-FEPN3BDN.js +16543 -0
  26. package/dist/dist-FEPN3BDN.js.map +1 -0
  27. package/dist/{dist-WJRJNFLI.js → dist-FRURQI7Q.js} +4 -2
  28. package/dist/index.js +3 -3
  29. package/dist/internal.js +159 -124
  30. package/dist/internal.js.map +1 -1
  31. package/dist/runtime-packages/tailwind-config/shared-styles.css +146 -0
  32. package/dist/runtime-packages/ui/src/components/.gitkeep +0 -0
  33. package/dist/runtime-packages/ui/src/components/accordion.tsx +66 -0
  34. package/dist/runtime-packages/ui/src/components/ai-elements/actions.tsx +65 -0
  35. package/dist/runtime-packages/ui/src/components/ai-elements/artifact.tsx +147 -0
  36. package/dist/runtime-packages/ui/src/components/ai-elements/branch.tsx +215 -0
  37. package/dist/runtime-packages/ui/src/components/ai-elements/canvas.tsx +22 -0
  38. package/dist/runtime-packages/ui/src/components/ai-elements/chain-of-thought.tsx +228 -0
  39. package/dist/runtime-packages/ui/src/components/ai-elements/code-block.tsx +179 -0
  40. package/dist/runtime-packages/ui/src/components/ai-elements/confirmation.tsx +158 -0
  41. package/dist/runtime-packages/ui/src/components/ai-elements/connection.tsx +28 -0
  42. package/dist/runtime-packages/ui/src/components/ai-elements/context.tsx +408 -0
  43. package/dist/runtime-packages/ui/src/components/ai-elements/controls.tsx +18 -0
  44. package/dist/runtime-packages/ui/src/components/ai-elements/conversation.tsx +97 -0
  45. package/dist/runtime-packages/ui/src/components/ai-elements/edge.tsx +140 -0
  46. package/dist/runtime-packages/ui/src/components/ai-elements/image.tsx +24 -0
  47. package/dist/runtime-packages/ui/src/components/ai-elements/inline-citation.tsx +287 -0
  48. package/dist/runtime-packages/ui/src/components/ai-elements/loader.tsx +96 -0
  49. package/dist/runtime-packages/ui/src/components/ai-elements/message.tsx +463 -0
  50. package/dist/runtime-packages/ui/src/components/ai-elements/node.tsx +71 -0
  51. package/dist/runtime-packages/ui/src/components/ai-elements/open-in-chat.tsx +365 -0
  52. package/dist/runtime-packages/ui/src/components/ai-elements/panel.tsx +15 -0
  53. package/dist/runtime-packages/ui/src/components/ai-elements/plan.tsx +142 -0
  54. package/dist/runtime-packages/ui/src/components/ai-elements/prompt-input.tsx +1380 -0
  55. package/dist/runtime-packages/ui/src/components/ai-elements/queue.tsx +274 -0
  56. package/dist/runtime-packages/ui/src/components/ai-elements/reasoning.tsx +182 -0
  57. package/dist/runtime-packages/ui/src/components/ai-elements/response.tsx +22 -0
  58. package/dist/runtime-packages/ui/src/components/ai-elements/shimmer.tsx +64 -0
  59. package/dist/runtime-packages/ui/src/components/ai-elements/sources.tsx +77 -0
  60. package/dist/runtime-packages/ui/src/components/ai-elements/suggestion.tsx +53 -0
  61. package/dist/runtime-packages/ui/src/components/ai-elements/task.tsx +87 -0
  62. package/dist/runtime-packages/ui/src/components/ai-elements/tool.tsx +165 -0
  63. package/dist/runtime-packages/ui/src/components/ai-elements/toolbar.tsx +16 -0
  64. package/dist/runtime-packages/ui/src/components/ai-elements/web-preview.tsx +263 -0
  65. package/dist/runtime-packages/ui/src/components/alert-dialog.tsx +157 -0
  66. package/dist/runtime-packages/ui/src/components/alert.tsx +66 -0
  67. package/dist/runtime-packages/ui/src/components/avatar.tsx +53 -0
  68. package/dist/runtime-packages/ui/src/components/badge.tsx +46 -0
  69. package/dist/runtime-packages/ui/src/components/button-group.tsx +83 -0
  70. package/dist/runtime-packages/ui/src/components/button.tsx +65 -0
  71. package/dist/runtime-packages/ui/src/components/card.tsx +92 -0
  72. package/dist/runtime-packages/ui/src/components/carousel.tsx +241 -0
  73. package/dist/runtime-packages/ui/src/components/collapsible.tsx +33 -0
  74. package/dist/runtime-packages/ui/src/components/command.tsx +184 -0
  75. package/dist/runtime-packages/ui/src/components/context-menu.tsx +252 -0
  76. package/dist/runtime-packages/ui/src/components/counter.tsx +11 -0
  77. package/dist/runtime-packages/ui/src/components/dialog.tsx +146 -0
  78. package/dist/runtime-packages/ui/src/components/drawer.tsx +132 -0
  79. package/dist/runtime-packages/ui/src/components/dropdown-menu.tsx +257 -0
  80. package/dist/runtime-packages/ui/src/components/form.tsx +168 -0
  81. package/dist/runtime-packages/ui/src/components/header.tsx +13 -0
  82. package/dist/runtime-packages/ui/src/components/hover-card.tsx +44 -0
  83. package/dist/runtime-packages/ui/src/components/input-group.tsx +178 -0
  84. package/dist/runtime-packages/ui/src/components/input.tsx +21 -0
  85. package/dist/runtime-packages/ui/src/components/item.tsx +193 -0
  86. package/dist/runtime-packages/ui/src/components/label.tsx +24 -0
  87. package/dist/runtime-packages/ui/src/components/navigation-menu.tsx +168 -0
  88. package/dist/runtime-packages/ui/src/components/popover.tsx +55 -0
  89. package/dist/runtime-packages/ui/src/components/progress.tsx +31 -0
  90. package/dist/runtime-packages/ui/src/components/radio-group.tsx +45 -0
  91. package/dist/runtime-packages/ui/src/components/resizable.tsx +56 -0
  92. package/dist/runtime-packages/ui/src/components/scroll-area.tsx +58 -0
  93. package/dist/runtime-packages/ui/src/components/select.tsx +187 -0
  94. package/dist/runtime-packages/ui/src/components/separator.tsx +28 -0
  95. package/dist/runtime-packages/ui/src/components/skeleton.tsx +13 -0
  96. package/dist/runtime-packages/ui/src/components/sonner.tsx +25 -0
  97. package/dist/runtime-packages/ui/src/components/spinner.tsx +16 -0
  98. package/dist/runtime-packages/ui/src/components/switch.tsx +31 -0
  99. package/dist/runtime-packages/ui/src/components/table.tsx +137 -0
  100. package/dist/runtime-packages/ui/src/components/tabs.tsx +74 -0
  101. package/dist/runtime-packages/ui/src/components/textarea.tsx +23 -0
  102. package/dist/runtime-packages/ui/src/components/theme-provider.tsx +73 -0
  103. package/dist/runtime-packages/ui/src/components/tooltip.tsx +61 -0
  104. package/dist/runtime-packages/ui/src/components/ui/file-upload.tsx +195 -0
  105. package/dist/runtime-packages/ui/src/hooks/.gitkeep +0 -0
  106. package/dist/runtime-packages/ui/src/index.ts +68 -0
  107. package/dist/runtime-packages/ui/src/lib/utils.ts +6 -0
  108. package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +68 -0
  109. package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +359 -0
  110. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +274 -0
  111. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +214 -0
  112. package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +135 -0
  113. package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +5 -0
  114. package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +194 -0
  115. package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +1 -0
  116. package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +1 -0
  117. package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +162 -0
  118. package/dist/runtime-packages/ui-host-runtime/src/index.ts +2 -0
  119. package/dist/runtime-packages/ui-host-runtime/src/logger.ts +11 -0
  120. package/dist/runtime-packages/ui-host-runtime/src/perf.ts +253 -0
  121. package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +195 -0
  122. package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +138 -0
  123. package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +159 -0
  124. package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +524 -0
  125. package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +12 -0
  126. package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +122 -0
  127. package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +26 -0
  128. package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +477 -0
  129. package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +209 -0
  130. package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +112 -0
  131. package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +318 -0
  132. package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +211 -0
  133. package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +814 -0
  134. package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +334 -0
  135. package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +180 -0
  136. package/dist/scaffold/assets/static/app/tsconfig.framework.json +3 -1
  137. package/dist/scaffold/assets/static/ui/tsconfig.framework.json +7 -19
  138. package/package.json +52 -17
  139. package/skills/dreamboard/SKILL.md +41 -75
  140. package/LICENSE +0 -89
  141. package/NOTICE +0 -1
  142. package/dist/chunk-BMYC6772.js.map +0 -1
  143. package/dist/chunk-EYYWGWTO.js.map +0 -1
  144. package/dist/chunk-MOVHYB6E.js.map +0 -1
  145. package/dist/scaffold/assets/static/ui/App.tsx +0 -22
  146. package/dist/src-CUL7EGGG.js +0 -634
  147. package/dist/src-CUL7EGGG.js.map +0 -1
  148. package/skills/dreamboard/references/authoring-lifecycle.md +0 -102
  149. package/skills/dreamboard/references/board-surfaces.md +0 -36
  150. package/skills/dreamboard/references/board-topology.md +0 -443
  151. package/skills/dreamboard/references/boards-and-topology.md +0 -100
  152. package/skills/dreamboard/references/card-actions.md +0 -107
  153. package/skills/dreamboard/references/cli.md +0 -120
  154. package/skills/dreamboard/references/core-concepts.md +0 -514
  155. package/skills/dreamboard/references/custom-renderers.md +0 -26
  156. package/skills/dreamboard/references/derived-values.md +0 -55
  157. package/skills/dreamboard/references/effects.md +0 -111
  158. package/skills/dreamboard/references/game-contract.md +0 -89
  159. package/skills/dreamboard/references/game-definition.md +0 -89
  160. package/skills/dreamboard/references/game-shell.md +0 -80
  161. package/skills/dreamboard/references/hand-surfaces.md +0 -33
  162. package/skills/dreamboard/references/index.md +0 -112
  163. package/skills/dreamboard/references/inputs-and-targets.md +0 -160
  164. package/skills/dreamboard/references/interactions.md +0 -158
  165. package/skills/dreamboard/references/manifest-fields.md +0 -137
  166. package/skills/dreamboard/references/manifest.md +0 -81
  167. package/skills/dreamboard/references/package-surfaces.md +0 -69
  168. package/skills/dreamboard/references/phases.md +0 -145
  169. package/skills/dreamboard/references/prompts-and-choices.md +0 -55
  170. package/skills/dreamboard/references/quickstart.md +0 -71
  171. package/skills/dreamboard/references/rule-authoring.md +0 -144
  172. package/skills/dreamboard/references/setup-bootstrap.md +0 -78
  173. package/skills/dreamboard/references/stages-and-zones.md +0 -59
  174. package/skills/dreamboard/references/static-views.md +0 -67
  175. package/skills/dreamboard/references/table-queries-and-ops.md +0 -103
  176. package/skills/dreamboard/references/testing-bases.md +0 -147
  177. package/skills/dreamboard/references/testing-generated-contracts.md +0 -142
  178. package/skills/dreamboard/references/testing-runtime-assertions.md +0 -124
  179. package/skills/dreamboard/references/testing-scenarios.md +0 -148
  180. package/skills/dreamboard/references/testing-ui-tests.md +0 -174
  181. package/skills/dreamboard/references/testing.md +0 -161
  182. package/skills/dreamboard/references/ui-architecture.md +0 -137
  183. package/skills/dreamboard/references/ui-components.md +0 -34
  184. package/skills/dreamboard/references/views.md +0 -72
  185. package/skills/dreamboard/references/workspace-layout.md +0 -136
  186. /package/dist/{dist-WJRJNFLI.js.map → dist-FRURQI7Q.js.map} +0 -0
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Host-owned inline feedback stack.
3
+ *
4
+ * Visual styling is driven entirely by the active `@dreamboard/ui-sdk`
5
+ * `Theme` so the stack re-skins with the rest of the host shell. The
6
+ * earlier implementation relied on raw Tailwind colour classes
7
+ * (`bg-emerald-500/10`, `text-amber-700`, …) which were untouchable
8
+ * by the theme tokens.
9
+ */
10
+
11
+ import { AlertTriangle, Bell, Clock3, X } from "lucide-react";
12
+ import {
13
+ intentForVariant,
14
+ surfaceStyle,
15
+ useTheme,
16
+ type ButtonVariant,
17
+ type Theme,
18
+ } from "@dreamboard/ui-sdk";
19
+ import type { HostFeedback } from "../unified-session-store.js";
20
+
21
+ export interface HostFeedbackStackProps {
22
+ feedback: HostFeedback[];
23
+ onDismiss?: (feedbackId: string) => void;
24
+ className?: string;
25
+ }
26
+
27
+ interface StackPresentation {
28
+ title: string;
29
+ description: string;
30
+ variant: Extract<ButtonVariant, "danger" | "warning" | "success" | "info">;
31
+ icon: typeof AlertTriangle;
32
+ }
33
+
34
+ function describeFeedback(item: HostFeedback): StackPresentation {
35
+ switch (item.type) {
36
+ case "YOUR_TURN": {
37
+ const description =
38
+ item.payload.type === "YOUR_TURN" &&
39
+ item.payload.activePlayers.length > 1
40
+ ? "You can act with one of your controlled players."
41
+ : "You can act now.";
42
+ return {
43
+ title: "Your turn",
44
+ description,
45
+ variant: "success",
46
+ icon: Bell,
47
+ };
48
+ }
49
+ case "PROMPT_OPENED": {
50
+ const payload = item.payload;
51
+ const description =
52
+ payload.type === "PROMPT_OPENED"
53
+ ? payload.targetPlayer
54
+ ? `${payload.title ?? "A prompt is waiting."} (${payload.targetPlayer})`
55
+ : (payload.title ?? "A prompt is waiting.")
56
+ : "A prompt is waiting.";
57
+ return {
58
+ title: "Response needed",
59
+ description,
60
+ variant: "warning",
61
+ icon: Clock3,
62
+ };
63
+ }
64
+ case "ACTION_REJECTED": {
65
+ const payload = item.payload;
66
+ const description =
67
+ payload.type === "ACTION_REJECTED"
68
+ ? payload.targetPlayer
69
+ ? `${payload.reason} (${payload.targetPlayer})`
70
+ : payload.reason
71
+ : "Action rejected.";
72
+ return {
73
+ title: "Action rejected",
74
+ description,
75
+ variant: "danger",
76
+ icon: AlertTriangle,
77
+ };
78
+ }
79
+ }
80
+ }
81
+
82
+ interface FeedbackEntryProps {
83
+ item: HostFeedback;
84
+ presentation: StackPresentation;
85
+ theme: Theme;
86
+ onDismiss?: (feedbackId: string) => void;
87
+ }
88
+
89
+ function FeedbackEntry({
90
+ item,
91
+ presentation,
92
+ theme,
93
+ onDismiss,
94
+ }: FeedbackEntryProps) {
95
+ const intent = intentForVariant(theme, presentation.variant);
96
+ const Icon = presentation.icon;
97
+ return (
98
+ <div
99
+ role="status"
100
+ aria-live="polite"
101
+ style={{
102
+ ...surfaceStyle(theme, { tone: "card", radius: "lg" }),
103
+ background: intent.soft,
104
+ color: intent.onSoft,
105
+ border: `1px solid ${intent.border}`,
106
+ boxShadow: theme.elevation.rest,
107
+ display: "flex",
108
+ alignItems: "flex-start",
109
+ gap: theme.space[3],
110
+ padding: theme.space[3],
111
+ fontFamily: theme.typography.fontFamily.body,
112
+ }}
113
+ >
114
+ <Icon
115
+ size={20}
116
+ strokeWidth={2.5}
117
+ aria-hidden="true"
118
+ style={{
119
+ flexShrink: 0,
120
+ marginTop: 2,
121
+ color: intent.solid,
122
+ }}
123
+ />
124
+ <div style={{ flex: 1, minWidth: 0 }}>
125
+ <div
126
+ style={{
127
+ fontFamily: theme.typography.fontFamily.display,
128
+ fontSize: theme.typography.fontSize.md,
129
+ fontWeight: theme.typography.fontWeight.bold,
130
+ lineHeight: theme.typography.lineHeight.tight,
131
+ color: intent.onSoft,
132
+ }}
133
+ >
134
+ {presentation.title}
135
+ </div>
136
+ <div
137
+ style={{
138
+ marginTop: theme.space[1],
139
+ fontSize: theme.typography.fontSize.sm,
140
+ fontWeight: theme.typography.fontWeight.medium,
141
+ lineHeight: theme.typography.lineHeight.normal,
142
+ color: intent.onSoft,
143
+ opacity: 0.92,
144
+ wordBreak: "break-word",
145
+ }}
146
+ >
147
+ {presentation.description}
148
+ </div>
149
+ </div>
150
+ {onDismiss ? (
151
+ <button
152
+ type="button"
153
+ aria-label="Dismiss feedback"
154
+ onClick={() => onDismiss(item.id)}
155
+ style={{
156
+ flexShrink: 0,
157
+ width: 28,
158
+ height: 28,
159
+ display: "inline-flex",
160
+ alignItems: "center",
161
+ justifyContent: "center",
162
+ background: "transparent",
163
+ border: "none",
164
+ borderRadius: theme.radius.pill,
165
+ color: intent.onSoft,
166
+ cursor: "pointer",
167
+ opacity: 0.7,
168
+ transition: `opacity ${theme.motion.duration.fast} ${theme.motion.easing.out}`,
169
+ }}
170
+ onMouseEnter={(event) => {
171
+ event.currentTarget.style.opacity = "1";
172
+ }}
173
+ onMouseLeave={(event) => {
174
+ event.currentTarget.style.opacity = "0.7";
175
+ }}
176
+ >
177
+ <X size={16} aria-hidden="true" />
178
+ </button>
179
+ ) : null}
180
+ </div>
181
+ );
182
+ }
183
+
184
+ export function HostFeedbackStack({
185
+ feedback,
186
+ onDismiss,
187
+ className,
188
+ }: HostFeedbackStackProps) {
189
+ const theme = useTheme();
190
+ if (feedback.length === 0) {
191
+ return null;
192
+ }
193
+
194
+ return (
195
+ <div
196
+ className={className}
197
+ style={{
198
+ display: "flex",
199
+ flexDirection: "column",
200
+ gap: theme.space[2],
201
+ }}
202
+ >
203
+ {[...feedback].reverse().map((item) => (
204
+ <FeedbackEntry
205
+ key={item.id}
206
+ item={item}
207
+ presentation={describeFeedback(item)}
208
+ theme={theme}
209
+ onDismiss={onDismiss}
210
+ />
211
+ ))}
212
+ </div>
213
+ );
214
+ }
@@ -0,0 +1,135 @@
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
+ }
@@ -0,0 +1,5 @@
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";
@@ -0,0 +1,194 @@
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
+ };
@@ -0,0 +1 @@
1
+ export * from "./components/host-controls.js";
@@ -0,0 +1 @@
1
+ export * from "./components/host-feedback.js";