Skip to content

feat(frontend): the agent page chrome and the session tab rail move into the packages - #5879

Draft
ardaerzin wants to merge 8 commits into
pkg/navigation-shellfrom
pkg/sessions-tabs
Draft

feat(frontend): the agent page chrome and the session tab rail move into the packages#5879
ardaerzin wants to merge 8 commits into
pkg/navigation-shellfrom
pkg/sessions-tabs

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

Two commits. First the agent page header, build panel and commit modal move into
@agenta/playground-ui. Then the session tab rail, list panel and filters bar move into
@agenta/sessions-ui.

Together they are what /m's tabbed session workspace is built from a few lanes up.
Not run in a browser — static gates only (pnpm lint-fix 24/24, tsc --noEmit clean
for @agenta/shared, ui, entities, entity-ui, settings-ui, oss, ee, mobile).

Stacked on pkg/navigation-shell; review only this lane's diff.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Error Error Aug 11, 2026 5:10am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a836fa9-3884-411b-ac50-9543fcc719b7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added responsive Build/Chat switching and refreshed agent configuration and playground headers.
    • Added reusable agent build and configuration panels with deployment, storage, operations, and commit controls.
    • Added session tab reordering with persistent order, improved session actions, and streamlined tab interactions.
    • Added session filter bars, status chips, agent filters, waiting counts, and responsive filter layouts.
    • Added revision status indicators and improved commit/create workflows.
  • Enhancements
    • Improved responsive layouts, scrolling, animations, and onboarding copy feedback.

Walkthrough

The PR extracts shared playground, session, chat, drive, and layout components into reusable packages. OSS chat, playground, and sessions screens now consume these components and shared state.

Changes

Shared agent playground UI

Layer / File(s) Summary
Shared playground components and contracts
web/packages/agenta-playground-ui/..., web/packages/agenta-chat/src/state/...
Adds reusable agent headers, build panels, revision status, commit controls, and Build/Chat mode state.
Shared session UI and state
web/packages/agenta-sessions-ui/..., web/packages/agenta-sessions/..., web/packages/agenta-ui/...
Adds reusable session tabs, filtering, actions, lists, ordering, waiting counts, and responsive filter-rail layout.
OSS chat and session integration
web/oss/src/components/AgentChatSlice/...
Migrates drive, onboarding, session actions, tab rendering, and tab reordering to shared packages.
OSS playground integration
web/oss/src/components/Playground/...
Migrates playground headers, revision status, commit controls, and storage wiring to shared components.
Sessions page layout integration
web/oss/src/components/pages/sessions/SessionsPage.tsx
Uses FilterRailLayout for the sessions page shell.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the move of agent page chrome and the session tab rail into shared packages.
Description check ✅ Passed The description directly explains the package migrations, workspace purpose, stacked branch, and validation status.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pkg/sessions-tabs

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (9)
web/packages/agenta-playground-ui/src/components/AgentBuildPanel.tsx (1)

40-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce the component comments to one short line.

These comments describe general component behavior. They do not document a bug, race, or ordering constraint.

  • web/packages/agenta-playground-ui/src/components/AgentBuildPanel.tsx#L40-L47: Replace the multi-line component rationale with a short component description.
  • web/packages/agenta-playground-ui/src/components/AgentPageHeader/AgentPageHeader.tsx#L23-L38: Replace the multi-line responsive and CSS rationale with a short component description.
  • web/packages/agenta-playground-ui/src/components/AgentPageHeader/AgentRevisionStatus.tsx#L93-L100: Replace the multi-line shared-UI rationale with a short component description.

As per coding guidelines, keep in-code comments to at most one short line unless they explain a genuinely surprising constraint.

Source: Coding guidelines

web/oss/src/components/Playground/Components/PlaygroundHeader/index.tsx (3)

638-652: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Memoize the leading and actions slot JSX.

Both slots are rebuilt on every PlaygroundHeader render. PlaygroundHeader subscribes to many atoms, including displayAgentName on each keystroke of a rename. Every one of those renders produces new element trees for AgentPageHeader, which defeats any React.memo on the shared header and on the nested EntityPicker subtree.

Wrap both in useMemo with their real dependencies.

♻️ Proposed memoization for the `leading` slot
-    const leading = currentWorkflow?.flags?.is_custom ? (
-        <DropdownMenu>
-            <DropdownMenuTrigger asChild>
-                <Button variant="ghost" size="icon" aria-label="Workflow options">
-                    <DotsThree size={16} />
-                </Button>
-            </DropdownMenuTrigger>
-            <DropdownMenuContent align="start" className="w-[180px]">
-                <DropdownMenuItem onSelect={openModal}>
-                    <PencilSimple size={16} />
-                    Configure workflow
-                </DropdownMenuItem>
-            </DropdownMenuContent>
-        </DropdownMenu>
-    ) : undefined
+    const leading = useMemo(
+        () =>
+            currentWorkflow?.flags?.is_custom ? (
+                <DropdownMenu>
+                    <DropdownMenuTrigger asChild>
+                        <Button variant="ghost" size="icon" aria-label="Workflow options">
+                            <DotsThree size={16} />
+                        </Button>
+                    </DropdownMenuTrigger>
+                    <DropdownMenuContent align="start" className="w-[180px]">
+                        <DropdownMenuItem onSelect={openModal}>
+                            <PencilSimple size={16} />
+                            Configure workflow
+                        </DropdownMenuItem>
+                    </DropdownMenuContent>
+                </DropdownMenu>
+            ) : undefined,
+        [currentWorkflow?.flags?.is_custom, openModal],
+    )

Apply the same treatment to the actions tree, or extract it into a small memoized child component so the evaluator picker does not re-render during a rename.

As per coding guidelines: "Memoize inline arrays containing objects or JSX when passing them as props to avoid unnecessary rerenders."

Also applies to: 678-841

Source: Coding guidelines


673-677: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the AgentRevisionSelector prop to match the value passed.

Line 675 passes rootEntityId, which is a revision id, into a prop named variantId. Inside AgentRevisionSelector that same value is forwarded as revisionId to AgentRevisionStatus. The mismatch invites a future caller to pass a real variant id.

Rename the prop to revisionId in web/oss/src/components/Playground/Components/AgentRevisionSelector/index.tsx and update this call site.


206-206: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Remove the unused BaseContainerProps extension.

There are no additional PlaygroundHeader call sites, but PlaygroundHeaderProps still extends HTMLProps<T> and accepts attributes that the component does not consume after destructuring only className. Restrict the prop type to className and keep key available via React’s standard props instead.

web/packages/agenta-sessions-ui/src/SessionTab.tsx (1)

66-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Spread rest after the component's own handlers, or compose them.

{...rest} comes first, so role, aria-selected, tabIndex, and every handler below it win. A host that passes onClick, onKeyDown, onFocus, onBlur, onMouseEnter, or onMouseLeave loses it without any warning. The file header states that rest props land on the root, so this is a surprising limit on the public contract.

Keep the ARIA and tabIndex defaults fixed, and compose the pointer/keyboard handlers with the incoming ones.

♻️ Proposed composition of host handlers
-export const SessionTab = ({
-    active,
-    label,
-    statusDot,
-    renderActions,
-    onSelect,
-    className,
-    ...rest
-}: SessionTabProps) => {
+export const SessionTab = ({
+    active,
+    label,
+    statusDot,
+    renderActions,
+    onSelect,
+    className,
+    onClick: onClickProp,
+    onKeyDown: onKeyDownProp,
+    onMouseEnter: onMouseEnterProp,
+    onMouseLeave: onMouseLeaveProp,
+    onFocus: onFocusProp,
+    onBlur: onBlurProp,
+    ...rest
+}: SessionTabProps) => {
         <div
             {...rest}
             role="tab"
             aria-selected={active}
             tabIndex={0}
-            onClick={onSelect}
-            onKeyDown={onKeyDown}
-            onMouseEnter={onEnter}
-            onMouseLeave={onLeave}
-            onFocus={onEnter}
-            onBlur={onBlurChip}
+            onClick={(e) => {
+                onClickProp?.(e)
+                onSelect()
+            }}
+            onKeyDown={(e) => {
+                onKeyDownProp?.(e)
+                onKeyDown(e)
+            }}
+            onMouseEnter={(e) => {
+                onMouseEnterProp?.(e)
+                onEnter()
+            }}
+            onMouseLeave={(e) => {
+                onMouseLeaveProp?.(e)
+                onLeave(e)
+            }}
+            onFocus={(e) => {
+                onFocusProp?.(e)
+                onEnter()
+            }}
+            onBlur={(e) => {
+                onBlurProp?.(e)
+                onBlurChip(e)
+            }}
web/packages/agenta-sessions-ui/src/SessionTabRail.tsx (1)

198-219: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Hoist the per-row menuFor and onMenuSelect closures out of the map.

Both props are new function identities for every row on every render. RailTab calls menuFor?.(vm) during render through SessionRowContextMenu, so the menu entries rebuild on each render of each chip. The rail re-renders on every session-list update.

Build one stable menuFor/onMenuSelect pair with useCallback that takes the row id and resolves the index from orderedIds.

web/packages/agenta-sessions/src/state/tabOrder.ts (1)

27-34: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

A hand-arranged session order is lost outside the visible window. setSessionTabOrderAtom replaces the stored id list for a scope, and the only caller supplies just the ids the rail currently renders. useSessionCardList caps that set with limit, so ids arranged earlier vanish from storage; applySessionTabOrder then treats those sessions as unseen and moves them to the front on the next visit.

  • web/packages/agenta-sessions/src/state/tabOrder.ts#L27-L34: merge ids with the existing entry instead of overwriting it. Keep the incoming order for ids present in ids, append the previously stored ids that are absent, and cap the list length so the record cannot grow without bound.
  • web/packages/agenta-sessions-ui/src/SessionTabRail.tsx#L171-L176: keep sending the full visible order, and update the inline comment to state that the atom merges rather than replaces.
♻️ Proposed merge in the setter
+/** Keeps a scope's stored order from growing without bound as sessions come and go. */
+const MAX_ORDERED_IDS = 200
+
 export const setSessionTabOrderAtom = atom(
     null,
     (get, set, {scope, ids}: {scope: string; ids: string[]}) => {
         const projectId = get(projectIdAtom)
         if (!projectId) return
-        set(orderByScopeAtom, {...get(orderByScopeAtom), [scopeKey(projectId, scope)]: ids})
+        const byScope = get(orderByScopeAtom)
+        const key = scopeKey(projectId, scope)
+        const visible = new Set(ids)
+        // The caller only sees a capped window; ids outside it keep their arrangement.
+        const merged = [...ids, ...(byScope[key] ?? []).filter((id) => !visible.has(id))]
+        set(orderByScopeAtom, {...byScope, [key]: merged.slice(0, MAX_ORDERED_IDS)})
     },
 )
web/oss/src/components/AgentChatSlice/state/sessions.ts (1)

255-263: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Dedupe the incoming ids and use a Set for the missing lookup.

ids.filter((id) => openSet.has(id)) keeps every occurrence of a repeated id. If a producer ever emits a duplicated id, the persisted open-ids order stores that duplicate, which yields duplicate React keys in the tab strip and survives reloads through atomWithStorage. next.includes(id) also makes the missing scan O(n·m).

A Set fixes both in the same expression.

♻️ Proposed refactor
 export const reorderSessionsAtomFamily = atomFamily((key: string) =>
     atom(null, (get, set, ids: string[]) => {
         const open = currentOpenIds(get, key)
         const openSet = new Set(open)
-        const next = ids.filter((id) => openSet.has(id))
-        const missing = open.filter((id) => !next.includes(id))
+        const seen = new Set<string>()
+        const next = ids.filter((id) => openSet.has(id) && !seen.has(id) && seen.add(id))
+        const missing = open.filter((id) => !seen.has(id))
         set(openIdsByAppAtom, {...get(openIdsByAppAtom), [key]: [...next, ...missing]})
     }),
 )
web/oss/src/components/AgentChatSlice/components/SessionTagBar.tsx (1)

297-307: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Stabilize the reorder object and prefer tabIds for remeasureKey.

reorder={{ids: tabIds, onReorder: reorderSessions}} builds a new object on every render. Both members are already stable, so SessionTabStrip receives a changed prop identity for no reason, and any effect it keys on reorder re-runs each render.

remeasureKey={sessions} passes the whole session array. Its identity changes whenever an upstream status or title update rebuilds the list, which remeasures more often than membership changes require. tabIds already tracks membership.

♻️ Proposed refactor
     const tabIds = useMemo(() => sessions.map((session) => session.id), [sessions])
+    const reorder = useMemo(
+        () => ({ids: tabIds, onReorder: reorderSessions}),
+        [tabIds, reorderSessions],
+    )
                 extra={extra}
-                remeasureKey={sessions}
-                reorder={{ids: tabIds, onReorder: reorderSessions}}
+                remeasureKey={tabIds}
+                reorder={reorder}

Confirm that SessionTabStrip only needs membership changes for remeasurement before you switch remeasureKey.

As per coding guidelines: "Memoize inline arrays containing objects or JSX when passing them as props to avoid unnecessary rerenders" and "Components should remain focused and decoupled, pass IDs or keys instead of entire data structures".

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ab31028-498e-43f2-ad7b-86f6a3fcf17b

📥 Commits

Reviewing files that changed from the base of the PR and between 7125323 and 7d387ac.

📒 Files selected for processing (47)
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/oss/src/components/AgentChatSlice/assets/markdown.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/lenses/RuntimeLens.tsx
  • web/oss/src/components/AgentChatSlice/components/SessionTagBar.tsx
  • web/oss/src/components/AgentChatSlice/components/ToolActivity.tsx
  • web/oss/src/components/AgentChatSlice/hooks/useOnboardingChat.ts
  • web/oss/src/components/AgentChatSlice/hooks/useSessionActions.tsx
  • web/oss/src/components/AgentChatSlice/state/panelLayout.ts
  • web/oss/src/components/AgentChatSlice/state/sessions.ts
  • web/oss/src/components/Playground/Components/AgentRevisionSelector/index.tsx
  • web/oss/src/components/Playground/Components/Modals/CommitVariantChangesModal/assets/CommitVariantChangesButton/index.tsx
  • web/oss/src/components/Playground/Components/Modals/CommitVariantChangesModal/assets/types.d.ts
  • web/oss/src/components/Playground/Components/PlaygroundHeader/index.tsx
  • web/oss/src/components/Playground/Components/PlaygroundVariantConfig/assets/PlaygroundVariantConfigHeader.tsx
  • web/oss/src/components/Playground/Components/PlaygroundVariantConfig/index.tsx
  • web/oss/src/components/pages/sessions/SessionsPage.tsx
  • web/packages/agenta-chat/src/state/index.ts
  • web/packages/agenta-chat/src/state/panelLayout.ts
  • web/packages/agenta-chat/src/state/sessionEphemera.ts
  • web/packages/agenta-playground-ui/package.json
  • web/packages/agenta-playground-ui/src/components/AgentBuildPanel.tsx
  • web/packages/agenta-playground-ui/src/components/AgentConfigHeader.tsx
  • web/packages/agenta-playground-ui/src/components/AgentPageHeader/AgentPageHeader.tsx
  • web/packages/agenta-playground-ui/src/components/AgentPageHeader/AgentRevisionStatus.tsx
  • web/packages/agenta-playground-ui/src/components/AgentPageHeader/index.ts
  • web/packages/agenta-playground-ui/src/components/CommitVariantChanges/CommitVariantChangesButton.tsx
  • web/packages/agenta-playground-ui/src/components/CommitVariantChanges/CommitVariantChangesModal.tsx
  • web/packages/agenta-playground-ui/src/components/CommitVariantChanges/index.ts
  • web/packages/agenta-playground-ui/src/components/CommitVariantChanges/types.ts
  • web/packages/agenta-playground-ui/src/components/PlaygroundModeSwitch.tsx
  • web/packages/agenta-sessions-ui/package.json
  • web/packages/agenta-sessions-ui/src/SessionCardList.tsx
  • web/packages/agenta-sessions-ui/src/SessionFiltersBar.tsx
  • web/packages/agenta-sessions-ui/src/SessionFiltersPanel.tsx
  • web/packages/agenta-sessions-ui/src/SessionListPanel.tsx
  • web/packages/agenta-sessions-ui/src/SessionTab.tsx
  • web/packages/agenta-sessions-ui/src/SessionTabDragItem.tsx
  • web/packages/agenta-sessions-ui/src/SessionTabRail.tsx
  • web/packages/agenta-sessions-ui/src/SessionTabStrip.tsx
  • web/packages/agenta-sessions-ui/src/controls/SessionFilterControls.tsx
  • web/packages/agenta-sessions-ui/src/index.ts
  • web/packages/agenta-sessions-ui/src/useSessionActions.tsx
  • web/packages/agenta-sessions/src/state/index.ts
  • web/packages/agenta-sessions/src/state/tabOrder.ts
  • web/packages/agenta-sessions/src/state/waitingByAgent.ts
  • web/packages/agenta-ui/src/components/presentational/layout/FilterRailLayout.tsx
  • web/packages/agenta-ui/src/components/presentational/layout/index.tsx
💤 Files with no reviewable changes (1)
  • web/oss/src/components/Playground/Components/Modals/CommitVariantChangesModal/assets/types.d.ts

Comment on lines +743 to +750

{TEMPLATE_STRIP_MODE ? (
<CopiedToast
open={onboardingChat.copiedToastOpen}
text={STRIP_COPY.copiedToast}
onDone={() => onboardingChat.setCopiedToastOpen(false)}
/>
) : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Resolve the onboarding hook used by AgentConversation and inspect its returned members.
set -euo pipefail

fd -t f 'AgentConversation.tsx' | xargs -r rg -n 'useOnboardingChat|copiedToastOpen|setCopiedToastOpen'

fd -t f 'useOnboardingChat.ts' | while IFS= read -r f; do
  echo "== $f =="
  rg -n 'copiedToastOpen|setCopiedToastOpen|return \{' -A 30 "$f"
done

# Any other producer of the copied-toast state?
rg -n 'copiedToastOpen' --glob '*.ts' --glob '*.tsx'

Repository: Agenta-AI/agenta

Length of output: 992


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate CopiedToast definitions/usages and the surrounding AgentConversation copy-toast block.
fd -t f 'CopiedToast.*' .
printf '== CopiedToast usages ==\n'
rg -n 'CopiedToast|copiedToastOpen|setCopiedToastOpen' --glob '*.ts' --glob '*.tsx' .

printf '== AgentConversation relevant lines ==\n'
while IFS= read -r f; do
  printf '--- %s ---\n' "$f"
  wc -l "$f"
  sed -n '50,90p' "$f"
  sed -n '220,265p' "$f"
  sed -n '730,760p' "$f"
done < <(fd -t f 'AgentConversation.tsx' .)

printf '== CopiedToast implementation candidates ==\n'
while IFS= read -r f; do
  printf '--- %s ---\n' "$f"
  wc -l "$f"
  sed -n '1,260p' "$f"
done < <(fd -t f 'CopiedToast.*' .)

Repository: Agenta-AI/agenta

Length of output: 1013


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== locate CopiedToast files ==\n'
fd -t f 'CopiedToast\.(tsx|ts)?' .

printf '== CopiedToast implementation candidates ==\n'
while IFS= read -r f; do
  printf '--- %s ---\n' "$f"
  wc -l "$f"
  sed -n '1,260p' "$f"
done < <(fd -t f 'CopiedToast\.(tsx|ts)?' .)

printf '== AgentConversation relevant lines ==\n'
f=$(fd -t f 'AgentConversation.tsx' . | head -n 1)
printf '--- %s ---\n' "$f"
sed -n '1,100p' "$f"
sed -n '220,265p' "$f"
sed -n '730,760p' "$f"

printf '== useOnboardingChat relevant lines ==\n'
f=$(fd -t f 'useOnboardingChat.ts' . | head -n 1)
printf '--- %s ---\n' "$f"
sed -n '200,270p' "$f"

Repository: Agenta-AI/agenta

Length of output: 282


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== locate CopiedToast files and imports exactly ==\n'
fd -a 'CopiedToast*' . | sed 's#^\./##'
rg -n 'CopiedToast|copiedToastOpen|setCopiedToastOpen' --glob '*.ts' --glob '*.tsx' . | sed -n '1,120p'

printf '\n== useOnboardingChat top of return and matching files ==\n'
use_file=$(fd 'useOnboardingChat.ts' . | head -n 1)
printf 'file=%s\n' "$use_file"
sed -n '200,270p' "$use_file"

printf '\n== AgentConversation relevant lines ==\n'
conv_file=$(fd 'AgentConversation.tsx' . | head -n 1)
printf 'file=%s\n' "$conv_file"
sed -n '1,100p' "$conv_file" | sed -n '35,70p'
sed -n '220,265p' "$conv_file"
sed -n '730,760p' "$conv_file"

printf '\n== StripHome toast block for comparison ==\n'
strip_file=$(fd 'StripHome.tsx' . | head -n 1)
printf 'file=%s\n' "$strip_file"
sed -n '280,310p' "$strip_file"

Repository: Agenta-AI/agenta

Length of output: 9893


🏁 Script executed:

#!/bin/bash
set -euо pipefail

printf '== files matching CopiedToast, excluding current checkout maybe? ==\n'
find web/oss -iname '*copiedtoast*' -print 2>/dev/null | sed -n '1,80p'

printf '\n== exact import path resolution in oss package (TS path aliases config) ==\n'
find web -maxdepth 4 -path '*config*' \( -name '*.json' -o -name '*.js' -o -name '*.mjs' \) -print 2>/dev/null | xargs -r -n 1 sh -c 'echo "--- $0 ---"; rg -n "paths|`@oss`|TemplateStrip|CopiedToast|baseUrl" "$0" || true'

printf '\n== repo-wide CopiedToast string ==\n'
rg -n 'export .*CopiedToast|const CopiedToast|function CopiedToast|CopiedToast|copiedToastOpen|setCopiedToastOpen|openedToast|setOpenedToast|toastOpen|setToastOpen' web/oss/src --glob '*.ts' --glob '*.tsx' --glob '*.json' | sed -n '1,240p'

Repository: Agenta-AI/agenta

Length of output: 4807


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== web/oss tsconfig paths relevant section ==\n'
sed -n '1,80p' web/oss/tsconfig.json

printf '\n== TemplateStrip component directory layout ==\n'
find web/oss/src/components/TemplateStrip -maxdepth 3 -type f | sort | sed -n '1,120p'

printf '\n== CopiedToast imports in AgentConversation/StripHome packages ==\n'
rg -n "from \"`@/oss/components/TemplateStrip/components/CopiedToast`\"|from \"`@ama`|from \"\\.\\.\\/components|from \"\\.\\/components" web/oss/src/components/AgentChatSlice/AgentConversation.tsx web/oss/src/components/pages/agent-home/StripHome.tsx

printf '\n== broader component export/search patterns in web/oss/src/components ==\n'
find web/oss/src/components -maxdepth 3 -type d -name '*TemplateStrip*' -print | sort
rg -n "export (default|const|function).*Copied|CopiedToast" web/oss/src/components --glob '*.ts' --glob '*.tsx' | sed -n '1,200p'

Repository: Agenta-AI/agenta

Length of output: 4595


Address the missing CopiedToast definition and hook state before rendering the toast.

AgentConversation imports CopiedToast from @/oss/components/TemplateStrip/components/CopiedToast, but no matching component/folder exists under that path; CopiedToast is imported by StripHome as well. Use an exported toast component shared by both pages. useOnboardingChat also does not return copiedToastOpen or setCopiedToastOpen, so this render also needs either added hook state or local toast state.

Comment on lines +659 to +672
title={isAgentWorkflow ? undefined : "Playground"}
name={
isAgentWorkflow ? (
renameWorkflowId ? (
<AgentNameInline
workflowId={renameWorkflowId}
name={agentName}
onRenamed={setDisplayAgentName}
/>
) : (
agentName || "Agent"
)
) : undefined
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The header title and name flash the non-agent state while the root revision loads.

isAgentWorkflow is false until the agent signal resolves. During that window title renders "Playground" and name renders undefined. When the signal resolves, the title disappears and the agent name appears.

The code directly below already solves this problem for the action stack. showEvalActions at lines 277-280 waits for a definitive signal and checks !rootEntityQuery.isPending. The title and name slots do not use that guard, so the header text still flashes on an agent reload.

Gate the non-agent title on the same confirmed-prompt signal.

🐛 Proposed fix using the existing confirmed-prompt signal
+    // Same neutral-until-confirmed rule as `showEvalActions`: never render the prompt
+    // title while the agent signal is still pending, or an agent reload flashes "Playground".
+    const showPlaygroundTitle =
+        !isAgentWorkflow &&
+        (earlyAgentState === "non-agent" ||
+            (hasRootNode && !nodeIsAgent && !rootEntityQuery.isPending))
+
     return (
         <>
             <AgentPageHeader
                 className={className}
                 leading={leading}
-                title={isAgentWorkflow ? undefined : "Playground"}
+                title={showPlaygroundTitle ? "Playground" : undefined}
📝 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.

Suggested change
title={isAgentWorkflow ? undefined : "Playground"}
name={
isAgentWorkflow ? (
renameWorkflowId ? (
<AgentNameInline
workflowId={renameWorkflowId}
name={agentName}
onRenamed={setDisplayAgentName}
/>
) : (
agentName || "Agent"
)
) : undefined
}
const showPlaygroundTitle =
!isAgentWorkflow &&
(earlyAgentState === "non-agent" ||
(hasRootNode && !nodeIsAgent && !rootEntityQuery.isPending))
return (
<>
<AgentPageHeader
className={className}
leading={leading}
title={showPlaygroundTitle ? "Playground" : undefined}
name={
isAgentWorkflow ? (
renameWorkflowId ? (
<AgentNameInline
workflowId={renameWorkflowId}
name={agentName}
onRenamed={setDisplayAgentName}
/>
) : (
agentName || "Agent"
)
) : undefined
}

Comment on lines +190 to +206
if (showAgentHeader && !embedded) {
return (
<AgentConfigHeader
revisionId={variantId}
className={className}
deploy={
isEvaluatorEntity ? null : (
<DeployVariantButton
revisionId={variantId}
label="Deploy"
type="default"
size="small"
/>
)
}
menu={<PlaygroundVariantHeaderMenu variantId={variantId} />}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restore the OSS commit host adapter for the agent header.

This branch does not pass appId, onAfterCommit, or onCommitted to AgentConfigHeader. The shared header then renders a commit button without the OSS cache refresh and onboarding callbacks.

Pass the selected appId. Also pass callbacks that clear and refresh the registry and evaluator stores and record playground_committed_change. Otherwise agent commits leave OSS list data stale.

* rail takes its place. This is the single source of truth for the playground's Build/Chat mode —
* the header switch writes it, and the layout, the config pane and the chat panel all read it.
*/
export const chatPanelMaximizedAtom = atom(false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file existence and relevant contents =="
fd -a 'panelLayout\.ts$' . | sed 's#^\./##'
file="$(fd 'panelLayout\.ts$' web | head -n 1)"
if [ -n "${file:-}" ]; then
  echo "-- $file"
  cat -n "$file"
fi

echo "\n== nearby usages of chatPanelMaximizedAtom =="
rg -n "chatPanelMaximizedAtom|chat-panel-maximized|Chat\Wmode|Build\Wmode|maximized" web/packages/agenta-chat web -g 'src/*.{ts,tsx}' -g '!node_modules' || true

echo "\n== package jotai and JSDOM/storage setup =="
fd -p 'package\.json$' web | xargs -r grep -l "jotai" || true
fd -p 'package\.json$' web | xargs -r grep -l "jsdom\|node" || true
fd -e 'ts' -e 'tsx' web | xargs -r grep -l "jotai/utils\|storageKey\|Storage\|setGlobalStorageKey\|JotaiContext" || true

Repository: Agenta-AI/agenta

Length of output: 2455


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Web root package/package-manager context =="
cat -n web/package.json | sed -n '1,220p'
echo "\n== relevant web package.json fields =="
node - <<'JS'
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('web/package.json','utf8'));
console.log(JSON.stringify({
  type: pkg.type,
  dependencies: pkg.dependencies && Object.fromEntries(Object.entries(pkg.dependencies).filter(([k]) => k.startsWith('`@testing-library`') || k.startsWith('jotai') || k === 'jsdom' || k === 'node')),
  devDependencies: pkg.devDependencies && Object.fromEntries(Object.entries(pkg.devDependencies).filter(([k]) => k.startsWith('`@testing-library`') || k.startsWith('jotai') || k === 'jsdom' || k === 'node')),
}, null, 2));
JS

echo "\n== search for JSDOM/browser/globals and Jotai storage setup =="
rg -n "jsdom|jsdom-global|JSDOM|DOMParser|setGlobalStorageKey|Storage\(|jotai/utils\|storageKey|enabledCheck|AtomStoreProvider|JotaiContext" web -g '!node_modules' || true

echo "\n== read exact target file =="
cat -n web/packages/agenta-chat/src/state/panelLayout.ts

echo "\n== agenta-chat package files =="
find web/packages/agenta-chat -maxdepth 3 -type f | sort | sed -n '1,160p'

Repository: Agenta-AI/agenta

Length of output: 17371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== session state that already persists =="
cat -n web/packages/agenta-chat/src/state/sessionMessages.ts | sed -n '1,110p'

echo "\n== agenta-chat package exports/imports =="
cat -n web/packages/agenta-chat/src/state/index.ts | sed -n '1,160p'
cat -n web/packages/agenta-chat/package.json | sed -n '1,160p'

echo "\n== tabLocalStorage definition/usage =="
rg -n -C 4 "function tabLocalStorage|const tabLocalStorage|tabLocalStorage\(\\{" web | sed -n '1,220p' || true

echo "\n== vitest env config =="
cat -n web/packages/agenta-chat/vitest.config.ts | sed -n '1,140p'

echo "\n== focused imports of atomWithStorage =="
rg -n 'from "jotai/utils"|from '"'"'jotai/utils'"'"'|atomWithStorage' agenta-chat web/state web/packages -g 'src/**/*.{ts,tsx}' -g '!node_modules' | sed -n '1,240p' || true

Repository: Agenta-AI/agenta

Length of output: 11698


Persist the selected chat layout mode.

This atom controls the Build/Chat layout and resets after a refresh because it is in-memory. Store it with atomWithStorage using an agenta:-prefixed key, and keep the tab-scoped storage pattern used by nearby persisted atoms.

Proposed fix
-import {atom} from "jotai"
+import {atomWithStorage, createJSONStorage} from "jotai/utils"
+
+const tabLocalStorage = () => {
+    const storage = createJSONStorage<boolean>()
+    delete storage.subscribe
+    return storage
+}
+
+export const chatPanelMaximizedAtom = atomWithStorage<boolean>(
+    "agenta:agent-chat:panel-maximized",
+    false,
+    tabLocalStorage(),
+    {getOnInit: true} as const,
+)
📝 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.

Suggested change
export const chatPanelMaximizedAtom = atom(false)
import {atomWithStorage, createJSONStorage} from "jotai/utils"
const tabLocalStorage = () => {
const storage = createJSONStorage<boolean>()
delete storage.subscribe
return storage
}
export const chatPanelMaximizedAtom = atomWithStorage<boolean>(
"agenta:agent-chat:panel-maximized",
false,
tabLocalStorage(),
{getOnInit: true} as const,
)

Source: Coding guidelines

<section
className={`h-[48px] flex items-center justify-between overflow-hidden ${
embedded ? "grow" : "sticky top-0 z-[10] w-full"
} border-b border-colorBorderSecondary py-2 px-4 bg-[var(--ag-c-FFFFFF)] bg-[image:linear-gradient(var(--ag-colorFillTertiary),var(--ag-colorFillTertiary))] ${

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Replace the legacy raw color token.

--ag-c-FFFFFF is a forbidden raw color token. It also fixes the base layer to white in dark appearance. Use a semantic Tailwind color utility or a supported var(--ag-color*) value.

As per coding guidelines, “Consume theme colors through Ant Design semantic tokens, Tailwind color utilities, or supported var(--ag-color*) variables; do not use raw hex colors or --ag-c-* literals.”

Source: Coding guidelines

Comment on lines +151 to +155
<nav
className={`flex gap-2 overflow-x-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden ${
className ?? ""
}`}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use role="group" with a label instead of <nav>.

This row is a filter control, not site navigation. <nav> registers a navigation landmark in the assistive-technology landmark list, and it has no accessible name here. SessionStatusListControl in this same file already presents the identical status choice with role="group" and aria-label="Filter sessions by status". Match it.

🛠️ Proposed change
-        <nav
+        <div
+            role="group"
+            aria-label="Filter sessions by status"
             className={`flex gap-2 overflow-x-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden ${
                 className ?? ""
             }`}
         >

Close the element with </div> at line 176.

Comment on lines +78 to +90
<button
type="button"
aria-label="Filters"
className="box-border flex h-8 shrink-0 cursor-pointer items-center gap-1.5 rounded-lg border-0 bg-colorFillQuaternary px-2.5 text-sm text-colorTextSecondary"
>
<FunnelIcon size={16} weight={activeCount ? "fill" : "regular"} />
Filters
{activeCount ? (
<span className="rounded bg-colorFillSecondary px-1.5 py-0.5 text-[11px] leading-none text-colorText">
{activeCount}
</span>
) : null}
</button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add visible hover and keyboard-focus states.

The Filters and Clear buttons define base and disabled styles, but they do not define hover or focus-visible styles. Add semantic interaction states for both buttons.

As per coding guidelines, "Implement light and dark appearance and interaction states for every added or changed UI element, and verify both themes."

Also applies to: 117-128

Source: Coding guidelines

Comment on lines +120 to +124
onClick={() => {
setAgentId(null)
setMode(false)
setIncludeArchived(false)
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not clear a hidden agent filter.

When showAgent is false, activeCount does not include agentId. A user who clears Mode or Include then also loses an agent filter that the sheet does not show.

Only call setAgentId(null) when showAgent is true.

Proposed fix
 onClick={() => {
-    setAgentId(null)
+    if (showAgent) setAgentId(null)
     setMode(false)
     setIncludeArchived(false)
 }}
📝 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.

Suggested change
onClick={() => {
setAgentId(null)
setMode(false)
setIncludeArchived(false)
}}
onClick={() => {
if (showAgent) setAgentId(null)
setMode(false)
setIncludeArchived(false)
}}

Comment on lines +55 to +82
const pressTimer = useRef<ReturnType<typeof setTimeout> | null>(null)

const clearPress = useCallback(() => {
if (pressTimer.current) clearTimeout(pressTimer.current)
pressTimer.current = null
}, [])

const onPointerDown = useCallback(
(event: React.PointerEvent<HTMLDivElement>) => {
clearPress()
// A press on the chip's own controls is that control's — dragging out of a rename input
// would steal the text selection, and out of the close button its click.
if (
(event.target as HTMLElement | null)?.closest(
"input, textarea, button, [contenteditable='true']",
)
) {
return
}
if (event.pointerType !== "touch") {
controls.start(event)
return
}
if (!touchDrag) return
pressTimer.current = setTimeout(() => controls.start(event), longPressMs)
},
[clearPress, controls, longPressMs, touchDrag],
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clear pressTimer on unmount.

clearPress runs on pointer up, cancel, leave, and drag end. It does not run on unmount. If the chip unmounts while a touch is still held, no pointer event arrives, the timer fires afterwards, and controls.start(event) runs against a detached item. The rail re-renders from a live session query, so an unmount during a press is possible.

Add an unmount cleanup.

🛡️ Proposed cleanup on unmount
-import {useCallback, useRef, useState, type ReactNode, type Ref} from "react"
+import {useCallback, useEffect, useRef, useState, type ReactNode, type Ref} from "react"
     const clearPress = useCallback(() => {
         if (pressTimer.current) clearTimeout(pressTimer.current)
         pressTimer.current = null
     }, [])
+
+    useEffect(() => clearPress, [clearPress])
📝 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.

Suggested change
const pressTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
const clearPress = useCallback(() => {
if (pressTimer.current) clearTimeout(pressTimer.current)
pressTimer.current = null
}, [])
const onPointerDown = useCallback(
(event: React.PointerEvent<HTMLDivElement>) => {
clearPress()
// A press on the chip's own controls is that control's — dragging out of a rename input
// would steal the text selection, and out of the close button its click.
if (
(event.target as HTMLElement | null)?.closest(
"input, textarea, button, [contenteditable='true']",
)
) {
return
}
if (event.pointerType !== "touch") {
controls.start(event)
return
}
if (!touchDrag) return
pressTimer.current = setTimeout(() => controls.start(event), longPressMs)
},
[clearPress, controls, longPressMs, touchDrag],
)
const pressTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
const clearPress = useCallback(() => {
if (pressTimer.current) clearTimeout(pressTimer.current)
pressTimer.current = null
}, [])
useEffect(() => clearPress, [clearPress])
const onPointerDown = useCallback(
(event: React.PointerEvent<HTMLDivElement>) => {
clearPress()
// A press on the chip's own controls is that control's — dragging out of a rename input
// would steal the text selection, and out of the close button its click.
if (
(event.target as HTMLElement | null)?.closest(
"input, textarea, button, [contenteditable='true']",
)
) {
return
}
if (event.pointerType !== "touch") {
controls.start(event)
return
}
if (!touchDrag) return
pressTimer.current = setTimeout(() => controls.start(event), longPressMs)
},
[clearPress, controls, longPressMs, touchDrag],
)

Comment on lines +39 to +46
return useMemo(() => {
const counts = new Map<string, number>()
for (const row of rowsFromPages(waitingQuery.data?.pages)) {
const appId = sessionOpenTarget(row)?.appId
if (appId) counts.set(appId, (counts.get(appId) ?? 0) + 1)
}
return counts
}, [waitingQuery.data?.pages])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find file and package manifests"
git ls-files | rg '(^|/)running|(^|/)waitingByAgent.ts$|package.json$|pnpm-lock.yaml$' | head -200

echo
echo "Inspect target file"
if [ -f web/packages/agenta-sessions/src/state/waitingByAgent.ts ]; then
  cat -n web/packages/agenta-sessions/src/state/waitingByAgent.ts
fi

echo
echo "Search useSessionList and queries"
rg -n "useSessionList|placeholderData|keepPreviousData|isPlaceholderData|waitingIds|waitingQuery" web/packages -S

Repository: Agenta-AI/agenta

Length of output: 8681


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Target useSessionList"
cat -n web/packages/agenta-sessions/src/state/useSessionList.ts

echo
echo "package version"
node - <<'JS'
const fs = require('fs')
const pkg = JSON.parse(fs.readFileSync('web/packages/agenta-sessions/package.json', 'utf8'))
console.log(pkg.dependencies?.['`@tanstack/react-query`'] ?? pkg.devDependencies?.['`@tanstack/react-query`'] ?? 'not declared in package')
JS

echo
echo "lock version snippets"
rg -n '"`@tanstack/react-query`"|"`@tanstack/react-query`@' web/pnpm-lock.yaml | head -100

Repository: Agenta-AI/agenta

Length of output: 7807


🌐 Web query:

TanStack Query infinite query placeholderData isPlaceholderData keepPreviousData documentation

💡 Result:

In TanStack Query (v5 and later), the keepPreviousData option has been removed in favor of using placeholderData [1]. To achieve the "keep previous data" functionality—where cached data from a previous query remains visible while a new query is fetching—you should now use the placeholderData option combined with the keepPreviousData identity function provided by TanStack Query [2][1]. For useInfiniteQuery, the placeholderData option works similarly to useQuery [3][4]. When the query key changes (for example, when a filter is applied to an infinite list), providing placeholderData: keepPreviousData allows the UI to continue displaying the old infinite data while the new data is being fetched [3][2]. Key points regarding this implementation: - Import keepPreviousData: You must import keepPreviousData from @tanstack/react-query [1]. - Usage: Set placeholderData: keepPreviousData in your useInfiniteQuery hook options [3][4]. - Data Structure: When using placeholderData with useInfiniteQuery, the placeholder data must match the required InfiniteData structure (an object containing pages and pageParams) [5][6]. - isPlaceholderData: When data is being served from the cache via placeholderData, the isPlaceholderData boolean will be true [2][4]. This is useful for indicating to the user that the displayed data is "stale" or from a previous state while a new request is in progress [3][2]. - Migration: If you are migrating from older versions of TanStack Query, the isPreviousData flag has also been replaced by isPlaceholderData [1]. Using placeholderData: keepPreviousData is the recommended, declarative way to maintain a seamless user experience during state transitions in infinite queries [3][1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "All waitingByAgent uses and counts"
rg -n "useWaitingByAgent|waitingByAgent|waitingSessionIds|waitingQuery" web -S

echo
echo "package files mentioning tanstack"
rg -n '"`@tanstack/react-query`"|"\`@tanstack/react-query`"' web/package.json web/packages/agenta-sessions/package.json web/pnpm-lock.yaml || true

echo
echo "Inspect sessionListQueryOptions and queryFn if accessible"
git ls-files | rg 'session.*\.ts$|session.*\.tsx$' | rg '`@agenta/entities`'

Repository: Agenta-AI/agenta

Length of output: 3759


Ignore placeholder rows when calculating waiting counts.

useSessionList keeps prior pages behind placeholderData: keepPreviousData. When waitingIds becomes empty and the query is disabled, waitingQuery.data can still contain old waiting rows, so useWaitingByAgent returns stale counts. If waitingQuery.isPlaceholderData is true, return an empty counts map.

Proposed fix
 return useMemo(() => {
     const counts = new Map<string, number>()
+    if (waitingQuery.isPlaceholderData) return counts
+
     for (const row of rowsFromPages(waitingQuery.data?.pages)) {
         const appId = sessionOpenTarget(row)?.appId
         if (appId) counts.set(appId, (counts.get(appId) ?? 0) + 1)
     }
     return counts
-}, [waitingQuery.data?.pages])
+}, [waitingQuery.data?.pages, waitingQuery.isPlaceholderData])
📝 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.

Suggested change
return useMemo(() => {
const counts = new Map<string, number>()
for (const row of rowsFromPages(waitingQuery.data?.pages)) {
const appId = sessionOpenTarget(row)?.appId
if (appId) counts.set(appId, (counts.get(appId) ?? 0) + 1)
}
return counts
}, [waitingQuery.data?.pages])
return useMemo(() => {
const counts = new Map<string, number>()
if (waitingQuery.isPlaceholderData) return counts
for (const row of rowsFromPages(waitingQuery.data?.pages)) {
const appId = sessionOpenTarget(row)?.appId
if (appId) counts.set(appId, (counts.get(appId) ?? 0) + 1)
}
return counts
}, [waitingQuery.data?.pages, waitingQuery.isPlaceholderData])

…-c-* literals

The config bar's opaque base was `--ag-c-FFFFFF`, a codemod shim that only stops
being white in dark mode because it happens to alias the container role; name that
role (`bg-colorBgContainer`) outright, so the translucent fill still layers over a
real surface in both themes. The agent glyph's `--ag-c-13C2C2` becomes `text-cyan-6`,
the generated antd scale token the desktop's other agent badges already use.
Two ways past it: the custom-child branch ignored `disabled` entirely, and the
standard branch spread `{...props}` AFTER it, so a caller's `disabled` won. Either
let an unchanged persisted variant open the commit modal. One effective value now,
applied after the spread and re-checked inside the open handler, so it holds
whichever branch renders — a caller may tighten the guard, never loosen it.

The modal's `handleSubmit` also closed over a stale `onAfterCommit`: a host that
re-renders the prop while the other deps hold still kept the old closure, so its
cache refresh never ran.
…dies with its chip

`useSessionList` keeps previous data as a placeholder, so when nothing is waiting
the query goes disabled and the last waiting page stood forever — the rail kept
counting sessions that had already been answered. Count only rows the current id
set produced.

`SessionTabDragItem` cleared its long-press timer on pointer up/cancel/leave and
drag end, none of which fire if the chip unmounts mid-press — and the strip
re-renders off a live session query, so that is reachable. The timer then called
`controls.start` for an item no longer in the Reorder group.
With the agent picker hidden, `activeCount` already excluded `agentId` — but Clear
still reset it, so clearing Mode or Include silently widened an agent-scoped list to
every agent. The button's enabled state and its effect now describe the same set.

The status chip strip was a `<nav>`, which registers an unnamed navigation landmark
for something that is not navigation; it becomes the same named `role="group"` its
stacked twin already uses. Filters and Clear gain hover and focus-visible states —
they had base and disabled only, so neither answered the keyboard.
A refresh is not a request to rearrange the window, but the layout mode was a plain
atom, so anyone working in Chat landed back in Build on every reload. It persists on
the same storage the package's other stores use — extracted out of sessionMessages
rather than copied — so a write in one browser tab does not rearrange another. The
value stays global, exactly as it was in memory; the mode belongs to the playground
surface, not to a session. No `getOnInit`: this page server-renders, and reading
localStorage during atom init would break hydration.
…its for a verdict

Moving the agent config bar into the package took its commit button with it, and the
OSS host stopped passing `appId`/`onAfterCommit`/`onCommitted` — so an agent commit
skipped the registry and evaluator cache refresh and the onboarding event, leaving
both lists stale. The adapter those props carry is now a hook, so the button wrapper
and the agent header share one definition instead of the header re-deriving it.

The page header read `isAgentWorkflow`, which is false while agent-ness is merely
UNKNOWN, so the title flashed "Playground" on every agent reload before swapping to
the agent identity. It waits for the same confirmed signal the eval action stack
already waits for.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant