Skip to content

Commit 1e9986d

Browse files
committed
fix: interactive terminal demo bugs
1 parent b23f5c9 commit 1e9986d

File tree

4 files changed

+80
-49
lines changed

4 files changed

+80
-49
lines changed

web/knowledge.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,25 @@ When showing code previews in the UI:
229229

230230
## Component Architecture
231231

232+
### React Query Mutation Patterns
233+
234+
Important: When using useMutation with UI state:
235+
- Let mutation handlers (onMutate, onSuccess, onError) own their state updates
236+
- Avoid setting state after awaiting mutation if handlers also set state
237+
- For components with both local and mutation-driven state:
238+
```typescript
239+
// Handle local state updates first
240+
if (isLocalAction(input)) {
241+
setLocalState(newState)
242+
return
243+
}
244+
245+
// Let mutation handlers own their state for API calls
246+
await mutation.mutateAsync(input)
247+
```
248+
- This pattern prevents race conditions between local state updates and mutation handlers
249+
- Keeps state management responsibilities clear and separated
250+
232251
### Success State Pattern
233252

234253
- Use shadcn Card component for consistent card styling

web/src/app/analytics.knowledge.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ Important: This pattern ensures accurate attribution even when users don't conve
139139
- Maintain consistent tracking parameters across all conversion points
140140
- Example: Subscription conversion tracking should use same campaign ID everywhere
141141

142+
2. API Security:
143+
- When checking origins for CORS:
144+
- Parse URLs to compare just domain and port
145+
- Ignore protocol (http/https) differences
146+
- Handle missing or malformed origin headers
147+
- Keep CORS headers consistent in both success and error responses
148+
142149
## UTM Source Handling
143150

144151
Special UTM sources:

web/src/app/api/demo/route.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,41 @@ const callDeepseekAPI = async (
202202
}
203203

204204
export async function POST(request: Request) {
205-
// Check origin
205+
// Check origin by comparing domain and port
206206
const origin = request.headers.get('origin')
207-
if (origin !== env.NEXT_PUBLIC_APP_URL) {
208-
return new Response(JSON.stringify({ error: 'Unauthorized origin' }), {
207+
if (!origin) {
208+
return new Response(JSON.stringify({ error: 'Missing origin header' }), {
209+
status: 403,
210+
headers: {
211+
'Content-Type': 'application/json',
212+
...corsHeaders,
213+
},
214+
})
215+
}
216+
217+
// Parse URLs to compare just domain and port
218+
try {
219+
const originUrl = new URL(origin)
220+
const allowedUrl = new URL(env.NEXT_PUBLIC_APP_URL)
221+
222+
const originBase = `${originUrl.hostname}${originUrl.port ?? ''}`
223+
const allowedBase = `${allowedUrl.hostname}${allowedUrl.port ?? ''}`
224+
225+
if (originBase !== allowedBase) {
226+
console.log('User origin differs from app', {
227+
originBase,
228+
allowedBase,
229+
})
230+
return new Response(JSON.stringify({ error: 'Unauthorized origin' }), {
231+
status: 403,
232+
headers: {
233+
'Content-Type': 'application/json',
234+
...corsHeaders,
235+
},
236+
})
237+
}
238+
} catch (error) {
239+
return new Response(JSON.stringify({ error: 'Invalid origin format' }), {
209240
status: 403,
210241
headers: {
211242
'Content-Type': 'application/json',

web/src/components/InteractiveTerminalDemo.tsx

Lines changed: 20 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,15 @@ const InteractiveTerminalDemo = () => {
338338
})
339339

340340
const handleInput = async (input: string) => {
341-
const newLines = [...terminalLines]
342-
343-
await match(input)
341+
match(input)
344342
.with('help', () => {
345343
posthog.capture('viewed demo terminal help', {
346344
theme: colorTheme,
347345
demo_terminal: true,
348346
})
349-
newLines.push(
347+
setTerminalLines((prev) => [
348+
...prev,
349+
350350
<TerminalOutput key={`help-${Date.now()}`} className="text-wrap">
351351
{'>'} help
352352
</TerminalOutput>,
@@ -362,13 +362,14 @@ const InteractiveTerminalDemo = () => {
362362
get the full experience!
363363
</b>
364364
</p>
365-
</TerminalOutput>
366-
)
365+
</TerminalOutput>,
366+
])
367367
})
368368
.with(P.string.includes('rainbow'), () => {
369369
posthog.capture('added demo terminal rainbow', { demo_terminal: true })
370370
setIsRainbow(true)
371-
newLines.push(
371+
setTerminalLines((prev) => [
372+
...prev,
372373
<TerminalOutput key={`rainbow-cmd-${Date.now()}`}>
373374
{'>'} please make the hello world background rainbow-colored
374375
</TerminalOutput>,
@@ -380,8 +381,8 @@ const InteractiveTerminalDemo = () => {
380381
</TerminalOutput>,
381382
<TerminalOutput key={`rainbow-1-${Date.now()}`}>
382383
🌈 Added a rainbow gradient to the component!
383-
</TerminalOutput>
384-
)
384+
</TerminalOutput>,
385+
])
385386
})
386387
.with('change theme', () => {
387388
const themes: PreviewTheme[] = ['terminal-y', 'retro', 'light']
@@ -395,7 +396,8 @@ const InteractiveTerminalDemo = () => {
395396
})
396397
setPreviewTheme(nextTheme)
397398

398-
newLines.push(
399+
setTerminalLines((prev) => [
400+
...prev,
399401
<TerminalOutput key={`theme-cmd-${Date.now()}`}>
400402
{'>'} change the theme to be more {nextTheme}
401403
</TerminalOutput>,
@@ -413,15 +415,16 @@ const InteractiveTerminalDemo = () => {
413415
<p className="text-green-400">
414416
- Updated web/src/components/app.tsx
415417
</p>
416-
</TerminalOutput>
417-
)
418+
</TerminalOutput>,
419+
])
418420
})
419421
.with(
420422
P.when((s: string) => s.includes('fix') && s.includes('bug')),
421423
() => {
422424
posthog.capture('fixed demo terminal bug', { demo_terminal: true })
423425
setShowError(false)
424-
newLines.push(
426+
setTerminalLines((prev) => [
427+
...prev,
425428
<TerminalOutput key={`fix-1-${Date.now()}`}>
426429
<b className="text-green-400">Codebuff:</b> I found a potential
427430
bug - the greeting is missing an exclamation mark.
@@ -435,46 +438,17 @@ const InteractiveTerminalDemo = () => {
435438
- Updated web/src/components/app.tsx
436439
</p>
437440
<p className="text-green-400">- Created web/tailwind.config.ts</p>
438-
</TerminalOutput>
439-
)
441+
</TerminalOutput>,
442+
])
440443
setPreviewContent('fixed')
441444
}
442445
)
443446
.with('clear', () => {
444447
setTerminalLines([])
445448
})
446-
.otherwise(async () => {
447-
const randomFiles = getRandomFiles()
448-
newLines.push(
449-
<TerminalOutput key={`ask-1-${Date.now()}`}>
450-
<p>
451-
{'> '}
452-
{input}
453-
</p>
454-
</TerminalOutput>,
455-
<TerminalOutput key={`files-${Date.now()}`}>
456-
<b className="text-green-400">Codebuff:</b> Reading additional
457-
files...
458-
{randomFiles.slice(0, 3).map((file) => (
459-
<p key={file} className="text-wrap">
460-
- {file}
461-
</p>
462-
))}
463-
{randomFiles.length > 3 && (
464-
<p className="text-wrap">
465-
and {randomFiles.length - 3} more:{' '}
466-
{randomFiles.slice(3).join(', ')}
467-
</p>
468-
)}
469-
</TerminalOutput>,
470-
<TerminalOutput key={`ask-${Date.now()}`}>Thinking...</TerminalOutput>
471-
)
472-
setTerminalLines(newLines)
473-
474-
await demoMutation.mutateAsync(input)
449+
.otherwise(() => {
450+
demoMutation.mutate(input)
475451
})
476-
477-
setTerminalLines(newLines)
478452
}
479453

480454
return (

0 commit comments

Comments
 (0)