Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7cefff5
feat(webapp): draggable/resizable window primitive
kathiekiwi Aug 26, 2026
c46f8b9
fix(webapp): SSR-safe viewport read and viewport-aware resize clamping
kathiekiwi Aug 26, 2026
8d8b039
feat(webapp): dashboard agent as a floating bottom-right window
kathiekiwi Aug 26, 2026
4b93738
fix(webapp): floating chat window review follow-ups
kathiekiwi Aug 26, 2026
9e36628
fix(webapp): keep the Ask Trigger button visible while chat is open
kathiekiwi Aug 26, 2026
b9e9e14
fix(webapp): eliminate the drag/resize gesture-baseline race
kathiekiwi Aug 26, 2026
a07500a
test(webapp): jsdom regression test for the pan-gesture ordering fix
kathiekiwi Aug 26, 2026
c2044a1
fix(webapp): Ask Trigger button closes the chat when clicked while open
kathiekiwi Aug 26, 2026
6e80fbe
fix(webapp): agent-ui story demos the floating window shell
kathiekiwi Aug 26, 2026
e1dab30
fix(webapp): close the storybook floating-chat demo on route change
kathiekiwi Aug 26, 2026
90b4002
feat(webapp): dot-matrix variants of the Investigate/Watch icons for …
kathiekiwi Aug 26, 2026
3e7007a
fix(webapp): rounder dot-matrix icons in the ai-agent Shape library
kathiekiwi Aug 26, 2026
2825e45
fix(webapp): dot-matrix icons drawn on the exact Shape library grid
kathiekiwi Aug 26, 2026
308c7d4
feat(webapp): five more Investigate dot-matrix icon candidates
kathiekiwi Aug 26, 2026
597879d
fix(webapp): replace dot-matrix icon candidates with an interactive e…
kathiekiwi Aug 26, 2026
cb9f478
feat(webapp): render the owner's 10 drawn dot-matrix icon bitmaps
kathiekiwi Aug 26, 2026
1b644e7
fix(webapp): Investigate/Watch surfaces use the Ask Trigger glyph; dr…
kathiekiwi Aug 26, 2026
aed73d5
fix(webapp): live watch result block uses the chat spinner, not a sta…
kathiekiwi Aug 26, 2026
73d0dde
fix(webapp): revert Investigate/Watch icons to original inside the ch…
kathiekiwi Aug 26, 2026
4474a85
docs(webapp): add server-changes note for the floating chat window
kathiekiwi Aug 26, 2026
1447674
fix(webapp): trim verbose comments; drag/resize gate fixes; kill dupl…
kathiekiwi Aug 26, 2026
8ce45c3
fix(webapp): drag filter opts out by marker, not tag, and lives in th…
kathiekiwi Aug 26, 2026
c59cf0e
fix(webapp): classify a drag gesture on whichever pan event lands first
kathiekiwi Aug 26, 2026
e705833
fix(webapp): resume a chat's in-flight turn on reopen; final comment …
kathiekiwi Aug 26, 2026
8e411e1
fix(webapp): resume prose-only turns too; behavioral resume test; res…
kathiekiwi Aug 26, 2026
8dea45a
fix(webapp): fullscreen toggle remounted the chat; settle-merge misse…
kathiekiwi Aug 26, 2026
00051e9
fix(webapp): raise dashboard agent chat z-index to 30
kathiekiwi Aug 28, 2026
91dbd9b
feat(webapp): add chat display mode icons
kathiekiwi Aug 28, 2026
23b958f
feat(webapp): chat display mode switcher (floating, right panel, full…
kathiekiwi Aug 28, 2026
78cca26
feat(webapp): move chat context and new-chat button under input; 360p…
kathiekiwi Aug 28, 2026
581c25b
fix(webapp): keep chat mounted across display modes; update mode guar…
kathiekiwi Aug 28, 2026
727f592
fix(webapp): floating chat default height 600px
kathiekiwi Aug 28, 2026
984eac5
fix(webapp): add right margin to new chat button
kathiekiwi Aug 28, 2026
a8c28b1
fix(webapp): restore fullscreen chat centering
kathiekiwi Aug 28, 2026
971c108
feat(webapp): compact animated chat mode toggle
kathiekiwi Aug 28, 2026
2d91412
fix(webapp): collapse degenerate resizable tracks so fullscreen fits …
kathiekiwi Aug 28, 2026
07a8480
fix(webapp): fullscreen chat column fills takeover height
kathiekiwi Aug 28, 2026
e091137
fix(webapp): clear floating geometry when chat docks
kathiekiwi Aug 28, 2026
5ff2924
docs(webapp): mention chat display modes in release note
kathiekiwi Aug 28, 2026
599ee59
fix(webapp): keep resize handle mounted so mode switches never remoun…
kathiekiwi Aug 28, 2026
7088a1a
fix(webapp): mode toggle escape, aria, external collapse
kathiekiwi Aug 28, 2026
fe35c95
fix(webapp): keep draft chat composer in the hero block
kathiekiwi Aug 28, 2026
153c64b
fix(webapp): launcher aria-label reflects open state
kathiekiwi Aug 28, 2026
d2e20f5
feat(webapp): drag floating chat to edges to dock right or fullscreen
kathiekiwi Aug 31, 2026
c7cc318
fix(webapp): restore floating rect on zoned drop; client coords for d…
kathiekiwi Aug 31, 2026
b0df83d
feat(webapp): account preference for the chat open position
kathiekiwi Aug 31, 2026
9789afd
fix(webapp): show chat open mode setting to everyone; test the real c…
kathiekiwi Aug 31, 2026
79a0764
fix(webapp): navigation always collapses fullscreen chat
kathiekiwi Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .server-changes/chat-open-mode-account-preference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
area: webapp
type: improvement
---

You can now set your preferred Ask Trigger chat position (floating, right panel, or fullscreen) in your account settings — it's used every time you open the chat.
Comment on lines +1 to +6

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.

🔍 Feature has duplicate release notes

One chat-display feature ships as two server-change entries, contrary to the repository’s instruction to add one concise user-facing note.

Devin Review

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

6 changes: 6 additions & 0 deletions .server-changes/floating-chat-window.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
area: webapp
type: improvement
---

Ask Trigger now opens as a floating window at the bottom of the page that you can drag anywhere and resize. The Ask Trigger button toggles the chat open and closed. You can also switch to a right-side panel or fullscreen view using a toggle in the chat header.
23 changes: 23 additions & 0 deletions apps/webapp/app/assets/icons/ChatFloatingPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function ChatFloatingPanel({ className }: { className?: string }) {
return (
<svg
className={className}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 5H5C3.89543 5 3 5.89543 3 7V17C3 18.1046 3.89543 19 5 19H19C20.1046 19 21 18.1046 21 17V7C21 5.89543 20.1046 5 19 5Z"
stroke="currentColor"
strokeWidth="2"
strokeLinejoin="round"
/>
<path
d="M17 10H15C14.4477 10 14 10.4477 14 11V15C14 15.5523 14.4477 16 15 16H17C17.5523 16 18 15.5523 18 15V11C18 10.4477 17.5523 10 17 10Z"
fill="currentColor"
/>
</svg>
);
}
20 changes: 20 additions & 0 deletions apps/webapp/app/assets/icons/ChatFullScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function ChatFullScreen({ className }: { className?: string }) {
return (
<svg
className={className}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 5H5C3.89543 5 3 5.89543 3 7V17C3 18.1046 3.89543 19 5 19H19C20.1046 19 21 18.1046 21 17V7C21 5.89543 20.1046 5 19 5Z"
stroke="currentColor"
strokeWidth="2"
strokeLinejoin="round"
/>
<rect x="5" y="7" width="14" height="10" rx="0.5" fill="currentColor" />
</svg>
);
}
20 changes: 20 additions & 0 deletions apps/webapp/app/assets/icons/ChatRightPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function ChatRightPanel({ className }: { className?: string }) {
return (
<svg
className={className}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 5H5C3.89543 5 3 5.89543 3 7V17C3 18.1046 3.89543 19 5 19H19C20.1046 19 21 18.1046 21 17V7C21 5.89543 20.1046 5 19 5Z"
stroke="currentColor"
strokeWidth="2"
strokeLinejoin="round"
/>
<rect x="15" y="7" width="4" height="10" rx="0.5" fill="currentColor" />
</svg>
);
}
104 changes: 67 additions & 37 deletions apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { SuggestedPrompt, WatchSpec } from "@internal/dashboard-agent-contr
import { useLocation } from "@remix-run/react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
collapsibleHandleClassName,
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
Expand All @@ -10,16 +11,16 @@ import { useEnvironment } from "~/hooks/useEnvironment";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
import { useUser } from "~/hooks/useUser";
import { useAskAiAvailability } from "~/hooks/useAskAiAvailability";
import { agentDeepLinkParams, ASK_AI_SHORTCUT, askAiChannelTarget } from "./ask-ai-channels";
import { DashboardAgentPanel } from "./DashboardAgentPanel";
import { DashboardAgentProvider, TOGGLE_PANEL_SHORTCUT } from "./dashboardAgentLauncher";
import { useDashboardAgentOpenRequests } from "./dashboardAgentOpenRequest";
import {
agentHiddenContentClassName,
agentTakeoverClassName,
readAgentFullscreen,
writeAgentFullscreen,
FloatingAgentWindow,
useAgentPanelMode,
} from "./panel-layout";
import { nextPendingTurnChatId } from "./pending-turn";
import { nextVisibleChat } from "./unread-counts";
Expand All @@ -35,6 +36,10 @@ import {

const TOASTED_WAKES_STORAGE_KEY = "tdev:dashboard-agent:toasted-wakes";

// Superseded by the account preference; a stray value here would otherwise pin the mode
// forever if this cleanup effect never ran.
const STALE_MODE_STORAGE_KEYS = ["tdev:dashboard-agent:mode", "tdev:dashboard-agent:fullscreen"];

// Shorter than the poll interval, so a stuck request is dropped before the next tick.
const UNREAD_REQUEST_TIMEOUT_MS = 30_000;

Expand All @@ -60,6 +65,8 @@ export function DashboardAgent({
const organization = useOrganization();
const project = useProject();
const environment = useEnvironment();
const user = useUser();
const modePreference = user.dashboardPreferences.chatOpenMode;
const actionPath = `/resources/orgs/${organization.slug}/projects/${project.slug}/env/${environment.slug}/dashboard-agent`;

const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -114,14 +121,19 @@ export function DashboardAgent({
setUnreadWakes(initialUnreadWakes);
setUnreadWork(initialUnreadWork);
}, [environment.id, initialUnreadWakes, initialUnreadWork]);
// Read lazily so SSR always renders the side panel.
const [fullscreen, setFullscreen] = useState(readAgentFullscreen);
// Every open starts from the account preference; in-chat switches (toggle, drag-to-dock)
// are transient and never write it back.
const { mode, changeMode, resetToPreference, revertFullscreen } =
useAgentPanelMode(modePreference);
const fullscreen = mode === "fullscreen";

const toggleFullscreen = useCallback(() => {
setFullscreen((current) => {
writeAgentFullscreen(!current);
return !current;
});
// Superseded localStorage keys; harmless to skip if storage is unavailable.
useEffect(() => {
try {
for (const key of STALE_MODE_STORAGE_KEYS) window.localStorage.removeItem(key);
} catch {
/* ignore */
}
}, []);

// Pathname only: filter and search-param changes must keep fullscreen.
Expand All @@ -130,11 +142,8 @@ export function DashboardAgent({
useEffect(() => {
if (previousPathname.current === pathname) return;
previousPathname.current = pathname;
setFullscreen((current) => {
if (current) writeAgentFullscreen(false);
return false;
});
}, [pathname]);
revertFullscreen();
}, [pathname, revertFullscreen]);
const [newChatSeq, setNewChatSeq] = useState(0);
const [requestedMessage, setRequestedMessage] = useState<
{ text: string; seq: number } | undefined
Expand Down Expand Up @@ -165,13 +174,14 @@ export function DashboardAgent({
setOpen(false);
// Pending requests must be dropped or a stale one re-applies on the next open.
visibleChat.current = null;
setFullscreen(false);
writeAgentFullscreen(false);
// Any transient in-chat mode switch applied only until close; the next open
// starts from the account preference again.
resetToPreference();
setRequestedMessage(undefined);
setOpenChatRequest(undefined);
setWatchRequest(undefined);
},
[openPanel]
[openPanel, resetToPreference]
);

const openChat = useCallback(
Expand Down Expand Up @@ -346,7 +356,10 @@ export function DashboardAgent({
return (
<DashboardAgentProvider value={context}>
{open ? (
// `relative` is the takeover's containing block.
// `relative` is the fullscreen takeover's containing block. The ResizablePanelGroup
// stays mounted across all three modes — only its sizing/handle degenerate outside
// rightPanel — so `FloatingAgentWindow` (and the chat panel inside it) sits at the
// same tree position in every mode and a mode switch never remounts it.
<div className="relative h-full min-h-0">
<ResizablePanelGroup
orientation="horizontal"
Expand All @@ -358,25 +371,42 @@ export function DashboardAgent({
</ResizablePanel>
<ResizableHandle
id="dashboard-agent-handle"
className={fullscreen ? "invisible" : undefined}
size={mode === "rightPanel" ? "3px" : "0px"}
className={collapsibleHandleClassName(mode === "rightPanel")}
/>
<ResizablePanel id="dashboard-agent-panel" default="380px" min="320px" max="720px">
<div className={agentTakeoverClassName(fullscreen)}>
<DashboardAgentPanel
onClose={() => setPanelOpen(false)}
requestedMessage={requestedMessage}
openChatRequest={openChatRequest}
watchRequest={watchRequest}
newChatSeq={newChatSeq}
promotedPrompt={promotedPrompt}
onChatRead={markChatRead}
// The panel's own count, off the chat list it has already marked read.
onUnreadWorkChange={setUnreadWork}
onTurnActivityChange={handleTurnActivityChange}
isFullscreen={fullscreen}
onToggleFullscreen={toggleFullscreen}
/>
</div>
<ResizablePanel
id="dashboard-agent-panel"
default="380px"
min="320px"
max="720px"
collapsible
collapsed={mode !== "rightPanel"}
collapsedSize="0px"
// Non-rightPanel modes render through position:fixed/absolute, which must
// escape this panel's own clipping box to avoid being cut to its 0px width.
// Tailwind v4's important modifier is a trailing `!`, not a leading one.
className={mode === "rightPanel" ? undefined : "overflow-visible!"}
>
<FloatingAgentWindow mode={mode} onRequestModeChange={changeMode}>
{({ dragHandleProps, dragHandleClassName }) => (
<DashboardAgentPanel
onClose={() => setPanelOpen(false)}
requestedMessage={requestedMessage}
openChatRequest={openChatRequest}
watchRequest={watchRequest}
newChatSeq={newChatSeq}
promotedPrompt={promotedPrompt}
onChatRead={markChatRead}
// The panel's own count, off the chat list it has already marked read.
onUnreadWorkChange={setUnreadWork}
onTurnActivityChange={handleTurnActivityChange}
mode={mode}
onModeChange={changeMode}
dragHandleProps={dragHandleProps}
dragHandleClassName={dragHandleClassName}
/>
)}
</FloatingAgentWindow>
</ResizablePanel>
</ResizablePanelGroup>
</div>
Expand Down
Loading
Loading