diff --git a/frontend/src/app/landing/page.tsx b/frontend/src/app/landing/page.tsx new file mode 100644 index 000000000..e5d3187b7 --- /dev/null +++ b/frontend/src/app/landing/page.tsx @@ -0,0 +1,25 @@ +import { Footer } from "@/components/landing/footer"; +import { Header } from "@/components/landing/header"; +import { Hero } from "@/components/landing/hero"; +import { CaseStudySection } from "@/components/landing/sections/case-study-section"; +import { CommunitySection } from "@/components/landing/sections/community-section"; +import { SandboxSection } from "@/components/landing/sections/sandbox-section"; +import { SkillsSection } from "@/components/landing/sections/skills-section"; +import { WhatsNewSection } from "@/components/landing/sections/whats-new-section"; + +export default function LandingPage() { + return ( +
+
+
+ + + + + + +
+
+ ); +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index c36c68f11..78d4165ad 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -18,7 +18,7 @@ export default async function RootLayout({ const locale = await detectLocaleServer(); return ( - + {children} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index e5d3187b7..8483a6a36 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,25 +1,5 @@ -import { Footer } from "@/components/landing/footer"; -import { Header } from "@/components/landing/header"; -import { Hero } from "@/components/landing/hero"; -import { CaseStudySection } from "@/components/landing/sections/case-study-section"; -import { CommunitySection } from "@/components/landing/sections/community-section"; -import { SandboxSection } from "@/components/landing/sections/sandbox-section"; -import { SkillsSection } from "@/components/landing/sections/skills-section"; -import { WhatsNewSection } from "@/components/landing/sections/whats-new-section"; +import { redirect } from "next/navigation"; -export default function LandingPage() { - return ( -
-
-
- - - - - - -
-
- ); +export default function HomePage() { + redirect("/workspace"); } diff --git a/frontend/src/components/ui/galaxy.jsx b/frontend/src/components/ui/galaxy.jsx index c1d20ab84..37c0fe81d 100644 --- a/frontend/src/components/ui/galaxy.jsx +++ b/frontend/src/components/ui/galaxy.jsx @@ -335,7 +335,9 @@ export default function Galaxy({ ctn.removeEventListener("mousemove", handleMouseMove); ctn.removeEventListener("mouseleave", handleMouseLeave); } - ctn.removeChild(gl.canvas); + if (gl.canvas.parentNode === ctn) { + ctn.removeChild(gl.canvas); + } gl.getExtension("WEBGL_lose_context")?.loseContext(); }; }, [ diff --git a/frontend/src/components/ui/magic-bento.tsx b/frontend/src/components/ui/magic-bento.tsx index 637d11b5c..29ea81cb2 100644 --- a/frontend/src/components/ui/magic-bento.tsx +++ b/frontend/src/components/ui/magic-bento.tsx @@ -130,7 +130,9 @@ const ParticleCard: React.FC<{ duration: 0.3, ease: "back.in(1.7)", onComplete: () => { - particle.parentNode?.removeChild(particle); + if (particle.parentNode) { + particle.parentNode.removeChild(particle); + } }, }); }); @@ -482,7 +484,9 @@ const GlobalSpotlight: React.FC<{ return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseleave", handleMouseLeave); - spotlightRef.current?.parentNode?.removeChild(spotlightRef.current); + if (spotlightRef.current?.parentNode) { + spotlightRef.current.parentNode.removeChild(spotlightRef.current); + } }; }, [gridRef, disableAnimations, enabled, spotlightRadius, glowColor]); diff --git a/frontend/src/core/messages/utils.ts b/frontend/src/core/messages/utils.ts index 22f985009..666d6caca 100644 --- a/frontend/src/core/messages/utils.ts +++ b/frontend/src/core/messages/utils.ts @@ -40,17 +40,28 @@ export function getMessageGroups(messages: Message[]): MessageGroup[] { const groups: MessageGroup[] = []; - // Returns the last group if it can still accept tool messages - // (i.e. it's an in-flight processing group, not a terminal human/assistant group). + // Returns the nearest prior group that can still accept tool messages. + // This intentionally scans backwards instead of only checking the last group, + // because an AI message can now render both: + // 1. an in-flight processing group for tool calls, and + // 2. a normal assistant bubble for user-visible text. + // In that case, the assistant bubble is appended after the processing group, + // and later tool messages still belong to that earlier processing group. function lastOpenGroup() { - const last = groups[groups.length - 1]; - if ( - last && - last.type !== "human" && - last.type !== "assistant" && - last.type !== "assistant:clarification" - ) { - return last; + for (let i = groups.length - 1; i >= 0; i -= 1) { + const group = groups[i]; + if (!group) { + continue; + } + if (group.type === "human") { + return null; + } + if ( + group.type !== "assistant" && + group.type !== "assistant:clarification" + ) { + return group; + } } return null; } @@ -79,11 +90,6 @@ export function getMessageGroups(messages: Message[]): MessageGroup[] { const open = lastOpenGroup(); if (open) { open.messages.push(message); - } else { - console.error( - "Unexpected tool message outside a processing group", - message, - ); } } continue; @@ -116,9 +122,14 @@ export function getMessageGroups(messages: Message[]): MessageGroup[] { } } - // Not an else-if: a message with reasoning + content (but no tool calls) goes - // into the processing group above AND gets its own assistant bubble here. - if (hasContent(message) && !hasToolCalls(message)) { + // Not an else-if: intermediate assistant messages can contain both + // user-facing text and tool calls. We still want to show that text as a + // normal assistant bubble instead of rendering only the tool timeline. + if ( + hasContent(message) && + !hasPresentFiles(message) && + !hasSubagent(message) + ) { groups.push({ id: message.id, type: "assistant", messages: [message] }); } } diff --git a/frontend/src/core/threads/export.ts b/frontend/src/core/threads/export.ts index cf1f92e47..ffdec8f39 100644 --- a/frontend/src/core/threads/export.ts +++ b/frontend/src/core/threads/export.ts @@ -120,7 +120,9 @@ export function downloadAsFile( a.download = filename; document.body.appendChild(a); a.click(); - document.body.removeChild(a); + if (a.parentNode === document.body) { + document.body.removeChild(a); + } URL.revokeObjectURL(url); }