dreamboard 0.1.21 → 0.1.23
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/dist/{chunk-EYYWGWTO.js → chunk-2RCUHMGL.js} +343 -1911
- package/dist/chunk-2RCUHMGL.js.map +1 -0
- package/dist/{chunk-MOVHYB6E.js → chunk-GN7232BY.js} +1025 -695
- package/dist/chunk-GN7232BY.js.map +1 -0
- package/dist/{chunk-BMYC6772.js → chunk-H3O43F5P.js} +9765 -3068
- package/dist/chunk-H3O43F5P.js.map +1 -0
- package/dist/dev-host/dev-api-proxy-plugin.ts +330 -0
- package/dist/dev-host/dev-diagnostics.ts +62 -0
- package/dist/dev-host/dev-fallback-stylesheet.ts +50 -0
- package/dist/dev-host/dev-host-controller.ts +686 -0
- package/dist/dev-host/dev-host-player-query.ts +17 -0
- package/dist/dev-host/dev-host-session-transport.ts +52 -0
- package/dist/dev-host/dev-host-storage.ts +56 -0
- package/dist/dev-host/dev-log-relay-plugin.ts +469 -0
- package/dist/dev-host/dev-runtime-config.ts +14 -0
- package/dist/dev-host/dev-runtime-platform.ts +419 -0
- package/dist/dev-host/dev-virtual-modules-plugin.ts +63 -0
- package/dist/dev-host/host-main.css +182 -0
- package/dist/dev-host/host-main.tsx +754 -0
- package/dist/dev-host/index.html +56 -0
- package/dist/dev-host/plugin-main.ts +55 -0
- package/dist/dev-host/plugin.html +24 -0
- package/dist/dev-host/start-dev-server.ts +138 -0
- package/dist/dev-host/virtual-modules.d.ts +27 -0
- package/dist/dist-FEPN3BDN.js +16543 -0
- package/dist/dist-FEPN3BDN.js.map +1 -0
- package/dist/{dist-WJRJNFLI.js → dist-FRURQI7Q.js} +4 -2
- package/dist/index.js +3 -3
- package/dist/internal.js +159 -124
- package/dist/internal.js.map +1 -1
- package/dist/runtime-packages/tailwind-config/shared-styles.css +146 -0
- package/dist/runtime-packages/ui/src/components/.gitkeep +0 -0
- package/dist/runtime-packages/ui/src/components/accordion.tsx +66 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/actions.tsx +65 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/artifact.tsx +147 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/branch.tsx +215 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/canvas.tsx +22 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/chain-of-thought.tsx +228 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/code-block.tsx +179 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/confirmation.tsx +158 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/connection.tsx +28 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/context.tsx +408 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/controls.tsx +18 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/conversation.tsx +97 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/edge.tsx +140 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/image.tsx +24 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/inline-citation.tsx +287 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/loader.tsx +96 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/message.tsx +463 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/node.tsx +71 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/open-in-chat.tsx +365 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/panel.tsx +15 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/plan.tsx +142 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/prompt-input.tsx +1380 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/queue.tsx +274 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/reasoning.tsx +182 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/response.tsx +22 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/shimmer.tsx +64 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/sources.tsx +77 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/suggestion.tsx +53 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/task.tsx +87 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/tool.tsx +165 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/toolbar.tsx +16 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/web-preview.tsx +263 -0
- package/dist/runtime-packages/ui/src/components/alert-dialog.tsx +157 -0
- package/dist/runtime-packages/ui/src/components/alert.tsx +66 -0
- package/dist/runtime-packages/ui/src/components/avatar.tsx +53 -0
- package/dist/runtime-packages/ui/src/components/badge.tsx +46 -0
- package/dist/runtime-packages/ui/src/components/button-group.tsx +83 -0
- package/dist/runtime-packages/ui/src/components/button.tsx +65 -0
- package/dist/runtime-packages/ui/src/components/card.tsx +92 -0
- package/dist/runtime-packages/ui/src/components/carousel.tsx +241 -0
- package/dist/runtime-packages/ui/src/components/collapsible.tsx +33 -0
- package/dist/runtime-packages/ui/src/components/command.tsx +184 -0
- package/dist/runtime-packages/ui/src/components/context-menu.tsx +252 -0
- package/dist/runtime-packages/ui/src/components/counter.tsx +11 -0
- package/dist/runtime-packages/ui/src/components/dialog.tsx +146 -0
- package/dist/runtime-packages/ui/src/components/drawer.tsx +132 -0
- package/dist/runtime-packages/ui/src/components/dropdown-menu.tsx +257 -0
- package/dist/runtime-packages/ui/src/components/form.tsx +168 -0
- package/dist/runtime-packages/ui/src/components/header.tsx +13 -0
- package/dist/runtime-packages/ui/src/components/hover-card.tsx +44 -0
- package/dist/runtime-packages/ui/src/components/input-group.tsx +178 -0
- package/dist/runtime-packages/ui/src/components/input.tsx +21 -0
- package/dist/runtime-packages/ui/src/components/item.tsx +193 -0
- package/dist/runtime-packages/ui/src/components/label.tsx +24 -0
- package/dist/runtime-packages/ui/src/components/navigation-menu.tsx +168 -0
- package/dist/runtime-packages/ui/src/components/popover.tsx +55 -0
- package/dist/runtime-packages/ui/src/components/progress.tsx +31 -0
- package/dist/runtime-packages/ui/src/components/radio-group.tsx +45 -0
- package/dist/runtime-packages/ui/src/components/resizable.tsx +56 -0
- package/dist/runtime-packages/ui/src/components/scroll-area.tsx +58 -0
- package/dist/runtime-packages/ui/src/components/select.tsx +187 -0
- package/dist/runtime-packages/ui/src/components/separator.tsx +28 -0
- package/dist/runtime-packages/ui/src/components/skeleton.tsx +13 -0
- package/dist/runtime-packages/ui/src/components/sonner.tsx +25 -0
- package/dist/runtime-packages/ui/src/components/spinner.tsx +16 -0
- package/dist/runtime-packages/ui/src/components/switch.tsx +31 -0
- package/dist/runtime-packages/ui/src/components/table.tsx +137 -0
- package/dist/runtime-packages/ui/src/components/tabs.tsx +74 -0
- package/dist/runtime-packages/ui/src/components/textarea.tsx +23 -0
- package/dist/runtime-packages/ui/src/components/theme-provider.tsx +73 -0
- package/dist/runtime-packages/ui/src/components/tooltip.tsx +61 -0
- package/dist/runtime-packages/ui/src/components/ui/file-upload.tsx +195 -0
- package/dist/runtime-packages/ui/src/hooks/.gitkeep +0 -0
- package/dist/runtime-packages/ui/src/index.ts +68 -0
- package/dist/runtime-packages/ui/src/lib/utils.ts +6 -0
- package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +68 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +359 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +274 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +214 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +135 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +5 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +194 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +1 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +1 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +162 -0
- package/dist/runtime-packages/ui-host-runtime/src/index.ts +2 -0
- package/dist/runtime-packages/ui-host-runtime/src/logger.ts +11 -0
- package/dist/runtime-packages/ui-host-runtime/src/perf.ts +253 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +195 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +138 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +159 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +524 -0
- package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +12 -0
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +122 -0
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +26 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +477 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +209 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +112 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +318 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +211 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +814 -0
- package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +334 -0
- package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +180 -0
- package/dist/scaffold/assets/static/app/tsconfig.framework.json +3 -1
- package/dist/scaffold/assets/static/ui/tsconfig.framework.json +7 -19
- package/package.json +52 -17
- package/skills/dreamboard/SKILL.md +41 -75
- package/LICENSE +0 -89
- package/NOTICE +0 -1
- package/dist/chunk-BMYC6772.js.map +0 -1
- package/dist/chunk-EYYWGWTO.js.map +0 -1
- package/dist/chunk-MOVHYB6E.js.map +0 -1
- package/dist/scaffold/assets/static/ui/App.tsx +0 -22
- package/dist/src-CUL7EGGG.js +0 -634
- package/dist/src-CUL7EGGG.js.map +0 -1
- package/skills/dreamboard/references/authoring-lifecycle.md +0 -102
- package/skills/dreamboard/references/board-surfaces.md +0 -36
- package/skills/dreamboard/references/board-topology.md +0 -443
- package/skills/dreamboard/references/boards-and-topology.md +0 -100
- package/skills/dreamboard/references/card-actions.md +0 -107
- package/skills/dreamboard/references/cli.md +0 -120
- package/skills/dreamboard/references/core-concepts.md +0 -514
- package/skills/dreamboard/references/custom-renderers.md +0 -26
- package/skills/dreamboard/references/derived-values.md +0 -55
- package/skills/dreamboard/references/effects.md +0 -111
- package/skills/dreamboard/references/game-contract.md +0 -89
- package/skills/dreamboard/references/game-definition.md +0 -89
- package/skills/dreamboard/references/game-shell.md +0 -80
- package/skills/dreamboard/references/hand-surfaces.md +0 -33
- package/skills/dreamboard/references/index.md +0 -112
- package/skills/dreamboard/references/inputs-and-targets.md +0 -160
- package/skills/dreamboard/references/interactions.md +0 -158
- package/skills/dreamboard/references/manifest-fields.md +0 -137
- package/skills/dreamboard/references/manifest.md +0 -81
- package/skills/dreamboard/references/package-surfaces.md +0 -69
- package/skills/dreamboard/references/phases.md +0 -145
- package/skills/dreamboard/references/prompts-and-choices.md +0 -55
- package/skills/dreamboard/references/quickstart.md +0 -71
- package/skills/dreamboard/references/rule-authoring.md +0 -144
- package/skills/dreamboard/references/setup-bootstrap.md +0 -78
- package/skills/dreamboard/references/stages-and-zones.md +0 -59
- package/skills/dreamboard/references/static-views.md +0 -67
- package/skills/dreamboard/references/table-queries-and-ops.md +0 -103
- package/skills/dreamboard/references/testing-bases.md +0 -147
- package/skills/dreamboard/references/testing-generated-contracts.md +0 -142
- package/skills/dreamboard/references/testing-runtime-assertions.md +0 -124
- package/skills/dreamboard/references/testing-scenarios.md +0 -148
- package/skills/dreamboard/references/testing-ui-tests.md +0 -174
- package/skills/dreamboard/references/testing.md +0 -161
- package/skills/dreamboard/references/ui-architecture.md +0 -137
- package/skills/dreamboard/references/ui-components.md +0 -34
- package/skills/dreamboard/references/views.md +0 -72
- package/skills/dreamboard/references/workspace-layout.md +0 -136
- /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,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";
|