[fix] A deleted young session no longer comes back on the next reconcile - #5830
Conversation
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
|
@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. |
📝 WalkthroughSummary by CodeRabbit
WalkthroughSession 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. ChangesSession deletion lifecycle
Documentation navigation and styling
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
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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
deleteSessionRemotefor deletes regardless ofserverKnown, 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.
Addresses review feedback: the `...(args as [])` spread made the mock look zero-arity and skipped type checking of the call payload.
There was a problem hiding this comment.
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 inbeforeEach(or generating unique scope keys) will make the suite deterministic.
beforeEach(() => {
deleteSessionRemote.mockClear()
})
|
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 // state/projectSessions.ts
const isQueryableScope = (appId: string): boolean => Boolean(appId) && isValidUUID(appId)So for 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. Agreed with the scope call in the PR body — the symptom is different enough (the optimistic One detail I want to explicitly endorse: |
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.
|
Thanks @ardaerzin — this is a better characterisation than mine. Verified point 1 against the code and you are right: 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. |
…ness-audit-ccb282 [fix] Polish docs responsiveness, dark mode, and search UX
|
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. |
afccaab
into
Agenta-AI:release/v0.112.0
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
docs/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx (2)
44-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a single source for the CTA/social class markers.
The four class names are repeated in the
findcalls and in thefilterpredicate. If a class name changes indocusaurus.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 valueThe
dangerouslySetInnerHTMLwarnings are not exploitable here, but the trust boundary deserves a comment.The injected markup comes from
navbar.items[].htmlindocs/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 Docusaurushtmlnavbar 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 valueThe third selector is redundant.
youtube-nocookieembeds use the hostwww.youtube-nocookie.com, which already contains the substringyoutube. 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
📒 Files selected for processing (7)
docs/docusaurus.config.tsdocs/src/components/GoogleColabButton.tsxdocs/src/css/custom.cssdocs/src/pages/roadmap.module.cssdocs/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsxweb/oss/src/components/AgentChatSlice/state/sessions.delete.test.tsweb/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
| .navbar__toggle { | ||
| width: 24px; | ||
| height: 24px; | ||
| } | ||
|
|
||
| .navbar__toggle svg { | ||
| width: 24px; | ||
| height: 24px; | ||
| } |
There was a problem hiding this comment.
📐 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.
| .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; | |
| } |
| /* 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 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.
| /* 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; | |
| } | |
| } |
| /* --- 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; | ||
| } |
There was a problem hiding this comment.
📐 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.
| /* --- 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; | |
| } |
| .mobileSidebarCommunityLabel { | ||
| font-size: 12px; | ||
| font-weight: var(--ifm-font-weight-semibold); | ||
| letter-spacing: 0.08em; | ||
| text-transform: uppercase; | ||
| color: var(--contenttertiary); | ||
| } |
There was a problem hiding this comment.
📐 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))
PYRepository: 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)' docsRepository: 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' docsRepository: 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")
PYRepository: 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.
| .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); | ||
| } |
There was a problem hiding this comment.
📐 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:
- 1: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/color-mix
- 2: https://web-platform-dx.github.io/web-features-explorer/features/color-mix/
- 3: https://www.w3schools.com/cssref/func_color-mix.php
- 4: https://caniuse.com/mdn-css_types_color_color-mix
- 5: https://caniuse.com/wf-color-mix
🏁 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 || trueRepository: 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 || trueRepository: 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*' \) -printRepository: 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*' \) -printRepository: 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.jsonRepository: 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:
- 1: https://algolia-docsearch.mintlify.app/concepts/styling
- 2: https://algolia-docsearch.mintlify.app/api/theme-options
- 3: https://algolia-docsearch.mintlify.app/guides/theming
- 4: https://mintlify.wiki/algolia/docsearch/installation/docusaurus
- 5: https://docsearch.algolia.com/docs/v3/styling
- 6: https://algolia-docsearch.mintlify.app/installation/javascript
🏁 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.jsonRepository: 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.")
PYRepository: 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.")
PYRepository: 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.
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:But that flag lags the durable row. The row is created as soon as the first user message exists —
autoTitleSessionAtomFamilycallssetSessionHeaderfrom an effect inAgentConversation.tsx— whileserverKnownonly 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.
projectSessionsQueryAtomFamilyis gated onisQueryableScope, which requires the scope key to be a real app UUID. The__global__,drawer:<entityId>andonboardingscope keys are not UUIDs, so the query is disabled,reconcileServerSessionsAtomFamilynever fires, andserverKnownis 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:
deleteSessionRemotefor any session, not just aserverKnownone. This is safe:callFernswallows non-abort errors and returnsnull, so a 404 for a session the server never had logs and moves on — no unhandled rejection.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:Two details worth flagging for review:
if (!changed) returnearly-return in the reconciler. The steady state after a failed delete is "server list unchanged", which would otherwise skip the retry forever.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/unarchiveSessionAtomFamilycarry the sameserverKnownguard and have a related-but-distinct symptom (the optimisticarchivedflag 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, comparingmainagainst this branch.main: create a session, send one message, delete it from the rail within ~30s, refresh — the session returns, auto-titled, with its content.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:
expected [ 'young-2' ] to deeply equal []);!existingIds.has(id)guard makes it fail.Full slice suite green:
19 test files, 137 tests passed.prettier,eslint, andtsc --noEmitclean on both files.QA follow-up
drawer:<entityId>scope).Demo
https://www.loom.com/share/7e3497a1086c4ae2b399c1f377f09903
The recording compares
mainagainst this branch: create a session, send one message, delete it from the session-history popover within the pre-reconcile window, then refresh. Onmainthe session returns, auto-titled; on this branch it stays gone.Checklist