Skip to content

feat(map): merge same-groupId map operations onto one stack card (#1227)#1504

Open
isamu wants to merge 7 commits into
mainfrom
feat/map-group-merge-1227
Open

feat(map): merge same-groupId map operations onto one stack card (#1227)#1504
isamu wants to merge 7 commits into
mainfrom
feat/map-group-merge-1227

Conversation

@isamu
Copy link
Copy Markdown
Contributor

@isamu isamu commented May 25, 2026

Summary

When the agent makes several mapControl calls for one task (search places, drop markers, draw a route), they previously each rendered as a separate map card. This consumes @gui-chat-plugin/google-map 0.5.0's new groupId to merge them: results sharing a groupId collapse into ONE stack card and accumulate on a single map.

Depends on the just-published plugin release: @gui-chat-plugin/google-map@0.5.0 (PR receptron/GUIChatPluginGoogleMap#19).

Items to Confirm / Review

  • Grouping is session-wide, positioned at the group's first occurrence. A later same-groupId update lands on the existing card (not a new one) — this matches the "更新まで考えると" requirement. The alternative (contiguous-only) was rejected because it wouldn't catch later updates.
  • Scroll-spy / selection compatibility. A group card spans multiple result uuids. The card element is registered under every member uuid (setItemRefForMembers), so computeActiveUuidFromScroll and the scroll-to-selection watch (both keyed on individual uuids) resolve any member to the one card without changes to that machinery. Please sanity-check that interaction.
  • Scope: stack layout only. The single-layout (selectedResult) path in App.vue still shows one result at a time. Tracked as a follow-up (see below).

Changes

  • package.json: @gui-chat-plugin/google-map ^0.4.0^0.5.0 (+ yarn.lock).
  • src/components/StackView.vue:
    • displayItems computed — walks toolResults, collapses mapControl results with the same non-empty groupId into one item (members[], ordered; head = latest, drives the card header). Everything else stays a singleton.
    • Map-group cards mount one View with :results="item.members" (the View replays the group onto one map); all other cards keep :selected-result only.
    • setItemRefForMembers registers the group element under every member uuid; selection highlight fires if any member is selected; badge shows op count.

Follow-up (separate issue)

Single-layout (App.vue) map grouping + any UX polish on group card headers. Will file per the earlier recommendation.

Test plan

  • yarn typecheck / yarn lint / yarn build clean
  • component tests (incl. stackview googlemap wiring) green
  • Verified end-to-end locally against the linked plugin before publish (reporter confirmed merge behavior)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Results with the same group ID collapse into single stacked cards showing the latest item as header; selecting any member highlights the card and emits the group's head.
    • Plugins receive group member data to render full-group views.
  • Bug Fixes

    • Scroll and active-selection now operate on rendered cards, improving visibility and auto-scroll behavior for merged results.
  • Tests

    • Unit tests for grouping, active-card detection, and scroll-target logic; new end-to-end tests and a pub/sub test fixture for streaming/scroll scenarios.
  • Chores

    • Updated google-map plugin dependency to 0.5.0.

Review Change Stack

…1227)

Consumes @gui-chat-plugin/google-map 0.5.0's new `groupId` + View
`results` replay prop. StackView now groups `mapControl` results by
`groupId` (session-wide, positioned at the group's first occurrence)
into ONE card and passes the ordered group to a single map View via
`:results`, so markers / routes accumulate on one map and later
same-groupId updates land on the same card instead of spawning a new
one per call.

- bump `@gui-chat-plugin/google-map` ^0.4.0 → ^0.5.0
- StackView: `displayItems` computed collapses same-groupId map
  results; group cards register their element under every member
  uuid so the scroll-spy / scroll-to-selection (keyed on individual
  uuids) keep working; `N×` badge shows the op count
- non-grouped results and all other plugins render exactly as before
  (singletons, `:selected-result` only)

Scope: stack layout only. The single-layout (`selectedResult`) path
in App.vue still shows one result at a time — tracked as a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Sorry @isamu, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Bumps google-map to v0.5.0, adds StackDisplayItem and helpers to group ordered mapControl results, integrates grouping into StackView (shared refs/head/members), and adds unit and E2E tests plus a Playwright pub/sub fixture for streaming behavior.

Changes

Map Result Grouping and Plugin Update

Layer / File(s) Summary
Google-map plugin dependency update
package.json, packages/mulmoclaude/package.json
@gui-chat-plugin/google-map updated from ^0.4.2 to ^0.5.0.
Grouping utility and scroll helpers
src/utils/canvas/stackGrouping.ts
Add StackDisplayItem<T>, buildStackDisplayItems, pickActiveCardUuid, and resolveLatestScrollTarget to collapse ordered results into DOM-ordered cards and compute active/scroll targets.
Unit tests for grouping and scroll logic
test/utils/canvas/test_stackGrouping.ts
Add tests exercising grouping (consecutive and non-contiguous merges, ordering, singletons), pickActiveCardUuid regression, and resolveLatestScrollTarget behaviors.
StackView template, integration, and scroll/selection fixes
src/components/StackView.vue
Render computed displayItems (keyed by item.key), map grouped member UUIDs to the same card ref, use item.head for plugin rendering, pass item.members only for group cards, and update scroll-spy and auto-scroll to use new helpers.
Playwright pub/sub fixture and E2E tests
e2e/fixtures/pubsub.ts, e2e/tests/stack-map-grouping-scroll.spec.ts, e2e/tests/streaming-autoscroll.spec.ts
Add a WebSocket pub/sub test harness (mocking subscribe/stream frames), scroll-stability/metrics helpers, an E2E regression ensuring grouped map cards merge and are scrolled into view, and update streaming-autoscroll to use shared fixtures.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A rabbit hops where maps converge,
Heads lead the way, the members merge,
One click, one scroll finds clustered friends,
The stack now hums where order bends,
Small paws applaud the tidy ends.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% 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 title clearly describes the main feature: merging map operations with the same groupId onto a single stack card, which is the primary purpose of this pull request.
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
  • Commit unit tests in branch feat/map-group-merge-1227

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.

@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 25, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​gui-chat-plugin/​google-map@​0.4.2 ⏵ 0.5.075 +110098 +193 +1100

View full report

// scroll-spy and scroll-to-selection logic (which key on individual
// result uuids) resolve any member to this one card.
function setItemRefForMembers(members: ToolResultComplete[], element: HTMLElement | null): void {
for (const member of members) setItemRef(member.uuid, element);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Mapping every grouped member UUID to the same DOM element is necessary, but it breaks the assumption in computeActiveUuidFromScroll() that props.toolResults order matches DOM order one-to-one. With non-contiguous groups (A(map:g1), B(text), C(map:g1)), itemRefs.get(C.uuid) points to A's card, so the scroll-spy loop can overwrite the active item with C while the viewport is actually on B. That causes incorrect sidebar selection/jumps after grouping. Please update active-item computation to iterate rendered cards (displayItems) or otherwise dedupe by element before applying the top-edge scan.

@github-actions
Copy link
Copy Markdown

CODEX VERDICT: CHANGES REQUESTED

  • src/components/StackView.vue: Grouped map members now share one DOM card, but scroll-spy still computes active selection by iterating props.toolResults as if each UUID mapped to a distinct card. For non-contiguous group reuse (A(map:g1), B(text), C(map:g1)), the later member (C) maps back to A's element and can incorrectly override the active UUID while viewing B, causing wrong sidebar selection / jump behavior. Please align active-item computation with rendered card order (e.g., displayItems) and add a regression test for this non-contiguous grouping case.

@github-actions
Copy link
Copy Markdown

CODEX VERDICT: CHANGES REQUESTED

  • src/components/StackView.vue:427computeActiveUuidFromScroll() still iterates props.toolResults (UUID list order) instead of rendered card order after map grouping. In non-contiguous reuse (A(map:g1), B(text), C(map:g1)), both A and C resolve to the same DOM element, so the loop can advance active selection to C while viewport is on B, causing incorrect sidebar highlight/selection jumps. Please compute active selection from displayItems (or dedupe by element before scan) and add a regression test for the non-contiguous grouped case.

Copy link
Copy Markdown

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/StackView.vue`:
- Around line 231-249: The scroll logic is using props.toolResults order while
the rendered cards come from computed displayItems; update both
computeActiveUuidFromScroll() and the "scroll to latest" watcher to iterate over
displayItems and use a single canonical UUID per rendered card (e.g. use
item.head.uuid) instead of raw result.uuid, so scroll-spy and latest-result
scrolling follow the DOM order created by displayItems (ensure any map group
keys like `mapgroup:${groupId}` remain referenced only for identity, not for
scroll position).
🪄 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

Run ID: 2181f3d2-2c68-487e-b769-e8e092ad0857

📥 Commits

Reviewing files that changed from the base of the PR and between f3157fd and ba37e96.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • package.json
  • src/components/StackView.vue

Comment thread src/components/StackView.vue Outdated
…#1504)

Codex CHANGES REQUESTED: with session-wide map grouping a group card
shares one DOM element across multiple member uuids. The scroll-spy's
`computeActiveUuidFromScroll` iterated the flat `toolResults`, so for
non-contiguous reuse (`A(g1), B(text), C(g1)`) the later member `C`
resolved back to the group's earlier element and could wrongly win
the active slot while viewing `B` — corrupting sidebar selection.

- Extract the grouping into a pure, testable helper
  `src/utils/canvas/stackGrouping.ts` (`buildStackDisplayItems`).
- Scroll-spy now iterates `displayItems` (rendered card order) and
  emits each card's head uuid, so active-item tracking matches what's
  actually on screen.
- Regression test `test/utils/canvas/test_stackGrouping.ts` covers the
  non-contiguous `A(g1), B, C(g1)` case + contiguous, distinct-group,
  ungrouped, and lone-group cases (5 tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

CODEX VERDICT: CHANGES REQUESTED

  • src/components/StackView.vue:456-467latestResultScrollKey still always forces container.scrollTop = container.scrollHeight on every new tool result. After this PR’s session-wide grouping, a new mapControl result can merge into an earlier card (A(g1), B, C(g1)), so the selected-result watcher scrolls to the grouped card but this watcher immediately overrides it and jumps to the bottom. In practice, updates can land off-screen and selection/viewport can desync. Please make auto-scroll target the rendered card for the latest result (via displayItems / member→card mapping) instead of unconditional bottom scroll, and add a regression test for non-contiguous grouped updates.

…e (Codex #1504)

Second scroll-order fix from the Codex review. `latestResultScrollKey`
auto-scrolled the stack to the bottom on every new tool result. With
session-wide map grouping a new `mapControl` result can merge into an
EARLIER group card, so bottom-scrolling jumped away from where the
update rendered. Now: bottom-scroll only when the newest result is in
the last card; otherwise bring its (earlier) card into view via
scrollIntoView({ block: "nearest" }).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@isamu
Copy link
Copy Markdown
Contributor Author

isamu commented May 25, 2026

Addressed both scroll-order findings from the Codex / CodeRabbit review (same root cause: scroll logic assumed toolResults order == DOM order, which session-wide grouping breaks).

  1. computeActiveUuidFromScroll iterated the flat toolResults (1472086). For non-contiguous reuse (A(g1), B, C(g1)), member C resolved back to the g1 card's element and could wrongly win the active slot while viewing B. Now iterates displayItems (rendered card order) and emits each card's head uuid. Extracted the grouping into a pure, tested helper src/utils/canvas/stackGrouping.ts with a regression test (test_stackGrouping.ts, 5 cases incl. the non-contiguous one).

  2. latestResultScrollKey always bottom-scrolled on a new result (f1ae5d9). A new mapControl result that merges into an earlier group card would jump the view to the bottom, away from where the update rendered. Now bottom-scrolls only when the newest result is in the last card; otherwise scrollIntoView({ block: "nearest" }) on its (earlier) card.

yarn typecheck / lint / build clean; component + grouping tests green (8).

@github-actions
Copy link
Copy Markdown

CODEX VERDICT: CHANGES REQUESTED

  • src/components/StackView.vue scroll/selection fixes for grouped map cards look correct, and the previously raised logic defects appear resolved in the current diff. However, there is still no behavior-level regression test covering the non-contiguous grouped sequence (A(g1), B, C(g1)) through StackView scroll handling (computeActiveUuidFromScroll and latestResultScrollKey watcher). The new test file validates buildStackDisplayItems, but not the DOM/scroll interactions that previously regressed. Please add at least one component-level regression test for this path so these fixes remain protected.

…r level (Codex #1504)

Codex's remaining ask: the scroll fixes were correct but only
buildStackDisplayItems had a unit test — the DOM/scroll decisions that
actually regressed (active-item selection, latest-result auto-scroll)
were untested. Extract their pure cores out of StackView so they can be
exercised without a browser:

- pickActiveCardUuid — scroll-spy walk over rendered card order; returns
  the canonical (head) uuid of the last card at/above the padded line.
- resolveLatestScrollTarget — bottom vs. earlier-card decision for a new
  result under session-wide grouping.

StackView now delegates to both. Regression tests drive the exact
non-contiguous sequence A(g1), B, C(g1) through buildStackDisplayItems
into each helper: viewing B must select B (not member C), and a new C
merging into the top group card must target that card, not the bottom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
beginSuppressScrollSync();
containerRef.value.scrollTop = containerRef.value.scrollHeight;
const newest = props.toolResults[props.toolResults.length - 1];
const target = resolveLatestScrollTarget(displayItems.value, newest, (result) => result.uuid);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The scroll-order fixes look correct, but this path is still only regression-tested at helper level (stackGrouping.ts). We still do not have a StackView-level test that exercises the actual watcher/DOM wiring here (nextTick + scrollIntoView + beginSuppressScrollSync) for a non-contiguous grouped sequence like A(g1), B, C(g1). Please add one component test that proves (1) active selection resolves to B when viewport is on the B card, and (2) a latest C(g1) update targets the grouped card rather than unconditional bottom scroll.

@github-actions
Copy link
Copy Markdown

CODEX VERDICT: CHANGES REQUESTED

  • Add a StackView-level regression test for the non-contiguous grouped sequence (A(g1), B, C(g1)) that exercises the real Vue watcher/DOM wiring (computeActiveUuidFromScroll path + latestResultScrollKey auto-scroll path). The helper-level tests in test_stackGrouping.ts are good, but they do not protect against regressions in nextTick/scrollIntoView/suppression interaction inside StackView.vue.

@isamu
Copy link
Copy Markdown
Contributor Author

isamu commented May 25, 2026

Addressed the remaining Codex ask (regression test coverage for the scroll path itself, f60c59e).

The earlier commits fixed the logic; Codex correctly noted only buildStackDisplayItems was unit-tested — the DOM/scroll decisions that actually regressed had no guard. Extracted their pure cores out of StackView.vue so they're testable without a browser (the repo has no Vue component-unit harness — e2e/ covers true DOM):

  • pickActiveCardUuid — the scroll-spy walk over rendered card order; returns the canonical (head) uuid of the last card at/above the padded line.
  • resolveLatestScrollTarget — the bottom-vs-earlier-card decision for a new result under session-wide grouping.

StackView now delegates to both. New tests drive the exact non-contiguous sequence A(g1), B, C(g1) through buildStackDisplayItems into each helper:

  • viewing B selects B, never member C (the flat-walk regression);
  • a new C merging into the top group card targets that card, not the bottom.

test/utils/canvas/test_stackGrouping.ts is now 12 cases (5 grouping + 3 scroll-spy + 4 latest-scroll). lint / typecheck / build clean.

#1504)

Codex asked for a component-level regression covering the real Vue
watcher / DOM wiring (nextTick + scrollIntoView + scroll-suppression),
not just the pure helpers. The repo has no Vue component-unit harness —
e2e/ is the sanctioned DOM surface — so add a Playwright spec:

- preload a session whose transcript already has A(map,g1) + B(map,g2),
  then stream a live C(map,g1) AFTER the load settles (a startDelayMs
  avoids racing the transcript fetch, which otherwise overwrites live
  events);
- assert C merges into the earlier g1 card (two map cards, never three)
  and that the latest-result watcher scrolls to that g1 card rather than
  slamming scrollTop to the bottom.

A tall transcript turn between the two map cards separates them by more
than a viewport so "scrolled to g1" and "bottom-pinned" are
unambiguously distinct positions.

Extract the pub/sub socket harness shared with streaming-autoscroll into
e2e/fixtures/pubsub.ts (DRY) and add an optional startDelayMs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

CODEX VERDICT: CHANGES REQUESTED

  • The previously reported latest-result auto-scroll issue is now addressed and covered by e2e/tests/stack-map-grouping-scroll.spec.ts (good). However, the remaining part of the earlier Codex request is still unprotected at DOM level: there is no StackView behavior test that asserts the scroll-spy active-card path (computeActiveUuidFromScroll) in the non-contiguous grouped sequence (A(g1), B, C(g1)) resolves to B while viewing the B card. The helper-level unit tests in test/utils/canvas/test_stackGrouping.ts are useful, but they do not exercise the Vue watcher/DOM integration for active selection. Please add one behavior-level assertion for that path so the regression is fully covered end-to-end.

@isamu
Copy link
Copy Markdown
Contributor Author

isamu commented May 26, 2026

Added the component-level regression Codex asked for (real Vue watcher / DOM wiring, not just the pure helpers) — 3cd68c95.

This repo has no Vue component-unit harness; e2e/ is the sanctioned DOM surface (same call the existing streaming-autoscroll.spec.ts makes). New spec e2e/tests/stack-map-grouping-scroll.spec.ts:

  • preloads a session whose transcript already has A(map, g1) + B(map, g2), then streams a live C(map, g1) after the load settles (a startDelayMs on the socket harness avoids racing the transcript fetch, which otherwise overwrites live events — confirmed empirically while building this);
  • asserts C merges into the earlier g1 card (two map cards rendered, never three) and that the latestResultScrollKey watcher scrolls to that g1 card via scrollIntoView rather than slamming scrollTop to the bottom.

A tall transcript turn between the two map cards separates them by more than a viewport, so "scrolled to g1" (g1 in viewport, distance-from-bottom ≈ 1800px) and "bottom-pinned" are unambiguously distinct — the regression would surface g2 and push g1 off the top.

The pub/sub socket harness shared with streaming-autoscroll is extracted into e2e/fixtures/pubsub.ts (DRY).

Validation: full e2e 438 passed, test_stackGrouping.ts 12 cases, lint / typecheck / build clean.

Coverage now spans all three layers: pure decision logic (unit), thin StackView delegation, and the end-to-end DOM/scroll wiring.

Copy link
Copy Markdown

@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)
e2e/tests/stack-map-grouping-scroll.spec.ts (2)

116-117: ⚡ Quick win

Prefer data-testid selectors over text/position selectors in E2E assertions.

getByText(...).first()/count checks are brittle to copy or localization changes. Please target stable functional test-ids instead.

As per coding guidelines, e2e/**/*.{ts,js}: "use data-testid for element selection named by function, not position".

Also applies to: 123-123, 129-129

🤖 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 `@e2e/tests/stack-map-grouping-scroll.spec.ts` around lines 116 - 117, Replace
brittle text/position selectors using page.getByText(MAP_PLACEHOLDER).first()
and page.getByText(MAP_PLACEHOLDER).toHaveCount(2) with stable data-testid
selectors; change the assertions to use
page.getByTestId('map-placeholder').first() and
page.getByTestId('map-placeholder')/toHaveCount(2) (or the existing test id
string used in the DOM) and update the test fixture/markup that renders
MAP_PLACEHOLDER to include the corresponding data-testid attribute if it doesn't
already exist so the selectors are stable across copy/localization changes.

57-59: ⚡ Quick win

Replace event/tool string literals with shared constants.

Use the existing as const event/tool name objects for these transcript/live event payloads to keep tests aligned with app contracts.

As per coding guidelines, **/*.{ts,tsx,vue}: "Use existing as const objects for scheduler types, event types, API routes, and tool names instead of string literals".

Also applies to: 67-68, 70-70, 77-78

🤖 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 `@e2e/tests/stack-map-grouping-scroll.spec.ts` around lines 57 - 59, Replace
the string literals in the test payloads with the shared as-const constants:
swap "tool_result" and "tool" for the project's event/type constants (e.g.,
EVENT_TYPES.TOOL_RESULT and EVENT_SOURCES.TOOL) and replace the inline
"mapControl"/toolName and the `Map operation ${action}` message template with
the shared tool name and message constants (e.g., TOOL_NAMES.MAP_CONTROL and the
corresponding TOOL_MESSAGES.MAP_OPERATION template) used by the app; update all
occurrences around the mapData(action, groupId) uses (and the other spots noted)
so the test uses the same exported as‑const objects for event types, sources,
tool names, and messages instead of string literals.
🤖 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 `@e2e/fixtures/pubsub.ts`:
- Around line 60-84: The function mockAgentWithPubSub is doing two jobs
(WebSocket mock + HTTP /api/agent mock) and exceeds the max length; refactor by
extracting the WebSocket part into a helper (e.g., mockPubSubWebSocket(page:
Page, events: readonly unknown[], opts: StreamOptions)) that calls
page.routeWebSocket, sends the initial "0" frame, and wires handleSocketFrame,
and extract the HTTP route into a helper (e.g., mockAgentHttpRoute(page: Page))
that calls page.route for urlEndsWith("/api/agent") and fulfills the 202 json;
then replace the body of mockAgentWithPubSub with two short calls to those
helpers to keep mockAgentWithPubSub small and preserve existing behavior and
signatures.
- Around line 88-105: The waitForScrollHeightStable function can return silently
on timeout which hides failures; update waitForScrollHeightStable to throw a
descriptive error if the deadline is reached without stability, including
relevant debug details (e.g., last observed scrollHeight, last sample time,
testId and maxWaitMs) so callers fail fast and tests are diagnostic; keep the
existing loop and stability logic (references: function
waitForScrollHeightStable, variables last, stable, deadline) and add a final
throw after the while loop that includes those values.

In `@e2e/tests/stack-map-grouping-scroll.spec.ts`:
- Around line 111-136: The test body in the "a newly streamed same-groupId..."
callback is too long; split it into small helper functions and call them from
the test to reduce function size. Create helpers such as setupStackSession(page)
that wraps mockAgentWithPubSub and openSessionInStackMode,
assertPreloadedMapsVisible(page) that checks MAP_PLACEHOLDER count and
visibility, waitForStreamMerge(page) that waits STREAM_AFTER_LOAD_MS and calls
waitForScrollHeightStable("stack-scroll") and re-checks map count, and
assertScrollNotBottom(page) that uses scrollMetrics("stack-scroll") to assert
distanceFromBottomPx > BOTTOM_TOLERANCE_PX and that the first MAP_PLACEHOLDER is
in viewport; replace the inline blocks in the test with these helper calls so
the top-level test callback is under the function-size guideline.

---

Nitpick comments:
In `@e2e/tests/stack-map-grouping-scroll.spec.ts`:
- Around line 116-117: Replace brittle text/position selectors using
page.getByText(MAP_PLACEHOLDER).first() and
page.getByText(MAP_PLACEHOLDER).toHaveCount(2) with stable data-testid
selectors; change the assertions to use
page.getByTestId('map-placeholder').first() and
page.getByTestId('map-placeholder')/toHaveCount(2) (or the existing test id
string used in the DOM) and update the test fixture/markup that renders
MAP_PLACEHOLDER to include the corresponding data-testid attribute if it doesn't
already exist so the selectors are stable across copy/localization changes.
- Around line 57-59: Replace the string literals in the test payloads with the
shared as-const constants: swap "tool_result" and "tool" for the project's
event/type constants (e.g., EVENT_TYPES.TOOL_RESULT and EVENT_SOURCES.TOOL) and
replace the inline "mapControl"/toolName and the `Map operation ${action}`
message template with the shared tool name and message constants (e.g.,
TOOL_NAMES.MAP_CONTROL and the corresponding TOOL_MESSAGES.MAP_OPERATION
template) used by the app; update all occurrences around the mapData(action,
groupId) uses (and the other spots noted) so the test uses the same exported
as‑const objects for event types, sources, tool names, and messages instead of
string literals.
🪄 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

Run ID: 87a89bb6-aaaf-4d13-81b4-eb6dfaa64f1b

📥 Commits

Reviewing files that changed from the base of the PR and between f60c59e and 3cd68c9.

📒 Files selected for processing (3)
  • e2e/fixtures/pubsub.ts
  • e2e/tests/stack-map-grouping-scroll.spec.ts
  • e2e/tests/streaming-autoscroll.spec.ts

Comment thread e2e/fixtures/pubsub.ts Outdated
Comment thread e2e/fixtures/pubsub.ts
Comment thread e2e/tests/stack-map-grouping-scroll.spec.ts Outdated
…only at mount (Codex #1504)

Writing the DOM-level scroll-spy regression Codex asked for surfaced a
latent bug: the scroll container is in StackView's `v-else` branch
(rendered only once `toolResults` is non-empty), but the scroll-spy
listener was bound in `onMounted` against `containerRef`. Sessions mount
empty — the transcript / stream populates them after mount — so the
listener attached to a null ref and never fired; scroll-spy
active-card selection silently did nothing for any session that started
empty (i.e. essentially all of them). Pre-existing; not introduced by
the grouping work, but it blocked the behaviour under test.

Bind via `watch(containerRef)` instead so the listener (re)attaches
whenever the container appears or is replaced.

Also adds the scroll-spy regression test Codex requested and addresses
CodeRabbit's review of the e2e additions:
- new spec case: in the non-contiguous sequence A(g1), B, C(g1),
  scrolling to B selects B — not the merged group whose later member C
  sits above B in the flat list (the exact computeActiveUuidFromScroll
  regression);
- split `mockAgentWithPubSub` into `mockPubSubSocket` + `mockAgentEndpoint`;
- `waitForScrollHeightStable` now throws on timeout instead of returning
  silently;
- extract shared spec helpers (serveTranscript / openStackSession /
  stackCard / expectNotBottomPinned) to keep each test body small.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

CODEX VERDICT: LGTM

Reviewed current head (07f098456c9ffae93e4589b7c4b8eeaca015805f) against the existing PR thread.

  • Prior Codex CHANGES REQUESTED items around non-contiguous grouped-map scroll behavior are now resolved in the current diff.
  • StackView now computes active/auto-scroll targets from rendered displayItems (not raw toolResults order), which addresses the original A(g1), B, C(g1) regression class.
  • Behavior-level coverage is now present for both paths in e2e/tests/stack-map-grouping-scroll.spec.ts (latest-result auto-scroll and scroll-spy active-card selection).

I did not find additional correctness/security/accessibility/i18n lockstep concerns beyond what the thread already covered.

@socket-security
Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm brotli is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/brotli@1.3.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/brotli@1.3.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm brotli is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/brotli@1.3.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/brotli@1.3.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@isamu
Copy link
Copy Markdown
Contributor Author

isamu commented May 26, 2026

Added the scroll-spy DOM regression Codex asked for — and it surfaced a latent bug, which is exactly what that test was meant to catch (07f09845).

Bug found & fixed

StackView's scroll container is in the v-else branch (rendered only once toolResults is non-empty), but the scroll-spy listener was bound in onMounted against containerRef. Sessions mount empty (transcript/stream populates them after mount), so the listener attached to a null ref and never fired — scroll-spy active-card selection silently did nothing for any session that starts empty. Pre-existing (not introduced by the grouping work), but it blocked the behaviour under test. Fixed by binding via watch(containerRef) so the listener (re)attaches whenever the container appears or is replaced.

I confirmed this empirically: onMounted logged hasContainer:false, and no scroll handler fired until the watch-based bind was in place.

Codex's scroll-spy ask

New spec case: in the non-contiguous sequence A(g1), B, C(g1), scrolling to B selects B — not the merged group whose later member C sits above B in the flat result list (the exact computeActiveUuidFromScroll regression). Asserted via the card's selection highlight, driven by a real wheel gesture. 8/8 across --repeat-each (no flakiness).

CodeRabbit's review of the e2e additions

  • split mockAgentWithPubSubmockPubSubSocket + mockAgentEndpoint;
  • waitForScrollHeightStable now throws on timeout instead of returning silently;
  • extracted shared spec helpers (serveTranscript / openStackSession / stackCard / expectNotBottomPinned) so each test body stays small.

Validation: full e2e 439 passed, test_stackGrouping.ts 12 cases, lint / typecheck / build clean.

Coverage now spans pure decision logic (unit) + both DOM/scroll paths (auto-scroll and scroll-spy) end-to-end.

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.

1 participant