Skip to content

Commit 1798a24

Browse files
fix(judge): auto-dismiss banner without collapsing canvas
- Fixed overlay + grid 1fr prevents page disappearing after 3s - Preview on 4173 with strictPort fallback; docs updated Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ab03aa9 commit 1798a24

8 files changed

Lines changed: 92 additions & 81 deletions

File tree

‎client/App.tsx‎

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useState } from 'react'
1+
import { useCallback, useEffect, useMemo, useState } from 'react'
22
import {
33
DefaultSizeStyle,
44
DefaultStylePanel,
@@ -65,6 +65,8 @@ const overrides: TLUiOverrides = {
6565

6666
function App() {
6767
const [agent, setAgent] = useState<TldrawAgent | undefined>()
68+
const [judgeBannerVisible, setJudgeBannerVisible] = useState(isJudgeMode)
69+
const dismissJudgeBanner = useCallback(() => setJudgeBannerVisible(false), [])
6870

6971
useEffect(() => {
7072
if (isJudgeMode) document.title = 'WebMCP Draw Canvas'
@@ -91,7 +93,9 @@ function App() {
9193
return (
9294
<TldrawUiToastsProvider>
9395
<div className={`tldraw-agent-container${isJudgeMode ? ' is-judge-mode' : ''}`}>
94-
{isJudgeMode && <JudgeBanner />}
96+
{isJudgeMode && judgeBannerVisible && (
97+
<JudgeBanner onDismiss={dismissJudgeBanner} />
98+
)}
9599
<div className="tldraw-canvas">
96100
<Tldraw
97101
persistenceKey={isJudgeMode ? 'tldraw-webmcp-judge' : 'tldraw-agent-demo'}
@@ -109,11 +113,31 @@ function App() {
109113
)
110114
}
111115

112-
function JudgeBanner() {
116+
function JudgeBanner({ onDismiss }: { onDismiss: () => void }) {
117+
const [phase, setPhase] = useState<'visible' | 'dismissing'>('visible')
118+
119+
useEffect(() => {
120+
const dismissTimer = window.setTimeout(() => setPhase('dismissing'), 3000)
121+
return () => window.clearTimeout(dismissTimer)
122+
}, [])
123+
124+
useEffect(() => {
125+
if (phase !== 'dismissing') return
126+
const fallbackTimer = window.setTimeout(onDismiss, 500)
127+
return () => window.clearTimeout(fallbackTimer)
128+
}, [phase, onDismiss])
129+
113130
return (
114-
<aside className="judge-banner" aria-label="WebMCP judge instructions">
115-
<strong>WebMCP demo.</strong> Enable{' '}
116-
<code>chrome://flags/#enable-webmcp-testing</code>, relaunch Chromium, then go to codex to use web mcp tools
131+
<aside
132+
className={`judge-banner${phase === 'dismissing' ? ' is-dismissing' : ''}`}
133+
aria-label="WebMCP judge instructions"
134+
onAnimationEnd={(event) => {
135+
if (event.target !== event.currentTarget) return
136+
if (event.animationName === 'judge-banner-slide-up') onDismiss()
137+
}}
138+
>
139+
<strong>WebMCP draw canvas.</strong> In Chrome 146+, enable{' '}
140+
<code>chrome://flags/#enable-webmcp-testing</code> and relaunch (or use the Codex browser).
117141
</aside>
118142
)
119143
}

‎client/index.css‎

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,50 @@ body {
1515
inset: 0;
1616
display: grid;
1717
grid-template-columns: 1fr auto;
18+
--tl-top-panel-chip-size: 34px;
1819
}
1920

2021
.tldraw-agent-container.is-judge-mode {
2122
grid-template-columns: 1fr;
22-
grid-template-rows: auto 1fr;
23+
grid-template-rows: 1fr;
2324
}
2425

2526
.judge-banner {
26-
grid-column: 1 / -1;
27+
position: fixed;
28+
top: 0;
29+
left: 0;
30+
right: 0;
31+
z-index: 500;
32+
max-height: 6rem;
2733
padding: 10px 14px;
2834
font-size: 13px;
2935
line-height: 1.45;
3036
color: #1a1a1a;
3137
background: #f3f0ff;
3238
border-bottom: 1px solid #d8d0f0;
39+
overflow: hidden;
40+
}
41+
42+
.judge-banner.is-dismissing {
43+
pointer-events: none;
44+
animation: judge-banner-slide-up 0.4s ease-in forwards;
45+
}
46+
47+
@keyframes judge-banner-slide-up {
48+
from {
49+
transform: translateY(0);
50+
opacity: 1;
51+
max-height: 6rem;
52+
padding-block: 10px;
53+
border-bottom-width: 1px;
54+
}
55+
to {
56+
transform: translateY(-100%);
57+
opacity: 0;
58+
max-height: 0;
59+
padding-block: 0;
60+
border-bottom-width: 0;
61+
}
3362
}
3463

3564
.judge-banner code {
@@ -73,8 +102,8 @@ body.is-chat-resizing iframe {
73102
display: flex;
74103
align-items: center;
75104
justify-content: center;
76-
width: 40px;
77-
height: 40px;
105+
width: var(--tl-top-panel-chip-size);
106+
height: var(--tl-top-panel-chip-size);
78107
overflow: hidden;
79108
}
80109

@@ -88,8 +117,8 @@ body.is-chat-resizing iframe {
88117
display: inline-flex;
89118
align-items: center;
90119
justify-content: center;
91-
width: 28px;
92-
height: 28px;
120+
width: calc(var(--tl-top-panel-chip-size) - 10px);
121+
height: calc(var(--tl-top-panel-chip-size) - 10px);
93122
padding: 0;
94123
appearance: none;
95124
border: 0;
@@ -109,8 +138,8 @@ body.is-chat-resizing iframe {
109138
}
110139

111140
.style-panel-collapse-button .chat-icon {
112-
width: 16px;
113-
height: 16px;
141+
width: 14px;
142+
height: 14px;
114143
margin: 0;
115144
}
116145

‎client/mermaid/MermaidEditor.tsx‎

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { useEffect, useRef, useState } from 'react'
22
import {
33
CenteredTopPanelContainer,
44
TLShapeId,
5-
TldrawUiButton,
65
useEditor,
76
useValue,
87
} from 'tldraw'
@@ -34,10 +33,10 @@ export function MermaidPanelToggle() {
3433
const label = panelState.isOpen ? 'Close Mermaid preview' : 'Open Mermaid preview'
3534

3635
return (
37-
<div className="tlui-style-panel__wrapper mermaid-toggle-wrap">
38-
<TldrawUiButton
39-
type="icon"
40-
className="mermaid-toggle"
36+
<div className="tlui-style-panel__wrapper collapsible-style-panel is-collapsed mermaid-toggle-wrap">
37+
<button
38+
type="button"
39+
className="style-panel-collapse-button mermaid-toggle"
4140
title={disabled ? 'No Mermaid preview or diagram on this page' : label}
4241
aria-label={disabled ? 'No Mermaid preview or diagram on this page' : label}
4342
disabled={disabled}
@@ -47,8 +46,10 @@ export function MermaidPanelToggle() {
4746
else if (target) openMermaidEditor(target.id)
4847
}}
4948
>
50-
<span aria-hidden="true" className="mermaid-toggle-icon">M</span>
51-
</TldrawUiButton>
49+
<span aria-hidden="true" className="mermaid-toggle-icon">
50+
M
51+
</span>
52+
</button>
5253
</div>
5354
)
5455
}
@@ -139,33 +140,6 @@ export function MermaidEditorOverlay() {
139140
if (panelState.isOpen && !shape && !pending) closeMermaidEditor()
140141
}, [panelState.isOpen, pending, shape])
141142

142-
// #region agent log
143-
useEffect(() => {
144-
const w = window as Window & { __debugOpenMermaidPreview?: () => Promise<unknown> }
145-
w.__debugOpenMermaidPreview = () =>
146-
previewMermaidDiagram(editor, {
147-
shapeId: 'debug-mermaid',
148-
source: 'flowchart LR\n Idea --> Review\n Review -->|Keep| Canvas\n Review -->|Edit| Idea',
149-
})
150-
const describeDrag = (phase: string, event: DragEvent, hypothesisId: string) => {
151-
const target = event.target
152-
const el = target instanceof HTMLElement ? target : null
153-
const img = target instanceof HTMLImageElement ? target : null
154-
const url = (event.dataTransfer?.getData('text/uri-list') || event.dataTransfer?.getData('text/plain') || img?.src || '').slice(0, 80)
155-
fetch('http://127.0.0.1:7359/ingest/b7873e6d-6601-4395-87ad-d5018b4378d6',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'19e42e'},body:JSON.stringify({sessionId:'19e42e',hypothesisId,location:'MermaidEditor.tsx:document-drag',message:phase,data:{tag:el?.tagName,className:el?.className,srcPrefix:img?.src?.slice(0,48),types:event.dataTransfer?Array.from(event.dataTransfer.types):[],urlPrefix:url,defaultPrevented:event.defaultPrevented,panelOpen:mermaidEditorState.get().isOpen},timestamp:Date.now()})}).catch(()=>{})
156-
}
157-
const onDragStart = (event: DragEvent) => describeDrag('document dragstart', event, 'A')
158-
const onDrop = (event: DragEvent) => describeDrag('document drop', event, 'D')
159-
document.addEventListener('dragstart', onDragStart, true)
160-
document.addEventListener('drop', onDrop, true)
161-
return () => {
162-
delete w.__debugOpenMermaidPreview
163-
document.removeEventListener('dragstart', onDragStart, true)
164-
document.removeEventListener('drop', onDrop, true)
165-
}
166-
}, [editor])
167-
// #endregion
168-
169143
const canApply = Boolean(draft !== source && previewSvg && !error && !rendering)
170144

171145
if (!panelState.isOpen || (!shape && !pending)) return null
@@ -199,20 +173,13 @@ export function MermaidEditorOverlay() {
199173
try {
200174
addMermaidPreviewToCanvas(editor, pending)
201175
} catch (nextError) {
202-
// #region agent log
203-
fetch('http://127.0.0.1:7359/ingest/b7873e6d-6601-4395-87ad-d5018b4378d6',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'19e42e'},body:JSON.stringify({sessionId:'19e42e',hypothesisId:'D',location:'MermaidEditor.tsx:addToCanvas',message:'addToCanvas catch',data:{message:nextError instanceof Error ? nextError.message : String(nextError)},timestamp:Date.now()})}).catch(()=>{})
204-
// #endregion
205176
setNotice(nextError instanceof Error ? nextError.message : 'Could not add the diagram to the canvas.')
206177
}
207178
}
208179

209180
function blockPreviewDrag(event: React.DragEvent) {
210181
event.preventDefault()
211182
event.stopPropagation()
212-
// #region agent log
213-
const img = event.target instanceof HTMLImageElement ? event.target : null
214-
fetch('http://127.0.0.1:7359/ingest/b7873e6d-6601-4395-87ad-d5018b4378d6',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'19e42e'},body:JSON.stringify({sessionId:'19e42e',hypothesisId:'B',location:'MermaidEditor.tsx:blockPreviewDrag',message:'blockPreviewDrag fired',data:{tag:event.target instanceof HTMLElement ? event.target.tagName : typeof event.target,srcPrefix:img?.src?.slice(0,48),defaultPrevented:event.defaultPrevented},timestamp:Date.now()})}).catch(()=>{})
215-
// #endregion
216183
setNotice('Drag is not supported. Use Add to canvas.')
217184
}
218185

‎client/mermaid/MermaidShapeUtil.tsx‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@ export class MermaidShapeUtil extends BaseBoxShapeUtil<MermaidShape> {
4242
src={svgToDataUrl(shape.props.svg)}
4343
alt={shape.props.title}
4444
draggable={false}
45-
onDragStart={(event) => {
46-
// #region agent log
47-
fetch('http://127.0.0.1:7359/ingest/b7873e6d-6601-4395-87ad-d5018b4378d6',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'19e42e'},body:JSON.stringify({sessionId:'19e42e',hypothesisId:'C',location:'MermaidShapeUtil.tsx:img',message:'canvas mermaid img dragstart',data:{srcPrefix:event.currentTarget.src.slice(0,48)},timestamp:Date.now()})}).catch(()=>{})
48-
// #endregion
49-
}}
5045
/>
5146
</div>
5247
</HTMLContainer>

‎client/mermaid/mermaid.css‎

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,19 @@
2020
margin-right: 8px;
2121
}
2222

23-
.mermaid-toggle-wrap {
24-
display: flex;
25-
align-items: center;
26-
justify-content: center;
27-
width: 40px;
28-
height: 40px;
29-
overflow: hidden;
30-
}
31-
32-
.mermaid-toggle {
33-
width: 40px;
34-
height: 40px;
35-
min-width: 40px;
36-
padding: 0;
37-
background: transparent !important;
38-
box-shadow: none !important;
23+
.mermaid-toggle:disabled {
24+
cursor: default;
25+
opacity: 0.45;
3926
}
4027

4128
.mermaid-toggle-icon {
4229
display: grid;
4330
place-items: center;
44-
width: 18px;
45-
height: 18px;
46-
font-size: 12px;
31+
width: 14px;
32+
height: 14px;
33+
font-size: 11px;
4734
font-weight: 800;
35+
line-height: 1;
4836
}
4937

5038
.mermaid-shape {

‎docs/judge-chatgpt-sites.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This repo supports a **judge-only** static build without removing chat, Worker,
55
## Local preview
66

77
```bash
8-
npm run dev:judge
8+
npm run dev:judge
99
```
1010

1111
Open http://127.0.0.1:5173/ — canvas only, WebMCP tools register as usual, no chat panel.
@@ -17,6 +17,8 @@ npm run build:judge
1717
npm run preview:judge
1818
```
1919

20+
Open http://127.0.0.1:4173/ (preview prefers port **4173**; if busy, Vite picks the next free port — check the terminal URL). Stop stale preview servers with `lsof -nP -iTCP:4173 -sTCP:LISTEN`.
21+
2022
Output directory: `dist-judge/` (static SPA, no Cloudflare Worker).
2123

2224
## How it works

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"dev:app": "vite --host 127.0.0.1 --port 5173 --strictPort",
1010
"dev:judge": "vite --config vite.config.judge.ts --host 127.0.0.1 --port 5173 --strictPort",
1111
"build:judge": "npm run typecheck && vite build --config vite.config.judge.ts",
12-
"preview:judge": "vite preview --config vite.config.judge.ts --host 127.0.0.1 --port 5173 --strictPort",
12+
"preview:judge": "vite preview --config vite.config.judge.ts",
1313
"dev:mcp": "tsx watch server/index.ts",
1414
"start:mcp": "tsx server/index.ts",
1515
"typecheck": "tsc --noEmit",

‎vite.config.judge.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { defineConfig } from 'vite'
33

44
/** Static SPA for WebMCP judge / ChatGPT Sites — no Cloudflare Worker. */
55
export default defineConfig({
6+
root: '.',
67
plugins: [react()],
78
define: {
89
'import.meta.env.VITE_JUDGE_MODE': JSON.stringify('true'),
@@ -11,4 +12,9 @@ export default defineConfig({
1112
outDir: 'dist-judge',
1213
emptyOutDir: true,
1314
},
15+
preview: {
16+
host: '127.0.0.1',
17+
port: 4173,
18+
strictPort: false,
19+
},
1420
})

0 commit comments

Comments
 (0)