@@ -11,6 +11,7 @@ import { useEnvironment } from "~/hooks/useEnvironment";
1111import { useOrganization } from "~/hooks/useOrganizations" ;
1212import { useProject } from "~/hooks/useProject" ;
1313import { useShortcutKeys } from "~/hooks/useShortcutKeys" ;
14+ import { useUser } from "~/hooks/useUser" ;
1415import { useAskAiAvailability } from "~/hooks/useAskAiAvailability" ;
1516import { agentDeepLinkParams , ASK_AI_SHORTCUT , askAiChannelTarget } from "./ask-ai-channels" ;
1617import { DashboardAgentPanel } from "./DashboardAgentPanel" ;
@@ -19,8 +20,7 @@ import { useDashboardAgentOpenRequests } from "./dashboardAgentOpenRequest";
1920import {
2021 agentHiddenContentClassName ,
2122 FloatingAgentWindow ,
22- readAgentMode ,
23- writeAgentMode ,
23+ initialAgentMode ,
2424 type DashboardAgentMode ,
2525} from "./panel-layout" ;
2626import { nextPendingTurnChatId } from "./pending-turn" ;
@@ -37,6 +37,10 @@ import {
3737
3838const TOASTED_WAKES_STORAGE_KEY = "tdev:dashboard-agent:toasted-wakes" ;
3939
40+ // Superseded by the account preference; a stray value here would otherwise pin the mode
41+ // forever if this cleanup effect never ran.
42+ const STALE_MODE_STORAGE_KEYS = [ "tdev:dashboard-agent:mode" , "tdev:dashboard-agent:fullscreen" ] ;
43+
4044// Shorter than the poll interval, so a stuck request is dropped before the next tick.
4145const UNREAD_REQUEST_TIMEOUT_MS = 30_000 ;
4246
@@ -62,6 +66,8 @@ export function DashboardAgent({
6266 const organization = useOrganization ( ) ;
6367 const project = useProject ( ) ;
6468 const environment = useEnvironment ( ) ;
69+ const user = useUser ( ) ;
70+ const modePreference = user . dashboardPreferences . chatOpenMode ;
6571 const actionPath = `/resources/orgs/${ organization . slug } /projects/${ project . slug } /env/${ environment . slug } /dashboard-agent` ;
6672
6773 const [ open , setOpen ] = useState ( false ) ;
@@ -116,27 +122,32 @@ export function DashboardAgent({
116122 setUnreadWakes ( initialUnreadWakes ) ;
117123 setUnreadWork ( initialUnreadWork ) ;
118124 } , [ environment . id , initialUnreadWakes , initialUnreadWork ] ) ;
119- // Read lazily: SSR has no localStorage, so the server always renders the floating default.
120- const [ mode , setMode ] = useState < DashboardAgentMode > ( readAgentMode ) ;
125+ // Every open starts from the account preference; in-chat switches (toggle, drag-to-dock)
126+ // are transient and never write it back.
127+ const [ mode , setMode ] = useState < DashboardAgentMode > ( ( ) => initialAgentMode ( modePreference ) ) ;
121128 const fullscreen = mode === "fullscreen" ;
122129
123130 const changeMode = useCallback ( ( next : DashboardAgentMode ) => {
124- writeAgentMode ( next ) ;
125131 setMode ( next ) ;
126132 } , [ ] ) ;
127133
134+ // Superseded localStorage keys; harmless to skip if storage is unavailable.
135+ useEffect ( ( ) => {
136+ try {
137+ for ( const key of STALE_MODE_STORAGE_KEYS ) window . localStorage . removeItem ( key ) ;
138+ } catch {
139+ /* ignore */
140+ }
141+ } , [ ] ) ;
142+
128143 // Pathname only: filter and search-param changes must keep fullscreen.
129144 const { pathname } = useLocation ( ) ;
130145 const previousPathname = useRef ( pathname ) ;
131146 useEffect ( ( ) => {
132147 if ( previousPathname . current === pathname ) return ;
133148 previousPathname . current = pathname ;
134- setMode ( ( current ) => {
135- if ( current !== "fullscreen" ) return current ;
136- writeAgentMode ( "floating" ) ;
137- return "floating" ;
138- } ) ;
139- } , [ pathname ] ) ;
149+ setMode ( ( current ) => ( current !== "fullscreen" ? current : initialAgentMode ( modePreference ) ) ) ;
150+ } , [ pathname , modePreference ] ) ;
140151 const [ newChatSeq , setNewChatSeq ] = useState ( 0 ) ;
141152 const [ requestedMessage , setRequestedMessage ] = useState <
142153 { text : string ; seq : number } | undefined
@@ -167,16 +178,14 @@ export function DashboardAgent({
167178 setOpen ( false ) ;
168179 // Pending requests must be dropped or a stale one re-applies on the next open.
169180 visibleChat . current = null ;
170- setMode ( ( current ) => {
171- if ( current !== "fullscreen" ) return current ;
172- writeAgentMode ( "floating" ) ;
173- return "floating" ;
174- } ) ;
181+ // Any transient in-chat mode switch applied only until close; the next open
182+ // starts from the account preference again.
183+ setMode ( initialAgentMode ( modePreference ) ) ;
175184 setRequestedMessage ( undefined ) ;
176185 setOpenChatRequest ( undefined ) ;
177186 setWatchRequest ( undefined ) ;
178187 } ,
179- [ openPanel ]
188+ [ openPanel , modePreference ]
180189 ) ;
181190
182191 const openChat = useCallback (
0 commit comments