Set the same explicit AppUserModelID that the legacy Electron app used so Windows taskbar grouping and notification attribution stay consistent in the Tauri desktop build.
Match the Tauri package, Cargo, and bundle version metadata to the current legacy desktop version so About dialogs and installer artifacts stop reporting 0.1.0.
Reconcile optimistic user messages by replacing the oldest synthetic pending message when the server-backed message arrives. Stop sending prompt part ids and rely on message-level replacement so v1.2.25 validation passes without duplicating optimistic content.
Disable follow-mode virtualization churn and simplify reasoning header layout so streaming thinking blocks stop nudging the scroll position while the list is pinned to bottom.
Keep visible rows mounted during follow-up measurements and clear stale refs so async message rendering no longer flickers or measures detached blocks. Coalesce per-item render notifications so content-heavy rows only trigger one remeasurement per frame.
Complete re-review of PR #188 (commits 224cab6 feature + 2c27fc5 perf/i18n follow-up). Gatekeeper focus: standards, correctness, perf/complexity, and translation completeness.
What this changes (pre -> post)
Pre: timeline primarily navigation/hover preview; bulk delete selection message-level and token metrics tied to backend assistant output tokens (missing tool payload weight).
Post: segment-level timeline selection + range (Shift) + toggle (Ctrl/Meta) + mobile long-press; histogram ribs overlay showing relative + absolute (~10k cap) token weight; assistant-turn grouping to avoid adjacency bugs; bulk-delete toolbar shows Before / Selection / After token pills.
Code standards / correctness
OK: Solid signal/memo/effect patterns with cleanup; no obvious lifecycle leaks. Grouping avoids adjacency overlap by mapping messageId to turns.
Fix: selection-id stability is mitigated by pruning stale ids after segment rebuilds; long term stable ids from part ids/toolPartIds remain recommended.
Fix: token counts now share getPartCharCount in both x-ray overlay and bulk-delete toolbar, keeping estimates consistent with live store updates.
Performance / complexity
OK: O(n^2) hotspots removed for liveSegmentChars and selectedTokenTotal. groupRole + deleteUpTo hover checks now memoize messageId sets/maps.
Note: getPartCharCount can be heavy for large tool payloads but remains gated behind selection mode.
CSS / UI integration
Fix: x-ray token label now uses theme tokens instead of hard-coded colors. Delete toolbar now uses menu-based controls with selection-mode toggle.
i18n
Fix: selection hint now renders Cmd/Ctrl via localized modifier placeholder; all locales updated.
Addresses bot review feedback on commit 224cab6.
## Performance: liveSegmentChars O(n²) → O(n)
The memo had three inner loops scanning all props.segments per unique
messageId. Added a single O(n) pre-pass building a
segmentsByMessageId Map, then replaced all three inner loops with
map lookups. Total complexity: O(n) instead of O(m×n).
File: packages/ui/src/components/message-timeline.tsx
## Performance: selectedTokenTotal O(n²) → O(n)
For each selected messageId, the memo scanned all segments to sum
chars. On "Select all" this was O(selected × segments). Now builds a
charsByMessageId map in one O(n) pass and does O(1) lookups per
selected message. Same pattern as aggregateTokensByMessageId.
File: packages/ui/src/components/message-section.tsx
## SSR guard: resize listener
window.addEventListener("resize", computeBadgeLayout) lacked a
typeof window !== "undefined" guard. Other window usage in the file
was guarded. Wrapped the addEventListener, requestAnimationFrame, and
onCleanup block in the guard.
File: packages/ui/src/components/message-timeline.tsx
## i18n: mirror selectionHint key in 5 locale files
messageSection.bulkDelete.selectionHint was only defined in
en/messaging.ts. Added the key (English string, since Ctrl/Shift/Esc
are universal keyboard labels) to es, fr, ja, ru, and zh-Hans.
Files: packages/ui/src/lib/i18n/messages/{es,fr,ja,ru,zh-Hans}/messaging.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Overhauls the message timeline sidebar with segment-level selection,
token-aware xray histogram bars, and messageId-based grouping — replacing
the previous message-level selection and positional adjacency logic.
## Selection System (SELECTION-SYSTEM)
- Dual-level selection: `selectedTimelineIds` (segment IDs) as the
source of truth, bridged to `selectedForDeletion` (message IDs) via
a reactive `createEffect`.
- CTRL+Click: toggles individual segments. Clicking an assistant parent
with unexpanded tools expands the group and selects all members.
Re-clicking collapses and deselects.
- SHIFT+Click: range selection. Direction follows anchor state — if the
anchor is selected the range is additive; if not, subtractive.
- Escape: clears all selection via a global keydown listener.
- Long-press (500ms, 10px jitter tolerance): mobile/touch selection
via pointer events with context-menu suppression.
- Scroll anchor preservation: captures badge offsetTop before toggling
visibility, restores scrollTop after layout shift.
## Token Count Fix (TOKEN-COUNT-FIX)
- New `getPartCharCount()` estimates characters for any `ClientPart`.
Handles text, tool state (input/output/metadata), and content arrays.
- **Skips `filediff` metadata key** — this key contains full before/after
file content that inflated character counts by 10-100x.
- `totalChars` field added to `TimelineSegment` and `PendingSegment`,
accumulated during `buildTimelineSegments()`.
## Scroll Performance (SCROLL-PERF)
- Two-tier positioning replaces per-badge `getBoundingClientRect` on
every scroll event:
1. `computeBadgeLayout()` — expensive pass, runs once on activation,
resize, or expansion. Stores `layoutTop` relative to scroll content.
2. `handleScrollRaf()` — RAF-throttled, reads 1 container rect per
frame. Derives all badge screen positions arithmetically.
- `clipBounds` subtracts delete toolbar height + 16px gap when toolbar
is visible, preventing xray bars from overlapping the toolbar.
## Group Logic (GROUP-LOGIC)
- `getAdjacentGroup()`: changed from backward positional walk to
`segments.filter(s => s.messageId === clicked.messageId)`. Fixes
cross-message group overlap when consecutive tool segments belong to
different assistant messages.
- `groupRole()`: checks for sibling tools via `messageId`.
- `isGroupStart()`: checks previous segment's `messageId`.
- Only assistant badges trigger group selection; tool and user badges
are always standalone.
## Active Highlight (ACTIVE-HIGHLIGHT)
- Renamed `activeMessageId` → `activeSegmentId` (signal, prop, and
comparison). Clicking a badge now highlights only that specific badge,
not all badges sharing the same messageId.
- Intersection observer resolves messageId → first segment's id.
- Auto-scroll effect uses segment id directly (no `.find()` lookup).
## XRay Histogram Bars (XRAY-BARS)
- Portal-based overlay with two bars per segment:
- Relative bar: width = tokens/maxTokens, green-to-red gradient.
- Absolute bar: width = tokens/10000 (capped), grey, with red glow
overflow indicator when tokens exceed ABSOLUTE_TOKEN_CAP (10K).
- Token labels as pill-shaped badges (white bg, dark border, 12px font,
1.5rem height matching badge height) at the left tip of each bar.
- `liveSegmentChars` memo fetches fresh char counts from the message
store to handle stale tool output that arrived after segment creation.
- `aggregateTokensByMessageId` memo: O(n) pre-computation replacing the
previous O(n²) per-segment iteration inside `<For>`.
- `clip-path: inset(...)` clips bars at layout edges.
## Delete Toolbar Token Display (TOKEN-TOTAL-IN-TOOLBAR)
- Removed `outputTokensByMessageId` (backend `entry.outputTokens` only
counted assistant output, missing tool result content entirely).
- `selectedTokenTotal` now sums `seg.totalChars` across all segments
for each selected messageId, divides by 4. Consistent with xray bars.
- Three color-coded pills: Before (muted, current context), Selection
(red, tokens being removed), After (green, remaining after deletion).
Eliminates mental arithmetic for users targeting a context token count.
## Delete Hover Fix
- Removed `selected.has(segment.messageId)` → `return true` from
`isDeleteHovered()`. The red delete overlay now only activates from
actual hover interactions (kind === "message" or "deleteUpTo"), not
from the selection state. This prevents the red overlay from masking
the blue segment-level selection highlight.
## CSS Changes
- message-selection.css: Restyled toolbar with accent-primary scheme,
three-pill token group, button variants (--delete, --cancel), hint.
- message-timeline.css: Selection styling (!important overrides), group
indicators (left border), xray overlay (fixed fullscreen, z-index 40),
rib/bar/label styles, container layout, stacking context isolation.
## Files Changed
- packages/ui/src/components/message-section.tsx (+345/-197)
- packages/ui/src/components/message-timeline.tsx (+671/-199)
- packages/ui/src/lib/i18n/messages/en/messaging.ts (+1/-2)
- packages/ui/src/styles/messaging/message-selection.css (+107/-34)
- packages/ui/src/styles/messaging/message-timeline.css (+146/-0)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>