Skip to content

Commit c744f57

Browse files
fix: stabilize replay viewer dashboard layout
Made-with: Cursor
1 parent 8e26d85 commit c744f57

1 file changed

Lines changed: 56 additions & 27 deletions

File tree

demo/replay-viewer/app.ts

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { CanvasRenderer } from '@geometra/renderer-canvas'
33
import replay from '../../examples/replays/claims-review.json'
44

55
const selectedStep = signal(0)
6+
const viewportWidth = signal(window.innerWidth)
7+
const viewportHeight = signal(window.innerHeight)
68
const action = replay.actions[0]
79
const before = action?.frameBefore
810
const after = action?.frameAfter
@@ -28,8 +30,14 @@ function label(value: unknown): string {
2830
return JSON.stringify(value)
2931
}
3032

33+
function outputField(name: string): string {
34+
if (!action?.output || typeof action.output !== 'object') return 'n/a'
35+
return label((action.output as Record<string, unknown>)[name])
36+
}
37+
3138
function card(title: string, lines: string[], accent = colors.border) {
3239
return box({
40+
flexDirection: 'column',
3341
backgroundColor: colors.card,
3442
borderColor: accent,
3543
borderWidth: 1,
@@ -38,14 +46,17 @@ function card(title: string, lines: string[], accent = colors.border) {
3846
gap: 8,
3947
}, [
4048
text({ text: title, font: '700 18px Inter, system-ui', lineHeight: 24, color: colors.text }),
41-
...lines.map(line => text({ text: line, font: '14px Inter, system-ui', lineHeight: 21, color: colors.muted })),
49+
...lines.map(line => text({ text: line, font: '14px Inter, system-ui', lineHeight: 21, color: colors.muted, whiteSpace: 'normal' })),
4250
])
4351
}
4452

4553
function framePreview(title: string, node: typeof beforeNode | typeof afterNode, status: string) {
4654
const bounds = node?.bounds
4755
return box({
48-
flex: 1,
56+
flexDirection: 'column',
57+
flexGrow: 1,
58+
flexShrink: 1,
59+
flexBasis: 0,
4960
backgroundColor: '#020617',
5061
borderColor: colors.border,
5162
borderWidth: 1,
@@ -55,7 +66,8 @@ function framePreview(title: string, node: typeof beforeNode | typeof afterNode,
5566
}, [
5667
text({ text: title, font: '700 17px Inter, system-ui', lineHeight: 24, color: colors.text }),
5768
box({
58-
height: 178,
69+
flexDirection: 'column',
70+
height: 132,
5971
backgroundColor: colors.panel,
6072
borderColor: colors.border,
6173
borderWidth: 1,
@@ -66,6 +78,7 @@ function framePreview(title: string, node: typeof beforeNode | typeof afterNode,
6678
text({ text: 'Claims review surface', font: '700 15px Inter, system-ui', lineHeight: 20, color: colors.text }),
6779
text({ text: 'CLM-1042 / Northstar Fabrication', font: '14px Inter, system-ui', lineHeight: 20, color: colors.muted }),
6880
box({
81+
flexDirection: 'column',
6982
width: bounds ? Math.max(132, Math.min(220, bounds.width)) : 160,
7083
height: bounds ? Math.max(38, Math.min(54, bounds.height)) : 44,
7184
backgroundColor: status === 'completed' ? '#064e3b' : '#1e3a8a',
@@ -84,6 +97,7 @@ function framePreview(title: string, node: typeof beforeNode | typeof afterNode,
8497
font: '13px Inter, system-ui',
8598
lineHeight: 19,
8699
color: colors.muted,
100+
whiteSpace: 'normal',
87101
}),
88102
])
89103
}
@@ -92,8 +106,12 @@ function timelineItem(index: number, title: string, detail: string) {
92106
const active = selectedStep.value === index
93107
return box({
94108
onClick: () => {
95-
selectedStep.value = index
109+
selectedStep.set(index)
96110
},
111+
flexDirection: 'column',
112+
flexGrow: 1,
113+
flexShrink: 1,
114+
flexBasis: 0,
97115
backgroundColor: active ? '#0c4a6e' : colors.card,
98116
borderColor: active ? colors.accent : colors.border,
99117
borderWidth: 1,
@@ -103,7 +121,7 @@ function timelineItem(index: number, title: string, detail: string) {
103121
semantic: { id: `replay-step-${index}`, role: 'button', ariaLabel: title },
104122
}, [
105123
text({ text: title, font: '700 14px Inter, system-ui', lineHeight: 19, color: colors.text }),
106-
text({ text: detail, font: '12px Inter, system-ui', lineHeight: 17, color: colors.muted }),
124+
text({ text: detail, font: '12px Inter, system-ui', lineHeight: 17, color: colors.muted, whiteSpace: 'normal' }),
107125
])
108126
}
109127

@@ -116,16 +134,17 @@ function root() {
116134
: 'Replay now contains frame-before, output, approval, and frame-after proof.'
117135

118136
return box({
119-
width: '100%',
120-
height: '100%',
137+
flexDirection: 'column',
138+
width: viewportWidth.value,
139+
height: viewportHeight.value,
121140
backgroundColor: colors.bg,
122141
padding: 28,
123142
gap: 22,
124143
}, [
125144
box({ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, [
126-
box({ gap: 6 }, [
145+
box({ flexDirection: 'column', flexGrow: 1, flexShrink: 1, flexBasis: 0, gap: 6 }, [
127146
text({ text: 'Agent-Native Audit Replay Viewer', font: '800 30px Inter, system-ui', lineHeight: 38, color: colors.text }),
128-
text({ text: 'A visual packet of exactly what the agent saw, requested, approved, and completed.', font: '15px Inter, system-ui', lineHeight: 22, color: colors.muted }),
147+
text({ text: 'A visual packet of exactly what the agent saw, requested, approved, and completed.', font: '15px Inter, system-ui', lineHeight: 22, color: colors.muted, whiteSpace: 'normal' }),
129148
]),
130149
card('Replay artifact', [
131150
`Session ${replay.sessionId}`,
@@ -135,29 +154,39 @@ function root() {
135154
box({ flexDirection: 'row', gap: 16 }, [
136155
timelineItem(0, '1. Inspect', `${before?.geometry.nodes.length ?? 0} geometry nodes`),
137156
timelineItem(1, '2. Approval', `${action?.approval?.actor ?? 'manager'} approved`),
138-
timelineItem(2, '3. Completion', `${label(action?.output)}`),
139-
]),
140-
card('Selected replay step', [selected], selectedStep.value === 2 ? colors.success : colors.accent),
141-
box({ flexDirection: 'row', gap: 18, flex: 1 }, [
142-
framePreview('Frame before', beforeNode, 'pending'),
143-
framePreview('Frame after', afterNode, action?.status ?? 'completed'),
157+
timelineItem(2, '3. Completion', 'completed with audit proof'),
144158
]),
145-
box({ flexDirection: 'row', gap: 18 }, [
146-
card('Action contract', [
147-
`id: ${action?.actionId ?? 'unknown'}`,
148-
`risk: ${target?.risk ?? 'unknown'}`,
149-
`requires confirmation: ${target?.requiresConfirmation ? 'yes' : 'no'}`,
150-
], colors.warning),
151-
card('Audit result', [
152-
`status: ${action?.status ?? 'unknown'}`,
153-
`approval: ${action?.approval?.approved ? 'approved' : 'not approved'}`,
154-
`output: ${label(action?.output)}`,
155-
], colors.success),
159+
box({ flexDirection: 'row', gap: 18, flexGrow: 1, flexShrink: 1, flexBasis: 0 }, [
160+
box({ flexDirection: 'column', gap: 16, flexGrow: 1, flexShrink: 1, flexBasis: 0 }, [
161+
card('Selected replay step', [selected], selectedStep.value === 2 ? colors.success : colors.accent),
162+
box({ flexDirection: 'row', gap: 18, flexGrow: 1, flexShrink: 1, flexBasis: 0 }, [
163+
framePreview('Frame before', beforeNode, 'pending'),
164+
framePreview('Frame after', afterNode, action?.status ?? 'completed'),
165+
]),
166+
]),
167+
box({ flexDirection: 'column', gap: 16, width: 300 }, [
168+
card('Action contract', [
169+
`id: ${action?.actionId ?? 'unknown'}`,
170+
`risk: ${target?.risk ?? 'unknown'}`,
171+
`requires confirmation: ${target?.requiresConfirmation ? 'yes' : 'no'}`,
172+
], colors.warning),
173+
card('Audit result', [
174+
`status: ${action?.status ?? 'unknown'}`,
175+
`approval: ${action?.approval?.approved ? 'approved' : 'not approved'}`,
176+
`claim: ${outputField('claimId')}`,
177+
`audit: ${outputField('auditId')}`,
178+
], colors.success),
179+
]),
156180
]),
157181
])
158182
}
159183

160184
const canvas = document.getElementById('app') as HTMLCanvasElement
161185
const renderer = new CanvasRenderer({ canvas, background: colors.bg })
162-
const app = await createApp(root, renderer, { width: window.innerWidth, height: window.innerHeight, waitForFonts: true })
186+
const app = await createApp(root, renderer, { width: viewportWidth.value, height: viewportHeight.value, waitForFonts: true })
163187
app.update()
188+
189+
window.addEventListener('resize', () => {
190+
viewportWidth.set(window.innerWidth)
191+
viewportHeight.set(window.innerHeight)
192+
})

0 commit comments

Comments
 (0)