Skip to content

[fix] A deleted young session no longer comes back on the next reconcile - #5830

Merged
ashrafchowdury merged 13 commits into
Agenta-AI:release/v0.112.0from
moataz-hjaiji:fix/5543-deleted-session-resurrects
Aug 11, 2026
Merged

[fix] A deleted young session no longer comes back on the next reconcile#5830
ashrafchowdury merged 13 commits into
Agenta-AI:release/v0.112.0from
moataz-hjaiji:fix/5543-deleted-session-resurrects

Conversation

@moataz-hjaiji

@moataz-hjaiji moataz-hjaiji commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #5543 — a session deleted shortly after creation reappears, auto-titled and with its full content, on the next reconcile.

Fixes #5831 — deleting from Session History sends no backend request at all, so the session survives on other clients. Same gate, permanent rather than transient; see the scope note below (thanks @ardaerzin for spotting that these are the same root cause).

Root cause. The rail's delete only propagated to the server for a session the client had marked serverKnown:

if (projectId && target?.serverKnown) void deleteSessionRemote({sessionId: id, projectId})

But that flag lags the durable row. The row is created as soon as the first user message exists — autoTitleSessionAtomFamily calls setSessionHeader from an effect in AgentConversation.tsx — while serverKnown only flips on the next successful reconcile (staleTime: 30s, refetchInterval: 60s). Delete inside that window and the delete stayed local only: the server row survived, and because the id was no longer in the local list, the very next reconcile treated it as a session it had never seen and re-adopted it. That is also why the resurrected session comes back auto-titled — the server had the header all along.

This explains the report's "~69s" observation: the window is not a fixed 60s, it is "until the next reconcile actually runs".

And in some scopes it never runs at all. projectSessionsQueryAtomFamily is gated on isQueryableScope, which requires the scope key to be a real app UUID. The __global__, drawer:<entityId> and onboarding scope keys are not UUIDs, so the query is disabled, reconcileServerSessionsAtomFamily never fires, and serverKnown is never set on anything in those scopes. There the delete was local-only permanently — no DELETE request ever sent, session alive on every other client. That is #5831, and it is the same gate rather than a separate bug.

Fix. Two parts, because firing the request unconditionally is not sufficient on its own:

  1. Fire deleteSessionRemote for any session, not just a serverKnown one. This is safe: callFern swallows non-abort errors and returns null, so a 404 for a session the server never had logs and moves on — no unhandled rejection.
  2. Record the id in a per-scope tombstone set (deletedIdsByAppAtom, persisted alongside the other session atoms). The reconciler refuses to adopt a tombstoned id, and re-fires its delete until the server stops listing it. This closes the two windows an unconditional request alone leaves open:
    • the delete failed (offline / 5xx), so the row outlives it;
    • a server list fetched before the delete landed still carries the row.

Two details worth flagging for review:

  • The tombstone block runs before the existing if (!changed) return early-return in the reconciler. The steady state after a failed delete is "server list unchanged", which would otherwise skip the retry forever.
  • Tombstones prune against the server list, so the set is self-bounding in an app scope: an id the server never had clears on the first reconcile after the delete, and one that is successfully deleted clears as soon as the server stops listing it. In the non-queryable scopes above no reconcile runs, so nothing prunes there — but nothing can re-adopt there either, so the tombstone has nothing to guard and is bounded by how many sessions the user deletes in that scope. Left as-is rather than special-cased; the doc comment on the atom says so explicitly.

I also added one guard beyond the report: an id that is back in local history (a deep link re-adopts by id) drops its tombstone. Without it, a stale tombstone would keep re-deleting a session the user deliberately reopened.

Scope note: archiveSessionAtomFamily / unarchiveSessionAtomFamily carry the same serverKnown guard and have a related-but-distinct symptom (the optimistic archived flag is reverted by the next reconcile rather than the session being resurrected). I left them alone to keep this PR to the reported bug — happy to open a separate issue if you'd like.

Testing

Verified locally

Dev stack via ./hosting/docker-compose/run.sh --oss --dev --web-local, comparing main against this branch.

  • On main: create a session, send one message, delete it from the rail within ~30s, refresh — the session returns, auto-titled, with its content.
  • On this branch: same steps — it stays gone across a refresh, and across a second hard refresh (the tombstone is persisted, and the remote delete actually fires).

Added or updated tests

web/oss/src/components/AgentChatSlice/state/sessions.delete.test.ts — 5 tests using the real store, real atoms, and the real reconciler, with only the network boundary (@agenta/entities/session) stubbed:

I checked these are not vacuous:

  • reverting the fix fails 4 of the 5, including the repro (expected [ 'young-2' ] to deeply equal []);
  • the 5th passes trivially pre-fix, so I mutation-tested it — removing its !existingIds.has(id) guard makes it fail.

Full slice suite green: 19 test files, 137 tests passed. prettier, eslint, and tsc --noEmit clean on both files.

QA follow-up

  • Cross-device: delete a young session on device A, confirm it does not reappear on device B after its next reconcile.
  • Offline delete: delete with the network cut, restore it, confirm the retry lands and the session does not reappear.
  • Multi-scope: confirm the tombstone stays scoped (playground vs. the create/edit drawer's drawer:<entityId> scope).
  • Confirm localStorage growth is a non-issue in practice — pruning is per-reconcile, so a scope that never reconciles (e.g. a non-UUID scope where the query is disabled) retains its tombstones until it does.

Demo

https://www.loom.com/share/7e3497a1086c4ae2b399c1f377f09903

The recording compares main against this branch: create a session, send one message, delete it from the session-history popover within the pre-reconcile window, then refresh. On main the session returns, auto-titled; on this branch it stays gone.

Checklist

  • I have included a video or screen recording for UI changes, or marked Demo as N/A
  • Relevant tests pass locally
  • Relevant linting and formatting pass locally
  • I have signed the CLA, or I will sign it when the bot prompts me

ashrafchowdury and others added 6 commits July 30, 2026 23:12
Audit-driven fixes across the docs site, focused on the hamburger sidebar,
dark mode, and a few content/theme gaps:

- Dark mode: make the GitHub/Slack navbar icons theme-aware (they were the
  same color as the navbar background and thus invisible); give the
  secondary CTA a readable content color in both themes.
- Hamburger sidebar: replace the fragile absolute-positioned layout with a
  swizzled PrimaryMenu that renders nav links in the scrolling list and a
  pinned footer holding side-by-side CTA buttons and a "Community" row with
  bordered GitHub/Slack icons. Group the (borderless) theme toggle and the
  bordered close button as a tight cluster; shrink the header controls,
  search button, logo and hamburger on mobile.
- Search: collapse to a compact square icon button on the mobile navbar.
- Video embeds: force a responsive 16:9 box for doc-page YouTube iframes so
  they no longer render portrait/stretched on phones.
- Pagination: refine the Previous/Next typography to a mature size/weight.
- Roadmap: stack the card title/date/tags on mobile so they no longer
  collide.
- Colab button: resolve the badge image via useBaseUrl so it renders under
  any baseUrl instead of showing a broken image.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neutralize the Algolia DocSearch modal and the /search results page to the
doc's palette, and refine several navbar/CTA details.

Search modal:
- Frosted-glass modal (translucent + backdrop blur, rounded), neutral overlay
  scrim, and neutral dark greys instead of DocSearch's navy backgrounds.
- Remove every lavender/blue accent: placeholder, ⌘K keys, "Clear the query",
  matched-keyword highlight (now semibold, no yellow/blue), Ask AI row,
  selected result, focus, action-button hovers, and the no-results icon.
- Give the "Ask AI" conversation menu a solid opaque surface (it floated
  transparent so page content bled through), and keep sticky section headers
  transparent so they blend with the panel.

Search results page (/search):
- Heading -> h3 size; result titles 16px semibold, no underline; matched text
  neutral semibold (no yellow); "N documents found" and the source/breadcrumb
  text muted; tighter row spacing.

Navbar / buttons:
- Same hover treatment on the desktop CTAs as the mobile footer.
- Close the gap between "Get started" and the social icons; neutralize the
  blue ⌘K keys; consistent, slightly larger mobile header icons.
- Rename the CTAs: "Book a demo" and "Get started".

Back-to-top button:
- Match the secondary product button (bordered outline), standard radius,
  smaller size.

All changes are theme-aware (light + dark). Only the required "Powered by
Algolia" logo keeps its brand color.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Loop the tag-stripping regex until the string is stable so nested or
malformed tags can't survive a single pass. Resolves the CodeQL
"incomplete multi-character sanitization" alert on the mobile sidebar
PrimaryMenu swizzle. Behavior is unchanged for the well-formed navbar
config labels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s page

The /search results input was capped at 60% width above 576px by the
theme's SearchPage CSS module; scope the override under .container so it
outranks the hashed class and spans the full container. Also recolor the
native type="search" clear button, which rendered in the OS accent (blue),
to a neutral X drawn in the doc palette so it themes correctly in light
and dark.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ess-audit-ccb282

# Conflicts:
#	docs/docusaurus.config.dev.ts
The rail's delete only propagated to the server for a `serverKnown`
session, but that flag lags the durable row: the row exists from the
first message, while `serverKnown` flips only on the next successful
reconcile. Deleting inside that window deleted locally ONLY, so the very
next reconcile re-adopted the still-listed row as a brand-new server
session — auto-titled, full content. The delete visibly undid itself.

Fire the remote delete for any session, not just a `serverKnown` one,
and record the id in a per-scope tombstone set. The reconciler refuses to
adopt a tombstoned id and re-fires its delete until the server stops
listing it, which also covers the two windows an unconditional request
alone leaves open: a delete that failed (offline/5xx), and a server list
fetched before the delete landed. Tombstones prune against the server
list, so an id the server never had clears on the next reconcile and the
set cannot grow without bound.

Fixes Agenta-AI#5543
Copilot AI lite review requested due to automatic review settings August 9, 2026 15:49
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

@moataz-hjaiji is attempting to deploy a commit to the agenta projects Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

CLAassistant commented Aug 9, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved session deletion reliability, including before server confirmation.
    • Deleted sessions no longer reappear during synchronization.
    • Failed deletion requests are retried automatically until removal succeeds.
    • Intentionally restored sessions can be adopted again, while unrelated sessions continue syncing normally.
  • Documentation
    • Refined navigation labels, mobile navigation, search, responsive layouts, styling, and video embeds.
    • Improved Google Colab links across documentation deployments.
  • Tests
    • Added comprehensive coverage for deletion, retries, synchronization, restoration, and failure scenarios.

Walkthrough

Session deletion now persists per-scope tombstones, sends remote deletion requests, retries failed deletions during reconciliation, and prevents resurrection. Documentation navigation, search styling, responsive layouts, CTA labels, and asset paths were also updated.

Changes

Session deletion lifecycle

Layer / File(s) Summary
Deletion tombstone state
web/oss/src/components/AgentChatSlice/state/sessions.ts
Session state stores persisted, scope-keyed deletion tombstones.
Deletion request handling
web/oss/src/components/AgentChatSlice/state/sessions.ts, web/oss/src/components/AgentChatSlice/state/sessions.delete.test.ts
Deletion records a tombstone and sends a remote request without checking serverKnown. Failures remain available for retry.
Reconciliation and validation
web/oss/src/components/AgentChatSlice/state/sessions.ts, web/oss/src/components/AgentChatSlice/state/sessions.delete.test.ts
Reconciliation retries pending deletions, clears settled or re-adopted tombstones, skips tombstoned sessions, and adopts unrelated sessions.

Documentation navigation and styling

Layer / File(s) Summary
Mobile navigation structure
docs/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx, docs/src/css/custom.css
The mobile sidebar separates primary navigation from CTA and community links. Link clicks close the sidebar, and external links use external-link attributes.
Documentation presentation and search
docs/src/css/custom.css, docs/src/pages/roadmap.module.css
Responsive styling was added for navigation, DocSearch, videos, pagination cards, and roadmap feature rows.
Navbar labels and asset paths
docs/docusaurus.config.ts, docs/src/components/GoogleColabButton.tsx
Navbar CTA labels were revised, and the Google Colab logo now uses the Docusaurus base URL.

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

Sequence Diagram(s)

sequenceDiagram
  participant SessionStore
  participant RemoteDeleteAPI
  participant SessionServer
  participant Reconciler

  SessionStore->>SessionStore: record deletion tombstone
  SessionStore->>RemoteDeleteAPI: request session deletion
  RemoteDeleteAPI->>SessionServer: delete session
  Reconciler->>SessionServer: list sessions
  SessionServer-->>Reconciler: return server sessions
  Reconciler->>SessionStore: retry retained tombstoned deletions
  Reconciler->>SessionStore: skip tombstoned sessions
  Reconciler->>SessionStore: adopt unrelated sessions
Loading

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The session deletion changes are in scope, but the navbar, sidebar, roadmap, and extensive documentation CSS changes are unrelated to the linked issues. Remove the unrelated documentation and navigation changes, or link them to separate issues or pull requests.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary fix for deleted young sessions reappearing during reconciliation.
Description check ✅ Passed The description directly explains the root cause, fix, tests, and scope for both linked session deletion issues.
Linked Issues check ✅ Passed The changes satisfy issues [#5543] and [#5831] by sending remote deletes, persisting tombstones, blocking resurrection, and retrying deletion.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a session-rail reconciliation bug where deleting a recently-created session could be undone on the next server reconcile because the delete was previously gated on a lagging serverKnown flag.

Changes:

  • Add a persisted per-scope “tombstone” set (deletedIdsByAppAtom) to prevent re-adoption of recently deleted sessions and to drive retry deletes during reconcile.
  • Fire deleteSessionRemote for deletes regardless of serverKnown, and retry deletes from the reconciler until the server no longer lists the session.
  • Add a dedicated vitest suite covering the regression and retry/tombstone behaviors.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
web/oss/src/components/AgentChatSlice/state/sessions.ts Adds tombstone persistence + reconcile behavior to prevent deleted sessions being re-adopted and to retry remote deletes.
web/oss/src/components/AgentChatSlice/state/sessions.delete.test.ts Adds regression + durability tests for delete behavior using real atoms/store with the network boundary mocked.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread web/oss/src/components/AgentChatSlice/state/sessions.ts
Comment thread web/oss/src/components/AgentChatSlice/state/sessions.delete.test.ts Outdated
Addresses review feedback: the `...(args as [])` spread made the mock look
zero-arity and skipped type checking of the call payload.
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. bug report Something isn't working frontend tests labels Aug 9, 2026
Copilot AI review requested due to automatic review settings August 9, 2026 15:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

web/oss/src/components/AgentChatSlice/state/sessions.ts:117

  • Doc comment uses "Ids"; project style elsewhere typically uses the acronym "IDs", and the current phrasing reads a bit awkwardly. Consider updating to "IDs of sessions…" for clarity.
 * Ids the user deleted whose server row may still be listed — a tombstone set, per scope.

web/oss/src/components/AgentChatSlice/state/sessions.delete.test.ts:54

  • These tests exercise atoms backed by atomWithStorage with getOnInit: true, so they can rehydrate prior localStorage state. Because the scopes are fixed strings (e.g. "delete-retry"), reruns in watch mode can become order-dependent/flaky if prior runs left data behind. Clearing the relevant localStorage keys in beforeEach (or generating unique scope keys) will make the suite deterministic.
beforeEach(() => {
    deleteSessionRemote.mockClear()
})

@ardaerzin

Copy link
Copy Markdown
Contributor

Reviewed this against the code — the fix is right, and I want to flag two things for whoever merges it.

1. This also closes #5831, not just #5543.

The PR frames the bug as the window between "durable row exists" and "next reconcile stamps serverKnown". That is the common case, but the gate fails permanently in some scopes. serverKnown is only ever set by reconcileServerSessionsAtomFamily, which only runs when the session-list query is enabled — and that query requires the scope key to be a real app UUID:

// state/projectSessions.ts
const isQueryableScope = (appId: string): boolean => Boolean(appId) && isValidUUID(appId)

So for __global__, drawer:<entityId> (drawerScopeKey) and onboarding (ONBOARDING_SCOPE_KEY), no reconcile ever runs, serverKnown is never set on anything, and delete was local-only forever — not just for a minute. That matches the #5831 report ("no DELETE request is sent", session persists on other clients) better than a timing window does. Worth adding Fixes #5831 alongside Fixes #5543.

Note this also means the tombstone set in those scopes is never pruned (pruning happens in the reconciler, which never runs there). It is bounded by how many sessions a user deletes in a drawer, so not a blocker — but it never self-clears there, unlike in an app scope.

2. archiveSessionAtomFamily / unarchiveSessionAtomFamily still carry the identical gate.

Agreed with the scope call in the PR body — the symptom is different enough (the optimistic archived flag gets reverted by the next reconcile, rather than the session resurrecting) that it deserves its own change. Flagging it here so it does not get lost when this merges; happy to open a follow-up issue.

One detail I want to explicitly endorse: .catch(() => {}) rather than void on the fire-and-forget deletes is correct and not cosmetic. callFern rethrows aborts (if (isAbortError(error)) throw error), so a bare void here would be an unhandled rejection rather than a swallowed failure.

Review feedback: the non-UUID scopes (__global__, drawer:<id>, onboarding)
never run a reconcile, so serverKnown was never set there and the delete
stayed local permanently rather than for one poll cycle (Agenta-AI#5831). Those
scopes also never prune tombstones — harmless, since nothing re-adopts
there, but the comment claimed pruning bounds the set unconditionally.
Copilot AI review requested due to automatic review settings August 9, 2026 22:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@moataz-hjaiji

moataz-hjaiji commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @ardaerzin — this is a better characterisation than mine.

Verified point 1 against the code and you are right: isQueryableScope requires a UUID, and __global__, drawer:<entityId> (drawerScopeKey) and ONBOARDING_SCOPE_KEY are none of them, so those scopes never reconcile, serverKnown is never set, and the delete was local-only permanently rather than for one poll cycle. That describes #5831 far better than a timing window does. Added Fixes #5831 and reworked the root-cause section to lead with the permanent case.

Took the tombstone-pruning note too — my comment claimed pruning bounds the set unconditionally, which only holds in a reconciling scope. Corrected in e4dd360: in the non-queryable scopes nothing prunes, but nothing can re-adopt there either, so the tombstone has nothing to guard and is bounded by how many sessions the user deletes in that scope. I left the behaviour alone rather than special-casing the scope check into this atom, since you called it a non-blocker — say the word if you would rather it skipped tombstoning entirely when the scope is not queryable.

On point 2: opened #5861 for the archive/unarchive gate so it does not get lost. Happy to reframe or close it if you had a different shape in mind.

Copilot AI review requested due to automatic review settings August 10, 2026 10:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Aug 10, 2026
Copilot AI review requested due to automatic review settings August 10, 2026 22:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@ashrafchowdury
ashrafchowdury changed the base branch from main to release/v0.112.0 August 11, 2026 10:37
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@ashrafchowdury
ashrafchowdury merged commit afccaab into Agenta-AI:release/v0.112.0 Aug 11, 2026
16 of 26 checks passed

@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: 5

🧹 Nitpick comments (3)
docs/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx (2)

44-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a single source for the CTA/social class markers.

The four class names are repeated in the find calls and in the filter predicate. If a class name changes in docusaurus.config.ts, both places must change. Extract the names into one constant to keep the two lists in sync.

♻️ Proposed refactor
+const FOOTER_CLASSES = [
+  "nav_secondary_button",
+  "nav_primary_button",
+  "nav_github_icons",
+  "nav_slack_icons",
+] as const;
+
 export default function NavbarMobilePrimaryMenu(): ReactNode {
@@
   const navItems = items.filter(
     (i) =>
       (i as { type?: string }).type !== "search" &&
       (i as { type?: string }).type !== "docsVersionDropdown" &&
-      !hasClass(i, "nav_secondary_button") &&
-      !hasClass(i, "nav_primary_button") &&
-      !hasClass(i, "nav_github_icons") &&
-      !hasClass(i, "nav_slack_icons"),
+      !FOOTER_CLASSES.some((cls) => hasClass(i, cls)),
   );

110-138: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

The dangerouslySetInnerHTML warnings are not exploitable here, but the trust boundary deserves a comment.

The injected markup comes from navbar.items[].html in docs/docusaurus.config.ts. That value is static, build-time, repository-authored SVG, not user or remote input, so the static analysis hint at Lines 121 and 132 does not describe a reachable XSS path. The default Docusaurus html navbar item type renders the same values the same way.

Add a short comment above the injection so a future reader does not wire a dynamic source into the same path. The file header comment describes the class-name contract but not the trust assumption.

📝 Proposed comment
           <div className="mobileSidebarCommunityIcons">
+            {/* The `html` values come from the static navbar config in
+                docusaurus.config.ts. Never feed user or remote content here. */}
             {github && (

Source: Linters/SAST tools

docs/src/css/custom.css (1)

1277-1279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The third selector is redundant.

youtube-nocookie embeds use the host www.youtube-nocookie.com, which already contains the substring youtube. The selector at Line 1277 matches those iframes, so Line 1279 adds no coverage.

♻️ Proposed cleanup
 .theme-doc-markdown iframe[src*="youtube"],
-.theme-doc-markdown iframe[src*="youtu.be"],
-.theme-doc-markdown iframe[src*="youtube-nocookie"] {
+.theme-doc-markdown iframe[src*="youtu.be"] {

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: e3add390-e7dc-4b1a-b590-8a515a4cb2e7

📥 Commits

Reviewing files that changed from the base of the PR and between 79e378a and 083e924.

📒 Files selected for processing (7)
  • docs/docusaurus.config.ts
  • docs/src/components/GoogleColabButton.tsx
  • docs/src/css/custom.css
  • docs/src/pages/roadmap.module.css
  • docs/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx
  • web/oss/src/components/AgentChatSlice/state/sessions.delete.test.ts
  • web/oss/src/components/AgentChatSlice/state/sessions.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/oss/src/components/AgentChatSlice/state/sessions.ts
  • web/oss/src/components/AgentChatSlice/state/sessions.delete.test.ts

Comment thread docs/src/css/custom.css
Comment on lines +586 to +594
.navbar__toggle {
width: 24px;
height: 24px;
}

.navbar__toggle svg {
width: 24px;
height: 24px;
}

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

Increase the hamburger tap target.

.navbar__toggle is constrained to exactly 24x24 px. That is the WCAG 2.2 target-size minimum, with no margin for error on the primary mobile navigation control. If Infima applies box-sizing: border-box, the button padding is absorbed into the 24px, so the hit area equals the icon size.

Keep the 24px icon but give the button a larger hit area.

♿ Proposed change
   .navbar__toggle {
-    width: 24px;
-    height: 24px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 36px;
+    height: 36px;
+    padding: 0;
   }

   .navbar__toggle svg {
     width: 24px;
     height: 24px;
   }
📝 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
.navbar__toggle {
width: 24px;
height: 24px;
}
.navbar__toggle svg {
width: 24px;
height: 24px;
}
.navbar__toggle {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
}
.navbar__toggle svg {
width: 24px;
height: 24px;
}

Comment thread docs/src/css/custom.css
Comment on lines +709 to +716
/* Hide the desktop-navbar social icons; on the hamburger breakpoint they are
surfaced inside the sidebar footer instead. */
@media (max-width: 995px) {
.navbar__item:has(.nav_github_icons),
.navbar__item:has(.nav_slack_icons) {
display: none;
}
}

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

Align this breakpoint with the 996px breakpoint used elsewhere.

This rule hides the navbar social icons below 996px, but the mobile navbar rules at Line 570 and the mobile DocSearch rules at Line 1044 use max-width: 996px. Docusaurus switches to the hamburger sidebar at 996px as well. At exactly 996px the sidebar footer shows the GitHub and Slack links while the navbar still shows them, so both appear at once.

🐛 Proposed fix
-@media (max-width: 995px) {
+@media (max-width: 996px) {
   .navbar__item:has(.nav_github_icons),
   .navbar__item:has(.nav_slack_icons) {
     display: none;
   }
 }
📝 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
/* Hide the desktop-navbar social icons; on the hamburger breakpoint they are
surfaced inside the sidebar footer instead. */
@media (max-width: 995px) {
.navbar__item:has(.nav_github_icons),
.navbar__item:has(.nav_slack_icons) {
display: none;
}
}
/* Hide the desktop-navbar social icons; on the hamburger breakpoint they are
surfaced inside the sidebar footer instead. */
`@media` (max-width: 996px) {
.navbar__item:has(.nav_github_icons),
.navbar__item:has(.nav_slack_icons) {
display: none;
}
}

Comment thread docs/src/css/custom.css
Comment on lines +765 to +782
/* --- Body: pin the footer to the bottom of the primary panel only --- */
.navbar-sidebar__item:has(.mobileSidebarFooter) {
display: flex;
flex-direction: column;
overflow-y: auto;
}

.navbar-sidebar__item:has(.mobileSidebarFooter) > .menu__list {
flex: 0 0 auto;
}

.mobileSidebarFooter {
margin-top: auto;
/* The primary panel has asymmetric padding (0 left, 8px right); cancel the
right side and pad symmetrically so the footer aligns on both edges. */
margin-right: -8px;
padding: 16px 12px 14px;
}

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

The footer is pinned to the content bottom, not the viewport bottom.

The comment at Line 765 and the component doc comment both state that the footer is pinned to the bottom of the screen. The container uses overflow-y: auto, so when the nav list is taller than the panel the footer scrolls out of view with the list. margin-top: auto only pushes the footer down when the content is shorter than the container.

With the six configured nav links this does not overflow on a typical phone in portrait. It can overflow in landscape or at large browser font sizes, and the CTA buttons then become reachable only by scrolling.

If you want a truly pinned footer, scroll the list and keep the footer outside the scroll area.

♻️ Proposed change
 .navbar-sidebar__item:has(.mobileSidebarFooter) {
   display: flex;
   flex-direction: column;
-  overflow-y: auto;
+  overflow: hidden;
 }

 .navbar-sidebar__item:has(.mobileSidebarFooter) > .menu__list {
-  flex: 0 0 auto;
+  flex: 1 1 auto;
+  overflow-y: auto;
 }

Otherwise, reword the comments to say the footer sits below the nav list.

📝 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
/* --- Body: pin the footer to the bottom of the primary panel only --- */
.navbar-sidebar__item:has(.mobileSidebarFooter) {
display: flex;
flex-direction: column;
overflow-y: auto;
}
.navbar-sidebar__item:has(.mobileSidebarFooter) > .menu__list {
flex: 0 0 auto;
}
.mobileSidebarFooter {
margin-top: auto;
/* The primary panel has asymmetric padding (0 left, 8px right); cancel the
right side and pad symmetrically so the footer aligns on both edges. */
margin-right: -8px;
padding: 16px 12px 14px;
}
/* --- Body: pin the footer to the bottom of the primary panel only --- */
.navbar-sidebar__item:has(.mobileSidebarFooter) {
display: flex;
flex-direction: column;
overflow: hidden;
}
.navbar-sidebar__item:has(.mobileSidebarFooter) > .menu__list {
flex: 1 1 auto;
overflow-y: auto;
}
.mobileSidebarFooter {
margin-top: auto;
/* The primary panel has asymmetric padding (0 left, 8px right); cancel the
right side and pad symmetrically so the footer aligns on both edges. */
margin-right: -8px;
padding: 16px 12px 14px;
}

Comment thread docs/src/css/custom.css
Comment on lines +833 to +839
.mobileSidebarCommunityLabel {
font-size: 12px;
font-weight: var(--ifm-font-weight-semibold);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--contenttertiary);
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Extract the palette values used by the new sidebar footer and compute contrast ratios.
set -euo pipefail

rg -n -- '--contenttertiary|--contentsecondary|--contentprimary|--bgdefault|--bgelevated2|--borderdefault|--borderlight' docs/src/css/custom.css | head -60

python3 - <<'PY'
def lin(c):
    c = c / 255
    return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4

def lum(rgb):
    r, g, b = (lin(v) for v in rgb)
    return 0.2126 * r + 0.7152 * g + 0.0722 * b

def ratio(a, b):
    la, lb = lum(a), lum(b)
    hi, lo = max(la, lb), min(la, lb)
    return (hi + 0.05) / (lo + 0.05)

print("Replace the tuples below with the resolved --contenttertiary and background values.")
print("example light:", round(ratio((0x8c, 0x8c, 0x8c), (0xff, 0xff, 0xff)), 2))
PY

Repository: Agenta-AI/agenta

Length of output: 3205


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- palette and theme definitions ---'
sed -n '1,180p' docs/src/css/custom.css
printf '%s\n' '--- community label context ---'
sed -n '780,875p' docs/src/css/custom.css
printf '%s\n' '--- grey variable definitions ---'
rg -n --glob '*.css' --glob '*.scss' --glob '*.sass' --glob '*.js' --glob '*.ts' --glob '*.tsx' -- '--grey(10|20|30|40|50|60|70|80|90)' docs

Repository: Agenta-AI/agenta

Length of output: 10115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- mobile footer markup and related backgrounds ---'
rg -n -C 8 --glob '*.{js,jsx,ts,tsx,css,scss}' 'mobileSidebarFooter|mobileSidebarCommunity|navbar-sidebar|sidebar' docs/src docs | head -240

printf '%s\n' '--- explicit sidebar background declarations ---'
rg -n -C 3 --glob '*.css' --glob '*.scss' 'navbar-sidebar|sidebar.*background|background.*sidebar|ifm-background-surface-color|ifm-navbar-background-color' docs

Repository: Agenta-AI/agenta

Length of output: 22296


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
def channel(value):
    value /= 255
    return value / 12.92 if value <= 0.04045 else ((value + 0.055) / 1.055) ** 2.4

def luminance(color):
    rgb = [int(color[i:i + 2], 16) for i in (1, 3, 5)]
    return sum(weight * channel(value) for weight, value in zip((0.2126, 0.7152, 0.0722), rgb))

def contrast(first, second):
    high, low = sorted((luminance(first), luminance(second)), reverse=True)
    return (high + 0.05) / (low + 0.05)

cases = {
    "light": ("`#8e8784`", "`#ffffff`"),
    "dark": ("`#b5ada8`", "`#282726`"),
}
for theme, (foreground, background) in cases.items():
    print(f"{theme}: {foreground} on {background} = {contrast(foreground, background):.2f}:1")
PY

Repository: Agenta-AI/agenta

Length of output: 223


Increase the light-theme label contrast.

#8e8784 on #fff provides only 3.53:1 contrast, below the 4.5:1 WCAG AA requirement for 12px text. The dark-theme ratio is 6.75:1.

Comment thread docs/src/css/custom.css
Comment on lines +895 to +907
.DocSearch-Modal {
border-radius: 8px !important;
/* 92% of the theme background so busy page content stays obscured behind the
muted text; color-mix keeps it linked to the palette in both themes. */
background-color: color-mix(
in srgb,
var(--bgdefault) 92%,
transparent
) !important;
-webkit-backdrop-filter: blur(24px) saturate(180%);
backdrop-filter: blur(24px) saturate(180%);
border: 1px solid rgba(0, 0, 0, 0.06);
}

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

🧩 Analysis chain

🌐 Web query:

Which browser versions support the CSS color-mix() function?

💡 Result:

The CSS color-mix function is widely supported across modern web browsers, having reached broad compatibility as of May 2023 [1]. The minimum browser versions required to support this function are as follows: - Google Chrome: Version 111 [2][3] - Microsoft Edge: Version 111 [2][3] - Mozilla Firefox: Version 113 [2][3] - Apple Safari: Version 16.2 [2][3] These versions apply to both desktop and corresponding mobile browser variants (e.g., Chrome for Android, Firefox for Android, and Safari on iOS) [2][4]. Legacy versions of these browsers do not support the function [5][4].

Citations:


🏁 Script executed:

#!/bin/bash
printf '%s\n' '--- target CSS ---'
cat -n docs/src/css/custom.css | sed -n '875,965p'
printf '%s\n' '--- relevant declarations and variables ---'
rg -n -C 3 -- '--docsearch-modal-background|--bgdefault|DocSearch-Modal|color-mix' docs/src/css docs
printf '%s\n' '--- repository browser-support guidance ---'
rg -n -i -C 2 'browser support|browserslist|color-mix|Safari|Firefox|Chrome|Edge' package.json .browserslistrc browserslist* docs 2>/dev/null || true

Repository: Agenta-AI/agenta

Length of output: 50373


🏁 Script executed:

#!/bin/bash
cat -n docs/src/css/custom.css | sed -n '875,965p'
rg -n -C 3 -- '--docsearch-modal-background|--bgdefault|DocSearch-Modal|color-mix' docs/src/css docs
rg -n -i -C 2 'browser support|browserslist|color-mix|Safari|Firefox|Chrome|Edge' package.json .browserslistrc browserslist* docs 2>/dev/null || true

Repository: Agenta-AI/agenta

Length of output: 50375


🏁 Script executed:

#!/bin/bash
printf '%s\n' '--- target CSS ---'
sed -n '885,960p' docs/src/css/custom.css
printf '%s\n' '--- declarations in target file ---'
rg -n -C 4 -- '--docsearch-modal-background|--bgdefault|DocSearch-Modal|color-mix' docs/src/css/custom.css
printf '%s\n' '--- browser support configuration files ---'
find . -maxdepth 3 -type f \( -name 'package.json' -o -name '.browserslistrc' -o -name 'browserslist' -o -name '*browser*support*' \) -print

Repository: Agenta-AI/agenta

Length of output: 6697


🏁 Script executed:

#!/bin/bash
sed -n '885,960p' docs/src/css/custom.css
rg -n -C 4 -- '--docsearch-modal-background|--bgdefault|DocSearch-Modal|color-mix' docs/src/css/custom.css
find . -maxdepth 3 -type f \( -name 'package.json' -o -name '.browserslistrc' -o -name 'browserslist' -o -name '*browser*support*' \) -print

Repository: Agenta-AI/agenta

Length of output: 6598


🏁 Script executed:

#!/bin/bash
printf '%s\n' '--- local DocSearch references ---'
rg -n -C 3 --hidden \
  -g '!docs/docs/**' -g '!**/node_modules/**' -g '!**/.git/**' \
  'docsearch-modal-background|DocSearch-Modal|`@docsearch/css`|algolia' .
printf '%s\n' '--- docs package metadata ---'
cat docs/package.json
printf '%s\n' '--- CSS imports ---'
rg -n -C 3 --hidden -g '!docs/docs/**' -g '!**/node_modules/**' \
  'custom\.css|docsearch' docs/src docs/package.json

Repository: Agenta-AI/agenta

Length of output: 50372


🌐 Web query:

Algolia DocSearch CSS source --docsearch-modal-background .DocSearch-Modal background

💡 Result:

To customize the Algolia DocSearch modal background color, you use the CSS custom property (variable) --docsearch-modal-background [1][2]. You can override this variable within your CSS by targeting the:root selector or any other scope where your CSS is applied [1][3]. For example::root { --docsearch-modal-background: #ffffff; } Because DocSearch supports light and dark themes, you should typically define this variable for both modes if your site uses them [2][4]. A common pattern for implementing this with a dark theme toggle is as follows [3][4]::root { /* Light mode / --docsearch-modal-background: #f5f6f7; } [data-theme='dark'] { / Dark mode */ --docsearch-modal-background: #15172a; } DocSearch uses several other CSS variables to control the appearance of the modal, such as --docsearch-container-background (for the overlay covering the page) and --docsearch-modal-shadow [1][2][3]. You can find these variables defined in the @docsearch/css package if you are using a package manager, or you can override them directly in your stylesheet [5][1][6].

Citations:


🏁 Script executed:

#!/bin/bash
rg -n -C 3 --hidden -g '!docs/docs/**' -g '!**/node_modules/**' -g '!**/.git/**' \
  'docsearch-modal-background|DocSearch-Modal|`@docsearch/css`|algolia' .
cat docs/package.json
rg -n -C 3 --hidden -g '!docs/docs/**' -g '!**/node_modules/**' \
  'custom\.css|docsearch' docs/src docs/package.json

Repository: Agenta-AI/agenta

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -eu
url='https://unpkg.com/@docsearch/css@4.6.3/dist/style.css'
css="$(curl -fsSL "$url")"
printf '%s\n' '--- upstream modal rule ---'
printf '%s' "$css" | tr '}' '\n' | rg -n -C 2 'DocSearch-Modal|docsearch-modal-background' | head -n 80
printf '%s\n' '--- local fallback semantics ---'
python3 - <<'PY'
from pathlib import Path
text = Path("docs/src/css/custom.css").read_text()
start = text.index(".DocSearch-Modal {")
end = text.index("}", start) + 1
rule = text[start:end]
print(rule)
assert "background-color: color-mix(" in rule
assert "background-color: var(--bgdefault) !important;" not in rule
print("Current rule has no solid fallback.")
PY

Repository: Agenta-AI/agenta

Length of output: 8496


🏁 Script executed:

#!/bin/bash
set -eu
url='https://unpkg.com/@docsearch/css@4.6.3/dist/style.css'
css="$(curl -fsSL "$url")"
printf '%s\n' '--- upstream modal rule ---'
printf '%s' "$css" | tr '}' '\n' | rg -n -C 2 'DocSearch-Modal|docsearch-modal-background' | head -n 80
python3 - <<'PY'
from pathlib import Path
text = Path("docs/src/css/custom.css").read_text()
start = text.index(".DocSearch-Modal {")
end = text.index("}", start) + 1
rule = text[start:end]
print(rule)
assert "background-color: color-mix(" in rule
assert "background-color: var(--bgdefault) !important;" not in rule
print("Current rule has no solid fallback.")
PY

Repository: Agenta-AI/agenta

Length of output: 8463


Add a solid fallback before color-mix().

If the browser does not support color-mix(), the declaration is ignored. @docsearch/css then uses the inherited transparent --docsearch-modal-background, which exposes page content through the search modal. Add background-color: var(--bgdefault) !important; before the color-mix() declaration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug report Something isn't working frontend lgtm This PR has been approved by a maintainer size:XL This PR changes 500-999 lines, ignoring generated files. tests

Projects

None yet

7 participants