Skip to content

Handle remote thread archive notifications#187

Open
tdtdtq-ux wants to merge 2 commits into
friuns2:mainfrom
tdtdtq-ux:codex/fix-archived-thread-live-sync
Open

Handle remote thread archive notifications#187
tdtdtq-ux wants to merge 2 commits into
friuns2:mainfrom
tdtdtq-ux:codex/fix-archived-thread-live-sync

Conversation

@tdtdtq-ux
Copy link
Copy Markdown

@tdtdtq-ux tdtdtq-ux commented May 21, 2026

Summary

Fixes realtime synchronization when a thread is archived from another client.

The app-server broadcasts thread/archived, but the web client only removed archived threads after a local archive action or a full thread list refresh. This caused threads archived from Codex Desktop to remain visible in the web UI until refresh.

Changes

  • Handle thread/archived in the realtime notification path.
  • Remove remotely archived threads from the loaded thread list immediately.
  • Move selection to an adjacent thread when the selected thread is archived.
  • Add regression tests for remote archive notifications.

Tests

  • pnpm test:unit -- src/composables/useDesktopState.test.ts
  • pnpm run build:frontend

Summary by CodeRabbit

  • New Features

    • Context compaction: a compact-context action (button + ring indicator) is available in thread composer; tapping it compacts the current thread and inserts a pending system message.
    • Archived threads are removed automatically; selection moves to an adjacent thread when the selected thread is archived.
  • UI

    • New visual separator and styling for context-compaction messages; tooltip and dark-mode styles for the context ring.
  • Tests

    • Added tests for archive notification handling and context-compaction message normalization.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

Adds a context-compaction UI and end-to-end flow (UI, API, normalization, runtime handling, rendering) plus realtime handling for thread/archived notifications that remove archived threads, update selection with adjacent-thread fallback, and prune thread-scoped state.

Changes

Context compaction feature

Layer / File(s) Summary
Composer UI, App wiring, and styles
src/components/content/ThreadComposer.vue, src/App.vue, src/style.css
Adds a context-usage ring with tooltip and a compact-context action; App listens for compact-context and calls compactSelectedThread(); dark-mode CSS for the ring and tooltip added.
API and normalization
src/api/codexGateway.ts, src/api/normalizers/v2.ts, src/api/normalizers/v2.test.ts, src/composables/useDesktopState.test.ts
Adds compactThread(threadId) RPC, normalizer handling and test for contextCompaction items, and stubs compactThread in the Codex gateway test mock.
Runtime handling, compact action, and rendering
src/composables/useDesktopState.ts, src/components/content/ThreadConversation.vue
Maps context-compaction notifications into activity and live/system messages, upserts pending live compaction messages, exposes compactSelectedThread() which triggers the RPC and manages pending state/errors, and renders a contextCompaction separator in conversations.

Thread archive notification handling

Layer / File(s) Summary
Archive notification handler
src/composables/useDesktopState.ts
Adds an early thread/archived branch in applyRealtimeUpdates that calls applyArchivedThreadNotification, computes adjacent replacement selection when needed, updates selection, removes the archived thread from loaded groups, prunes thread-scoped state, and silently loads messages for the new selection.
Archive notification tests
src/composables/useDesktopState.test.ts
Test suite verifies archived threads are removed from projectGroups and that when the selected thread is archived, selection advances to an adjacent remaining thread and messages for the new selection are loaded.

Sequence Diagram(s)

sequenceDiagram
  participant ThreadComposer as ThreadComposer (UI)
  participant App as App.vue
  participant DesktopState as useDesktopState
  participant CodexGateway as codexGateway.compactThread
  participant Codex as Codex notifications
  participant ThreadConversation as ThreadConversation (renderer)

  ThreadComposer->>App: emit compact-context
  App->>DesktopState: compactSelectedThread()
  DesktopState->>DesktopState: insert pending contextCompaction.live message
  DesktopState->>CodexGateway: callRpc thread/compact/start
  CodexGateway->>Codex: RPC request -> compaction job started
  Codex-->>DesktopState: notification item/started (contextcompaction)
  DesktopState->>DesktopState: upsert pending live compaction message
  Codex-->>DesktopState: notification item/completed (contextcompaction)
  DesktopState->>DesktopState: replace pending with completed system message
  DesktopState->>ThreadConversation: new message list includes contextCompaction system/live
  ThreadConversation->>ThreadConversation: render context compaction separator
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A rabbit nudges context tight, 🐇
Rings and tooltips gleam at night,
Compact command hops to the wire,
Server trims threads, UI transpires,
Threads archived, selections hop — all right!

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Handle remote thread archive notifications' clearly and accurately describes the main change: implementing handling of thread archive notifications in the realtime notification path, which aligns with the primary objective of fixing real-time synchronization when threads are archived from other clients.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Handle remote thread archive notifications in realtime

🐞 Bug fix 🧪 Tests

Grey Divider

Walkthroughs

Description
• Handle thread/archived realtime notifications from app-server
• Remove remotely archived threads from UI immediately
• Move selection to adjacent thread when selected thread archived
• Add regression tests for remote archive synchronization
Diagram
flowchart LR
  A["thread/archived notification"] -->|"extractThreadId"| B["applyArchivedThreadNotification"]
  B -->|"wasSelectedThread"| C["findAdjacentThreadId"]
  B -->|"removeThread"| D["removeArchivedThreadFromLoadedLists"]
  C -->|"setSelectedThreadId"| E["loadMessages"]
  D -->|"updateUI"| F["projectGroups updated"]

Loading

File Changes

1. src/composables/useDesktopState.ts 🐞 Bug fix +29/-0

Implement remote thread archive notification handling

• Added applyArchivedThreadNotification() function to handle remote archive events
• Detects if archived thread was selected and finds adjacent thread for selection
• Removes archived thread from loaded lists and prunes thread-scoped state
• Loads messages for new selected thread if needed
• Integrated notification handler for thread/archived method in realtime path

src/composables/useDesktopState.ts


2. src/composables/useDesktopState.test.ts 🧪 Tests +78/-0

Add regression tests for remote archive notifications

• Added test for removing remotely archived thread from live thread list
• Added test for moving selection away when selected thread archived remotely
• Tests verify thread removal and selection migration work correctly
• Tests mock notification handler and verify state updates

src/composables/useDesktopState.test.ts


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 21, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. No perf audit for thread/archived 📘 Rule violation ➹ Performance
Description
This PR adds realtime handling for thread/archived notifications but does not include any
performance audit evidence or explicit code-path analysis for the new realtime path. This risks
regressions (extra work per notification and/or extra message loads) without the required documented
evaluation.
Code

src/composables/useDesktopState.ts[R3755-3761]

Evidence
PR Compliance ID 1 requires a performance audit (measurements or explicit analysis) for behavior
changes, explicitly considering duplicates/blocking/fanout/payload/cache risks in high-risk areas
like realtime rendering. The diff adds a new realtime thread/archived path and a handler that
mutates loaded lists, flattens thread state, and can trigger message loading, but includes no
performance audit comments/docs/artifacts alongside the change.

AGENTS.md: Performance Audit Required for Feature/Behavior Changes: AGENTS.md: Performance Audit Required for Feature/Behavior Changes
src/composables/useDesktopState.ts[3755-3761]
src/composables/useDesktopState.ts[4231-4249]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A feature/behavior change was introduced in a high-risk area (realtime notification handling / thread list updates) without the required performance audit evidence or explicit analysis.
## Issue Context
The new `thread/archived` handler can introduce per-notification work (e.g., repeated `flattenThreads(...)`) and can trigger an additional `loadMessages(...)` call when the archived thread was selected. Compliance requires documenting concrete evidence (measurements/traces/request counts) or explicitly stating what could not be measured.
## Fix Focus Areas
- src/composables/useDesktopState.ts[3755-3761]
- src/composables/useDesktopState.ts[4231-4249]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Manual test docs not updated 📘 Rule violation ⚙ Maintainability
Description
The PR changes thread-list/selection behavior when a thread is archived remotely but does not update
the relevant manual test documentation under tests//. This leaves manual verification instructions
stale for the new behavior.
Code

src/composables/useDesktopState.ts[R4231-4249]

Evidence
PR Compliance ID 2 requires updating the relevant tests// manual test doc after feature work. The
PR introduces new remote-archive behavior in useDesktopState, while the existing thread-archive
manual test doc describes local delete/archive flows and has no steps covering remote
thread/archived behavior, indicating the manual doc was not updated for this change.

AGENTS.md: Update Manual Test Documentation After Feature Work (tests/<domain>/), Keep tests.md as Index: AGENTS.md: Update Manual Test Documentation After Feature Work (tests/<domain>/), Keep tests.md as Index
src/composables/useDesktopState.ts[4231-4249]
tests/thread-loading-state/thread-archive-recovery-and-sidebar-pruning.md[1-27]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Feature work was completed without updating the relevant manual test documentation under `tests/<domain>/`.
## Issue Context
This PR adds behavior for remotely-archived threads (immediate removal from loaded list; selection moves to adjacent thread). The existing thread-archive manual test document focuses on local archive/delete behavior and does not cover remote archive notifications.
## Fix Focus Areas
- tests/thread-loading-state/thread-archive-recovery-and-sidebar-pruning.md[1-30]
- src/composables/useDesktopState.ts[3755-3761]
- src/composables/useDesktopState.ts[4231-4249]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Unhandled loadMessages rejection 🐞 Bug ☼ Reliability
Description
applyArchivedThreadNotification calls loadMessages() with void (no await/catch), so a
loadMessages failure becomes an unhandled Promise rejection and gets recorded by the global
unhandledrejection diagnostics handler. This can pollute error diagnostics and make real failures
harder to spot when remote archives happen during transient backend/network issues.
Code

src/composables/useDesktopState.ts[R4247-4249]

Evidence
The new remote-archive handler triggers void loadMessages(...) (so any rejection is not handled).
loadMessages() explicitly rethrows errors, and the app registers a global unhandledrejection
listener that records these rejections as diagnostics, making this a real observable side effect.

src/composables/useDesktopState.ts[4231-4249]
src/composables/useDesktopState.ts[4391-4513]
src/composables/useFeedbackDiagnostics.ts[196-215]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`applyArchivedThreadNotification()` triggers a background `loadMessages()` when the selected thread is archived, but it does so with `void` and without a `.catch()`. Since `loadMessages()` rethrows on failure, this creates unhandled Promise rejections that are captured by the app’s global `unhandledrejection` listener.
## Issue Context
This path can be hit by external clients (remote archive), so failures are more likely to be “background noise” (race with thread deletion, transient network/bridge failures, etc.) and should be handled as best-effort.
## Fix Focus Areas
- src/composables/useDesktopState.ts[4231-4249]
- src/composables/useDesktopState.ts[4391-4513]
## Suggested fix
- Change the fire-and-forget call to handle rejection, e.g.:
- `void loadMessages(...).catch(() => { /* best-effort */ })`
- or wrap in an async IIFE with try/catch.
- (Optional but recommended) Apply the same pattern to the existing similar call in `archiveThreadById()` to keep behavior consistent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +3755 to +3761
if (notification.method === 'thread/archived') {
const threadId = extractThreadIdFromNotification(notification)
if (threadId) {
applyArchivedThreadNotification(threadId)
}
return
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. No perf audit for thread/archived 📘 Rule violation ➹ Performance

This PR adds realtime handling for thread/archived notifications but does not include any
performance audit evidence or explicit code-path analysis for the new realtime path. This risks
regressions (extra work per notification and/or extra message loads) without the required documented
evaluation.
Agent Prompt
## Issue description
A feature/behavior change was introduced in a high-risk area (realtime notification handling / thread list updates) without the required performance audit evidence or explicit analysis.

## Issue Context
The new `thread/archived` handler can introduce per-notification work (e.g., repeated `flattenThreads(...)`) and can trigger an additional `loadMessages(...)` call when the archived thread was selected. Compliance requires documenting concrete evidence (measurements/traces/request counts) or explicitly stating what could not be measured.

## Fix Focus Areas
- src/composables/useDesktopState.ts[3755-3761]
- src/composables/useDesktopState.ts[4231-4249]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +4231 to +4249
function applyArchivedThreadNotification(threadId: string): void {
const normalizedThreadId = threadId.trim()
if (!normalizedThreadId) return

const wasSelectedThread = selectedThreadId.value === normalizedThreadId
const nextSelectedThreadId = wasSelectedThread
? findAdjacentThreadId(flattenThreads(projectGroups.value), normalizedThreadId)
: selectedThreadId.value

if (wasSelectedThread) {
setSelectedThreadId(nextSelectedThreadId)
}

removeArchivedThreadFromLoadedLists(normalizedThreadId)
pruneThreadScopedState(flattenThreads(projectGroups.value))

if (wasSelectedThread && nextSelectedThreadId) {
void loadMessages(nextSelectedThreadId, { silent: true })
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Manual test docs not updated 📘 Rule violation ⚙ Maintainability

The PR changes thread-list/selection behavior when a thread is archived remotely but does not update
the relevant manual test documentation under tests/<domain>/. This leaves manual verification
instructions stale for the new behavior.
Agent Prompt
## Issue description
Feature work was completed without updating the relevant manual test documentation under `tests/<domain>/`.

## Issue Context
This PR adds behavior for remotely-archived threads (immediate removal from loaded list; selection moves to adjacent thread). The existing thread-archive manual test document focuses on local archive/delete behavior and does not cover remote archive notifications.

## Fix Focus Areas
- tests/thread-loading-state/thread-archive-recovery-and-sidebar-pruning.md[1-30]
- src/composables/useDesktopState.ts[3755-3761]
- src/composables/useDesktopState.ts[4231-4249]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Surface thread token usage directly in the composer as a circular context indicator with a hover/focus details popover. The popover includes a compact context action that calls thread/compact/start for the active thread while keeping the new-thread composer disabled for compaction.

Render v2 contextCompaction items in the conversation as Codex-style divider rows, and show live compaction progress from item/started without duplicating the activity overlay. Historical thread/read payloads now normalize contextCompaction items so completed compactions remain visible after reload.

Tests:
- pnpm test:unit -- src/api/normalizers/v2.test.ts src/composables/useDesktopState.test.ts
- pnpm run build:frontend
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: 4

🧹 Nitpick comments (1)
src/composables/useDesktopState.test.ts (1)

680-758: ⚡ Quick win

Add a rejection-path test for manual compaction.

Current coverage validates success and notification flow, but not the compactThread failure path (e.g., pending live marker cleanup and surfaced error state). A focused failure test here would harden this feature.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/composables/useDesktopState.test.ts` around lines 680 - 758, Add a test
that mocks gatewayMocks.compactThread to reject (e.g., mockRejectedValue(new
Error('fail'))), calls state.primeSelectedThread('thread-compact') and then
awaits state.compactSelectedThread() (handling the thrown rejection with
try/catch or expect(...).rejects), and assert that gatewayMocks.compactThread
was called with 'thread-compact', that the pending "Compacting context…" live
marker is removed from state.messages (no lingering pending id like the manual
pending marker), that state.selectedLiveOverlay.value is null, and that a system
error message reflecting the compaction failure (e.g., a message with
role:'system' and a messageType indicating compaction error) is appended to
state.messages so the failure is surfaced. Ensure you reference
compactSelectedThread, gatewayMocks.compactThread, state.messages, and
state.selectedLiveOverlay in the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/content/ThreadComposer.vue`:
- Around line 796-802: The new user-facing strings in the computed
contextCompactButtonText (and the similar strings added around the second
occurrence) are hardcoded English; replace each literal ('Open thread to
compact', 'Compact after current turn', 'Compact context', and the other added
literals at the second location) with calls to the i18n translator (e.g.
t('thread.openToCompact'), t('thread.compactAfterTurn'),
t('thread.compactContext') or equivalent keys) so the component uses t(...) like
the rest of the file; update or add the corresponding translation keys in your
locale files to provide the English text and translations.

In `@src/components/content/ThreadConversation.vue`:
- Around line 4621-4630: Remove the dark-mode override from the component
stylesheet in ThreadConversation.vue and instead add the dark-theme rule for the
shared selector ".context-compaction-separator" into src/style.css;
specifically, delete the :global(.dark) .context-compaction-separator block from
ThreadConversation.vue and create a top-level rule in src/style.css using the
same selector ".dark .context-compaction-separator" with the identical
linear-gradient background so the decisive dark override lives in the shared
stylesheet rather than the component.

In `@src/composables/useDesktopState.ts`:
- Around line 3688-3693: The code currently extracts only camelCase ids via
readString(params?.threadId)/readString(params?.turnId), causing snake_case or
nested turn payloads to be missed; replace this manual extraction with the
shared notification-ID extraction helper used elsewhere (call it where you
create params) to populate threadId and turnId (so it handles thread_id/turn_id
and nested payload shapes), then compute turnIndex via
turnIndexByTurnIdByThreadId.value[threadId]?.[turnId] as before; update the
variables params, threadId, turnId, and turnIndex to use that shared helper
instead of direct readString calls.

---

Nitpick comments:
In `@src/composables/useDesktopState.test.ts`:
- Around line 680-758: Add a test that mocks gatewayMocks.compactThread to
reject (e.g., mockRejectedValue(new Error('fail'))), calls
state.primeSelectedThread('thread-compact') and then awaits
state.compactSelectedThread() (handling the thrown rejection with try/catch or
expect(...).rejects), and assert that gatewayMocks.compactThread was called with
'thread-compact', that the pending "Compacting context…" live marker is removed
from state.messages (no lingering pending id like the manual pending marker),
that state.selectedLiveOverlay.value is null, and that a system error message
reflecting the compaction failure (e.g., a message with role:'system' and a
messageType indicating compaction error) is appended to state.messages so the
failure is surfaced. Ensure you reference compactSelectedThread,
gatewayMocks.compactThread, state.messages, and state.selectedLiveOverlay in the
test.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a72fa05c-9808-4479-a137-6bfeac53a12c

📥 Commits

Reviewing files that changed from the base of the PR and between 8423549 and c35db60.

📒 Files selected for processing (9)
  • src/App.vue
  • src/api/codexGateway.ts
  • src/api/normalizers/v2.test.ts
  • src/api/normalizers/v2.ts
  • src/components/content/ThreadComposer.vue
  • src/components/content/ThreadConversation.vue
  • src/composables/useDesktopState.test.ts
  • src/composables/useDesktopState.ts
  • src/style.css

Comment on lines +796 to +802
const contextCompactButtonText = computed(() =>
props.contextCompactionAvailable === false
? 'Open thread to compact'
: props.isTurnInProgress
? 'Compact after current turn'
: 'Compact context',
)
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 | ⚡ Quick win

Localize newly added context-compaction copy.

Line 796 and Line 1007 introduce new user-facing text as hardcoded English, while this component otherwise uses t(...). This will bypass localization.

💡 Suggested fix
const contextCompactButtonText = computed(() =>
  props.contextCompactionAvailable === false
-    ? 'Open thread to compact'
+    ? t('Open thread to compact')
    : props.isTurnInProgress
-      ? 'Compact after current turn'
-      : 'Compact context',
+      ? t('Compact after current turn')
+      : t('Compact context'),
)

Also applies to: 1007-1013

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/content/ThreadComposer.vue` around lines 796 - 802, The new
user-facing strings in the computed contextCompactButtonText (and the similar
strings added around the second occurrence) are hardcoded English; replace each
literal ('Open thread to compact', 'Compact after current turn', 'Compact
context', and the other added literals at the second location) with calls to the
i18n translator (e.g. t('thread.openToCompact'), t('thread.compactAfterTurn'),
t('thread.compactContext') or equivalent keys) so the component uses t(...) like
the rest of the file; update or add the corresponding translation keys in your
locale files to provide the English text and translations.

Comment on lines +4621 to +4630
:global(.dark) .context-compaction-separator {
background:
linear-gradient(
to bottom,
transparent calc(50% - 0.5px),
rgb(63 63 70) calc(50% - 0.5px),
rgb(63 63 70) calc(50% + 0.5px),
transparent calc(50% + 0.5px)
);
}
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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Move dark-theme override for context compaction separator to src/style.css.

Line 4621 adds a decisive dark-mode surface rule in a component-scoped stylesheet. For this shared conversation surface, keep dark-theme override wiring in src/style.css and leave the component with base styles only.

Proposed change
-:global(.dark) .context-compaction-separator {
-  background:
-    linear-gradient(
-      to bottom,
-      transparent calc(50% - 0.5px),
-      rgb(63 63 70) calc(50% - 0.5px),
-      rgb(63 63 70) calc(50% + 0.5px),
-      transparent calc(50% + 0.5px)
-    );
-}
/* add in src/style.css */
.dark .context-compaction-separator {
  background:
    linear-gradient(
      to bottom,
      transparent calc(50% - 0.5px),
      rgb(63 63 70) calc(50% - 0.5px),
      rgb(63 63 70) calc(50% + 0.5px),
      transparent calc(50% + 0.5px)
    );
}

As per coding guidelines: “src/style.css: For shared route surfaces and large feature UIs, put decisive dark-theme overrides in src/style.css instead of relying only on component-scoped :global(:root.dark) blocks”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/content/ThreadConversation.vue` around lines 4621 - 4630,
Remove the dark-mode override from the component stylesheet in
ThreadConversation.vue and instead add the dark-theme rule for the shared
selector ".context-compaction-separator" into src/style.css; specifically,
delete the :global(.dark) .context-compaction-separator block from
ThreadConversation.vue and create a top-level rule in src/style.css using the
same selector ".dark .context-compaction-separator" with the identical
linear-gradient background so the decisive dark override lives in the shared
stylesheet rather than the component.

Comment on lines +3688 to +3693
const params = asRecord(notification.params)
const threadId = readString(params?.threadId)
const turnId = readString(params?.turnId)
const turnIndex = threadId && turnId
? turnIndexByTurnIdByThreadId.value[threadId]?.[turnId]
: undefined
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 | ⚡ Quick win

Use the shared notification ID extraction here.

This helper only reads camelCase threadId/turnId. The rest of the file accepts snake_case and nested turn payloads, so compaction messages can lose their turnIndex binding and render out of order for those payload shapes.

Suggested fix
   const params = asRecord(notification.params)
-  const threadId = readString(params?.threadId)
-  const turnId = readString(params?.turnId)
+  const threadId = extractThreadIdFromNotification(notification)
+  const turn = asRecord(params?.turn)
+  const turnId =
+    readString(params?.turnId) ||
+    readString(params?.turn_id) ||
+    readString(turn?.id)
   const turnIndex = threadId && turnId
     ? turnIndexByTurnIdByThreadId.value[threadId]?.[turnId]
     : undefined
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/composables/useDesktopState.ts` around lines 3688 - 3693, The code
currently extracts only camelCase ids via
readString(params?.threadId)/readString(params?.turnId), causing snake_case or
nested turn payloads to be missed; replace this manual extraction with the
shared notification-ID extraction helper used elsewhere (call it where you
create params) to populate threadId and turnId (so it handles thread_id/turn_id
and nested payload shapes), then compute turnIndex via
turnIndexByTurnIdByThreadId.value[threadId]?.[turnId] as before; update the
variables params, threadId, turnId, and turnIndex to use that shared helper
instead of direct readString calls.

Comment on lines +4017 to +4026
const contextCompactionMessage = readContextCompactionNotification(notification)
if (contextCompactionMessage && notificationThreadId) {
const pendingId = `context-compaction:pending:${notificationThreadId}`
const previousLiveAgent = liveAgentMessagesByThreadId.value[notificationThreadId] ?? []
const withoutPending = previousLiveAgent.filter((message) => message.id !== pendingId)
setLiveAgentMessagesForThread(
notificationThreadId,
upsertMessage(withoutPending, contextCompactionMessage),
)
}
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 | ⚡ Quick win

Successful compactions can leave the thread stuck busy.

compactSelectedThread() marks the thread inProgress and inserts a pending compaction message, but only the failure path clears that state. This completion block merely swaps the live message for the currently selected thread, so navigating away before item/completed lands can leave the original thread permanently busy until a later reload.

Also applies to: 4922-4946

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant