Skip to content

fix: stabilize web ui startup and reduce usage load#83

Merged
ymkiux merged 17 commits intomainfrom
fix/web-ui-startup-and-usage-performance
Apr 11, 2026
Merged

fix: stabilize web ui startup and reduce usage load#83
ymkiux merged 17 commits intomainfrom
fix/web-ui-startup-and-usage-performance

Conversation

@ymkiux
Copy link
Copy Markdown
Collaborator

@ymkiux ymkiux commented Apr 10, 2026

Summary

  • switch Usage aggregation to a lightweight session listing path
  • delay browser auto-open until the local Web UI API responds successfully
  • load the bundled Vue production build and defer the initial frontend refresh
  • tighten session preview spacing and simplify non-functional UI copy
  • add regression coverage for startup readiness, usage loading, and layout updates

Tests

  • npm run test:unit -- session-usage session-tab-switch-performance config-tabs-ui
  • npm run test:unit -- web-run-host web-ui-startup-init web-ui-source-bundle

Summary by CodeRabbit

  • New Features

    • Fast tail-based session previews, preview-mode detail responses, deferred initial UI load, and incremental viewport-driven session-list rendering; session usage now includes totalTokens and contextWindow.
  • Reliability

    • UI readiness health-check before auto-opening the browser; preview responses may be clipped and report non-exact totalMessages when appropriate.
  • Performance

    • Lightweight browse-mode listings with inventory-backed in-memory caches to speed session discovery and reduce IO.
  • UI/UX

    • Production Vue runtime, adjusted layout/spacing, concise copy, and capped preview message lengths.
  • Tests

    • Expanded unit and e2e coverage for browsing, previews, startup readiness, assets, timeline, and large-message handling.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

Adds lightweight session inventory/browse with cached lookups and a bounded tail-based JSONL preview extractor; makes session-list rendering incremental and preview-aware; defers Web UI initial load and probes UI readiness before auto-opening the browser; updates UI assets, templates, styles, and extensive unit/e2e tests.

Changes

Cohort / File(s) Summary
CLI: inventory, browse & preview
cli.js
Adds inventory scanning/listing (listSessionInventoryBySource, listSessionBrowse), browseLightweight modes, inventory & file-lookup caches, cache invalidation, and browse-aware session listing/usage APIs.
Fast tail preview & parsing
cli.js (extract...Fast, tail helpers, parseCodex/Claude...)
Implements bounded tail-based JSONL preview extraction, configurable summary/title read-byte budgets, usage-parsing for totalTokens/contextWindow, and preview flag support with fallback full-read parsing.
Session path resolution & usage
cli.js (resolveSessionFilePath, listSessionPaths, listSessionUsage)
Prefers inventory-derived file-path lookup cache, normalizes listing/limit/forceRefresh behaviors, strips internal __messageCountExact from browse results.
Web UI lifecycle & rendering
web-ui/app.js, web-ui/modules/..., web-ui/session-helpers.mjs, web-ui/logic.sessions.mjs
Defers initial load via RAF+timeout or window load; adds viewport-driven incremental visibleSessionsList, priming/expand/scroll handling, hydration-ticketing, guarded timeline sync, native-dialog hooks, and reduces fast list default limit (40→20).
Templates, copy & assets
web-ui/index.html, web-ui/partials/index/*
Switches to production Vue runtime (/res/vue.global.prod.js) and updates multiple Chinese UI copy strings across header, market, skills, docs, usage, and sessions.
Styles
web-ui/styles/navigation-panels.css, web-ui/styles/sessions-preview.css
Updates .content-wrapper sizing to width: min(100%, 1480px)/max-width: none, sets .mode-content width, and tightens session-preview padding/timeline offsets.
Session browser UI template hooks
web-ui/partials/index/panel-sessions.html
Renders visibleSessionsList, attaches ref via setSessionListRef, adds @scroll.passive="onSessionListScroll", and triggers loadSessionPathOptions on focus.
Timeline & offset logic
web-ui/modules/app.methods.session-timeline.mjs
Adds hasRenderableSessionTimeline(), caches header offset, early-exits when not renderable, and removes ResizeObserver timing sync.
Tests: unit & e2e additions/updates
tests/unit/*.test.mjs, tests/e2e/*.js, new tests/unit/session-usage-backend.test.mjs, tests/e2e/test-web-ui-session-browser.js, tests/unit/session-detail-preview-fast.test.mjs
Adds many unit/e2e tests for inventory/browse, caching, fast tail preview, health-check probe retry, deferred initial-load scheduling, preview semantics, layout CSS, and huge-message fixtures; updates runners to include new tests.
Runners & fixtures
tests/unit/run.mjs, tests/e2e/run.js, tests/e2e/test-setup.js
Includes new unit/e2e modules in runners and introduces large-message JSONL fixtures used to validate preview vs full-read recovery.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI process
    participant Web as Local Web Server (/api)
    participant OS as Platform (exec)
    participant FS as Filesystem

    CLI->>Web: POST /api { action: "health-check", params: {} }
    alt 200 OK
        Web-->>CLI: 200 OK
        CLI->>OS: exec(open browser URL)
    else non-200 / timeout
        CLI-->>CLI: schedule retry (setTimeout ms=150)
        CLI->>Web: POST /api { action: "health-check" } (retry)
        alt eventually 200
            Web-->>CLI: 200 OK
            CLI->>OS: exec(open browser URL)
        else exhausted
            CLI-->>OS: skip open
        end
    end

    Note right of CLI: session browse & preview flow
    CLI->>FS: listSessionInventoryBySource(browseLightweight)
    FS-->>CLI: inventory (cached or scanned)
    CLI->>FS: resolveSessionFilePath(sessionId) (lookup-cache preferred)
    FS-->>CLI: file path
    CLI->>FS: extractSessionDetailPreviewFromFileFast(path, preview:true, budgets)
    FS-->>CLI: preview (tail-based, clipped/total info)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • SurviveM

Poem

"我是小兔敲键忙,尾段预览又轻巧,
列表渐进不贪多,先探服务再开窗,
缓存守护文件路,界面文案更俐落,
兔儿欢跃测已全,代码安稳去打包。 🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main changes: stabilizing web UI startup through delayed browser auto-open and reduced Usage panel load by switching to lightweight session-listing path.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/web-ui-startup-and-usage-performance

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
web-ui/app.js (1)

414-447: Extract the startup defer delay into a named constant.

Line 423 and Line 431 both use 120 directly. A local constant keeps this easier to tune and aligns the two branches.

♻️ Proposed refactor
+            const INITIAL_LOAD_DELAY_MS = 120;
             const runInitialLoad = () => {
                 const triggerLoad = () => {
                     this._initialLoadTimer = 0;
                     void this.loadAll();
                 };
                 if (typeof requestAnimationFrame === 'function') {
                     this._initialLoadRafId = requestAnimationFrame(() => {
                         this._initialLoadRafId = 0;
                         if (typeof setTimeout === 'function') {
-                            this._initialLoadTimer = setTimeout(triggerLoad, 120);
+                            this._initialLoadTimer = setTimeout(triggerLoad, INITIAL_LOAD_DELAY_MS);
                             return;
                         }
                         triggerLoad();
                     });
                     return;
                 }
                 if (typeof setTimeout === 'function') {
-                    this._initialLoadTimer = setTimeout(triggerLoad, 120);
+                    this._initialLoadTimer = setTimeout(triggerLoad, INITIAL_LOAD_DELAY_MS);
                     return;
                 }
                 triggerLoad();
             };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/app.js` around lines 414 - 447, Extract the hard-coded delay used for
initial startup deferral into a named constant (e.g., const INITIAL_LOAD_DELAY =
120) and use that constant instead of the literal 120 in both places inside
runInitialLoad where setTimeout is called (referencing the triggerLoad
invocation and this._initialLoadTimer assignment within runInitialLoad). Ensure
the constant is declared in the same scope as runInitialLoad so both branches
(requestAnimationFrame and fallback setTimeout) reference the same value and
update any comments if present.
tests/unit/session-usage-backend.test.mjs (1)

82-115: Consider adding one edge-case test for non-array upstream responses.

You’re already validating normalization well; adding this case will lock in the [] fallback behavior and prevent regressions there.

Suggested test addition
+test('listSessionUsage returns [] when listAllSessions returns non-array data', async () => {
+    const listSessionUsage = instantiateListSessionUsage({
+        MAX_SESSION_LIST_SIZE: 300,
+        listAllSessions: async () => ({ unexpected: true }),
+        listAllSessionsData: async () => {
+            throw new Error('should not call listAllSessionsData');
+        }
+    });
+
+    const result = await listSessionUsage({ source: 'all', limit: 10, forceRefresh: false });
+    assert.deepStrictEqual(result, []);
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/session-usage-backend.test.mjs` around lines 82 - 115, Add an
edge-case unit test to verify that listSessionUsage (created via
instantiateListSessionUsage) handles non-array upstream responses by falling
back to an empty array; specifically, add a test that stubs listAllSessions or
listAllSessionsData to return a non-array value (e.g., null or an object) and
assert that the function still calls listAllSessions with normalized params and
does not throw, preserving the existing [] fallback behavior in
listSessionUsage/listAllSessionsData handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli.js`:
- Around line 10779-10809: The readiness probe in the client sends a POST to
/api with action:'status' and causes the server-side status handler (which calls
consumeInitNotice()) to clear initNotice before the UI reads it; to fix, modify
the probe or server action handling so the probe is side-effect free: either
change the probe payload/action or add a branch in the /api action switch (the
server-side handler that inspects action === 'status' and calls
consumeInitNotice()) to return status without calling consumeInitNotice() for
requests coming from the readiness probe (identify probe via a distinct action
value or an extra header/query flag), and update the probe code
(payload/requestOptions in the client: the JSON payload variable and
http.request usage like req.end(payload, 'utf-8')) to use that distinct,
non-destructive action or header so the frontend’s first real status fetch still
receives the initNotice.
- Around line 10824-10836: The current code builds a shell string using the
user-controlled url and calls exec(command...), which allows shell injection;
replace this with child_process.spawn to pass the URL as an argument (shell:
false) instead of interpolating it. Specifically, stop constructing the string
in the platform branches and instead spawn the platform-specific binary with the
URL as an argument (e.g. on darwin spawn('open', [url]), on Linux
spawn('xdg-open', [url]), and on Windows spawn('cmd', ['/c', 'start', '', url])
or equivalent), handle the spawned process' error/exit events in place of the
exec callback, and ensure you import/require spawn from 'child_process'; update
the code that currently references platform, command, and exec(command, (error)
=> { ... }) accordingly.

In `@web-ui/index.html`:
- Line 9: The script tag in web-ui/index.html currently references the prod
runtime "/res/vue.global.prod.js" which conflicts with the E2E test expectations
in tests/e2e/test-web-ui-assets.js that assert the compiler-enabled
"/res/vue.global.js"; change the <script src="..."> back to "/res/vue.global.js"
to restore the compiler-enabled runtime (or, alternatively, update the
assertions in tests/e2e/test-web-ui-assets.js to accept the prod variant),
ensuring consistency between web-ui/index.html's script src and the test
expectations.

---

Nitpick comments:
In `@tests/unit/session-usage-backend.test.mjs`:
- Around line 82-115: Add an edge-case unit test to verify that listSessionUsage
(created via instantiateListSessionUsage) handles non-array upstream responses
by falling back to an empty array; specifically, add a test that stubs
listAllSessions or listAllSessionsData to return a non-array value (e.g., null
or an object) and assert that the function still calls listAllSessions with
normalized params and does not throw, preserving the existing [] fallback
behavior in listSessionUsage/listAllSessionsData handling.

In `@web-ui/app.js`:
- Around line 414-447: Extract the hard-coded delay used for initial startup
deferral into a named constant (e.g., const INITIAL_LOAD_DELAY = 120) and use
that constant instead of the literal 120 in both places inside runInitialLoad
where setTimeout is called (referencing the triggerLoad invocation and
this._initialLoadTimer assignment within runInitialLoad). Ensure the constant is
declared in the same scope as runInitialLoad so both branches
(requestAnimationFrame and fallback setTimeout) reference the same value and
update any comments if present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9ae24d97-f7aa-4f65-90d7-6a4849864476

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1d618 and 977dc0e.

📒 Files selected for processing (17)
  • cli.js
  • tests/unit/compact-layout-ui.test.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/run.mjs
  • tests/unit/session-usage-backend.test.mjs
  • tests/unit/web-run-host.test.mjs
  • tests/unit/web-ui-source-bundle.test.mjs
  • tests/unit/web-ui-startup-init.test.mjs
  • web-ui/app.js
  • web-ui/index.html
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/modal-skills.html
  • web-ui/partials/index/panel-docs.html
  • web-ui/partials/index/panel-market.html
  • web-ui/partials/index/panel-usage.html
  • web-ui/styles/navigation-panels.css
  • web-ui/styles/sessions-preview.css
📜 Review details
🧰 Additional context used
🪛 GitHub Actions: ci
web-ui/index.html

[error] 1-1: E2E failed: /web-ui/index.html should use the compiler-enabled vue runtime

🔇 Additional comments (16)
web-ui/partials/index/panel-docs.html (1)

11-11: Copy simplification here is clean and consistent with the docs panel flow.

No functional regressions in these template edits; the updated wording remains clear and concise.

Also applies to: 80-80, 110-110, 122-122, 124-125

web-ui/partials/index/panel-usage.html (1)

19-19: Loading-state update looks good.

Line 19 keeps the same rendering condition while improving wording; scope is minimal and safe.

web-ui/partials/index/layout-header.html (1)

236-236: Subtitle copy refresh is clear and consistent.

Line 236 preserves existing tab-based logic and only tightens user-facing text.

web-ui/partials/index/panel-market.html (1)

11-11: Market panel copy changes are cohesive and non-breaking.

These edits stay strictly presentational and keep behavior unchanged.

Also applies to: 77-77, 84-84, 102-102, 109-109, 127-127, 133-133, 137-137, 141-141, 149-149, 156-156, 162-162, 167-169

web-ui/partials/index/modal-skills.html (1)

6-6: Skills modal wording updates look good.

Copy is shorter while keeping intent intact; no functional impact detected.

Also applies to: 65-65, 125-125

cli.js (1)

5778-5792: Good cache hygiene on the lightweight usage path.

Cloning each session before deleting __messageCountExact avoids mutating the cached objects returned by listAllSessions().

web-ui/app.js (1)

454-465: Unmount cleanup for deferred startup work is correctly handled.

The listener/timer/RAF teardown is complete and prevents stale callbacks after unmount.

tests/unit/web-ui-startup-init.test.mjs (1)

7-93: Good regression coverage for startup deferral sequencing.

This test cleanly verifies the load → RAF → timeout chain and ensures cleanup behavior is exercised.

tests/unit/web-run-host.test.mjs (1)

804-1035: Readiness gating tests are strong and targeted.

The added cases validate both “don’t auto-open before probe success” and “retry after probe failure” behavior with clear command/timer assertions.

tests/unit/web-ui-source-bundle.test.mjs (1)

27-28: Bundle assertion update is correct.

Verifying inclusion of /res/vue.global.prod.js and exclusion of /res/vue.global.js is the right regression guard.

tests/unit/run.mjs (1)

34-34: Good test-runner integration.

Including the new backend usage test in the central runner closes a common coverage gap.

tests/unit/config-tabs-ui.test.mjs (1)

31-32: These template assertions are aligned with the UI changes.

The production Vue script checks plus explicit loading/error/empty usage-state checks are valuable regressions.

Also applies to: 73-75

web-ui/styles/sessions-preview.css (1)

12-12: Spacing adjustments are coherent.

The reduced timeline width/offset and matching scroll padding update are consistent with the tighter preview layout goal.

Also applies to: 120-123, 174-175

web-ui/styles/navigation-panels.css (1)

338-339: Layout constraint update looks good.

Using width: min(100%, 1480px) with explicit .mode-content { width: 100%; } gives a clearer, predictable container sizing model.

Also applies to: 343-343

tests/unit/compact-layout-ui.test.mjs (1)

58-64: Updated layout assertions look correct and aligned with the CSS changes.

These checks appropriately lock in the new desktop width behavior and session preview/timeline spacing updates.

tests/unit/session-usage-backend.test.mjs (1)

31-80: Good regression coverage for lightweight usage retrieval path.

This test clearly verifies the expected call path and payload normalization.

@ymkiux
Copy link
Copy Markdown
Collaborator Author

ymkiux commented Apr 10, 2026

@coderabbitai If the current suggestion is not adopted, will it lead to disruptive changes?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh pr view 83 --repo SakuraByteCore/codexmate --json reviews,comments
gh api repos/SakuraByteCore/codexmate/pulls/83/comments --jq '.[].body' 2>/dev/null | head -100
gh api repos/SakuraByteCore/codexmate/issues/83/comments --jq '.[].body' 2>/dev/null | head -100

Length of output: 43366


@ymkiux Here's a breakdown of each suggestion and whether skipping it causes real problems:


🔴 Already causing a CI failure — action needed

web-ui/index.html — Vue script reference (vue.global.prod.js vs vue.global.js)

The GitHub Actions CI run already reports:

E2E failed: /web-ui/index.html should use the compiler-enabled vue runtime

The E2E test in tests/e2e/test-web-ui-assets.js expects the compiler-enabled vue.global.js. The HTML now points to vue.global.prod.js, which breaks that assertion. This is already a failing test, so it does need to be resolved — either by reverting web-ui/index.html to use vue.global.js, or by updating the E2E assertion to accept the prod variant (whichever reflects the intended behavior).


⚠️ Functional correctness issue — worth fixing

cli.js — Readiness probe consuming initNotice

The probe sends action: 'status', which the server handles by calling consumeInitNotice(). Since the probe fires before the browser even opens, the init notice is consumed before the frontend's first real status poll. This means the UI would never see the initNotice on startup. This is a silent functional regression, not a crash — but it's a real behavioral bug in the existing flow. Adopting a side-effect-free probe action (e.g., action: 'health-check') avoids the problem.


🔒 Security concern — low urgency for a local tool

cli.jsexec() with interpolated URL string

The platform-specific browser-open command interpolates the URL directly into a shell string and passes it to exec(). For a local CLI tool where the URL is derived from host + port config, the practical risk is low — but it is a shell injection surface. This is a quality/safety improvement, not something that breaks existing behavior if left unchanged.


✅ Nitpicks — no runtime impact if skipped

  • web-ui/app.js — extracting 120 into a named constant is purely a maintainability improvement.
  • tests/unit/session-usage-backend.test.mjs — the edge-case test for non-array upstream responses adds coverage but skipping it doesn't break anything.

In summary: The web-ui/index.html change needs attention because it's already failing CI. The initNotice probe issue is a real but silent functional regression worth addressing. The rest can be deferred without breaking anything.


🧠 Learnings used
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: SurviveM
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-29T02:42:32.785Z
Learning: The repository maintainer SurviveM (GitHub: SurviveM, repo: SakuraByteCore/codexmate) prefers that CodeRabbit review suggestions do not introduce breaking changes. Reviews should focus on correctness, safety, and quality without recommending refactors or changes that would break existing APIs, interfaces, or behavior.

@ymkiux
Copy link
Copy Markdown
Collaborator Author

ymkiux commented Apr 10, 2026

@coderabbitai please confirm whether there are any remaining issues on this PR.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check latest CI status and PR details
gh pr view 83 --repo SakuraByteCore/codexmate --json statusCheckRollup,commits,state

# Check latest comments
gh api repos/SakuraByteCore/codexmate/issues/83/comments --jq '.[-3:][].body' 2>/dev/null

# Check the e2e test file current state
gh api repos/SakuraByteCore/codexmate/contents/tests/e2e/test-web-ui-assets.js --jq '.content' 2>/dev/null | base64 -d | grep -A3 -B3 'vue'

Length of output: 9323


@ymkiux All previously flagged issues have been resolved. Here's the confirmed status:

Issue Status
🔴 E2E CI failure (vue.global.prod.js vs vue.global.js) ✅ Fixed — commit test: align web ui vue asset assertion with production build updated the E2E assertions to match the prod build
⚠️ initNotice consumed by readiness probe ✅ Fixed — commit fix: keep startup probe from consuming init notice switched the probe to the side-effect-free health-check action
All CI checks (Node 18, 20, 22) ✅ All passing with SUCCESS

The two remaining items from the earlier review are non-blocking:

  • 🔒 exec() shell injection surface — low urgency for a local tool, no functional breakage if left as-is.
  • 🔵 Magic number 120 in web-ui/app.js — purely cosmetic/maintainability, no runtime impact.

The PR is in a clean state and ready for merge. 🎉


🧠 Learnings used
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: SurviveM
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-29T02:42:32.785Z
Learning: The repository maintainer SurviveM (GitHub: SurviveM, repo: SakuraByteCore/codexmate) prefers that CodeRabbit review suggestions do not introduce breaking changes. Reviews should focus on correctness, safety, and quality without recommending refactors or changes that would break existing APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-05T03:58:26.778Z
Learning: For SakuraByteCore/codexmate reviews, only provide non-breaking feedback (correctness, safety, quality). Avoid API, behavior, or refactor suggestions. This rule applies across PRs `#55`, `#59`, `#67`, and `#68`.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli.js`:
- Around line 5554-5567: listSessionInventoryBySource currently returns cached
session objects by reference which allows later mutation (e.g.,
applySessionQueryFilter adding match) to leak into other responses; fix this by
returning deep clones of cached data and storing clones in the cache so callers
always get independent objects: when calling getSessionInventoryCache(cacheKey,
forceRefresh) return a deep-cloned copy of the sessions, and when storing with
setSessionInventoryCache(cacheKey, normalizedSource, sessions) store a deep
clone of the sessions array/object rather than the original reference (use a
safe deep-clone util or JSON round-trip if structures are plain).
- Around line 4743-4800: The fast preview must only be accepted when we know
it's complete/valid: modify extractSessionDetailPreviewFromFileFast so it never
returns latest unless latest.totalMessages is non-null (i.e., the tail parser
confirmed total count). Concretely, in the while-loop when you would
early-return after reaching safeMessageLimit, only return if
latest.totalMessages !== null; otherwise continue scanning/fallthrough. After
the loop (before the final return latest), if latest.totalMessages === null
return null so callers fall back to the full reader; keep setting latest.clipped
based on position>0 as already done.

In `@web-ui/session-helpers.mjs`:
- Around line 14-19: normalizeSessionLoadOptions currently defaults
includeActiveDetail to false causing loadSessions() and loadSessions({
forceRefresh: true }) to drop activeSessionMessages; update
normalizeSessionLoadOptions (function name: normalizeSessionLoadOptions) so
includeActiveDetail defaults to true unless callers explicitly pass
includeActiveDetail: false (i.e., treat missing/undefined as true, while keeping
forceRefresh behavior unchanged), ensuring existing callers like loadSessions()
and the refresh action preserve detail hydration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 81d24ef0-0c35-4bea-9630-5d03991b31fd

📥 Commits

Reviewing files that changed from the base of the PR and between dd17660 and 4ebd49b.

📒 Files selected for processing (17)
  • cli.js
  • tests/e2e/test-mcp.js
  • tests/e2e/test-sessions.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/session-browser-timeline-regression.test.mjs
  • tests/unit/session-tab-switch-performance.test.mjs
  • tests/unit/session-usage-backend.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • tests/unit/web-ui-runtime-navigation-regression.test.mjs
  • tests/unit/web-ui-startup-init.test.mjs
  • web-ui/app.js
  • web-ui/modules/app.computed.session.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • web-ui/modules/app.methods.session-browser.mjs
  • web-ui/partials/index/panel-sessions.html
  • web-ui/session-helpers.mjs
✅ Files skipped from review due to trivial changes (2)
  • tests/unit/web-ui-logic.test.mjs
  • tests/unit/session-browser-timeline-regression.test.mjs
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/web-ui-startup-init.test.mjs
  • tests/unit/session-usage-backend.test.mjs
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: SurviveM
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-29T02:42:32.785Z
Learning: The repository maintainer SurviveM (GitHub: SurviveM, repo: SakuraByteCore/codexmate) prefers that CodeRabbit review suggestions do not introduce breaking changes. Reviews should focus on correctness, safety, and quality without recommending refactors or changes that would break existing APIs, interfaces, or behavior.
🪛 Biome (2.4.10)
web-ui/session-helpers.mjs

[error] 210-210: Unsafe usage of 'return'.

(lint/correctness/noUnsafeFinally)

🔇 Additional comments (8)
tests/e2e/test-mcp.js (2)

212-213: Good assertion update for long-session preview counts.

This keeps the test resilient to non-exact preview totals while still enforcing type/sanity guarantees (finite and >= 0).


227-228: Resource-path assertion change is consistent and correct.

The same numeric/non-negative contract here keeps MCP resource validation aligned with the updated preview semantics.

web-ui/modules/app.computed.session.mjs (2)

46-70: visibleSessionsList logic is robust and well-bounded.

The render gate, count sanitization, and active-session inclusion behavior are implemented cleanly and defensively.


81-84: Configurable initial preview batch fallback looks solid.

Using sessionPreviewInitialBatchSize with integer/minimum guards preserves safe behavior and improves control over first render volume.

web-ui/modules/app.methods.navigation.mjs (4)

399-507: LGTM! Well-structured viewport-based rendering implementation.

The incremental session list rendering logic is well-designed:

  • Proper idle scheduling with cancellation support
  • Defensive guards for tab/render state before operations
  • ensureActive logic correctly ensures the active session is always visible
  • Recursive scheduling in scheduleSessionListViewportFill is properly guarded by state checks

The magic numbers (120, 160) were already noted in PR discussion as deferrable maintainability items.


543-565: LGTM! Proper cleanup integration.

The suspend and teardown methods correctly clean up the new viewport rendering state:

  • Cancels scheduled viewport fills to prevent work on inactive tabs
  • Clears the session list DOM reference to avoid stale refs
  • Uses resetSessionListRender() for consistent state reset

572-584: LGTM! Correct initialization sequence.

The preparation flow follows the right order: reset → enable → prime, ensuring a clean state before starting the viewport rendering pipeline.


597-610: LGTM! Good efficiency improvement.

Gating scheduleSessionTimelineSync() on sessionTimelineEnabled prevents unnecessary work when the timeline is disabled. The frame-deferred execution with fallback is a sensible approach for smoother rendering.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
cli.js (1)

4790-4793: ⚠️ Potential issue | 🟠 Major

Fall back when the fast scan never finds a complete tail message.

If the 1 MB budget is exhausted before any complete JSONL message can be reconstructed, this still returns an empty preview object. readSessionDetail() treats that as success, so large tail records can render as a blank conversation instead of using the existing streaming fallback.

Minimal fix
-        if (position > 0) {
+        if (latest.messages.length === 0) {
+            return null;
+        }
+        if (position > 0) {
             latest.clipped = true;
         }
         return latest;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 4790 - 4793, The preview builder currently returns an
empty latest object when the fast tail scan exhausts the 1MB budget without
reconstructing any complete JSONL message; change the return semantics so that
when no complete messages were found (e.g., latest has no entries/content) after
the fast scan budget is exhausted, return a failure sentinel (null/undefined)
instead of an empty object so readSessionDetail() can trigger the streaming
fallback; locate the logic that sets latest/clipped (references: latest,
latest.clipped, position) and add a check for “no reconstructed messages &&
budget exhausted” to return null/undefined rather than latest.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli.js`:
- Around line 4783-4786: The tail parser can already set latest.clipped=true
inside extractSessionDetailPreviewFromTailText when it stopped due to
messageLimit, but the caller overwrites it with latest.clipped = position > 0;
change that assignment to preserve any prior truncation by using latest.clipped
= latest.clipped || (position > 0) (referencing latest.clipped, position,
safeMessageLimit, extractSessionDetailPreviewFromTailText and messageLimit) so
once clipped is true it remains true.

---

Duplicate comments:
In `@cli.js`:
- Around line 4790-4793: The preview builder currently returns an empty latest
object when the fast tail scan exhausts the 1MB budget without reconstructing
any complete JSONL message; change the return semantics so that when no complete
messages were found (e.g., latest has no entries/content) after the fast scan
budget is exhausted, return a failure sentinel (null/undefined) instead of an
empty object so readSessionDetail() can trigger the streaming fallback; locate
the logic that sets latest/clipped (references: latest, latest.clipped,
position) and add a check for “no reconstructed messages && budget exhausted” to
return null/undefined rather than latest.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 666e85e4-fb34-4436-bd90-b6823fa7ff0f

📥 Commits

Reviewing files that changed from the base of the PR and between 4ebd49b and 53b5a0c.

📒 Files selected for processing (4)
  • cli.js
  • tests/unit/session-tab-switch-performance.test.mjs
  • tests/unit/session-usage-backend.test.mjs
  • web-ui/session-helpers.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/session-usage-backend.test.mjs
  • tests/unit/session-tab-switch-performance.test.mjs
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: SurviveM
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-29T02:42:32.785Z
Learning: The repository maintainer SurviveM (GitHub: SurviveM, repo: SakuraByteCore/codexmate) prefers that CodeRabbit review suggestions do not introduce breaking changes. Reviews should focus on correctness, safety, and quality without recommending refactors or changes that would break existing APIs, interfaces, or behavior.
🪛 Biome (2.4.10)
web-ui/session-helpers.mjs

[error] 260-260: Unsafe usage of 'return'.

(lint/correctness/noUnsafeFinally)

🔇 Additional comments (4)
cli.js (4)

11184-11196: Prefer argument-based process launch over exec() here.

The browser-open path still interpolates url into a shell command string. Since url is derived from --host / CODEXMATE_HOST, that keeps the same shell-injection surface the earlier review already called out.

This read-only check should show the user-controlled host flowing into the exec(command, ...) path:

#!/bin/bash
rg -n -C3 'function resolveWebHost|CODEXMATE_HOST|openBrowserAfterReady|exec\(command|formatHostForUrl' cli.js

5500-5576: Good: inventory cache entries are now isolated per request.

Cloning on both cache write and cache read avoids leaking match metadata across responses while keeping the existing API shape intact.


6097-6137: Good trade-off on the hot path.

These responses still keep messageCount intact while dropping only __messageCountExact, so the usage UI can continue aggregating and sorting without forcing exact hydration on every request.


11138-11213: Good fix: the readiness probe is now side-effect free.

Using health-check here avoids consuming initNotice before the frontend’s first real status poll.

Also applies to: 11261-11263

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
cli.js (1)

4743-4793: ⚠️ Potential issue | 🟠 Major

Fast preview still returns unverified results as if they were authoritative.

Line 4783 can still return a preview with totalMessages === null, and Line 4785 still overwrites an already-true clipped flag with position > 0. That means some large-session previews can disagree with the full reader while being reported as complete enough to use.

🩹 Minimal correctness fix
             latest = extractSessionDetailPreviewFromTailText(combined.toString('utf-8'), source, messageLimit);
             if (latest.messages.length >= safeMessageLimit) {
-                latest.clipped = position > 0;
-                return latest;
+                latest.clipped = latest.clipped || position > 0;
+                if (latest.totalMessages !== null) {
+                    return latest;
+                }
             }
         }
 
-        if (position > 0) {
-            latest.clipped = true;
-        }
-        return latest;
+        if (latest.totalMessages === null) {
+            return null;
+        }
+        latest.clipped = latest.clipped || position > 0;
+        return latest;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 4743 - 4793, In extractSessionDetailPreviewFromFileFast:
ensure the preview isn't returned as authoritative when totalMessages is unknown
and don't overwrite a true clipped flag; call
extractSessionDetailPreviewFromTailText with safeMessageLimit (not
messageLimit), change the early-return condition to require latest.totalMessages
!== null && latest.messages.length >= safeMessageLimit, and set clipped using
logical-or (latest.clipped = latest.clipped || position > 0) both before the
early return and at the end so an existing true clipped isn't reset.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli.js`:
- Around line 6122-6136: The usage endpoint (listSessionUsage) is still calling
the heavy listAllSessions path; replace that call with the new lightweight
browse implementation you added (the lightweight counterpart to
listAllSessions—e.g., browseAllSessions or listAllSessionsLight) so the endpoint
uses the cheap browse path while preserving the same params (source, limit,
forceRefresh) and the normalization/map logic that removes __messageCountExact;
keep the Array.isArray check and fallback to [] so response shape is unchanged.

---

Duplicate comments:
In `@cli.js`:
- Around line 4743-4793: In extractSessionDetailPreviewFromFileFast: ensure the
preview isn't returned as authoritative when totalMessages is unknown and don't
overwrite a true clipped flag; call extractSessionDetailPreviewFromTailText with
safeMessageLimit (not messageLimit), change the early-return condition to
require latest.totalMessages !== null && latest.messages.length >=
safeMessageLimit, and set clipped using logical-or (latest.clipped =
latest.clipped || position > 0) both before the early return and at the end so
an existing true clipped isn't reset.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6bf0459d-bf83-4d23-8ca8-bd0ffd0bad1e

📥 Commits

Reviewing files that changed from the base of the PR and between 53b5a0c and 0259829.

📒 Files selected for processing (2)
  • cli.js
  • tests/e2e/test-sessions.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/e2e/test-sessions.js
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-ui/session-helpers.mjs`:
- Around line 257-261: The finally block currently reads and clears
this.__sessionPendingLoadOptions and then returns loadSessions(...), which can
swallow exceptions thrown in the try; instead, remove the recursive return from
the finally block: capture pendingOptions from this.__sessionPendingLoadOptions
and clear it inside finally (preserve that behavior), but perform the
conditional call to loadSessions(api, pendingOptions) after the
try/catch/finally has completed so any exception is propagated (or rethrown)
first; update references in this function to __sessionPendingLoadOptions,
pendingOptions and the loadSessions call accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c0264a31-a1ba-430e-97b5-09e71750f1b8

📥 Commits

Reviewing files that changed from the base of the PR and between 0259829 and c4970ac.

📒 Files selected for processing (4)
  • tests/e2e/run.js
  • tests/e2e/test-web-ui-session-browser.js
  • tests/unit/session-tab-switch-performance.test.mjs
  • web-ui/session-helpers.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/session-tab-switch-performance.test.mjs
📜 Review details
🧰 Additional context used
🪛 Biome (2.4.10)
web-ui/session-helpers.mjs

[error] 260-260: Unsafe usage of 'return'.

(lint/correctness/noUnsafeFinally)

🔇 Additional comments (11)
web-ui/session-helpers.mjs (5)

14-46: LGTM - Context-aware option normalization addresses prior concerns.

The combination of hasIncludeActiveDetail tracking and shouldIncludeActiveSessionDetail (lines 41-46) properly handles the default case: when callers don't explicitly pass includeActiveDetail, the function checks vm.mainTab === 'sessions' || !!vm.sessionStandalone to determine the appropriate behavior. This preserves detail hydration for existing callers on the sessions tab.


52-55: LGTM - Hydration ticket prevents stale task execution.

The incrementing ticket pattern properly guards against race conditions where multiple scheduleSessionDetailHydration calls could queue competing tasks.


289-297: LGTM - Preview parameter correctly scoped to session tab context.

The preview flag appropriately reflects when the user is viewing sessions in the tab (not standalone), enabling the backend to return optimized responses without full message counting overhead.


368-382: LGTM - Timeline sync scheduling has proper staleness guards.

The syncTask captures currentSession and verifies both tab state and session identity before invoking scheduleSessionTimelineSync(). Deferring via scheduleAfterFrame when available avoids unnecessary synchronous work.


459-461: LGTM - Limit clamping condition correctly tightened.

Only capping nextLimit to totalKnown when totalKnown > total is logically correct; it avoids artificially constraining the fetch when the known count equals or trails the loaded count.

tests/e2e/run.js (1)

27-27: LGTM - Test module integration follows existing patterns.

The new session browser E2E test is correctly required and invoked in sequence after testWebUiAssets.

Also applies to: 136-136

tests/e2e/test-web-ui-session-browser.js (5)

1-18: LGTM - Lazy-loaded harness with proper caching.

The singleton pattern for bundledWebUiHarnessPromise avoids redundant parsing of the bundled web UI script across test runs.


59-82: LGTM - Mock fetch correctly bridges UI requests to real API.

Routing through ctx.api enables testing the actual backend behavior rather than stubbing responses, which provides more meaningful coverage.


99-148: LGTM - Lightweight VM harness correctly mirrors Vue app structure.

The stub appropriately copies data(), binds methods, and wires computed as getters. Scheduler stubs enable controlled execution without browser APIs.


231-250: LGTM - Assertions thoroughly validate incremental loading behavior.

The test verifies that the session browser:

  • Stays within the isolated dataset
  • Respects initial batch sizes for both list and detail
  • Grows detail hydration incrementally without pulling entire large sessions
  • Maintains the clipped flag appropriately

252-268: LGTM - Huge-line session regression test validates recovery path.

This validates the real UI→API flow for sessions with extremely large messages, ensuring:

  • Selection completes without hanging
  • Messages are recovered without duplication
  • Clipped state is correctly set to false when all messages fit
  • Visible count matches actual message count

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (3)
cli.js (3)

11184-11196: ⚠️ Potential issue | 🟠 Major

Avoid exec() with an interpolated URL.

Line 11194 still shells out with url, which is built from --host / CODEXMATE_HOST. A crafted host containing quotes or shell metacharacters can escape the command string; pass the URL as an argument via spawn(..., { shell: false }) instead.

Suggested fix
-            const platform = process.platform;
-            let command;
-            if (platform === 'win32') {
-                command = `start \"\" \"${url}\"`;
-            } else if (platform === 'darwin') {
-                command = `open \"${url}\"`;
-            } else {
-                command = `xdg-open \"${url}\"`;
-            }
-
-            exec(command, (error) => {
-                if (error) console.warn('无法自动打开浏览器,请手动访问:', url);
-            });
+            const commandSpec = process.platform === 'win32'
+                ? { command: 'cmd', args: ['/c', 'start', '', url] }
+                : (process.platform === 'darwin'
+                    ? { command: 'open', args: [url] }
+                    : { command: 'xdg-open', args: [url] });
+
+            const child = spawn(commandSpec.command, commandSpec.args, {
+                stdio: 'ignore',
+                detached: true,
+                windowsHide: true,
+                shell: false
+            });
+            child.on('error', () => {
+                console.warn('无法自动打开浏览器,请手动访问:', url);
+            });
+            child.unref();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 11184 - 11196, The current code builds a shell
interpolated command using platform/command and calls exec(command, ...) with
the interpolated url, which allows shell injection via a crafted CODEXMATE_HOST;
change this to use child_process.spawn (or execFile) and pass the URL as a
separate argument (do not interpolate into a shell string) and set shell: false
so the URL cannot be interpreted by a shell—update the logic around platform,
command, and the exec call to call spawn/execFile with args (e.g., ['open', url]
on darwin, ['xdg-open', url] on linux, or use 'cmd' with ['/c','start','',url']
on win32) and handle the callback/exit to log the same warning on error.

4743-4793: ⚠️ Potential issue | 🟠 Major

Normalize the full-scan fast preview before returning.

When Line 4793 returns latest after position reaches 0, the result still comes straight from extractSessionDetailPreviewFromTailText(), which never strips leading bootstrap/system messages. That makes preview=true disagree with the normal reader for large sessions that happen to have fewer than messageLimit conversation messages.

Suggested fix
-        if (position > 0) {
-            latest.clipped = latest.clipped || position > 0;
-        }
-        return latest;
+        if (position > 0) {
+            latest.clipped = true;
+            return latest;
+        }
+        latest.messages = removeLeadingSystemMessage(latest.messages);
+        latest.totalMessages = latest.messages.length;
+        latest.clipped = false;
+        return latest;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 4743 - 4793, The fast full-scan return path in
extractSessionDetailPreviewFromFileFast currently returns the raw output from
extractSessionDetailPreviewFromTailText, which still contains leading
bootstrap/system messages; after the loop when position reaches 0, post-process
latest to normalize it the same way the normal reader does by removing leading
bootstrap/system messages and trimming to safeMessageLimit (i.e., call or
implement the same normalization/stripping routine used by the normal reader),
ensure latest.messages is recalculated to respect safeMessageLimit and set
latest.clipped appropriately (latest.clipped = latest.clipped || position > 0),
so preview=true results match the normal reader for large sessions with fewer
than messageLimit conversation messages.

6114-6136: ⚠️ Potential issue | 🟠 Major

Use the lightweight browse path for usage.

Line 6122 still calls listAllSessions(), so the usage endpoint keeps the heavier default scan budgets instead of the new browse budgets. On larger session stores, that largely cancels the PR's load-reduction win.

Suggested fix
-    const sessions = await listAllSessions({
+    const sessions = await listSessionBrowse({
         source,
         limit,
         forceRefresh: !!params.forceRefresh
     });
-    return Array.isArray(sessions)
-        ? sessions.map((item) => {
-            if (!item || typeof item !== 'object' || Array.isArray(item)) {
-                return item;
-            }
-            const normalized = { ...item };
-            delete normalized.__messageCountExact;
-            return normalized;
-        })
-        : [];
+    return Array.isArray(sessions) ? sessions : [];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 6114 - 6136, The usage listing still calls
listAllSessions() with default (heavy) scan budgets; update listSessionUsage to
invoke the lightweight/browse path by passing the browse flag or using the
browse-specific helper—e.g., call listAllSessions({ source, limit, forceRefresh:
!!params.forceRefresh, browse: true }) or replace with the equivalent
listAllSessionsBrowse(...) helper so the endpoint uses the new
lightweight/browse budgets (refer to listSessionUsage and listAllSessions in the
diff).
🧹 Nitpick comments (1)
tests/unit/session-detail-preview-fast.test.mjs (1)

15-35: Extract these utility functions to a dedicated test helper module.

This test currently parses raw cli.js source code using regex to extract four internal functions (applySessionDetailRecordMetadata, extractSessionDetailPreviewFromTailText, extractSessionDetailPreviewFromFileFast, extractMessageFromRecord). The regex pattern only matches traditional function declarations—any refactor to arrow functions, class methods, or even syntax reorganization will break this test despite no behavioral change.

These functions are not exported from cli.js (which is a CLI entry point). Move them to a dedicated test utilities module (e.g., tests/unit/session-detail-preview-helpers.js) and import directly instead of parsing source. This decouples the test from syntax specifics and makes the actual behavior testable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/session-detail-preview-fast.test.mjs` around lines 15 - 35, The
test brittlely extracts internal functions via regex; instead move the four
functions applySessionDetailRecordMetadata,
extractSessionDetailPreviewFromTailText,
extractSessionDetailPreviewFromFileFast, and extractMessageFromRecord into a new
test helper module (e.g., tests/unit/session-detail-preview-helpers.js), export
them, update this test to import those functions directly, remove the
extractFunction/regex logic and the dynamic Function(...) wrapper, and adapt
instantiateExtractSessionDetailPreviewFromFileFast to return the imported
extractSessionDetailPreviewFromFileFast (wiring any required test bindings via
parameters or simple wrapper functions) so the test no longer depends on cli.js
source shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@cli.js`:
- Around line 11184-11196: The current code builds a shell interpolated command
using platform/command and calls exec(command, ...) with the interpolated url,
which allows shell injection via a crafted CODEXMATE_HOST; change this to use
child_process.spawn (or execFile) and pass the URL as a separate argument (do
not interpolate into a shell string) and set shell: false so the URL cannot be
interpreted by a shell—update the logic around platform, command, and the exec
call to call spawn/execFile with args (e.g., ['open', url] on darwin,
['xdg-open', url] on linux, or use 'cmd' with ['/c','start','',url'] on win32)
and handle the callback/exit to log the same warning on error.
- Around line 4743-4793: The fast full-scan return path in
extractSessionDetailPreviewFromFileFast currently returns the raw output from
extractSessionDetailPreviewFromTailText, which still contains leading
bootstrap/system messages; after the loop when position reaches 0, post-process
latest to normalize it the same way the normal reader does by removing leading
bootstrap/system messages and trimming to safeMessageLimit (i.e., call or
implement the same normalization/stripping routine used by the normal reader),
ensure latest.messages is recalculated to respect safeMessageLimit and set
latest.clipped appropriately (latest.clipped = latest.clipped || position > 0),
so preview=true results match the normal reader for large sessions with fewer
than messageLimit conversation messages.
- Around line 6114-6136: The usage listing still calls listAllSessions() with
default (heavy) scan budgets; update listSessionUsage to invoke the
lightweight/browse path by passing the browse flag or using the browse-specific
helper—e.g., call listAllSessions({ source, limit, forceRefresh:
!!params.forceRefresh, browse: true }) or replace with the equivalent
listAllSessionsBrowse(...) helper so the endpoint uses the new
lightweight/browse budgets (refer to listSessionUsage and listAllSessions in the
diff).

---

Nitpick comments:
In `@tests/unit/session-detail-preview-fast.test.mjs`:
- Around line 15-35: The test brittlely extracts internal functions via regex;
instead move the four functions applySessionDetailRecordMetadata,
extractSessionDetailPreviewFromTailText,
extractSessionDetailPreviewFromFileFast, and extractMessageFromRecord into a new
test helper module (e.g., tests/unit/session-detail-preview-helpers.js), export
them, update this test to import those functions directly, remove the
extractFunction/regex logic and the dynamic Function(...) wrapper, and adapt
instantiateExtractSessionDetailPreviewFromFileFast to return the imported
extractSessionDetailPreviewFromFileFast (wiring any required test bindings via
parameters or simple wrapper functions) so the test no longer depends on cli.js
source shape.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6608459e-a064-4f89-83b6-264f7486b4b2

📥 Commits

Reviewing files that changed from the base of the PR and between c4970ac and 96af1f5.

📒 Files selected for processing (3)
  • cli.js
  • tests/unit/run.mjs
  • tests/unit/session-detail-preview-fast.test.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/run.mjs
📜 Review details
🔇 Additional comments (3)
tests/unit/session-detail-preview-fast.test.mjs (1)

38-100: Good regression coverage for fast-tail preview semantics.

The test data shape and assertions directly protect the intended contract (tail window, clipped, non-exact totalMessages) and include reliable cleanup.

cli.js (2)

5500-5575: Cache cloning looks good.

Cloning on both store and read isolates cached session objects, so request-local mutations like match.snippets will not leak into later responses.


11138-11169: Nice readiness-probe split.

Using a dedicated health-check action here keeps browser auto-open side-effect free and avoids consuming initNotice during startup.

Also applies to: 11260-11263

@awsl233777
Copy link
Copy Markdown
Collaborator

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

@awsl233777 Sure! I'll re-review the PR now.


🧠 Learnings used
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: SurviveM
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-29T02:42:32.785Z
Learning: The repository maintainer SurviveM (GitHub: SurviveM, repo: SakuraByteCore/codexmate) prefers that CodeRabbit review suggestions do not introduce breaking changes. Reviews should focus on correctness, safety, and quality without recommending refactors or changes that would break existing APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-05T03:58:26.787Z
Learning: For SakuraByteCore/codexmate reviews, only provide non-breaking feedback (correctness, safety, quality). Avoid API, behavior, or refactor suggestions. This rule applies across PRs `#55`, `#59`, `#67`, and `#68`.
✅ Actions performed

Full review triggered.

@awsl233777
Copy link
Copy Markdown
Collaborator

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

@awsl233777 Sure! I'll kick off a full re-review of PR #83 now.


🧠 Learnings used
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: SurviveM
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-29T02:42:32.785Z
Learning: The repository maintainer SurviveM (GitHub: SurviveM, repo: SakuraByteCore/codexmate) prefers that CodeRabbit review suggestions do not introduce breaking changes. Reviews should focus on correctness, safety, and quality without recommending refactors or changes that would break existing APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-05T03:58:26.787Z
Learning: For SakuraByteCore/codexmate reviews, only provide non-breaking feedback (correctness, safety, quality). Avoid API, behavior, or refactor suggestions. This rule applies across PRs `#55`, `#59`, `#67`, and `#68`.
✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
tests/unit/session-usage-backend.test.mjs (2)

157-190: Add lower-bound and non-numeric limit cases.

This test already verifies default and upper clamp. Adding limit <= 0 and non-numeric input would fully pin the normalization behavior.

Suggested test extension
 test('listSessionUsage normalizes source and default limit for lightweight usage aggregation', async () => {
@@
     await listSessionUsage({
         source: 'invalid',
         limit: '9999',
         forceRefresh: 1
     });
+    await listSessionUsage({
+        source: 'codex',
+        limit: 0,
+        forceRefresh: 0
+    });
+    await listSessionUsage({
+        source: 'claude',
+        limit: 'not-a-number'
+    });
     await listSessionUsage({});
 
     assert.deepStrictEqual(calls, [
@@
         {
+            source: 'codex',
+            limit: 1,
+            forceRefresh: false
+        },
+        {
+            source: 'claude',
+            limit: 200,
+            forceRefresh: false
+        },
+        {
             source: 'all',
             limit: 200,
             forceRefresh: false
         }
     ]);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/session-usage-backend.test.mjs` around lines 157 - 190, Extend the
existing test for listSessionUsage to also assert normalization when limit is
non-positive or non-numeric: call listSessionUsage with limit: 0 and limit: -5
and with limit: 'abc' (and/or undefined numeric strings) and verify
listSessionBrowse receives the clamped minimum/default (e.g., 1 or the
function's lower bound) and correct forceRefresh coercion; update the expected
calls array alongside the existing cases so instantiateListSessionUsage,
listSessionBrowse, MAX_SESSION_LIST_SIZE and listSessionUsage are exercised for
these new inputs and confirm behavior matches the normalization rules.

14-21: Make extraction less formatting-sensitive.

Line 15’s regex-based function extraction is brittle; small formatting shifts in cli.js can break tests without behavior changes. Prefer a brace-balanced extractor (or AST parsing) for stability.

Proposed refactor
 function extractFunction(content, funcName) {
-    const regex = new RegExp(`(?:async\\s+)?function ${funcName}\\([^)]*\\)\\s*\\{[\\s\\S]*?^\\}`, 'm');
-    const match = content.match(regex);
-    if (!match) {
-        throw new Error(`Function ${funcName} not found`);
-    }
-    return match[0];
+    const signature = new RegExp(`(?:async\\s+)?function\\s+${funcName}\\s*\\(`);
+    const start = content.search(signature);
+    if (start === -1) {
+        throw new Error(`Function ${funcName} not found`);
+    }
+    const openBrace = content.indexOf('{', start);
+    if (openBrace === -1) {
+        throw new Error(`Function ${funcName} has no body`);
+    }
+
+    let depth = 0;
+    for (let i = openBrace; i < content.length; i += 1) {
+        const ch = content[i];
+        if (ch === '{') depth += 1;
+        if (ch === '}') depth -= 1;
+        if (depth === 0) {
+            return content.slice(start, i + 1);
+        }
+    }
+    throw new Error(`Function ${funcName} body is not balanced`);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/session-usage-backend.test.mjs` around lines 14 - 21, The current
extractFunction implementation using a fragile regex (in
tests/unit/session-usage-backend.test.mjs: extractFunction and its regex) fails
on minor formatting changes; replace it with a robust parser-based or
brace-balanced extractor: parse the file into an AST (e.g., acorn/espree) and
locate the FunctionDeclaration/FunctionExpression whose id.name equals funcName
(or a top-level async function with that name), then return the original source
slice using the node.start/node.end positions; alternatively implement a simple
brace-matching routine that finds the function keyword for funcName and walks
characters counting braces to locate the matching closing brace to extract the
exact source.
tests/e2e/test-web-ui-session-browser.js (1)

59-82: Consider adding error response handling capability to the mock fetch.

The mock fetch always returns ok: true, status: 200. While this works for the current happy-path tests, consider adding a mechanism to simulate error responses if you need to test error handling in the session browser.

💡 Example extension for error simulation
 function createApiFetch(api) {
     return async function fetch(_url, init = {}) {
         const payload = init && init.body ? JSON.parse(init.body) : {};
         const result = await api(payload.action, payload.params);
         const raw = JSON.stringify(result || {});
+        const hasError = result && result.error;
         return {
-            ok: true,
-            status: 200,
+            ok: !hasError,
+            status: hasError ? 400 : 200,
             headers: {
                 get(name) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/test-web-ui-session-browser.js` around lines 59 - 82, The mock
createApiFetch always returns a successful response (ok: true, status: 200), so
tests cannot simulate HTTP errors; update createApiFetch to support error
simulation by letting the provided api callback either throw or return an
error-shaped result and then map that into a non-200 response (e.g., ok: false,
status: <code>, and an error JSON body). Specifically, modify createApiFetch to
detect when api(payload.action, payload.params) throws or returns an object with
an error indicator (e.g., result.error or result.__status) and produce an
appropriate response object (status, ok boolean, headers, and json/text that
return the error payload) so tests can assert error handling in fetch consumers
like the session browser.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/e2e/test-web-ui-session-browser.js`:
- Around line 59-82: The mock createApiFetch always returns a successful
response (ok: true, status: 200), so tests cannot simulate HTTP errors; update
createApiFetch to support error simulation by letting the provided api callback
either throw or return an error-shaped result and then map that into a non-200
response (e.g., ok: false, status: <code>, and an error JSON body).
Specifically, modify createApiFetch to detect when api(payload.action,
payload.params) throws or returns an object with an error indicator (e.g.,
result.error or result.__status) and produce an appropriate response object
(status, ok boolean, headers, and json/text that return the error payload) so
tests can assert error handling in fetch consumers like the session browser.

In `@tests/unit/session-usage-backend.test.mjs`:
- Around line 157-190: Extend the existing test for listSessionUsage to also
assert normalization when limit is non-positive or non-numeric: call
listSessionUsage with limit: 0 and limit: -5 and with limit: 'abc' (and/or
undefined numeric strings) and verify listSessionBrowse receives the clamped
minimum/default (e.g., 1 or the function's lower bound) and correct forceRefresh
coercion; update the expected calls array alongside the existing cases so
instantiateListSessionUsage, listSessionBrowse, MAX_SESSION_LIST_SIZE and
listSessionUsage are exercised for these new inputs and confirm behavior matches
the normalization rules.
- Around line 14-21: The current extractFunction implementation using a fragile
regex (in tests/unit/session-usage-backend.test.mjs: extractFunction and its
regex) fails on minor formatting changes; replace it with a robust parser-based
or brace-balanced extractor: parse the file into an AST (e.g., acorn/espree) and
locate the FunctionDeclaration/FunctionExpression whose id.name equals funcName
(or a top-level async function with that name), then return the original source
slice using the node.start/node.end positions; alternatively implement a simple
brace-matching routine that finds the function keyword for funcName and walks
characters counting braces to locate the matching closing brace to extract the
exact source.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9fd0661d-30f4-4e09-a07b-b13bc7ee8341

📥 Commits

Reviewing files that changed from the base of the PR and between 96af1f5 and 605723a.

📒 Files selected for processing (7)
  • cli.js
  • tests/e2e/test-sessions.js
  • tests/e2e/test-web-ui-session-browser.js
  • tests/unit/session-detail-preview-fast.test.mjs
  • tests/unit/session-usage-backend.test.mjs
  • tests/unit/web-run-host.test.mjs
  • web-ui/session-helpers.mjs
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/web-run-host.test.mjs
  • tests/unit/session-detail-preview-fast.test.mjs
  • cli.js
📜 Review details
🔇 Additional comments (16)
tests/unit/session-usage-backend.test.mjs (1)

52-155: Strong regression coverage for lightweight and cache paths.

The tests do a good job protecting the lightweight browse/usage behavior and cache-hit short-circuit logic (including sessionId → filePath lookup).

Also applies to: 192-452

web-ui/session-helpers.mjs (7)

14-27: LGTM!

The option normalization helpers are well-structured. The hasOwnOption utility properly checks for explicit property presence, and normalizeSessionLoadOptions correctly tracks whether includeActiveDetail was explicitly provided via hasIncludeActiveDetail, allowing downstream code to apply contextual defaults.


30-46: LGTM!

The merge logic in mergeSessionLoadOptions correctly combines option sets: forceRefresh uses logical OR, and includeActiveDetail is only true if either set explicitly specified it as true. The shouldIncludeActiveSessionDetail function provides sensible contextual defaults based on tab state when no explicit value was provided.


52-55: Good use of a monotonic ticket to prevent stale hydration.

The ticket pattern ensures that if multiple hydration tasks are scheduled in quick succession, only the latest one will execute. This prevents stale data from overwriting more recent results.


165-173: LGTM!

When a load is already in progress, merging the new options into __sessionPendingLoadOptions correctly coalesces multiple concurrent requests. This avoids redundant API calls while preserving intent from all callers.


258-263: Good fix: recursive call now outside the finally block.

Moving the recursive loadSessions call outside the finally block ensures exceptions from the try block propagate correctly instead of being suppressed. The pending options are still captured and cleared within finally for proper cleanup.


369-383: LGTM!

The sync task now has proper guards checking that the sessions tab is still active, preview rendering is enabled, and the activeSession hasn't changed since the task was scheduled. This prevents timeline sync from running in stale contexts.


460-462: LGTM!

The condition change from totalKnown > 0 to totalKnown > total correctly limits the next fetch boundary only when we know there are more messages on disk than currently loaded. This avoids incorrectly capping when totalKnown is zero or equal to total.

tests/e2e/test-web-ui-session-browser.js (4)

150-160: LGTM!

The guard limit of 50 iterations is a sensible safeguard to detect infinite loop scenarios in tests where scheduled frames keep spawning new frames. The error message clearly indicates the failure mode.


173-266: LGTM!

The test setup is comprehensive: it creates isolated session fixtures with varying characteristics (dense list, huge-line, hot large sessions), initializes the VM with proper mocks, and exercises the session browser tab entry flow with appropriate assertions for list loading, detail hydration, and preview capping behavior.


268-295: LGTM!

The hot session test correctly validates that huge sessions are clipped during preview, that only the initial batch is rendered, and that incremental loading properly expands the detail window without fetching the entire session at once.


297-314: LGTM!

The huge-line session recovery test validates the fallback path when huge lines exceed the fast tail window, ensuring messages aren't duplicated and the preview is marked as fully recovered.

tests/e2e/test-sessions.js (4)

121-122: LGTM!

New test fixture variables for the huge-line session are properly scoped within the test function, following the same pattern as other session fixtures.


269-270: LGTM!

The relaxed assertions correctly accommodate the new preview behavior where messageCount may not be an exact value. Checking for Number.isFinite and >= 0 ensures the field is still a valid numeric value without requiring exact equality.


293-300: LGTM!

The preview mode assertions correctly validate that:

  • Preview respects the messageLimit parameter
  • Preview stays clipped for long sessions
  • Preview intentionally avoids exact totalMessages to keep the operation lightweight

309-353: LGTM!

Comprehensive test coverage for huge-line sessions:

  • Preview mode validates fallback behavior, no message duplication, unclipped status for small sessions, and 4000-character text capping
  • Non-preview mode validates exact totalMessages, full message count, and preserved original huge content

@awsl233777
Copy link
Copy Markdown
Collaborator

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

@awsl233777 Sure! I'll kick off a full re-review of PR #83 now.


🧠 Learnings used
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.

Learnt from: SurviveM
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-29T02:42:32.785Z
Learning: The repository maintainer SurviveM (GitHub: SurviveM, repo: SakuraByteCore/codexmate) prefers that CodeRabbit review suggestions do not introduce breaking changes. Reviews should focus on correctness, safety, and quality without recommending refactors or changes that would break existing APIs, interfaces, or behavior.
✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
web-ui/app.js (1)

415-437: Extract the initial-load delay constant

120 is duplicated in two scheduling branches, which makes tuning/error-prone later. Prefer a named constant (e.g., INITIAL_LOAD_DELAY_MS) used in both places.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/app.js` around lines 415 - 437, The hard-coded delay 120ms is
duplicated in runInitialLoad's scheduling branches; extract it into a single
named constant (e.g., INITIAL_LOAD_DELAY_MS) and use that constant in both
places where setTimeout is called (the branch inside requestAnimationFrame and
the fallback setTimeout) so tuning is centralized; update references in
runInitialLoad and ensure no other logic changes to this._initialLoadTimer or
this._initialLoadRafId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-ui/modules/app.methods.session-timeline.mjs`:
- Around line 186-201: updateSessionTimelineOffset short-circuits when the
numeric offset hasn't changed, so if sessionPreviewContainerEl is replaced the
new DOM node never receives the CSS variable; fix by tracking the previous
container element and treating a container change as requiring re-apply: add a
private field (e.g. __sessionPreviewContainerEl) and compare it to the current
container (sessionPreviewContainerEl or this.$refs.sessionPreviewContainer) and
only early-return when both the offset and container reference are unchanged;
when container differs, set the CSS variable on the new container via
container.style.setProperty('--session-preview-header-offset', `${offset}px`)
and update both __sessionPreviewHeaderOffsetPx and __sessionPreviewContainerEl
accordingly (also ensure you remove property from the old container if needed).

---

Nitpick comments:
In `@web-ui/app.js`:
- Around line 415-437: The hard-coded delay 120ms is duplicated in
runInitialLoad's scheduling branches; extract it into a single named constant
(e.g., INITIAL_LOAD_DELAY_MS) and use that constant in both places where
setTimeout is called (the branch inside requestAnimationFrame and the fallback
setTimeout) so tuning is centralized; update references in runInitialLoad and
ensure no other logic changes to this._initialLoadTimer or
this._initialLoadRafId.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 98ad69a8-f494-442a-917d-a7f7fdbf1620

📥 Commits

Reviewing files that changed from the base of the PR and between 605723a and 57b45c0.

📒 Files selected for processing (7)
  • tests/unit/session-browser-timeline-regression.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-runtime-navigation-regression.test.mjs
  • web-ui/app.js
  • web-ui/logic.sessions.mjs
  • web-ui/modules/app.methods.navigation.mjs
  • web-ui/modules/app.methods.session-timeline.mjs
✅ Files skipped from review due to trivial changes (2)
  • tests/unit/session-browser-timeline-regression.test.mjs
  • web-ui/logic.sessions.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • web-ui/modules/app.methods.navigation.mjs
📜 Review details
🔇 Additional comments (2)
tests/unit/web-ui-behavior-parity.test.mjs (1)

1022-1039: Good regression assertion for pre-prime preview visibility

The new check directly validates initial batching behavior (sessionPreviewInitialBatchSize) instead of depending on HEAD parity for intentionally changed behavior.

tests/unit/web-ui-runtime-navigation-regression.test.mjs (1)

404-441: Nice coverage for measured-element gating in viewport auto-grow

This test captures the intended idle-fill guard and prevents regressions where visible count expands before a measurable list ref exists.

Comment on lines 186 to 201
updateSessionTimelineOffset() {
const container = this.sessionPreviewContainerEl || this.$refs.sessionPreviewContainer;
if (!container || !container.style) return;
if (!this.hasRenderableSessionTimeline()) {
if (this.__sessionPreviewHeaderOffsetPx != null) {
container.style.removeProperty('--session-preview-header-offset');
this.__sessionPreviewHeaderOffsetPx = null;
}
return;
}
const offset = getSessionPreviewHeaderOffset(this);
if (this.__sessionPreviewHeaderOffsetPx === offset) {
return;
}
this.__sessionPreviewHeaderOffsetPx = offset;
container.style.setProperty('--session-preview-header-offset', `${offset}px`);
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.

⚠️ Potential issue | 🟠 Major

Cache short-circuit skips applying offset on container replacement

When sessionPreviewContainerEl changes (Line 35–37), the check on Line 197 can return early if the offset value is unchanged, so the new container never receives --session-preview-header-offset. That causes stale/fallback positioning on re-rendered containers.

Proposed fix
         updateSessionTimelineOffset() {
             const container = this.sessionPreviewContainerEl || this.$refs.sessionPreviewContainer;
             if (!container || !container.style) return;
+            const containerChanged = this.__sessionPreviewOffsetContainer !== container;
             if (!this.hasRenderableSessionTimeline()) {
-                if (this.__sessionPreviewHeaderOffsetPx != null) {
+                if (containerChanged || this.__sessionPreviewHeaderOffsetPx != null) {
                     container.style.removeProperty('--session-preview-header-offset');
                     this.__sessionPreviewHeaderOffsetPx = null;
                 }
+                this.__sessionPreviewOffsetContainer = container;
                 return;
             }
             const offset = getSessionPreviewHeaderOffset(this);
-            if (this.__sessionPreviewHeaderOffsetPx === offset) {
+            if (!containerChanged && this.__sessionPreviewHeaderOffsetPx === offset) {
                 return;
             }
             this.__sessionPreviewHeaderOffsetPx = offset;
+            this.__sessionPreviewOffsetContainer = container;
             container.style.setProperty('--session-preview-header-offset', `${offset}px`);
         },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/app.methods.session-timeline.mjs` around lines 186 - 201,
updateSessionTimelineOffset short-circuits when the numeric offset hasn't
changed, so if sessionPreviewContainerEl is replaced the new DOM node never
receives the CSS variable; fix by tracking the previous container element and
treating a container change as requiring re-apply: add a private field (e.g.
__sessionPreviewContainerEl) and compare it to the current container
(sessionPreviewContainerEl or this.$refs.sessionPreviewContainer) and only
early-return when both the offset and container reference are unchanged; when
container differs, set the CSS variable on the new container via
container.style.setProperty('--session-preview-header-offset', `${offset}px`)
and update both __sessionPreviewHeaderOffsetPx and __sessionPreviewContainerEl
accordingly (also ensure you remove property from the old container if needed).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-ui/modules/app.methods.session-browser.mjs`:
- Around line 8-15: The helper isSessionLoadNativeDialogEnabled can re-enter
itself when vm.isSessionLoadNativeDialogEnabled is a wrapper that calls back
into the helper; add a reentrancy guard on the vm to prevent recursion: define a
unique Symbol (e.g., const SESSION_PROBE_LOCK = Symbol.for('sessionProbeLock'))
and, before invoking vm.isSessionLoadNativeDialogEnabled(), check if
vm[SESSION_PROBE_LOCK] is truthy and return false if so; otherwise set
vm[SESSION_PROBE_LOCK] = true, call vm.isSessionLoadNativeDialogEnabled(), and
clear vm[SESSION_PROBE_LOCK] in a finally block to ensure the flag is removed
after the probe; apply this guard in isSessionLoadNativeDialogEnabled and any
wrapper that calls the helper (references: isSessionLoadNativeDialogEnabled and
vm.isSessionLoadNativeDialogEnabled).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 921ee8bd-209a-49ff-a296-fea9bf29bec3

📥 Commits

Reviewing files that changed from the base of the PR and between 57b45c0 and f298304.

📒 Files selected for processing (3)
  • tests/unit/web-ui-behavior-parity.test.mjs
  • web-ui/modules/app.methods.session-browser.mjs
  • web-ui/session-helpers.mjs
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: SurviveM
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-29T02:42:32.785Z
Learning: The repository maintainer SurviveM (GitHub: SurviveM, repo: SakuraByteCore/codexmate) prefers that CodeRabbit review suggestions do not introduce breaking changes. Reviews should focus on correctness, safety, and quality without recommending refactors or changes that would break existing APIs, interfaces, or behavior.

Comment on lines +8 to +15
function isSessionLoadNativeDialogEnabled(vm) {
if (vm && typeof vm.isSessionLoadNativeDialogEnabled === 'function' && vm.isSessionLoadNativeDialogEnabled !== isSessionLoadNativeDialogEnabled) {
try {
return !!vm.isSessionLoadNativeDialogEnabled();
} catch (_) {
// fall through to shared detection
}
}
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.

⚠️ Potential issue | 🔴 Critical

Avoid the self-recursive native-dialog probe.

With the wrapper at Lines 150-152, the helper now calls back into vm.isSessionLoadNativeDialogEnabled(), which re-enters the same helper and recurses until a RangeError is thrown and caught. Every session-load debug probe goes through that path, so this adds a stack overflow to normal control flow.

🐛 Minimal fix
 function isSessionLoadNativeDialogEnabled(vm) {
-    if (vm && typeof vm.isSessionLoadNativeDialogEnabled === 'function' && vm.isSessionLoadNativeDialogEnabled !== isSessionLoadNativeDialogEnabled) {
-        try {
-            return !!vm.isSessionLoadNativeDialogEnabled();
-        } catch (_) {
-            // fall through to shared detection
-        }
-    }
-
     try {
         if (globalThis && globalThis.__CODEXMATE_SESSION_LOAD_NATIVE_DIALOG__ === true) {
             return true;
         }

Also applies to: 150-152

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/app.methods.session-browser.mjs` around lines 8 - 15, The
helper isSessionLoadNativeDialogEnabled can re-enter itself when
vm.isSessionLoadNativeDialogEnabled is a wrapper that calls back into the
helper; add a reentrancy guard on the vm to prevent recursion: define a unique
Symbol (e.g., const SESSION_PROBE_LOCK = Symbol.for('sessionProbeLock')) and,
before invoking vm.isSessionLoadNativeDialogEnabled(), check if
vm[SESSION_PROBE_LOCK] is truthy and return false if so; otherwise set
vm[SESSION_PROBE_LOCK] = true, call vm.isSessionLoadNativeDialogEnabled(), and
clear vm[SESSION_PROBE_LOCK] in a finally block to ensure the flag is removed
after the probe; apply this guard in isSessionLoadNativeDialogEnabled and any
wrapper that calls the helper (references: isSessionLoadNativeDialogEnabled and
vm.isSessionLoadNativeDialogEnabled).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
cli.js (1)

4784-4801: ⚠️ Potential issue | 🟠 Major

Use a real exact pass after the last chunk has been read.

When position reaches 0, combined already contains the whole file, but this code still returns or derives from the tail-limited latest state. That leaves preview mode inexact for 256 KB–1 MB sessions with more than messageLimit messages: today it returns totalMessages: null, and even without the early return the post-loop block would still compute totals from a truncated slice.

🩹 Minimal fix
             totalBytesRead += bytesRead;
             combined = Buffer.concat([chunk.subarray(0, bytesRead), combined]);
             latest = extractSessionDetailPreviewFromTailText(combined.toString('utf-8'), source, safeMessageLimit);
-            if (latest.messages.length >= safeMessageLimit) {
+            if (latest.messages.length >= safeMessageLimit && position > 0) {
                 latest.clipped = latest.clipped || position > 0;
                 return latest;
             }
         }

         if (position > 0) {
             latest.clipped = latest.clipped || position > 0;
             return latest;
         }
-        const normalizedMessages = removeLeadingSystemMessage(latest.messages);
-        latest.messages = normalizedMessages.length > safeMessageLimit
-            ? normalizedMessages.slice(-safeMessageLimit)
-            : normalizedMessages;
-        latest.totalMessages = normalizedMessages.length;
-        latest.clipped = latest.totalMessages > latest.messages.length;
-        return latest;
+        const exact = extractSessionDetailPreviewFromRecords(
+            parseJsonlContent(combined.toString('utf-8')),
+            source,
+            safeMessageLimit
+        );
+        exact.clipped = exact.totalMessages > exact.messages.length;
+        return exact;

Based on learnings the maintainer prefers non-breaking fixes only, so this keeps the current preview contract and only corrects the full-file fast-preview case.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 4784 - 4801, When position reaches 0 we must perform an
exact pass over the full file instead of returning the tail-limited preview:
call extractSessionDetailPreviewFromTailText(combined.toString('utf-8'), source,
safeMessageLimit) again to obtain a fresh exact latest, then run
removeLeadingSystemMessage(latest.messages) and recompute latest.messages
(slicing to safeMessageLimit), latest.totalMessages and latest.clipped from that
normalized array; replace the current early/late returns that use a possibly
truncated latest when position === 0 so totals and clipped reflect the full
content.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/test-sessions.js`:
- Around line 124-125: The huge-line fixture file created via hugeLineSessionId
/ hugeLineSessionPath is not removed in the test's finally cleanup and can leak
state; add a best-effort deletion in the finally block that mirrors other
cleanup (call bestEffortApi('delete-session', { source: 'codex', sessionId:
hugeLineSessionId, filePath: hugeLineSessionPath }) or otherwise unlink
hugeLineSessionPath) so the synthetic session file is removed after the test
completes.

In `@web-ui/modules/app.computed.session.mjs`:
- Around line 89-91: The current computation of initialBatchSize calls
Number.isFinite on the raw this.sessionPreviewInitialBatchSize, so numeric
strings like "24" fall back to 12; normalize the value first by coercing to a
number (e.g., const raw = Number(this.sessionPreviewInitialBatchSize) or
parseFloat) then check Number.isFinite(raw) and use Math.max(1, Math.floor(raw))
when valid, otherwise use 12—update the logic around
initialBatchSize/sessionPreviewInitialBatchSize accordingly.
- Around line 67-70: The findIndex callback calls
this.getSessionExportKey(session) without guarding against null/undefined
entries in the list (e.g., from sortedSessionsList), which can throw; update the
logic that computes activeIndex (or sanitize the list first) so you either
filter out falsy sessions from list before calling findIndex, or change the
callback to first check session truthiness (e.g., session &&
this.getSessionExportKey(session) === activeKey) to safely skip null entries;
refer to this.activeSession, getSessionExportKey, and the local variable list
when making the change.

---

Duplicate comments:
In `@cli.js`:
- Around line 4784-4801: When position reaches 0 we must perform an exact pass
over the full file instead of returning the tail-limited preview: call
extractSessionDetailPreviewFromTailText(combined.toString('utf-8'), source,
safeMessageLimit) again to obtain a fresh exact latest, then run
removeLeadingSystemMessage(latest.messages) and recompute latest.messages
(slicing to safeMessageLimit), latest.totalMessages and latest.clipped from that
normalized array; replace the current early/late returns that use a possibly
truncated latest when position === 0 so totals and clipped reflect the full
content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7e880ad8-beb2-4440-b017-2a05e248db27

📥 Commits

Reviewing files that changed from the base of the PR and between f298304 and dc6d587.

📒 Files selected for processing (7)
  • cli.js
  • tests/e2e/test-sessions.js
  • tests/e2e/test-setup.js
  • tests/unit/session-usage-backend.test.mjs
  • tests/unit/session-usage.test.mjs
  • web-ui/logic.sessions.mjs
  • web-ui/modules/app.computed.session.mjs
✅ Files skipped from review due to trivial changes (2)
  • tests/e2e/test-setup.js
  • tests/unit/session-usage-backend.test.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • web-ui/logic.sessions.mjs
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: SurviveM
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-29T02:42:32.785Z
Learning: The repository maintainer SurviveM (GitHub: SurviveM, repo: SakuraByteCore/codexmate) prefers that CodeRabbit review suggestions do not introduce breaking changes. Reviews should focus on correctness, safety, and quality without recommending refactors or changes that would break existing APIs, interfaces, or behavior.
🔇 Additional comments (7)
web-ui/modules/app.computed.session.mjs (1)

8-14: Good defensive formatting and summary-card extension.

The added number sanitizer plus totalTokens / totalContextWindow cards are consistent and keep invalid values from leaking into UI stats.

Also applies to: 150-156

tests/unit/session-usage.test.mjs (1)

13-15: Nice regression coverage for new usage totals.

These assertions correctly pin both positive aggregation and invalid-session zeroing for totalTokens and totalContextWindow.

Also applies to: 20-21, 49-49, 54-55

cli.js (5)

125-137: LGTM: the lightweight cache layers are cleanly separated.

Inventory cloning plus the separate sessionId -> filePath lookup cache avoids caller-mutation leakage while still short-circuiting repeated path scans.

Also applies to: 515-519, 5468-5607, 5610-5616, 6294-6353


5618-5712: LGTM: usage loading now stays on summary metadata.

Pulling totalTokens and contextWindow into the summary readers lets listSessionUsage() stay on the lightweight browse path without changing its response shape.

Also applies to: 5714-6127, 6231-6270


4680-4742: LGTM: preview-mode response shaping is explicit.

Preview-only text capping and nullable totalMessages make the preview/detail contract straightforward for the UI.

Also applies to: 4811-4817, 8095-8127


11280-11414: LGTM: the readiness probe is now side-effect-free.

health-check no longer consumes init state, and browser auto-open waits until the local API is actually responding.

Also applies to: 11975-11977


6142-6170: LGTM: the lightweight browse path is wired consistently across surfaces.

HTTP session listing, workflows, MCP tools, and MCP resources all route through listSessionBrowse(), so the cheaper browse behavior stays aligned everywhere.

Also applies to: 11642-11644, 13356-13360, 13725-13727, 14138-14145

Comment on lines +124 to +125
const hugeLineSessionId = 'codex-huge-line-preview-e2e';
const hugeLineSessionPath = path.join(tmpHome, '.codex', 'sessions', `${hugeLineSessionId}.jsonl`);
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.

⚠️ Potential issue | 🟡 Minor

Clean up the huge-line fixture in finally.

hugeLineSessionPath is created on Lines 312-328, but it is never removed in the cleanup block. Unlike longSessionId, this synthetic session is not carried forward via ctx, so a reused tmpHome can leak extra state into later session-list/usage assertions.

Minimal cleanup
await bestEffortApi('delete-session', {
    source: 'codex',
    sessionId: hugeLineSessionId,
    filePath: hugeLineSessionPath
});

Also applies to: 312-328

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/test-sessions.js` around lines 124 - 125, The huge-line fixture
file created via hugeLineSessionId / hugeLineSessionPath is not removed in the
test's finally cleanup and can leak state; add a best-effort deletion in the
finally block that mirrors other cleanup (call bestEffortApi('delete-session', {
source: 'codex', sessionId: hugeLineSessionId, filePath: hugeLineSessionPath })
or otherwise unlink hugeLineSessionPath) so the synthetic session file is
removed after the test completes.

Comment on lines +67 to +70
const activeKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
if (activeKey) {
const activeIndex = list.findIndex((session) => this.getSessionExportKey(session) === activeKey);
if (activeIndex >= 0) {
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.

⚠️ Potential issue | 🟠 Major

Guard session lookup against null entries in visibleSessionsList.

Line 69 calls this.getSessionExportKey(session) without a null check. If sortedSessionsList includes a falsy item, this can throw during render when activeKey is set.

Proposed non-breaking fix
-            if (activeKey) {
-                const activeIndex = list.findIndex((session) => this.getSessionExportKey(session) === activeKey);
+            if (activeKey) {
+                const activeIndex = list.findIndex((session) => session && this.getSessionExportKey(session) === activeKey);
                 if (activeIndex >= 0) {
                     targetCount = Math.max(targetCount, activeIndex + 1);
                 }
             }
📝 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 activeKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
if (activeKey) {
const activeIndex = list.findIndex((session) => this.getSessionExportKey(session) === activeKey);
if (activeIndex >= 0) {
const activeKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
if (activeKey) {
const activeIndex = list.findIndex((session) => session && this.getSessionExportKey(session) === activeKey);
if (activeIndex >= 0) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/app.computed.session.mjs` around lines 67 - 70, The findIndex
callback calls this.getSessionExportKey(session) without guarding against
null/undefined entries in the list (e.g., from sortedSessionsList), which can
throw; update the logic that computes activeIndex (or sanitize the list first)
so you either filter out falsy sessions from list before calling findIndex, or
change the callback to first check session truthiness (e.g., session &&
this.getSessionExportKey(session) === activeKey) to safely skip null entries;
refer to this.activeSession, getSessionExportKey, and the local variable list
when making the change.

Comment on lines +89 to +91
const initialBatchSize = Number.isFinite(this.sessionPreviewInitialBatchSize)
? Math.max(1, Math.floor(this.sessionPreviewInitialBatchSize))
: 12;
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.

⚠️ Potential issue | 🟡 Minor

Normalize sessionPreviewInitialBatchSize before finite check.

Line 89 checks finiteness on the raw value. Numeric strings (e.g. "24") are treated as invalid and silently fall back to 12 instead of the configured batch size.

Proposed non-breaking fix
-                const initialBatchSize = Number.isFinite(this.sessionPreviewInitialBatchSize)
-                    ? Math.max(1, Math.floor(this.sessionPreviewInitialBatchSize))
+                const rawInitialBatchSize = Number(this.sessionPreviewInitialBatchSize);
+                const initialBatchSize = Number.isFinite(rawInitialBatchSize)
+                    ? Math.max(1, Math.floor(rawInitialBatchSize))
                     : 12;
📝 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 initialBatchSize = Number.isFinite(this.sessionPreviewInitialBatchSize)
? Math.max(1, Math.floor(this.sessionPreviewInitialBatchSize))
: 12;
const rawInitialBatchSize = Number(this.sessionPreviewInitialBatchSize);
const initialBatchSize = Number.isFinite(rawInitialBatchSize)
? Math.max(1, Math.floor(rawInitialBatchSize))
: 12;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/app.computed.session.mjs` around lines 89 - 91, The current
computation of initialBatchSize calls Number.isFinite on the raw
this.sessionPreviewInitialBatchSize, so numeric strings like "24" fall back to
12; normalize the value first by coercing to a number (e.g., const raw =
Number(this.sessionPreviewInitialBatchSize) or parseFloat) then check
Number.isFinite(raw) and use Math.max(1, Math.floor(raw)) when valid, otherwise
use 12—update the logic around initialBatchSize/sessionPreviewInitialBatchSize
accordingly.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
web-ui/modules/app.computed.session.mjs (2)

101-103: ⚠️ Potential issue | 🟡 Minor

Coerce sessionPreviewInitialBatchSize before validating it.

Lines 101-103 still use Number.isFinite(this.sessionPreviewInitialBatchSize), so numeric strings like "24" are treated as invalid and silently fall back to 12.

Non-breaking fix
-                const initialBatchSize = Number.isFinite(this.sessionPreviewInitialBatchSize)
-                    ? Math.max(1, Math.floor(this.sessionPreviewInitialBatchSize))
+                const rawInitialBatchSize = Number(this.sessionPreviewInitialBatchSize);
+                const initialBatchSize = Number.isFinite(rawInitialBatchSize)
+                    ? Math.max(1, Math.floor(rawInitialBatchSize))
                     : 12;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/app.computed.session.mjs` around lines 101 - 103, The code
treats numeric strings as invalid because it checks
Number.isFinite(this.sessionPreviewInitialBatchSize) without coercion; change
the logic that computes initialBatchSize to coerce
sessionPreviewInitialBatchSize to a Number first (e.g., const value =
Number(this.sessionPreviewInitialBatchSize)), then validate with
Number.isFinite(value) and compute Math.max(1, Math.floor(value)) when valid,
otherwise fall back to 12; update the expressions referencing
sessionPreviewInitialBatchSize/initialBatchSize accordingly to use the coerced
value.

80-83: ⚠️ Potential issue | 🟠 Major

Guard the active-session lookup against falsy entries.

Line 81 still calls this.getSessionExportKey(session) unconditionally. If sortedSessionsList contains a null/undefined item, this can still throw during render as soon as activeKey is set.

Non-breaking fix
-            if (activeKey) {
-                const activeIndex = list.findIndex((session) => this.getSessionExportKey(session) === activeKey);
+            if (activeKey) {
+                const activeIndex = list.findIndex((session) => session && this.getSessionExportKey(session) === activeKey);
                 if (activeIndex >= 0) {
                     targetCount = Math.max(targetCount, activeIndex + 1);
                 }
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/app.computed.session.mjs` around lines 80 - 83, The
active-session lookup calls this.getSessionExportKey(session) unguarded and can
throw if the sessions list contains null/undefined; update the findIndex
predicate used when computing activeIndex (the list.findIndex callback that
references this.getSessionExportKey(session)) to first check session != null (or
session !== undefined) before calling this.getSessionExportKey, e.g. guard the
call with a null-check or optional chaining so the predicate safely returns
false for falsy entries and prevents runtime errors during render.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@web-ui/modules/app.computed.session.mjs`:
- Around line 101-103: The code treats numeric strings as invalid because it
checks Number.isFinite(this.sessionPreviewInitialBatchSize) without coercion;
change the logic that computes initialBatchSize to coerce
sessionPreviewInitialBatchSize to a Number first (e.g., const value =
Number(this.sessionPreviewInitialBatchSize)), then validate with
Number.isFinite(value) and compute Math.max(1, Math.floor(value)) when valid,
otherwise fall back to 12; update the expressions referencing
sessionPreviewInitialBatchSize/initialBatchSize accordingly to use the coerced
value.
- Around line 80-83: The active-session lookup calls
this.getSessionExportKey(session) unguarded and can throw if the sessions list
contains null/undefined; update the findIndex predicate used when computing
activeIndex (the list.findIndex callback that references
this.getSessionExportKey(session)) to first check session != null (or session
!== undefined) before calling this.getSessionExportKey, e.g. guard the call with
a null-check or optional chaining so the predicate safely returns false for
falsy entries and prevents runtime errors during render.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 71123cfc-8d87-4fe3-924f-c8ca77a73add

📥 Commits

Reviewing files that changed from the base of the PR and between dc6d587 and 74fc765.

📒 Files selected for processing (4)
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.computed.session.mjs
  • web-ui/partials/index/panel-usage.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • web-ui/partials/index/panel-usage.html
  • tests/unit/web-ui-logic.test.mjs
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-30T04:47:15.352Z
Learning: In PR `#55` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-04T01:36:26.964Z
Learning: In PR `#68` for SakuraByteCore/codexmate, github-actions[bot] again flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`, `#68`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-31T12:37:51.023Z
Learning: In PR `#59` for SakuraByteCore/codexmate, github-actions[bot] repeatedly flagged that CodeRabbit was introducing breaking changes. The maintainer's absolute constraint is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: github-actions[bot]
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-04-03T10:42:22.368Z
Learning: In PR `#67` for SakuraByteCore/codexmate, github-actions[bot] flagged that CodeRabbit was again introducing breaking changes. The maintainer's absolute constraint (reinforced across PRs `#55`, `#59`, `#67`) is: no breaking changes in any review suggestion; limit all feedback to correctness, safety, and quality only within the existing design, without refactoring or altering APIs, interfaces, or behavior.
Learnt from: SurviveM
Repo: SakuraByteCore/codexmate PR: 0
File: :0-0
Timestamp: 2026-03-29T02:42:32.785Z
Learning: The repository maintainer SurviveM (GitHub: SurviveM, repo: SakuraByteCore/codexmate) prefers that CodeRabbit review suggestions do not introduce breaking changes. Reviews should focus on correctness, safety, and quality without recommending refactors or changes that would break existing APIs, interfaces, or behavior.

Copy link
Copy Markdown
Collaborator

@SurviveM SurviveM left a comment

Choose a reason for hiding this comment

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

Approved for merge.

@ymkiux ymkiux merged commit c7833cd into main Apr 11, 2026
7 checks passed
@ymkiux ymkiux deleted the fix/web-ui-startup-and-usage-performance branch April 11, 2026 17:21
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.

3 participants