Skip to content

Commit e4b7831

Browse files
feat(GaiaScenario): state-machine chat template with branded tool-call editor (#7)
* feat(remotion): wire @heygaia/chat-ui + GaiaScenario composition - Install @heygaia/chat-ui@0.0.0-feat-chat-ui.547806a + transitive peer deps (HeroUI suite, motion, gaia-icons, mermaid, react-syntax-highlighter, tanstack, etc.) so the package's runtime deps resolve when its components are imported. - .npmrc: shamefully-hoist + auto-install-peers so chat-ui's nested pnpm context can resolve transitive imports. - src/shims/: drop-in replacements for next/image (plain <img>), next/dynamic (sync renderer), next/navigation (no-op router/params), next/link (plain <a>) so the chat-ui bundle (extracted from a Next.js app) loads in Remotion. - remotion.config.ts: webpack override that aliases next/* → shims and disables strict ESM extension resolution (chat-ui imports react-syntax-highlighter/dist/esm/... without .js extensions). - src/compositions/GaiaScenario/: types, timing engine, GaiaScenario component, meta. Uses real GAIA chat components from @heygaia/chat-ui to render scenario JSON as state-machine-driven video. Registered in components.ts and registry.ts (first in library). * WIP: post-merge state with stash applied (may contain conflict markers — checkpoint commit) * chore(biome): include apps/remotion + auto-fix imports across both apps - Removed apps/remotion from biome.json's exclude list per "biome everywhere, no eslint" directive. - Added apps/remotion-specific override that disables a11y rules, noExplicitAny, noArrayIndexKey, noImgElement, useTemplate — these are noisy for video composition code with pre-existing patterns. - biome --write fixed import-order across 59 files (organizeImports). - bun.lock regenerated post-merge. Verified: tsc --noEmit clean in both apps, biome check clean (220 files, 0 errors, 0 warnings). * feat(GaiaScenario): per-state editor + chat scale + advanced JSON section - New 'scenario' field kind: full per-state editor (ScenarioEditor.tsx) with collapsible accordion per state. Each state type has its own field group: * user_message: text, typingSpeed, pauseAfter * bot_message: text, streamingSpeed, follow_up_actions, pauseAfter * loading: text, duration, toolCategory, pauseAfter * thinking: content, duration, pauseAfter * tool_calls: entries (JSON), pauseAfter * pause: duration Add/remove/reorder via inline buttons; add-state Select picks type. - New 'section' field kind: collapsible accordion at the bottom of the inspector for grouping primitive fields. Used to host the raw scenario JSON under "Advanced options". - GaiaScenario rendering: removed the mobile viewport scaling — now fills whatever canvas size it gets. New 'scale' prop (default 2.5) uses CSS transform on the chat content so chat-ui's native pixel sizes read at MessageBubbles-equivalent scale on a 1080×1920 canvas. - Inspector: removed nested overflow-y-auto on ScenarioEditor so the whole right sidebar scrolls as a single unit. - Top-level primitive fields: title, theme, backgroundColor, padding, borderRadius, scale — all configurable from the right sidebar. - props-table updated to describe the new 'scenario' and 'section' field kinds. * feat(ScenarioEditor): quick-add buttons per state type + slimmer header * feat(GaiaScenario): avatars, tool-calls auto-open, complex default scenario - userAvatarUrl prop (default github.com/aryanranderiya.png) + botAvatarUrl prop (default GAIA logo at /images/logos/logo.webp). Both wire into next/image shim via window.__remotionImageOverrides so chat-ui's hardcoded image paths get swapped at render time without forking the package. GAIA logo file copied to apps/remotion/public/images/logos/. - toolCallsExpanded prop (default true) injects CSS that force-opens chat-ui's ToolCallsSection accordion in video output (no user interaction = always-expanded is the right default). - Default scenario is now power-morning-briefing.json from gaia-demo-videos — multi-tool flow with calendar+todos+search+rich bot synthesis. Exercises every chat-ui state type so users see the full surface on first load. Imported as JSON via tsconfig resolveJsonModule. - ScenarioEditor: dropped redundant preview text in headers; kept number/icon/type only (cleaner to scan when reordering states). - Right sidebar scroll: removed nested overflow-y-auto in ScenarioEditor so the inspector scrolls as one unit. * feat(GaiaScenario): bundle tool_calls + sidebar UX polish - Bundle consecutive tool_calls scenario states into ONE ToolCallsSection so the chat shows "Used N tools" with all stacked icons (matches the real GAIA UI) instead of multiple "Used 1 tool" stacks. Walks visible windows in render order and accumulates entries until a non-tool_calls state appears or the visible list ends. - Apply avatar overrides synchronously during render (not in useEffect) so the first frame's <img> already sees the override — Remotion's frame screenshots may capture before effects flush. - Copy GAIA logo to apps/web/public/images/logos/logo.webp (was only in apps/remotion/public). Now docs preview + studio both serve it. - Sidebar UX: * "Add state" moved to a Select-button at the top-right of the States header (compact, opens dropdown of state types). * Expand all / Collapse all toggle below the description. * Advanced options section moved to the bottom of the inspector (previously above scenario editor) — per-state editing is the primary surface, raw JSON is the rarely-used escape hatch. * ScenarioEditor accordion is now controlled by openKeys state so the expand/collapse-all toggle works. - Removed the old grid-of-buttons AddStateMenu in favor of the dropdown trigger. * fix(ScenarioEditor): SelectTrigger doesn't accept asChild — render icon+text directly The packages/ui SelectTrigger wraps Radix primitive but doesn't forward asChild. The runtime crash 'React.Children.only expected to receive a single React element child' was Radix complaining about the Button's multiple children inside an asChild slot. Removed asChild + Button wrapper, kept the icon and 'Add state' label as direct trigger contents with size='sm' for the compact look. Hid the trailing chevron with a selector so the trigger reads as a clean button. * feat(GaiaScenario): branded tool-call editor, loading polish, dynamic duration Editor: - Tool calls right-sidebar UI replaces the JSON textarea with proper per-call cards (category select with branded icons, tool name, message, output, optional inputs). Adding a "Tool calls" state from the menu now also inserts a paired loading state with a linked category, and changing the call's category propagates to the preceding loading state. - Loading state gains an "Indicator" select (Wave spinner / Tool integration) so wave-spinner is no longer a fake tool category. - Follow-up actions editor switches from a one-per-line textarea to a list of individual Input rows with add/remove buttons. - States editor and JSON view, which previously stacked, now render as tabs via a new shadcn Tabs component. Renderer: - All consecutive tool_calls states (even when separated by loading/thinking) bundle into one "Used N tools" accordion. Loading and thinking always trail at the bottom of the chat regardless of scenario position. - Force-expand the chat-ui ToolCallsSection accordion via aria-expanded selector + click(). - Inject CSS for the small set of utilities chat-ui's bundle uses but Tailwind v4 in lyon doesn't compile (animate-shine + bg-clip-text shimmer, pl-11.5 loading offset, ml-10.75 footer offset, outline-dashed + rounded-lg follow-ups, action button visibility/spacing, tool icon overlap). - User avatar painted as background-image on Radix's avatar-fallback slot to sidestep the async image-load state machine that broke in Remotion. - Wrap chat-ui in framer-motion's LazyMotion(domAnimation) so motion/react-m components actually render — fixes empty loading state. - Tool category keys aliased before passing to chat-ui (google_calendar → googlecalendar) and icon_url injected so Google Calendar / Gmail / etc. resolve to the right asset; copied 50 GAIA tool-icon assets into apps/web/public/images/icons/ to back the URLs. Composition metadata: - CompositionInfo gains an optional calculateMetadata callback. GaiaScenario uses it to derive durationInFrames from the active scenarioJson on every edit, so the timeline card hugs actual scenario length instead of a hardcoded 60s. toolCallsExpanded default fixed (string "true" so the select shows "Yes" as the active option). * fix(editor): strip calculateMetadata before passing info to client component Next.js cannot serialize functions across the server/client boundary. Destructure calculateMetadata out of the composition info in the server page before handing the rest to EditorView, and narrow its prop type to Omit<AnyCompositionInfo, "calculateMetadata"> accordingly. * fix(landing): restore master's page layout with RaisedButton CTAs * feat(landing): 4-column component grid with paginated view more - FeaturedComponents: 4-col grid (1→2→4 cols), shows 8 items initially, "View more" adds 8 more per click until all compositions are visible - Landing page: pass all compositions to grid, restore Button + ArrowRight02Icon CTAs, widen section container to max-w-7xl to fit 4 columns * fix(landing): strip calculateMetadata before passing compositions to client grid * feat(landing): hero redesign, docs copy button, GaiaScenario page - New hero ("Ship videos that look expensive") with inline clapperboard and scattered draggable stickers; BrandLink picks up the clapperboard mark. - Drop MobileNav and the mobile-only search trigger from the docs header. - Add CopyPageButton on docs pages, backed by the raw .mdx contents. - Wire up the GaiaScenario docs entry in lib/docs.ts and add the mdx page. - Guard FeaturedComponents Link against synthetic descendant clicks (e.detail === 0) — fixes the auto-redirect to /docs/GaiaScenario a few seconds into the landing page, caused by the composition programmatically clicking its accordion trigger to force-expand it for the video render. * fix(GaiaScenario): stop synthetic accordion click from bubbling ToolCallsView's useEffect calls .click() on the chat-ui accordion trigger to force-expand it for the video render. When this composition plays inside a Remotion Player wrapped by a Next.js <Link> (e.g. the landing page's component grid), that synthetic click bubbles up through React's synthetic event system and triggers Link navigation a few seconds in. Stop propagation at the ToolCallsView wrapper. React Aria's button handler fires before the bubble reaches us, so the accordion still expands; the click never reaches the surrounding Link. Also drop the speculative e.detail === 0 guard from FeaturedComponents: the source-level fix makes any wrapping Link safe by default. * chore(GaiaScenario): drop unused biome-ignore comments The lint rules I was suppressing don't actually fire on this <div>; the suppressions were dead noise. * fix(GaiaScenario): preventDefault synthetic accordion click; add nav probe Previous attempt used stopPropagation on the ToolCallsView wrapper. That killed the synthetic click before it reached the surrounding Next.js <Link>, which meant Link's own onClick (which calls e.preventDefault and swaps the navigation for router.push) never ran. With nothing preventing the default action, the browser performed a real anchor navigation — visible as a full page reload (console clears, history doesn't behave like client-side nav). Switch to preventDefault on synthetic (isTrusted=false) clicks only. That cancels the browser's native anchor navigation AND makes Link's onClick bail before router.push, so neither full-page nor client-side navigation fires. Real user clicks (isTrusted=true) pass through and still navigate normally. Also re-add DebugNavProbe on the landing page so we can verify the fix in the browser console — it logs every click whose path includes a link to /docs/GaiaScenario plus a stack trace, and intercepts pushState / replaceState to /docs/GaiaScenario. Will be removed once confirmed. * debug(GaiaScenario): multi-layer synthetic-click guard + diagnostic logs Single-layer React preventDefault on the ToolCallsView wrapper didn't stop the auto-redirect even though the fix was in the bundle. Add three layers so we can isolate which one wins: 1. ToolCallsView wrapper: keep React onClick preventDefault for !isTrusted plus a NATIVE addEventListener click listener (also preventDefault + stopPropagation). The native listener fires during the actual DOM bubble in deterministic order; React's synthetic dispatch can run after the native default has effectively committed. 2. FeaturedComponents Link: onClick preventDefault for !isTrusted as the last line of defense before navigation kicks in. 3. DebugNavProbe: log defaultPrevented + phase so we can see in the console exactly when preventDefault took effect and where. Each guard logs a console.warn when it fires, so the order tells us which layer is doing the work and which (if any) was bypassed. Also: chat-ui hits the shared QueryClient with queries like ["integrations","user"] and ["integrations","config"] that have no registered queryFn. React Query's default returns undefined, which it then rejects with "Query data cannot be undefined". Give the QueryClient a default queryFn that returns null + retry=false + staleTime=Infinity so those background queries stay quiet. * fix(GaiaScenario): attach native click guard before synthetic trigger.click The synthetic click was bubbling all the way to the wrapping <Link> with defaultPrevented=false at both capture and bubble phases, even though guards were present in the bundle. Diagnosis via browser instrumentation: - The wrapper div's React onClick never fired. React Aria's button (chat-ui accordion) calls stopPropagation on the synthetic event, so React doesn't dispatch it to ancestor onClick handlers. - The native addEventListener guard was attached in a SEPARATE useEffect declared after the tryOpen useEffect. Effects run in declaration order — the first useEffect synchronously called trigger.click() and the click event finished propagating BEFORE the second useEffect had a chance to attach the listener. The native guard was always too late. Combine both into a single useEffect with the guard registered before tryOpen runs. The guard now catches the synthetic click on its way up through the wrapper, preventDefault cancels the anchor's default navigation, and stopPropagation keeps the click from continuing to the Link's onClick (which would otherwise router.push anyway). * chore(GaiaScenario): drop diagnostic probe; simplify Link fallback Investigation is done — the GaiaScenario fix in 6046b83 (attaching the native click guard before trigger.click() runs) verified working in the browser via no redirect after 20+ seconds on /. Remove DebugNavProbe and its landing-page mount; trim the FeaturedComponents Link onClick down to the bare isTrusted check (no more console.warn noise). * fix(landing): tighten featured grid to md:4 cols and polish hero
1 parent e23d63f commit e4b7831

191 files changed

Lines changed: 24908 additions & 1054 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.npmrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
shamefully-hoist=true
2+
auto-install-peers=true

.prettierrc

Lines changed: 0 additions & 11 deletions
This file was deleted.

apps/remotion/.prettierrc

Lines changed: 0 additions & 5 deletions
This file was deleted.

apps/remotion/eslint.config.mjs

Lines changed: 0 additions & 3 deletions
This file was deleted.

apps/remotion/package.json

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,122 @@
99
"./*": "./src/*.tsx"
1010
},
1111
"dependencies": {
12+
"@heroui/accordion": "^2.2.29",
13+
"@heroui/autocomplete": "^2.3.34",
14+
"@heroui/avatar": "^2.2.26",
15+
"@heroui/badge": "^2.2.18",
16+
"@heroui/button": "^2.2.32",
17+
"@heroui/card": "^2.2.28",
18+
"@heroui/checkbox": "^2.3.32",
19+
"@heroui/chip": "^2.2.25",
20+
"@heroui/divider": "^2.2.24",
21+
"@heroui/dropdown": "^2.3.32",
22+
"@heroui/input": "^2.4.33",
23+
"@heroui/kbd": "^2.2.26",
24+
"@heroui/link": "^2.2.26",
25+
"@heroui/modal": "^2.2.29",
26+
"@heroui/popover": "^2.3.32",
27+
"@heroui/progress": "^2.2.25",
28+
"@heroui/radio": "^2.3.32",
29+
"@heroui/react": "2.8.8",
30+
"@heroui/scroll-shadow": "^2.3.19",
31+
"@heroui/select": "^2.4.33",
32+
"@heroui/skeleton": "^2.2.18",
33+
"@heroui/slider": "^2.4.29",
34+
"@heroui/spinner": "^2.2.29",
35+
"@heroui/switch": "^2.2.27",
36+
"@heroui/tabs": "^2.2.29",
37+
"@heroui/tooltip": "^2.2.29",
38+
"@heroui/user": "^2.2.26",
39+
"@heygaia/chat-ui": "0.0.0-feat-chat-ui.e62cbd4",
40+
"@hookform/resolvers": "^5.2.2",
1241
"@hugeicons/core-free-icons": "^4.1.1",
1342
"@hugeicons/react": "^1.1.6",
43+
"@mcp-ui/client": "^7.1.0",
44+
"@number-flow/react": "^0.6.0",
45+
"@openuidev/react-lang": "^0.2.4",
46+
"@radix-ui/react-dialog": "^1.1.15",
47+
"@radix-ui/react-dropdown-menu": "^2.1.16",
48+
"@radix-ui/react-separator": "^1.1.8",
49+
"@radix-ui/react-tooltip": "^1.2.8",
1450
"@remotion/cli": "^4.0.457",
1551
"@remotion/tailwind-v4": "^4.0.457",
52+
"@tanstack/react-query": "^5.100.9",
53+
"@tanstack/react-virtual": "^3.13.24",
54+
"@theexperiencecompany/gaia-icons": "^1.9.0",
55+
"@tiptap/react": "^3.23.1",
56+
"@tiptap/starter-kit": "^3.23.1",
1657
"@workspace/ui": "workspace:*",
58+
"axios": "^1.16.0",
59+
"canvas-confetti": "^1.9.4",
60+
"chrono-node": "^2.9.1",
61+
"class-variance-authority": "^0.7.1",
62+
"clsx": "^2.1.1",
63+
"d3": "^7.9.0",
64+
"date-fns": "^4.1.0",
65+
"dexie": "^4.4.2",
66+
"dexie-react-hooks": "^4.4.0",
67+
"dompurify": "^3.4.2",
68+
"embla-carousel-react": "^8.6.0",
69+
"emblor": "^1.4.8",
70+
"fuse.js": "^7.3.0",
71+
"he": "^1.2.0",
72+
"input-otp": "^1.4.2",
73+
"little-date": "^1.2.1",
74+
"lodash-es": "^4.18.1",
75+
"maplibre-gl": "^5.24.0",
76+
"marked": "^18.0.3",
77+
"mermaid": "^11.14.0",
78+
"motion": "^12.38.0",
79+
"next": "^16.2.6",
1780
"puppeteer": "^24.43.0",
1881
"react": "^19.2.6",
82+
"react-best-gradient-color-picker": "^3.0.14",
83+
"react-day-picker": "^10.0.0",
1984
"react-dom": "^19.2.6",
85+
"react-hook-form": "^7.75.0",
86+
"react-hotkeys-hook": "^5.3.2",
87+
"react-markdown": "^10.1.0",
88+
"react-share": "^5.3.0",
89+
"react-syntax-highlighter": "^16.1.1",
90+
"react-twemoji": "^0.7.2",
91+
"react-window": "^2.2.7",
92+
"react-window-infinite-loader": "^2.0.1",
93+
"recharts": "^3.8.1",
94+
"rehype-katex": "^7.0.1",
95+
"rehype-sanitize": "^6.0.0",
96+
"remark-breaks": "^4.0.0",
97+
"remark-gfm": "^4.0.1",
98+
"remark-math": "^6.0.0",
99+
"remark-smartypants": "^3.0.2",
100+
"remark-supersub": "^1.0.0",
20101
"remotion": "^4.0.457",
21-
"tailwindcss": "4.0.0"
102+
"schema-dts": "^2.0.0",
103+
"string-similarity": "^4.0.4",
104+
"tailwind-merge": "^3.5.0",
105+
"tailwindcss": "4.0.0",
106+
"tinycolor2": "^1.6.0",
107+
"uuid": "^14.0.0",
108+
"vaul": "^1.1.2",
109+
"zod": "^4.4.3",
110+
"zustand": "^5.0.13"
22111
},
23112
"devDependencies": {
24-
"@remotion/eslint-config-flat": "^4.0.457",
113+
"@biomejs/biome": "^2.4.14",
25114
"@types/node": "^25.6.0",
26115
"@types/react": "^19.2.10",
27116
"@types/web": "0.0.166",
28-
"eslint": "9.19.0",
29-
"prettier": "3.8.1",
30117
"typescript": "^5.9.3"
31118
},
32119
"scripts": {
33120
"dev": "remotionb studio",
34121
"build": "remotionb bundle",
35122
"upgrade": "remotionb upgrade",
36-
"lint": "eslint src",
37-
"typecheck": "tsc --noEmit"
123+
"type-check": "tsc --noEmit",
124+
"lint": "biome check .",
125+
"format": "biome check --write ."
38126
},
39127
"sideEffects": [
40128
"*.css"
41129
]
42-
}
130+
}

0 commit comments

Comments
 (0)