feat(frontend): playground-native agent onboarding - #5076
Conversation
Pull the catalog-tile layout (icon + title/adornment, 2-line description, category pills + meta slot) out of CatalogAppCard into a reusable EntityCard in @agenta/ui, and refit CatalogAppCard on top of it. Lets other "pick from a grid" surfaces (agent-home templates) share the same tile without duplicating the markup.
Add a getMarkdown() imperative handle (read content without submitting), a right-aligned trailing toolbar slot, and props to tune the editor for description-style inputs: minHeightClassName, textSizeClassName, hideShortcutHints, and submitOnEnter. Defaults preserve the existing chat-composer behavior.
Add a @agenta/entity-ui/drawers/shared subpath export and a barrel so app-layer surfaces can reuse the drawer rail/field primitives instead of re-implementing them. Add a `fill` prop to SectionRail that stretches it into a bounded flex parent (min-h-0 flex-1) so the content panel can host an internally-scrolling child, and re-export SectionRail from the root.
Introduce the agent onboarding Home page (composer, template gallery + setup drawer, your-agents table, usage summary, on-ramps) under components/pages/agent-home, plus a full templates gallery route. Both are gated by the NEXT_PUBLIC_AGENT_ONBOARDING flag: the /apps Home route renders AgentHome instead of the legacy AppManagement dashboard when the flag is on, and the /agent-templates route redirects Home when it is off. Wire the EE templates page to the OSS one and give the templates gallery the bounded full-height layout frame.
…-type mismatch The createEphemeralAppFromTemplate helper was hardcoding is_llm=true for all templates, causing agent templates to be typed as "llm" workflows instead of "agent". Now set is_llm based on the template type: false for agents, true otherwise, so workflowType() returns the correct handler key.
Wire the agent Home composer and template cards to a real create flow (useCreateAgent): create the ephemeral, commit it to a real agent app, stash a first-run seed on the new revision, and land in its playground — no drawer. The setup-drawer path is retained behind a renamed flag. - Rename NEXT_PUBLIC_AGENT_ONBOARDING -> NEXT_PUBLIC_AGENT_TEMPLATE_BUILDER: when on, a template card skips the config-review drawer and seeds the playground with a builder instruction; off keeps the drawer flow. - Home + templates gallery now render unconditionally (flag no longer gates the page). - Add agentFirstRunSeedAtom, produced here and consumed by the chat.
Detect whether the vault holds a key for the agent's model provider (useAgentModelKeyStatus) and, when it doesn't, surface a connect-a-model banner above the composer and disable the composer until connected. "Set up credentials" flips the playground to Build and opens the Model & harness drawer via the new openAgentConfigSectionAtom; saving the key clears the gate reactively. Also consume the first-run seed: a freshly-created agent surfaces its starting prompt in the empty state (not the composer) with a Start CTA, and auto-starts once the model gate clears (connecting the key is the user's go-ahead — no second click).
…gent config panel Let the user connect a model provider key without leaving the playground: add a ProviderKeyField to the Model & harness drawer that saves to the project vault and refetches, and a "Model / Provider key" rail that lands on the key tab when a key is missing. AgentTemplateControl consumes openAgentConfigSectionAtom so the chat's connect-a-model banner opens straight onto it. Surface model-section status: an amber "Connect key" incomplete tone / badge when the provider key is missing, a red "No model"/"Unavailable" when the model is missing or unsupported. Adds a status dot to SectionRail and a titleBadge slot to ConfigAccordionSection to carry the pills. Reworks the harness picker from cards to a [rail | detail] layout.
…olving The provider-key gate read the static provider catalog (standardSecretsAtom), whose keys are empty until vaultSecretsQueryAtom lands — so a playground reload flashed a false connect-a-model banner, disabled composer, and Connect key section warnings until the query resolved. Suppress every missing-key assertion (modelBlocked, ConnectModelBanner, providerNeedsKey) until the vault data is an array; an empty-but-loaded vault still blocks, and a fetch error no longer false-gates.
…(phase 1) Extract the theme colors currently scattered across theme-variables.css and ThemeContextProvider into a single palette.ts (light + dark). This is a lossless extraction — dark values antd's darkAlgorithm derives are snapshotted to their exact current output, so the app looks identical and every dark value is directly editable. Add generate-tailwind-tokens.ts to emit the theme artifacts from palette.ts. Neither is consumed yet: the generator writes to a scratch dir by default (GEN_OUT to target the live tree), so it can't collide with in-flight edits.
Replace the composer's clipboard-copy "Continue in IDE" action with a proper IDE-handoff modal (install command + the user's prompt, Copy button). Uses a declarative antd Modal so it inherits the app theme (dark-safe, unlike the static Modal.* helpers). Rework AgentComposer from a segmented Build/IDE switch to a two-button footer (Continue in IDE + primary Create agent), mirroring the playground onboarding composer. useAgentHomeActions drops the clipboard handler; the page owns the modal via useIdeHandoffModal.
Add an opt-in onboarding flow (NEXT_PUBLIC_AGENT_PLAYGROUND_ONBOARDING) that lives INSIDE the playground instead of on the agent-home page. First-run users land on an ephemeral agent at the project /playground route: the config panel shows a template list, the chat surface shows a "what do you want to build?" composer. Sending commits the ephemeral into a real agent IN PLACE (no redirect) and hands off to the live chat as one continuous conversation. - OnboardingEntry decides before painting (redirect first-run, list for returning) so we never flash the wrong surface; OnboardingLoader is the one "setting up" screen across every boundary. - Playground gains an `onboarding` mode: useAgentOnboarding mints/drives the ephemeral, injects the onboarding panels via MainLayout's renderConfigOverride + AgentGenerationPanel, and exposes the ephemeral→real transition through OnboardingContext. Chrome (session bar, config forms) hides while ephemeral. - appUtils gains `deferInspect` so the ephemeral renders immediately and refines schemas in the background. - Chat: optimistic first turn during commit, autoSend seed (explicit "go" sends once the model is ready), a dedicated onboarding chat scope + resetScope so a prior visit's session never leaks in, and a streamed "Continue in IDE" bubble.
Wire the generator into the app so palette.ts actually drives theme colors, completing the phase-1 extraction. The generator now writes the live theme-variables.css and a new theme/antd-overrides.generated.ts, and ThemeContextProvider consumes the generated dark-token overrides instead of its own inline DARK_TOKEN_OVERRIDES block. Adds legacy-shim.ts as the frozen --ag-c-* codemod input the generator re-emits. - generate:tailwind-tokens runs with GEN_WRITE=1 so it targets the live tree. - Regenerated theme-variables.css from palette.ts (no visual change). - Docs (root + web AGENTS.md, oss README, dark-mode.md) now point to palette.ts as the single source of truth: edit it + regenerate, never hand-edit the generated files.
Swap the static antd `message` import for the `App.useApp()` instance in ListOfProjects and the Projects settings page. The static helper renders outside the ConfigProvider tree, so its toasts show unstyled (light) in dark mode; the context instance inherits the app theme.
Smooth the ephemeral→real handoff and template browsing in the onboarding playground so nothing pops or snaps in. - Reveal / RevealCollapse primitives + ConfigAccordionSection `revealOnMount` (opt-in, staggered) fade the config sections and composer chrome in on mount instead of popping when the panel resolves after commit. - OnboardingBrowseTemplates renders the full gallery in place (right panel) instead of navigating to the standalone page; picking a card commits the ephemeral in place, same path as the left quick-pick list. - OnboardingConfigSettling shows a config-shaped skeleton for the beat between commit and the real config resolving, so the panel swaps in once cleanly. - Wire the transition through OnboardingContext / useAgentOnboarding and reuse TemplatesSection; ConnectModelBanner latches its content through the collapse.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR introduces a palette-driven theme token generation pipeline (palette.ts, generator script, generated CSS/antd overrides), a playground-native onboarding flow for creating agents from an ephemeral state, a new agent-home page with template gallery and setup drawer, model-harness provider-key configuration UI, and supporting shared UI primitives and fixes. ChangesTheme Token Generation Pipeline
Estimated code review effort: 4 (Complex) | ~75 minutes Playground Onboarding and Agent-Home Experience
Estimated code review effort: 5 (Critical) | ~120 minutes Model Harness and Provider-Key Configuration UI
Estimated code review effort: 3 (Moderate) | ~30 minutes Supporting UI/API Fixes
Estimated code review effort: 2 (Simple) | ~15 minutes Sequence Diagram(s)sequenceDiagram
participant AgentHome
participant useTemplateSelect
participant useCreateAgent
participant TemplateSetupDrawer
participant Playground
AgentHome->>useTemplateSelect: select template
useTemplateSelect->>useCreateAgent: create agent (builder mode)
useCreateAgent->>useCreateAgent: mint ephemeral, commit real revision
useCreateAgent->>Playground: navigate to playground with seed
AgentHome->>TemplateSetupDrawer: open setup (non-builder mode)
TemplateSetupDrawer->>useCreateAgent: onCreate({template, name})
useCreateAgent->>Playground: navigate with created agent
sequenceDiagram
participant PlaygroundRouter
participant useAgentOnboarding
participant AgentChatPanel
participant OnboardingContext
PlaygroundRouter->>useAgentOnboarding: active=true, mint ephemeral agent
useAgentOnboarding->>OnboardingContext: expose commit(), browseAll, chromeRevealed
AgentChatPanel->>OnboardingContext: read commit state, chromeHidden
AgentChatPanel->>useAgentOnboarding: commit(seedMessage)
useAgentOnboarding->>useAgentOnboarding: convert ephemeral to real revision
useAgentOnboarding->>AgentChatPanel: real entityId ready, chrome revealed
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
At commit the session bar, connect-model banner and header mode switch all revealed in the same frame the first message sent, so they fought the send and the transcript scroll. Gate that post-commit chrome on a delayed `chromeRevealed` signal (500ms after the real revision is set) so it eases in once the message has landed, and hand the heavy entity swap (`setEntityIds`) to `startTransition` so React yields to the browser and the commit animations aren't starved.
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (12)
web/packages/agenta-entity-ui/src/drawers/shared/SectionRail.tsx (1)
77-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStatus dot conveys state via color only.
No text alternative for the warning/invalid dot, unlike the Tooltip-wrapped connection dots in
CatalogAppCard.tsx. Screen-reader users get no signal that the item needs attention.♿ Proposed fix to add an accessible label
{item.status ? ( - <span + <span + role="img" + aria-label={item.status === "invalid" ? "Invalid" : "Needs attention"} className={`h-1.5 w-1.5 shrink-0 rounded-full ${ item.status === "invalid" ? "bg-[var(--ag-colorError)]" : "bg-[var(--ag-colorWarning)]" }`} /> ) : item.count != null ? (web/oss/src/components/pages/agent-home/index.tsx (1)
29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor duplication: composer-read logic repeated.
composerRef.current?.getMarkdown().trim() ?? ""is duplicated here and inuseAgentHomeActions.readPrompt. Consider exposingreadPromptfromuseAgentHomeActions(or a small shared helper) and reusing it foronContinueInIdeto avoid drift.web/oss/src/components/AgentChatSlice/components/ConnectModelBanner.tsx (1)
13-22: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMulti-line comment exceeds guideline terseness.
This 10-line JSDoc block goes well beyond "at most one short line per comment." The explanation of why the banner is always-mounted (lines 19-21) is a legitimate surprising-constraint callout, but the rest (lines 13-18) is standard prop/behavior description that could be trimmed to a single line.
As per coding guidelines,
web/**/*.{ts,tsx,js,jsx}: "Keep code comments terse: at most one short line per comment, with multi-line explanatory comments avoided unless a genuinely surprising constraint must be documented."Source: Coding guidelines
web/oss/src/components/pages/agent-home/assets/templates.ts (2)
1-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMulti-line file-level comment exceeds terseness guideline.
This 4-line block comment is purely descriptive (not a surprising constraint) and could be condensed to one line.
As per coding guidelines,
web/**/*.{ts,tsx,js,jsx}: "Keep code comments terse: at most one short line per comment, with multi-line explanatory comments avoided unless a genuinely surprising constraint must be documented."Source: Coding guidelines
47-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdditional multi-line comments (builderMessage field doc, templateBuilderMessage function doc).
Same terseness guideline applies here — both blocks span multiple lines describing non-surprising behavior.
As per coding guidelines,
web/**/*.{ts,tsx,js,jsx}: "Keep code comments terse: at most one short line per comment... unless a genuinely surprising constraint must be documented."Also applies to: 79-84
Source: Coding guidelines
web/oss/src/components/pages/agent-home/components/TemplateSetupDrawer/ToolsPreview.tsx (1)
7-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMulti-line JSDoc exceeds terseness guideline.
This 5-line comment describes design intent that isn't a genuinely surprising constraint; could be trimmed to one line.
As per coding guidelines,
web/**/*.{ts,tsx,js,jsx}: "Keep code comments terse: at most one short line per comment, with multi-line explanatory comments avoided unless a genuinely surprising constraint must be documented."Source: Coding guidelines
web/oss/src/components/pages/agent-home/components/TemplatesGallery/index.tsx (1)
101-108: 📐 Maintainability & Code Quality | 🔵 TrivialTODO: wire template creation to actually mint the agent.
handleTemplateCreatecurrently just shows a toast instead of creating the ephemeral agent and opening the playground, per theTODO(Phase B)comment. Confirm this is intentionally deferred and tracked, since as written "Create agent" produces no functional outcome.Want me to wire this to
useCreateAgent/useAgentOnboardingnow, or open a follow-up issue to track it?web/oss/src/components/pages/agent-home/OnboardingEntry.tsx (1)
35-39: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winUnconditional Playground chunk prefetch runs for returning users too.
This effect fires on every mount of
OnboardingEntry, regardless of whether the user is first-run or returning. The comment states the intent is to prefetch before the first-run redirect, but returning users (who render<AgentHome />here) also pay this download even though they may never navigate to the ephemeral onboarding/playground. Consider gating the prefetch onfirstRun(orloading, since the decision isn't known yet) so it doesn't unconditionally cost bandwidth for the common returning-user path.⚡ Proposed fix
useEffect(() => { - void import("`@/oss/components/Playground/Playground`") - }, []) + if (loading || firstRun) { + void import("`@/oss/components/Playground/Playground`") + } + }, [loading, firstRun])web/oss/src/pages/w/[workspace_id]/p/[project_id]/apps/agent-templates.tsx (1)
3-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNo loading fallback for the dynamically imported
TemplatesGallery.Without a
loadingoption,next/dynamicrenders nothing until the chunk downloads, producing a blank page flash on this route — inconsistent with theOnboardingLoaderpattern used elsewhere in this onboarding flow (e.g.,apps/index.tsx'sOnboardingEntry).💡 Proposed fix
+import OnboardingLoader from "`@/oss/components/pages/agent-home/PlaygroundOnboarding/OnboardingLoader`" const TemplatesGallery = dynamic( () => import("`@/oss/components/pages/agent-home/components/TemplatesGallery`"), + {loading: OnboardingLoader}, )web/oss/src/pages/w/[workspace_id]/p/[project_id]/apps/index.tsx (1)
6-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
AgentHomedynamic import has noloadingfallback, unlikeOnboardingEntry.
OnboardingEntryis given{loading: OnboardingLoader}so the chunk-load is covered by a spinner, butAgentHome(the default path whenPLAYGROUND_NATIVE_ONBOARDINGis off) has noloadingoption. Per Next.js docs, without aloadingcallback,next/dynamic"sends a minimal placeholder to the browser" and only renders once the chunk downloads — i.e., a blank screen flash for all users landing on/appsin the default (flag-off) configuration.💡 Proposed fix
-const AgentHome = dynamic(() => import("`@/oss/components/pages/agent-home`")) +const AgentHome = dynamic(() => import("`@/oss/components/pages/agent-home`"), { + loading: OnboardingLoader, +})web/oss/src/components/pages/agent-home/PlaygroundOnboarding/OnboardingLoader.tsx (1)
9-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider announcing the loading state to assistive tech.
This is the single reused loading surface across every onboarding boundary (redirect, chunk load, ephemeral mint). Adding
role="status"/aria-live="polite"would let screen-reader users hear "Setting up your agent…" instead of silently waiting.♿ Suggested addition
const OnboardingLoader = () => ( - <div className="flex h-[calc(100dvh-46px)] w-full flex-col items-center justify-center gap-3"> + <div + role="status" + aria-live="polite" + className="flex h-[calc(100dvh-46px)] w-full flex-col items-center justify-center gap-3" + > <Spin /> <span className="text-xs text-[var(--ag-colorTextSecondary)]">Setting up your agent…</span> </div> )web/package.json (1)
68-68: 🗄️ Data Integrity & Integration | 🔵 TrivialConsider running the parity harness by default when regenerating live theme files.
GEN_WRITE=1now writes directly to the livetheme-variables.cssandantd-overrides.generated.tswith noBASELINE_CSSset, soparityCheck()(generate-tailwind-tokens.ts lines 447-472) is skipped by default — a badpalette.tsedit could silently overwrite the live theme without any mismatch report. Since git diff review is the fallback safety net, this is more a guardrail improvement than a blocker.🛡️ Optional: default to a parity check on regenerate
- "generate:tailwind-tokens": "GEN_WRITE=1 tsx scripts/generate-tailwind-tokens.ts" + "generate:tailwind-tokens": "GEN_WRITE=1 BASELINE_CSS=oss/src/styles/theme-variables.css tsx scripts/generate-tailwind-tokens.ts"
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6298c86b-b2bb-47b5-8e26-8f75d0781480
📒 Files selected for processing (80)
AGENTS.mddocs/designs/dark-mode.mdweb/AGENTS.mdweb/ee/src/pages/w/[workspace_id]/p/[project_id]/apps/agent-templates.tsxweb/oss/README.mdweb/oss/src/components/AgentChatSlice/AgentChatPanel.tsxweb/oss/src/components/AgentChatSlice/components/AgentChatEmptyState.tsxweb/oss/src/components/AgentChatSlice/components/ConnectModelBanner.tsxweb/oss/src/components/AgentChatSlice/components/RevealCollapse.tsxweb/oss/src/components/AgentChatSlice/hooks/useAgentModelKeyStatus.tsweb/oss/src/components/AgentChatSlice/state/firstRunSeed.tsweb/oss/src/components/AgentChatSlice/state/scope.tsxweb/oss/src/components/AgentChatSlice/state/sessions.tsweb/oss/src/components/Layout/Layout.tsxweb/oss/src/components/Layout/ThemeContextProvider.tsxweb/oss/src/components/Playground/Components/MainLayout/index.tsxweb/oss/src/components/Playground/Components/PlaygroundHeader/index.tsxweb/oss/src/components/Playground/Playground.tsxweb/oss/src/components/PlaygroundRouter/index.tsxweb/oss/src/components/Sidebar/components/ListOfProjects.tsxweb/oss/src/components/pages/agent-home/OnboardingEntry.tsxweb/oss/src/components/pages/agent-home/PlaygroundOnboarding/OnboardingBrowseTemplates.tsxweb/oss/src/components/pages/agent-home/PlaygroundOnboarding/OnboardingConfigPanel.tsxweb/oss/src/components/pages/agent-home/PlaygroundOnboarding/OnboardingConfigSettling.tsxweb/oss/src/components/pages/agent-home/PlaygroundOnboarding/OnboardingContext.tsxweb/oss/src/components/pages/agent-home/PlaygroundOnboarding/OnboardingLoader.tsxweb/oss/src/components/pages/agent-home/PlaygroundOnboarding/Reveal.tsxweb/oss/src/components/pages/agent-home/PlaygroundOnboarding/useAgentOnboarding.tsweb/oss/src/components/pages/agent-home/assets/constants.tsweb/oss/src/components/pages/agent-home/assets/templates.tsweb/oss/src/components/pages/agent-home/components/AgentComposer/index.tsxweb/oss/src/components/pages/agent-home/components/ContinueInIdeModal.tsxweb/oss/src/components/pages/agent-home/components/EmptyAgents.tsxweb/oss/src/components/pages/agent-home/components/OnRamps.tsxweb/oss/src/components/pages/agent-home/components/TemplateSetupDrawer/IntegrationRow.tsxweb/oss/src/components/pages/agent-home/components/TemplateSetupDrawer/ModelRow.tsxweb/oss/src/components/pages/agent-home/components/TemplateSetupDrawer/SetupRow.tsxweb/oss/src/components/pages/agent-home/components/TemplateSetupDrawer/ToolsPreview.tsxweb/oss/src/components/pages/agent-home/components/TemplateSetupDrawer/index.tsxweb/oss/src/components/pages/agent-home/components/TemplatesGallery/TemplateSection.tsxweb/oss/src/components/pages/agent-home/components/TemplatesGallery/index.tsxweb/oss/src/components/pages/agent-home/components/TemplatesSection/ProviderMarks.tsxweb/oss/src/components/pages/agent-home/components/TemplatesSection/TemplateCard.tsxweb/oss/src/components/pages/agent-home/components/TemplatesSection/TemplateCategoryChips.tsxweb/oss/src/components/pages/agent-home/components/TemplatesSection/index.tsxweb/oss/src/components/pages/agent-home/components/TutorialVideoEmbed.tsxweb/oss/src/components/pages/agent-home/components/UsageSummary/index.tsxweb/oss/src/components/pages/agent-home/components/YourAgentsTable/columns.tsxweb/oss/src/components/pages/agent-home/components/YourAgentsTable/index.tsxweb/oss/src/components/pages/agent-home/hooks/useAgentHomeActions.tsweb/oss/src/components/pages/agent-home/hooks/useAgentHomeVariants.tsweb/oss/src/components/pages/agent-home/hooks/useCreateAgent.tsweb/oss/src/components/pages/agent-home/hooks/useIdeHandoffModal.tsxweb/oss/src/components/pages/agent-home/hooks/useTemplateSelect.tsweb/oss/src/components/pages/agent-home/index.tsxweb/oss/src/components/pages/settings/Projects/index.tsxweb/oss/src/lib/helpers/dynamicEnv.tsweb/oss/src/pages/w/[workspace_id]/p/[project_id]/apps/agent-templates.tsxweb/oss/src/pages/w/[workspace_id]/p/[project_id]/apps/index.tsxweb/oss/src/styles/theme-variables.cssweb/oss/src/styles/theme/antd-overrides.generated.tsweb/oss/src/styles/theme/legacy-shim.tsweb/oss/src/styles/theme/palette.tsweb/package.jsonweb/packages/agenta-entities/src/workflow/state/appUtils.tsweb/packages/agenta-entity-ui/package.jsonweb/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsxweb/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/ProviderKeyField.tsxweb/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsxweb/packages/agenta-entity-ui/src/drawers/shared/CatalogAppCard.tsxweb/packages/agenta-entity-ui/src/drawers/shared/SectionRail.tsxweb/packages/agenta-entity-ui/src/drawers/shared/index.tsweb/packages/agenta-entity-ui/src/index.tsweb/packages/agenta-shared/src/state/index.tsweb/packages/agenta-shared/src/state/openConfigSection.tsweb/packages/agenta-ui/src/RichChatInput/RichChatInput.tsxweb/packages/agenta-ui/src/components/EntityCard.tsxweb/packages/agenta-ui/src/components/index.tsweb/packages/agenta-ui/src/components/presentational/section/ConfigAccordionSection.tsxweb/scripts/generate-tailwind-tokens.ts
| const handleCreateAgent = useCallback(() => { | ||
| if (!onboarding || onboarding.committing) return | ||
| const text = richInputRef.current?.getMarkdown().trim() ?? "" | ||
| setPendingFirstTurn(text || null) | ||
| // The text becomes the sent first turn — clear the composer so it doesn't linger into the chat. | ||
| richInputRef.current?.setMarkdown("") | ||
| onboarding.commit(text) | ||
| }, [onboarding]) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether commit() in useAgentOnboarding guards against empty/blank seed text
fd useAgentOnboarding.ts --exec cat -n {}Repository: Agenta-AI/agenta
Length of output: 12075
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the Create Agent button and the create/commit path.
files=(
"web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx"
"web/oss/src/components/AgentChatSlice/hooks/useCreateAgent.ts"
)
for f in "${files[@]}"; do
echo "===== $f ====="
wc -l "$f"
echo
done
echo "===== AgentChatPanel relevant lines ====="
sed -n '400,470p' web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx
echo
echo "===== useCreateAgent ====="
cat -n web/oss/src/components/AgentChatSlice/hooks/useCreateAgent.tsRepository: Agenta-AI/agenta
Length of output: 453
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "===== search for useCreateAgent ====="
rg -n "useCreateAgent" web/oss/src
echo
echo "===== AgentChatPanel create button / handleCreateAgent ====="
rg -n "handleCreateAgent|Create agent|Create Agent|disabled=|onboarding.commit\\(" web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx
echo
echo "===== AgentChatPanel outline ====="
ast-grep outline web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx --view expanded | sed -n '1,240p'Repository: Agenta-AI/agenta
Length of output: 2995
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "===== useCreateAgent ====="
wc -l web/oss/src/components/pages/agent-home/hooks/useCreateAgent.ts
cat -n web/oss/src/components/pages/agent-home/hooks/useCreateAgent.ts
echo
echo "===== AgentChatPanel around handleCreateAgent and Create agent button ====="
sed -n '424,450p' web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx
sed -n '1438,1452p' web/oss/src/components/AgentChatSlice/AgentChatPanel.tsxRepository: Agenta-AI/agenta
Length of output: 8331
Guard Create agent against blank composers. handleCreateAgent still calls onboarding.commit(text) when the composer is empty, and useCreateAgent only skips the first-turn seed when seedMessage is blank. Add a disabled/early-return guard so the button can’t create an agent with no input.
| const commit = useCallback( | ||
| (seedMessage: string, name?: string) => { | ||
| if (!entityId || committing || realEntityId) return | ||
| setCommitting(true) | ||
| // Surface the seed so the chat can render it as an optimistic user turn during commit. | ||
| setCommittingSeed(seedMessage.trim() || null) | ||
| void createAgent({ | ||
| name, | ||
| seedMessage, | ||
| entityId, | ||
| // Create-agent is an explicit "go" → the chat sends the description as the first turn | ||
| // once the model is ready (no extra Start click), keeping the transition seamless. | ||
| autoSendSeed: true, | ||
| onCommitted: ({appId, revisionId}) => { | ||
| // Flip the onboarding state urgently — the settling skeleton, the `chromeRevealed` | ||
| // timer, and the commit-failure recovery all read `realEntityId` synchronously. | ||
| setRealEntityId(revisionId) | ||
| // Hand the heavy part (the playground re-rendering the real entity → config panel + | ||
| // generation surface) to a transition, so React can yield to the browser and the | ||
| // commit's CSS animations aren't starved by a blocking render. | ||
| startTransition(() => { | ||
| setEntityIds([revisionId]) | ||
| }) | ||
| if (typeof window !== "undefined") { | ||
| window.history.replaceState( | ||
| window.history.state, | ||
| "", | ||
| `${baseAppURL}/${appId}/playground?revisions=${revisionId}`, | ||
| ) | ||
| } | ||
| }, | ||
| }).finally(() => setCommitting(false)) | ||
| }, | ||
| [entityId, committing, realEntityId, createAgent, setEntityIds, baseAppURL], | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
committingSeed is never cleared when commit fails, leaving a stale optimistic chat turn.
useCreateAgent (see web/oss/src/components/pages/agent-home/hooks/useCreateAgent.ts) catches its own errors internally and only calls onCommitted on success — on failure it just shows a message.error and resolves normally. So .finally(() => setCommitting(false)) always runs, but committingSeed (set at Line 122) is only ever cleared implicitly by a successful commit setting realEntityId. On failure, committing goes back to false while realEntityId stays null and committingSeed stays set — per the type's own doc comment ("shows it as an optimistic user turn"), the chat is left showing a message that was never actually sent, with no indication that the commit failed.
🐛 Proposed fix
const commit = useCallback(
(seedMessage: string, name?: string) => {
if (!entityId || committing || realEntityId) return
setCommitting(true)
setCommittingSeed(seedMessage.trim() || null)
+ let committed = false
void createAgent({
name,
seedMessage,
entityId,
autoSendSeed: true,
onCommitted: ({appId, revisionId}) => {
+ committed = true
setRealEntityId(revisionId)
startTransition(() => {
setEntityIds([revisionId])
})
if (typeof window !== "undefined") {
window.history.replaceState(
window.history.state,
"",
`${baseAppURL}/${appId}/playground?revisions=${revisionId}`,
)
}
},
- }).finally(() => setCommitting(false))
+ }).finally(() => {
+ setCommitting(false)
+ if (!committed) setCommittingSeed(null)
+ })
},
[entityId, committing, realEntityId, createAgent, setEntityIds, baseAppURL],
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const commit = useCallback( | |
| (seedMessage: string, name?: string) => { | |
| if (!entityId || committing || realEntityId) return | |
| setCommitting(true) | |
| // Surface the seed so the chat can render it as an optimistic user turn during commit. | |
| setCommittingSeed(seedMessage.trim() || null) | |
| void createAgent({ | |
| name, | |
| seedMessage, | |
| entityId, | |
| // Create-agent is an explicit "go" → the chat sends the description as the first turn | |
| // once the model is ready (no extra Start click), keeping the transition seamless. | |
| autoSendSeed: true, | |
| onCommitted: ({appId, revisionId}) => { | |
| // Flip the onboarding state urgently — the settling skeleton, the `chromeRevealed` | |
| // timer, and the commit-failure recovery all read `realEntityId` synchronously. | |
| setRealEntityId(revisionId) | |
| // Hand the heavy part (the playground re-rendering the real entity → config panel + | |
| // generation surface) to a transition, so React can yield to the browser and the | |
| // commit's CSS animations aren't starved by a blocking render. | |
| startTransition(() => { | |
| setEntityIds([revisionId]) | |
| }) | |
| if (typeof window !== "undefined") { | |
| window.history.replaceState( | |
| window.history.state, | |
| "", | |
| `${baseAppURL}/${appId}/playground?revisions=${revisionId}`, | |
| ) | |
| } | |
| }, | |
| }).finally(() => setCommitting(false)) | |
| }, | |
| [entityId, committing, realEntityId, createAgent, setEntityIds, baseAppURL], | |
| ) | |
| const commit = useCallback( | |
| (seedMessage: string, name?: string) => { | |
| if (!entityId || committing || realEntityId) return | |
| setCommitting(true) | |
| // Surface the seed so the chat can render it as an optimistic user turn during commit. | |
| setCommittingSeed(seedMessage.trim() || null) | |
| let committed = false | |
| void createAgent({ | |
| name, | |
| seedMessage, | |
| entityId, | |
| // Create-agent is an explicit "go" → the chat sends the description as the first turn | |
| // once the model is ready (no extra Start click), keeping the transition seamless. | |
| autoSendSeed: true, | |
| onCommitted: ({appId, revisionId}) => { | |
| committed = true | |
| // Flip the onboarding state urgently — the settling skeleton, the `chromeRevealed` | |
| // timer, and the commit-failure recovery all read `realEntityId` synchronously. | |
| setRealEntityId(revisionId) | |
| // Hand the heavy part (the playground re-rendering the real entity → config panel + | |
| // generation surface) to a transition, so React can yield to the browser and the | |
| // commit's CSS animations aren't starved by a blocking render. | |
| startTransition(() => { | |
| setEntityIds([revisionId]) | |
| }) | |
| if (typeof window !== "undefined") { | |
| window.history.replaceState( | |
| window.history.state, | |
| "", | |
| `${baseAppURL}/${appId}/playground?revisions=${revisionId}`, | |
| ) | |
| } | |
| }, | |
| }).finally(() => { | |
| setCommitting(false) | |
| if (!committed) setCommittingSeed(null) | |
| }) | |
| }, | |
| [entityId, committing, realEntityId, createAgent, setEntityIds, baseAppURL], | |
| ) |
| export const border = { | ||
| default: {light: "#bdc7d1", dark: "#424242"}, // [absorbs] --ag-c-BDC7D1, zinc-4 | ||
| secondary: {light: "#eaeff5", dark: "#303030"}, | ||
| split: {light: "rgba(5, 23, 41, 0.06)", dark: "rgba(253, 253, 253, 0.12)"}, | ||
| } satisfies Record<string, Pair> |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
git ls-files 'web/oss/src/styles/theme/*' 'web/**/generate-tailwind-tokens.ts' | sed -n '1,200p'Repository: Agenta-AI/agenta
Length of output: 323
🏁 Script executed:
python3 - <<'PY'
from pathlib import Path
paths = [
Path('web/oss/src/styles/theme/palette.ts'),
Path('web/oss/src/styles/theme/legacy-shim.ts'),
Path('web/oss/src/styles/theme/generate-tailwind-tokens.ts'),
]
for p in paths:
print(f'## {p} ##')
if p.exists():
lines = p.read_text().splitlines()
for i, line in enumerate(lines[:320], 1):
if 'border' in line or 'aliasDark' in line or 'BDC7D1' in line or 'zinc' in line or 'absorbs' in line or 'legacy-shim' in line:
pass
print(f'lines={len(lines)}')
else:
print('MISSING')
PYRepository: Agenta-AI/agenta
Length of output: 328
🏁 Script executed:
python3 - <<'PY'
from pathlib import Path
targets = {
'web/oss/src/styles/theme/palette.ts': [('border.default', 1), ('zinc', 1), ('BDC7D1', 1)],
'web/oss/src/styles/theme/legacy-shim.ts': [('BDC7D1', 2)],
'web/oss/src/styles/theme/generate-tailwind-tokens.ts': [('aliasDark', 2), ('resolve', 2)],
}
for file, needles in targets.items():
p = Path(file)
print(f'\n### {file} ###')
if not p.exists():
print('MISSING')
continue
lines = p.read_text().splitlines()
for needle, pad in needles:
hits = [i for i, line in enumerate(lines, 1) if needle in line]
if not hits:
continue
for i in hits[:3]:
start = max(1, i - pad)
end = min(len(lines), i + pad)
print(f'-- around line {i} for {needle!r} --')
for j in range(start, end + 1):
print(f'{j:4}: {lines[j-1]}')
PYRepository: Agenta-AI/agenta
Length of output: 1539
🏁 Script executed:
python3 - <<'PY'
from pathlib import Path
p = Path('web/scripts/generate-tailwind-tokens.ts')
print(f'exists={p.exists()} lines={len(p.read_text().splitlines()) if p.exists() else "NA"}')
if p.exists():
lines = p.read_text().splitlines()
needles = ['aliasDark', 'legacy-shim', 'return', '===', 'exact', 'resolve', 'token']
hits = []
for i, line in enumerate(lines, 1):
if 'aliasDark' in line or 'legacy' in line or 'resolve' in line:
hits.append(i)
for i in hits[:20]:
start = max(1, i-8)
end = min(len(lines), i+20)
print(f'\n-- around line {i} --')
for j in range(start, end+1):
print(f'{j:4}: {lines[j-1]}')
PYRepository: Agenta-AI/agenta
Length of output: 30343
🏁 Script executed:
python3 - <<'PY'
from pathlib import Path
p = Path('web/scripts/generate-tailwind-tokens.ts')
print(f'exists={p.exists()} lines={len(p.read_text().splitlines()) if p.exists() else "NA"}')
if p.exists():
lines = p.read_text().splitlines()
hits = [i for i, line in enumerate(lines, 1) if 'aliasDark' in line or 'legacy' in line or 'resolve' in line]
for i in hits[:20]:
start = max(1, i-8)
end = min(len(lines), i+20)
print(f'\n-- around line {i} --')
for j in range(start, end+1):
print(f'{j:4}: {lines[j-1]}')
PYRepository: Agenta-AI/agenta
Length of output: 30343
border.default shouldn’t claim to absorb --ag-c-BDC7D1 in dark mode. legacy-shim.ts keeps that token at #2f2f2f, and the generator only aliases shim tokens when they resolve to the exact same dark value as a role. If this legacy token should track border.default.dark, update the shim value to #424242; otherwise remove it from the absorb comment.
The theme generator hand-copied palette's dark seed/link/shadow/elevated/placeholder values into DARK_TOKEN_OVERRIDES, so editing palette.ts silently would not update the generated --ag-* seed CSS vars. Derive the object from palette and reuse it for both the darkAlgorithm seed and buildAntdOverrides, restoring the single-source-of-truth guarantee. Generator output is byte-identical before/after.
The home composer called createAgent with no shared lock, so a rapid double-click could mint and commit two agents. Add an in-hook in-flight latch so every caller (composer, builder cards, setup drawer) is protected regardless of UI-level disabling.
createEphemeralAppFromTemplate had no .catch, so a rejected mint (or a null id) left ready false forever and the onboarding loader spun with no recovery. Add error handling that releases the mint guard, surfaces the failure, and exposes error/retry; OnboardingLoader now renders a Try again affordance instead of hanging.
The vault write was awaited with no catch, so a failed save for this security-sensitive credential gave the user no feedback. Add a catch that shows an error message and keeps the typed value so the user can retry.
OnRamps rendered both cards with no handlers, so first-run users got two no-op buttons. Wire Bring an existing app to the observability page, and render each card only when its handler is present (the unwired demo card is hidden) so there are no dead buttons.
streamIdeBubble's recursive setTimeout chain had no teardown, so unmounting mid-animation kept calling setMessages on a stale closure. Track the timer and clear it on unmount.
|
@coderabbitai review |
✅ Action performedReview finished.
|
On a failed onboarding commit, committingSeed was left set — inert today only because the chat's recovery effect clears the local optimistic turn separately. Clear it at the source so the hook state stays self-consistent and no stale sent turn can be re-surfaced.
What this adds
Agent onboarding for the big-agents branch, all behind env flags so nothing changes for existing users until enabled.
The headline is playground-native onboarding: a first-run user landing on the project-scoped
/playgroundgets a guided surface instead of the generic empty playground. The left panel offers templates, the right panel asks "what do you want to build?". Describing an agent (or picking a template) mints an ephemeral agent, then commits it in place into a real agent you can immediately chat with. No redirect, no separate create screen.Earlier screens in the same arc are also here: the agent Home onboarding (a create-agent composer + template gallery) and the connect-a-model / provider-key wiring the chat needs to actually run.
What's in here
Playground-native onboarding (
NEXT_PUBLIC_AGENT_PLAYGROUND_ONBOARDING)Playgroundvia auseAgentOnboardinghook (mints an ephemeral agent, drives the ephemeral→real transition), so it reuses all the existing playground machinery rather than a parallel mount.useCreateAgentgrew anonCommittedcallback so onboarding swaps the entity and updates the URL withhistory.replaceStateinstead of navigating.AgentChatEmptyStatestate; on submit it shows the description as an optimistic user turn + a loading placeholder, so the switch reads as one continuous chat.TemplatesSection) instead of navigating away.Agent Home onboarding (
NEXT_PUBLIC_AGENT_ONBOARDING)NEXT_PUBLIC_AGENT_TEMPLATE_BUILDER) seed the builder directly.Agent chat run-readiness
Theme palette source of truth (chore, phases 1–2)
palette.tsbecomes the single source for semantic color roles; a generator emits the Tailwind/antd token files. Prep for dark mode.Shared extractions
EntityCardcatalog tile,SectionRailfill mode + exposed drawer primitives, andRichChatInputmade configurable for non-chat inputs.Transitions
The onboarding surfaces enter/leave with consistent motion instead of popping: a
Revealfade+rise for once-on-mount surfaces (hero, gallery, composer), thegrid-rows 0fr↔1frheight idiom for appear/disappear chrome (connect-model banner, queued messages, approval dock), a height-0→auto stagger for the agent config sections, and a settling skeleton that holds the config slot until the real config resolves.Notes
tsc+eslintclean under Node 24. Package changes (@agenta/ui,@agenta/entity-ui) build clean.[agent-onboarding]console diagnostic is still inuseAgentOnboarding/useCreateAgentfor verifying the in-place-vs-redirect branch. It comes out before this leaves draft.What to QA
Enable
NEXT_PUBLIC_AGENT_ONBOARDINGandNEXT_PUBLIC_AGENT_PLAYGROUND_ONBOARDING./appsand/playgroundbehave exactly as before.