Commit e4b7831
authored
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 hero1 parent e23d63f commit e4b7831
191 files changed
Lines changed: 24908 additions & 1054 deletions
File tree
- apps
- remotion
- public/images/logos
- src
- compositions
- BrowserWindow
- CaptionTrack
- DiscordMessages
- GaiaScenario
- InstagramPost
- LaptopFrame
- MessageBubbles
- MessagePopup
- SlackMessages
- SplitScene
- TextBlurOutUp
- TextBottomUpLetters
- TextDepthParallaxWords
- TextFadeThrough
- TextFocusBlurResolve
- TextKineticCenterBuild
- TextLineByLineSlide
- TextMaskRevealUp
- TextMicroScaleFade
- TextPerCharacterRise
- TextPerWordCrossfade
- TextScaleDownFade
- TextShimmerSweep
- TextShortSlideDown
- TextShortSlideRight
- TextSoftBlurIn
- TextSpringScaleIn
- TextStaggerFromCenter
- TextStaggerFromEdges
- TextTopDownLetters
- TextTypewriter
- TitleFade
- TitlePopup
- TitleSlideUp
- TitleType
- TweetCard
- TwitterFollow
- TypingComposer
- TypingSearch
- WhatsAppMessages
- editors
- effects
- shims
- web
- app
- (shell)
- component/[id]/edit
- components
- docs
- content/docs
- features/studio/components
- hooks
- lib
- public/images
- icons
- logos
- stickers
- types
- packages/ui/src
- components
- styles
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
12 | 41 | | |
13 | 42 | | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
14 | 50 | | |
15 | 51 | | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
16 | 57 | | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
17 | 80 | | |
18 | 81 | | |
| 82 | + | |
| 83 | + | |
19 | 84 | | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
20 | 101 | | |
21 | | - | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
22 | 111 | | |
23 | 112 | | |
24 | | - | |
| 113 | + | |
25 | 114 | | |
26 | 115 | | |
27 | 116 | | |
28 | | - | |
29 | | - | |
30 | 117 | | |
31 | 118 | | |
32 | 119 | | |
33 | 120 | | |
34 | 121 | | |
35 | 122 | | |
36 | | - | |
37 | | - | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
38 | 126 | | |
39 | 127 | | |
40 | 128 | | |
41 | 129 | | |
42 | | - | |
| 130 | + | |
0 commit comments