Skip to content

feat(webapp): floating Ask Trigger chat window - #4794

Open
kathiekiwi wants to merge 26 commits into
mainfrom
feat/chat-bottom-default-mode
Open

feat(webapp): floating Ask Trigger chat window#4794
kathiekiwi wants to merge 26 commits into
mainfrom
feat/chat-bottom-default-mode

Conversation

@kathiekiwi

@kathiekiwi kathiekiwi commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

The Ask Trigger chat now opens as a floating window docked at the bottom right, replacing the right-hand column mode. The window can be dragged anywhere on the page by its header and resized from all edges and corners.

  • Live watch results show the agent spinner while watching; Investigate/Watch CTAs outside the chat use the Ask Trigger glyph.
  • Storybook: the Chat UI story demos the floating shell
Screenshot 2026-08-26 at 18 01 37 Screenshot 2026-08-26 at 18 01 21

useDraggableResizable hook (framer-motion pan gestures, pure geometry
helpers) for a floating window draggable and resizable from any edge,
clamped to the viewport. Adds a storybook demo route.
getViewport() no longer reads window during SSR; the resize effect
re-clamps once on mount. resizeRect now caps per-edge growth against
the viewport before deriving x/y, so the far edge never drifts. Drops
gratuitous useCallback in handler literals, renames the className
helper to reflect it returns a string.
Replace the slide-in right column with a draggable, resizable floating
chat window (variant-1 style), default and only mode now.
Drop dead exports for knip, register the storybook route, grab cursor
on the drag handle, and a source guard against the right-column mode
coming back.
Clicking it while open re-affirms the single floating window instead
of doing nothing, matching what live testing expected.
framer-motion defers onPanStart/onPanEnd by a frame (its internal
scheduler) but calls onPan synchronously, so a gesture-start rect
snapshot captured in onPanStart could still be stale (or the
mount-time initial rect) when a gesture's first onPan landed. Switch
to framer's per-event `delta` folded onto the latest committed rect
via functional setState, so there's no baseline left to race.
Pure-function tests can't fail on the deferred-onPanStart race by
construction. This drives the hook's real handlers in framer's actual
order (onPan, onPan, then a late onPanStart, then onPan) — fails
against an offset+baseline shape, passes with the delta fold.
Toggles instead of re-affirming a no-op; tooltip says "Close chat" when open.
Chat UI gallery gets a live floating-window section (drag, resize,
real header) and drops the "side panel" wording. Reuse
draggableResizeHandleClassName from the primitive instead of a local
duplicate. Move the floating-chat-window story next to Chat UI and
drop the now-standalone draggable-resizable story.
Scoped to the storybook stories; the real dashboard chat still
persists across navigation.
…comparison

Storybook only, in the ai-agent story's new "Action icons" tab next
to the current heroicons versions. Not wired into the app.
Investigate (magnifier + glasses), Watch (eye) and Alert (bell), all
generated from circular/elliptical bands instead of hand-drawn
corners, with a smaller edge dot to soften the curve. Moved into the
Shape library section; dropped the separate Action icons tab.
Magnifier, glasses, eye, bell now sit on the same MATRIX x MATRIX grid
nodes as DOT_SHAPES (exported dotMatrixGeometry from AgentDotMatrix),
same pitch and dot radius, no off-grid or resized dots. Shown
Face-options-style: faint full grid behind the lit silhouette.
Radar, fingerprint, crosshair, flashlight, sonar ping — same true
MATRIX bitmap contract as the existing four. Grouped with the
magnifier and glasses in the Shape library so all seven compare
side by side.
…ditor

Remove the nine candidate icon files and their story rows. In their
place: a clickable MATRIXxMATRIX grid (dotMatrixGeometry, 1.5x the
candidates' size) that toggles dots green and reads back as a
MATRIX-line bitmap string. Keeps MATRIX/dotMatrixGeometry in
AgentDotMatrix.tsx; drops the now-unconsumed static icon renderer.
One generic DotGrid component (shared with the editor) renders each
as a numbered row, editor size and 20px, ghost-grid style. #2/#5 are
an intentional duplicate pair.
…op editor's owner-bitmap row

Every feature surface (buttons, suggested prompts, chat history
status, watch card, watch result block) now renders AgentMonoLogo
instead of the heroicons magnifier/eye, matching the Ask Trigger nav
button. Unrelated magnifiers/eyes (search inputs, admin search,
password-visibility toggles) are untouched.

storybook.ai-agent: remove the owner-drawn candidates row and the
editor's description text; the interactive editor stays.
…tic glyph

WatchResultBlock's "watching" outcome (a watch just created and still
live) now shows AgentSpinner — the same animated dot-matrix spinner
the chat uses while thinking, and what WatchChips already shows for
an active watch. The one-shot terminal outcomes keep their icons.
…at panel

The Ask Trigger glyph swap stays only outside the chat window
(InvestigateButton, WatchButton — page CTAs that open the chat).
Surfaces rendered inside the panel tree revert to their original
icons: suggested-prompt chips, chat history's investigating status,
and the watch draft card header. The active-watch spinner in
WatchResultBlock is untouched.
@changeset-bot

changeset-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 8dea45a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

The dashboard agent now opens in a draggable and resizable floating window. Shared geometry utilities and a React hook enforce viewport, size, and position constraints. The panel supports fullscreen mode, drag exclusions, resize handles, and launcher toggling. Agent controls use updated icons and active-watch rendering. Reopened chats include streaming state. New DOM and math tests cover interaction behavior. Storybook includes a floating-window example and an accessible interactive dot-matrix editor. Legacy floating-window manifest entries were removed.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the main behavior changes and includes screenshots, but it omits the required issue reference, checklist, testing steps, and explicit Changelog and Screenshots sections. Add the required template sections. Include the linked issue, completed checklist items, specific testing steps, a Changelog entry, and the Screenshots section.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.98% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 51 functions across 29 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: a floating Ask Trigger chat window for the webapp.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chat-bottom-default-mode

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.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

…icate floating-window demo

Comment sweep: cap every survivor at two sentences, drop what-comments
and a stale dotMatrixIcon.tsx reference, across panel-layout,
DraggableResizable, draggableResizableMath, WatchResultBlock and the
storybook routes.

Gate fixes from cross-model review:
- resizeRect: minSize now wins over a viewport-derived cap smaller
  than it, for all four resize directions (+4 tests).
- Header drag no longer engages when a gesture starts on a button/
  link/input inside it.
- Fullscreen (no dragHandleProps.onPan) no longer applies drag cursor
  classes or local pan-state handlers.
- Ask Trigger's "Close chat" tooltip drops the new-chat shortcut key.
- Resize handles no longer get clipped by the window's
  overflow-hidden (moved to an inner content wrapper).
- Drag handle gets touch-action:none so touch drags don't fight
  scrolling.
- Dot-grid editor dots are keyboard-operable (role=button, Enter/
  Space toggles).
- storybook.dashboard-agent-floating: mount-gates the demo window
  (avoids an SSR/client position mismatch) and resets fullscreen on
  every close path.
- New panel-layout.dom.test.ts: initialFloatingRect against a stubbed
  viewport, and a hook-level render proving the rect (and the minSize
  fix) survive the real wiring.

Remove the "Floating window" section from storybook.agent-ui: the
standalone storybook.dashboard-agent-floating route is the one
floating-window demo going forward.
…e shell

closest("button, a, input, [role=button]") rejected pans starting on
the header title, since the whole title (including the truncated
text) sits inside a Popover trigger <button>. Replace with an opt-out
marker (data-agent-no-drag) on the header's action-button group and
on the popover trigger's chevron only, so the title text stays
draggable while Radix's click-to-open behavior is untouched.

Move the filter and cursor/touch-action state out of
DashboardAgentPanel and into FloatingAgentWindow itself, so every
consumer (the real panel, the standalone story) gets identical
behavior instead of reimplementing it. New tests in
panel-layout.dom.test.ts drive the filter directly: a pan starting on
ordinary content drags, one starting on a data-agent-no-drag element
doesn't.
coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

onPan can arrive before onPanStart, so a gesture starting on a
data-agent-no-drag element could leak its first delta(s) before
ignoringGesture was set. Both handlers now classify the gesture once
(gestureClassified) using whichever event's target shows up first.
New test drives onPan before onPanStart from a no-drag target and
asserts zero movement.

Also drop a gratuitous useMemo over a 25-item array in
storybook.ai-agent's DotGridEditor (CodeRabbit nitpick).

@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.

🧹 Nitpick comments (1)
apps/webapp/app/components/dashboard-agent/panel-layout.tsx (1)

106-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add crumb instrumentation for the gesture state changes.

Add // @crumbs`` markers or an @crumbs region around gesture classification and drag-state updates. This interaction path has ordering-dependent behavior.

As per coding guidelines: “Add crumbs as you write code — not just when debugging.”

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bfd69823-2072-4339-9347-8011283121bc

📥 Commits

Reviewing files that changed from the base of the PR and between 8ce45c3 and c59cf0e.

📒 Files selected for processing (3)
  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
  • apps/webapp/app/components/dashboard-agent/panel-layout.tsx
  • apps/webapp/app/routes/storybook.ai-agent/route.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webapp/app/routes/storybook.ai-agent/route.tsx

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

📜 Review details
⏰ Context from checks skipped due to timeout. (47)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (18, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (10, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (19, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (22, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (21, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (17, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (13, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (20, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (15, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (16, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (14, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (24, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (9, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (23, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 24)
  • GitHub Check: sdk-compat / Node.js 20.20 (warp-ubuntu-latest-x64-4x)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: sdk-compat / Node.js 26.4 (warp-ubuntu-latest-x64-4x)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: packages / 🧪 Unit Tests: Packages (2, 3)
  • GitHub Check: sdk-compat / Node.js 24.18 (warp-ubuntu-latest-x64-4x)
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-ubuntu-latest-x64-4x - npm)
  • GitHub Check: sdk-compat / Node.js 22.23 (warp-ubuntu-latest-x64-4x)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-windows-latest-x64-8x - npm)
  • GitHub Check: runops-guard / runops-guard
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (1, 2)
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (2, 2)
  • GitHub Check: fk-cascade-guard / fk-cascade-guard
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-ubuntu-latest-x64-4x - pnpm)
  • GitHub Check: internal / 🧪 Unit Tests: Internal
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-windows-latest-x64-8x - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: packages / 🧪 Unit Tests: Packages (1, 3)
  • GitHub Check: packages / 🧪 Unit Tests: Packages (3, 3)
  • GitHub Check: obsmap / 🧪 Unit Tests: Observability Map
  • GitHub Check: code-quality / code-quality
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (13)
Never use `request.signal` to detect client disconnects. Use `getRequestAbortSignal()` from `app/services/httpAsyncStorage.server.ts`, which is wired to Express response close events.

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
We use vitest exclusively. **Never mock anything** - use testcontainers instead.

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
Test files must not import `app/env.server.ts`; pass configuration as options instead.

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
For dashboard changes, visually verify the running Remix app with Chrome DevTools MCP, using snapshots, screenshots, interaction, and console-message checks as appropriate.

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
  • apps/webapp/app/components/dashboard-agent/panel-layout.tsx
**Prefer static imports over dynamic imports.** Only use dynamic `import()` when:

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
  • apps/webapp/app/components/dashboard-agent/panel-layout.tsx
Add crumbs as you write code — not just when debugging. Mark lines with

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
  • apps/webapp/app/components/dashboard-agent/panel-layout.tsx
Use zod for validation in packages/core and apps/webapp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
  • apps/webapp/app/components/dashboard-agent/panel-layout.tsx
Do not import `env.server.ts` directly or indirectly into test files; instead pass environment-dependent values through options/parameters to make code testable

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
Access environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env`

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
  • apps/webapp/app/components/dashboard-agent/panel-layout.tsx
Use vitest for all tests in the Trigger.dev repository

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
Use function declarations instead of default exports

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
  • apps/webapp/app/components/dashboard-agent/panel-layout.tsx
Use types over interfaces for TypeScript

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts
  • apps/webapp/app/components/dashboard-agent/panel-layout.tsx
When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

Files:

  • apps/webapp/app/components/dashboard-agent/panel-layout.dom.test.ts

devin-ai-integration[bot]

This comment was marked as resolved.

…sweep

Closing the panel already keeps the server turn alive
(teardownCancelsTurn returns false for panel-closed and chat-switched).
The missing piece was resume-on-reopen: opening a chat never marked it
streaming, so the transport never resumed the live `.out` stream and a
mid-turn reopen showed a stalled transcript.

resolveOpenedChat now derives `streaming` from whether the fetched
transcript still looks mid-turn (the same transcriptLooksUnfinished
check already used to poll a just-settled turn), and DashboardAgentPanel
already spreads it into ActiveChat, so useTriggerChatTransport's
isStreaming resume engages on reopen.

Chat-switch design decision: unchanged, matches the packet's
preference — teardownCancelsTurn already keeps chat-switched turns
alive, same as panel-closed, so switching away and back also resumes.

A true SSE integration test is impractical in jsdom; resume-wiring.test.ts
source-checks the streaming/isStreaming/stopGeneration wiring instead,
alongside new resolveOpenedChat unit tests for the streaming derivation.

Final comment sweep over the branch diff: every comment capped at two
plain lines, non-critical/signpost ones removed.
devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

…tore a comment clause

transcriptLooksUnfinished only checked open investigations and
in-flight tool parts, so a text-only assistant reply mid-stream
derived streaming:false on reopen. OR in a new check: the last
assistant message has a `text` part with state "streaming".

resume-wiring.test.ts: extracted the transport's per-chat sessions
option into chatSessionsOption (DashboardAgentChat.tsx) and test it
behaviorally, feeding it a real resolveOpenedChat result instead of
grepping for a source substring. Retitled the stopGeneration test to
say what it actually checks (occurrence count, not call sites).

panel-layout.tsx: restored the clause that FloatingAgentWindow owns
the drag-vs-click filter, so the panel and the story stay identical.
coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

…d streaming text

Confirmed both, refuted neither:

1. FloatingAgentWindow (Devin BUG, panel-layout.tsx): fullscreen and
   floating returned different element trees, so children() sat at a
   different tree position in each — flipping fullscreen unmounted and
   remounted the chat panel, resetting its session and interrupting a
   live stream. Verified with a jsdom test (mount count 1 -> 2 on
   toggle) before fixing; now one tree shape, only classes/style and a
   conditional drag-handler object change.

2. mergeSettledMessages (CodeRabbit MAJOR, settled-transcript.ts):
   stillRunning only checked tool-* parts, so a settled text message
   never replaced its still-"streaming" in-memory copy — a resumed
   prose-only chat could stay stuck showing partial text after the
   server settled it. Widened stillRunning to also treat a `text` part
   with state "streaming" as running.

Item 3 (crumbs markers) intentionally not touched.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +94 to +98
const classifyGesture = (event: PointerEvent) => {
if (gestureClassified.current) return;
gestureClassified.current = true;
ignoringGesture.current = !!(event.target as HTMLElement | null)?.closest(NO_DRAG_SELECTOR);
};

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.

🔍 Drag filter relies on event.target being the pressed element

The drag-vs-click filter reads (event.target).closest('[data-agent-no-drag]') to decide whether a gesture on a header control (e.g. Close) drags the window. This holds only if framer-motion's pan events report the DOM element under the pointer. If framer-motion pointer-captures the header motion.div, event.target becomes that div, closest returns null, and the window drags even from a no-drag control. The DOM tests inject the inner element as target, so they never exercise real framer-motion event delivery. Verify against framer-motion v10 pan behavior in a browser.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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