Skip to content

fix(app): stabilize sidebar session history#386

Merged
Astro-Han merged 5 commits into
devfrom
codex/fix-i382-session-window
May 2, 2026
Merged

fix(app): stabilize sidebar session history#386
Astro-Han merged 5 commits into
devfrom
codex/fix-i382-session-window

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a reusable PawWork sidebar session window: 30 sessions by default, global Show more in +30 steps, capped at 90.
  • Keep pinned and active sessions visible outside the normal window.
  • Switch the cap state to the existing Search entry for older sessions.
  • Preserve expanded sidebar history when session events arrive, and expose the experimental pagination cursor to browser clients.
  • Preserve legacy session time_updated during execution context backfill.

Why

Issue #382 is a bad data-loss-looking UI bug: older root sessions disappear from the sidebar, and creating/updating sessions could collapse already loaded history. The fix keeps the sidebar small by default while making older history reachable without per-project overfetching.

Related Issue

Closes #382

Human Review Status

Pending. A human should make the final merge decision after reviewing the final diff and verification evidence.

Review Focus

  • Confirm the global session window matches the accepted behavior: Time and Projects only change presentation, not pagination semantics.
  • Confirm pinned and active sessions are not counted against the 90 normal-session cap.
  • Confirm session.created / session.updated no longer trim expanded sidebar history.
  • Confirm exposing X-Next-Cursor is limited to the experimental route and only when a next page exists.

Risk Notes

  • Visible sidebar behavior changes for users with more than 30 root sessions.
  • No database migration or dependency changes.
  • Electron verification used a copied installed-app database in the dev app data directory only; the installed database was not modified.

How To Verify

App focused tests: 31 passed across pawwork-session-window, pawwork-session-nav, event-reducer, and global-sync tests
Session tests: 22 passed, including executionContext backfill preserving legacy updated time
Global session list tests: 14 passed, including exposed X-Next-Cursor header
App typecheck: passed (tsgo -b)
Diff check: no whitespace errors
Electron + Computer Use: copied installed DB into dev DB, verified 30-session default, Show more expands to 60, second Show more reaches 90, and 搜索历史会话 opens the existing Search modal

Screenshots or Recordings

Not attached because the Electron verification used a copied installed-app database containing private local session titles. Manual Computer Use verification covered the visible UI flow: 30 sessions, Show more, 60 sessions, Show more, 90-session cap, and Search entry.

Checklist

  • Human review status is stated above as pending, approved, or not required
  • I linked the related issue, or stated why there is no issue
  • This PR has type, scope, and priority labels, or I requested maintainer labeling
  • I described the review focus and any meaningful risks
  • I listed the relevant verification steps and the key result for each
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope
  • I manually checked visible UI or copy changes when needed, with screenshots or recordings
  • I considered macOS and Windows impact for desktop, packaging, updater, signing, paths, shell, or permissions changes
  • I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant
  • I reviewed the final diff for unrelated changes and suspicious dependency changes
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English

Summary by CodeRabbit

  • New Features

    • Pawwork sidebar: windowed session list with pagination, “Show more” button, and a “Search history” prompt when cap reached; pinned and active sessions stay visible while older sessions are paginated.
  • Bug Fixes

    • Pagination header exposed for browser clients to allow next-page navigation.
    • Backfill now preserves existing session update timestamps.
  • Localization

    • Added English and Chinese strings for “Show more” and search-history prompt.

@Astro-Han Astro-Han added bug Something isn't working P0 Blocking / highest priority app Application behavior and product flows labels May 2, 2026
@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fb638a81-57ba-4e4a-b62d-0046b00fce1e

📥 Commits

Reviewing files that changed from the base of the PR and between 213fe87 and 07caa29.

📒 Files selected for processing (3)
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-session-window.test.ts
  • packages/app/src/pages/layout/pawwork-session-window.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/app/src/pages/layout/pawwork-session-window.test.ts
  • packages/app/src/pages/layout.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/app/src/pages/layout/pawwork-session-window.ts

📝 Walkthrough

Walkthrough

Replaces trim-on-event session behavior with a paginated "pawwork session window" on the client, removes trimming from session.created/updated handlers, preserves session time_updated during execution-context backfill, and exposes the pagination cursor header for CORS.

Changes

Pawwork Session Window (client)

Layer / File(s) Summary
Data Shape & Helpers
packages/app/src/pages/layout/pawwork-session-window.ts
Adds window constants (PAWWORK_SESSION_WINDOW_INITIAL, _STEP, _MAX), nextPawworkSessionWindowLimit, mergeSessionsByID, sortPawworkSessionWindowSessions, and buildPawworkSessionWindow to compute de-duplicated, capped session windows with canShowMore/capReached.
Layout Wiring & State
packages/app/src/pages/layout.tsx
Introduces pawworkSessionWindowState, loadPawworkSessionWindow() effect, single-session fetch helper, pagination via x-next-cursor, and SDK listeners to upsert/remove sessions in the window state. Replaces previous collect-based root aggregation.
Sidebar Integration
packages/app/src/pages/layout/pawwork-sidebar.tsx
Adds sessionWindow accessor prop plus onShowMore and onSearchOlderSessions; renders "show more" and "search history" controls conditional on window flags and loading.
Tests & I18n
packages/app/src/pages/layout/pawwork-session-window.test.ts, packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts
Adds unit tests for limit stepping, merging, sorting, cap behavior; adds common.showMore and sidebar.pawwork.searchHistory translations; updates event-reducer test to assert preserved expanded history instead of trimming.
Event reducer change
packages/app/src/context/global-sync/event-reducer.ts, .../event-reducer.test.ts
"session.created" and "session.updated" insertion paths now reconcile the full next sessions array directly (reconcile(next, { key: "id" })) and no longer call trimSessions(...) / cleanupDroppedSessionCaches(...). Test updated to expect stable expanded history.

Backend / Data fixes

Layer / File(s) Summary
Backfill update
packages/opencode/src/session/execution-context-store.ts, packages/opencode/test/session/session.test.ts
backfillExecutionContextRows update now sets execution_context and preserves time_updated (uses existing row.time_updated); adds test ensuring time_updated is preserved after backfill.
Pagination header exposure
packages/opencode/src/server/instance/experimental.ts, packages/opencode/test/server/global-session-list.test.ts
GET /session now sets Access-Control-Expose-Headers: X-Next-Cursor so browser clients can read the X-Next-Cursor header; test updated to assert header exposure.

Sequence Diagram

sequenceDiagram
    participant User as User (Browser)
    participant Layout as Layout (Client)
    participant SDK as GlobalSDK
    participant Server as Backend

    rect rgba(100, 150, 200, 0.5)
    Note over User,Server: Initial window load
    User->>Layout: Open Pawwork
    Layout->>SDK: experimental.session.list(limit=30)
    SDK->>Server: GET /session (limit=30)
    Server-->>SDK: [sessions], x-next-cursor (header visible via CORS)
    SDK-->>Layout: sessions, hasMore=true
    Layout->>Layout: buildPawworkSessionWindow(...)
    Layout->>User: Render sessions + "Show more"
    end

    rect rgba(150, 100, 200, 0.5)
    Note over User,Server: New session created
    User->>Server: Create session
    Server-->>SDK: session.created event
    SDK-->>Layout: event listener fires
    Layout->>Layout: reconcile/upsert session into windowState (no trimming)
    Layout->>User: Sidebar updates with new session
    end

    rect rgba(200, 150, 100, 0.5)
    Note over User,Server: Load more
    User->>Layout: Click "Show more"
    Layout->>Layout: nextPawworkSessionWindowLimit(30)->60
    Layout->>SDK: experimental.session.list(limit=60, cursor=...)
    SDK->>Server: GET /session (limit=60, cursor)
    Server-->>SDK: [sessions], x-next-cursor
    SDK-->>Layout: sessions, hasMore=...
    Layout->>Layout: buildPawworkSessionWindow(...)
    Layout->>User: Render expanded list
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I nibbled at the sidebar light,

No sessions vanish in the night,
A window holds the threads so dear,
"Show more" will fetch the old ones near,
Hop, scroll, and history stays in sight!

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(app): stabilize sidebar session history' clearly describes the main change—preventing sidebar sessions from disappearing—and is directly related to the core issue (#382) and changeset.
Description check ✅ Passed The PR description comprehensively covers all required sections: Summary (listing key changes), Why (linking to issue #382 and explaining the data-loss bug), Related Issue (closes #382), Review Focus (identifying key validation points), Risk Notes, How To Verify (with test results), and a completed Checklist.
Linked Issues check ✅ Passed The PR directly addresses all coding objectives from issue #382: (1) increases default session window from 5 to 30 [pawwork-session-window.ts], (2) implements Show more pagination in +30 steps capped at 90 [pawwork-session-window.ts/layout.tsx], (3) prevents session.created/updated from trimming history by removing trimSessions calls [event-reducer.ts], (4) preserves time_updated during backfill [execution-context-store.ts], and (5) exposes X-Next-Cursor for pagination [experimental.ts].
Out of Scope Changes check ✅ Passed All changes align with scope: core pagination logic, sidebar UI, i18n strings (showMore and searchHistory), session window utilities, test coverage, and CORS header exposure—all supporting the #382 fix. No unrelated refactors, dependency additions, or out-of-scope modifications detected.

✏️ 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 codex/fix-i382-session-window

Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements a windowed session management system for the Pawwork sidebar, replacing the previous session trimming logic with a paginated approach. Key changes include the introduction of a pawworkSessionWindowState to manage session visibility, new UI components for 'Show more' and search prompts, and backend updates to support pagination headers and preserve session update times during database backfills. Feedback was provided regarding the efficiency of fetching pinned sessions individually, suggesting a bulk fetch API to improve scalability and reduce concurrent network requests.

Comment thread packages/app/src/pages/layout.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/opencode/test/session/session.test.ts (1)

268-293: Use testEffect(...) and it.live(...) for this Effect + live-state test.

This test exercises Effect services with live git/filesystem/database state, so it should follow the established pattern in other test files: import testEffect from test/lib/effect.ts, define const it = testEffect(...) at the top, and use it.live(...) instead of test(...) with manual Effect.runPromise(...).

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

In `@packages/opencode/test/session/session.test.ts` around lines 268 - 293,
Replace the plain Jest test with the project’s Effect-aware pattern: import
testEffect from "test/lib/effect.ts" and define const it = testEffect(...) at
the top of the file, then change test("backfill preserves legacy session updated
time", ...) to it.live("backfill preserves legacy session updated time", async
() => { ... }); inside the test body stop calling
Effect.runPromise(SessionNs.backfillExecutionContext) directly and instead run
the backfill as part of the effect test runtime (i.e., invoke
SessionNs.backfillExecutionContext through the it.live/Effect runtime), keeping
the rest of the logic (SessionNs.create, Database.use updates, assertions on
time_updated, and SessionNs.remove) intact so the test uses the live
git/filesystem/database fixtures consistently.
packages/app/src/pages/layout.tsx (1)

651-664: 💤 Low value

Optional: Consider batching upsert state updates.

The multiple setPawworkSessionWindowState calls could trigger separate re-renders. While the conditional guards limit this in practice, wrapping in batch() would ensure a single update cycle when multiple properties change.

♻️ Optional batch optimization
 const upsertPawworkWindowSession = (info: Session) => {
   if (info.parentID || info.time?.archived) return
+  batch(() => {
     setPawworkSessionWindowState("normal", (current) =>
       sortPawworkSessionWindowSessions([...current.filter((session) => session.id !== info.id), info]),
     )
     if (store.pawworkPinnedSessions.includes(info.id)) {
       setPawworkSessionWindowState("pinned", (current) =>
         sortPawworkSessionWindowSessions([...current.filter((session) => session.id !== info.id), info]),
       )
     }
     if (params.id === info.id) {
       setPawworkSessionWindowState("active", info)
     }
+  })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/pages/layout.tsx` around lines 651 - 664, The
upsertPawworkWindowSession function calls setPawworkSessionWindowState up to
three times which can cause multiple renders; wrap those state updates in a
single batch() call (or the appropriate React/third-party batching util) so the
updates to "normal", "pinned" and "active" are applied in one
transaction—identify upsertPawworkWindowSession and replace the separate
setPawworkSessionWindowState invocations with a single batch(() => { ... })
block that performs the same filtered/updated assignments for each state key,
preserving the conditional checks for info.parentID, info.time?.archived,
store.pawworkPinnedSessions.includes(info.id), and params.id === info.id.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/src/pages/layout/pawwork-session-window.ts`:
- Around line 43-46: The current logic computes `normal` by sorting and slicing
before removing `input.pinned` and `input.active`, causing pinned/active items
that appear in `normal` to consume the capped slots; change the flow so you
first sort the full `input.normal` via `sortPawworkSessionWindowSessions`, then
remove any sessions whose id appears in `input.pinned` or `input.active` before
applying the `limit` (computed as `Math.min(PAWWORK_SESSION_WINDOW_MAX,
Math.max(PAWWORK_SESSION_WINDOW_INITIAL, input.limit))`), and finally pass the
trimmed, sliced `normal` into `mergeSessionsByID` along with `input.pinned` and
`input.active`; ensure you use `normal.map(item => item.id)` (i.e., `normalIDs`)
after exclusion and slicing so IDs reflect the final window.

---

Nitpick comments:
In `@packages/app/src/pages/layout.tsx`:
- Around line 651-664: The upsertPawworkWindowSession function calls
setPawworkSessionWindowState up to three times which can cause multiple renders;
wrap those state updates in a single batch() call (or the appropriate
React/third-party batching util) so the updates to "normal", "pinned" and
"active" are applied in one transaction—identify upsertPawworkWindowSession and
replace the separate setPawworkSessionWindowState invocations with a single
batch(() => { ... }) block that performs the same filtered/updated assignments
for each state key, preserving the conditional checks for info.parentID,
info.time?.archived, store.pawworkPinnedSessions.includes(info.id), and
params.id === info.id.

In `@packages/opencode/test/session/session.test.ts`:
- Around line 268-293: Replace the plain Jest test with the project’s
Effect-aware pattern: import testEffect from "test/lib/effect.ts" and define
const it = testEffect(...) at the top of the file, then change test("backfill
preserves legacy session updated time", ...) to it.live("backfill preserves
legacy session updated time", async () => { ... }); inside the test body stop
calling Effect.runPromise(SessionNs.backfillExecutionContext) directly and
instead run the backfill as part of the effect test runtime (i.e., invoke
SessionNs.backfillExecutionContext through the it.live/Effect runtime), keeping
the rest of the logic (SessionNs.create, Database.use updates, assertions on
time_updated, and SessionNs.remove) intact so the test uses the live
git/filesystem/database fixtures consistently.
🪄 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 Plus

Run ID: fd30c43f-729b-4ec7-a488-835c8a024802

📥 Commits

Reviewing files that changed from the base of the PR and between a162453 and 213fe87.

📒 Files selected for processing (12)
  • packages/app/src/context/global-sync/event-reducer.test.ts
  • packages/app/src/context/global-sync/event-reducer.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-session-window.test.ts
  • packages/app/src/pages/layout/pawwork-session-window.ts
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/opencode/src/server/instance/experimental.ts
  • packages/opencode/src/session/execution-context-store.ts
  • packages/opencode/test/server/global-session-list.test.ts
  • packages/opencode/test/session/session.test.ts

Comment thread packages/app/src/pages/layout/pawwork-session-window.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows bug Something isn't working P0 Blocking / highest priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Sessions intermittently disappear: limit=5 trimming + session.created event causes session list churn

1 participant