Merge origin/main into feat/wait-banner: resolve use-host-feeds import overlap
Conflict: src/client/sidebar/use-host-feeds.ts
- ours: import { mirrorAgentWaits, ... } from '../state.ts'
- theirs: same line + new import { isNarrowWidth } from '../breakpoints.ts'
Resolution keeps both imports; the two sides' semantic changes are in
disjoint regions (ours: tab-disabled wait mirroring; theirs:
activateTasksPage native-landing) and both survive intact.
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import type { Context, SidebarSessionList } from '../../context-types.ts'
|
||||
import { mirrorAgentWaits, reconcileAgentTerminals, type SidebarStore } from '../state.ts'
|
||||
import { isNarrowWidth } from '../breakpoints.ts'
|
||||
import { detectNewDirectSubagent } from '../subagent-detect.ts'
|
||||
import { detectNewJob } from '../subagent-jobs.ts'
|
||||
import { t } from '../locales.ts'
|
||||
@@ -26,6 +27,55 @@ const FAILURE_LIMIT = 3
|
||||
*/
|
||||
const AUTO_OPEN_DEBOUNCE_MS = 500
|
||||
|
||||
/**
|
||||
* The native column's public face, as this module reaches it. Both actions
|
||||
* act on the session whose surface is MOUNTED (the controller reads its
|
||||
* binding), which is why the park below is gated on the target session being
|
||||
* the on-screen one.
|
||||
*/
|
||||
interface NativeColumnFace {
|
||||
isExpanded?: () => boolean
|
||||
toggleExpanded?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the Tasks page (the `subagent` tab type) in DSH's native right
|
||||
* Sidebar — the landing the two auto-open switches promise: the right column
|
||||
* IS the sidebar the user means, while the plugin's own bottom workbench only
|
||||
* serves its own flows (its `+` menu and the first-expansion terminal). Every
|
||||
* other open in this plugin already lands there, so a `target: 'bottom'` here
|
||||
* puts the Tasks page in the bottom bar instead.
|
||||
*
|
||||
* A background activation must not take over a narrow viewport: below 768px
|
||||
* the host draws that column FULLSCREEN, so the tab is placed and the column
|
||||
* is put back to collapsed — parked, waiting behind the expand control,
|
||||
* instead of covering the chat. The host expands on every open
|
||||
* (`openContent` plans `setExpanded(true)`) and offers no option to suppress
|
||||
* it, hence the read-then-restore pair: both commits land in one React batch,
|
||||
* so the column never renders the intermediate state. The viewport is read
|
||||
* when the activation FIRES (the debounced subagent trigger included), so a
|
||||
* resize while arming is honoured.
|
||||
*
|
||||
* @param ctx - the client context (`ctx.sidebarRight` + `ctx.betterSidebar`).
|
||||
* @param sessionId - the session the feed reports the activity for.
|
||||
* @param options.background - `true` for background activity (parks on narrow
|
||||
* viewports); `false` for the explicit topology jump-back, which is a user
|
||||
* gesture and always leaves the column as the host expanded it.
|
||||
*/
|
||||
function activateTasksPage(ctx: Context, sessionId: string, options: { background: boolean }): void {
|
||||
const column = ctx.get('sidebarRight') as unknown as NativeColumnFace | undefined
|
||||
const park = options.background
|
||||
// The face acts on the MOUNTED session: parking is only meaningful (and
|
||||
// only safe) when the activation targets the one on screen.
|
||||
&& ctx.sessions.list.getSnapshot().current === sessionId
|
||||
&& isNarrowWidth(window.innerWidth)
|
||||
// Only a column the user had COLLAPSED is put back: an expanded one is in
|
||||
// use, and closing it under the user would be worse than the takeover.
|
||||
&& column?.isExpanded?.() === false
|
||||
ctx.get('betterSidebar')?.openTab({ type: 'subagent', title: t('subagent') })
|
||||
if (park) column?.toggleExpanded?.()
|
||||
}
|
||||
|
||||
export function useHostFeeds(feeds: {
|
||||
ctx: Context
|
||||
store: SidebarStore
|
||||
@@ -171,11 +221,11 @@ export function useHostFeeds(feeds: {
|
||||
* Subagent auto-activation: the moment the current conversation spawns its
|
||||
* FIRST direct subagent (a 0 → N transition on the list feed), the "auto
|
||||
* open" pref is on, and the Tasks tab type is enabled in settings, activate
|
||||
* the Tasks page. Single-instance semantics focus an existing pane tab in
|
||||
* place or raise an existing free window; a new tab lands in the right pane
|
||||
* and is never duplicated. On wide viewports the right panel also expands;
|
||||
* on narrow viewports background activity never forces the full-screen
|
||||
* drawer open over the chat.
|
||||
* the Tasks page in DSH's native right Sidebar. Single-instance semantics
|
||||
* focus an existing tab in place; a new tab lands in that column and is
|
||||
* never duplicated. Landing it EXPANDS the column on wide viewports, while
|
||||
* a narrow viewport (where the host draws that column fullscreen) parks the
|
||||
* tab instead of taking the screen over — see {@link activateTasksPage}.
|
||||
* Switching to a session that already has subagents never triggers — its
|
||||
* baseline starts at the current count — so a deliberate layout is never
|
||||
* fought.
|
||||
@@ -201,7 +251,7 @@ export function useHostFeeds(feeds: {
|
||||
if (!detectNewDirectSubagent(baseline, ctx.sessions.list.getSnapshot(), sessionId)) return
|
||||
if (!store.getPrefs().autoOpenSubagent) return
|
||||
if (ctx.get('betterSidebar')?.isTabEnabled('subagent') === false) return
|
||||
ctx.get('betterSidebar')?.openTab({ type: 'subagent', title: t('subagent'), target: 'bottom' })
|
||||
activateTasksPage(ctx, sessionId, { background: true })
|
||||
}, AUTO_OPEN_DEBOUNCE_MS)
|
||||
autoOpenPendingRef.current = { baseline, timer }
|
||||
}, [sessionList, sessionId, store, ctx])
|
||||
@@ -217,11 +267,12 @@ export function useHostFeeds(feeds: {
|
||||
* Job auto-activation: the moment a NEW background job appears for the
|
||||
* current conversation (a job id the previous snapshot lacked), the
|
||||
* auto-open pref is on, and the Tasks tab type is enabled, activate the Tasks
|
||||
* page that contains the background-jobs section. The right panel expands
|
||||
* only on wide viewports. Unlike the subagent trigger (0 → N only), ANY
|
||||
* new job id triggers: the agent may start several jobs in one session, and
|
||||
* each should surface. A fresh page load never triggers — its baseline starts
|
||||
* at the current snapshot.
|
||||
* page that contains the background-jobs section — in DSH's native right
|
||||
* Sidebar, expanded on wide viewports and parked on narrow ones exactly like
|
||||
* the subagent trigger ({@link activateTasksPage}). Unlike that trigger
|
||||
* (0 → N only), ANY new job id triggers: the agent may start several jobs in
|
||||
* one session, and each should surface. A fresh page load never triggers —
|
||||
* its baseline starts at the current snapshot.
|
||||
*/
|
||||
const jobBaselineRef = useRef<SidebarSessionList | undefined>(undefined)
|
||||
useEffect(() => {
|
||||
@@ -231,7 +282,7 @@ export function useHostFeeds(feeds: {
|
||||
if (!detectNewJob(prev, sessionList, sessionId)) return
|
||||
if (!store.getPrefs().autoOpenJobs) return
|
||||
if (ctx.get('betterSidebar')?.isTabEnabled('subagent') === false) return
|
||||
ctx.get('betterSidebar')?.openTab({ type: 'subagent', title: t('subagent'), target: 'bottom' })
|
||||
activateTasksPage(ctx, sessionId, { background: true })
|
||||
}, [sessionList, sessionId, store, ctx])
|
||||
|
||||
/**
|
||||
@@ -240,17 +291,18 @@ export function useHostFeeds(feeds: {
|
||||
* session's OWN layout (a fresh child session defaults to the explorer).
|
||||
* The README contract says the Subagent page must stay open with the jumped
|
||||
* node highlighted — so once the current session becomes the recorded jump
|
||||
* target, re-open the Subagent page on top of the child's layout (expanding
|
||||
* the panel first if it is collapsed). Only this explicit node click arms
|
||||
* the flag, so switching to a subagent session by any other means keeps
|
||||
* that session's own layout untouched.
|
||||
* target, re-open the Tasks page on top of the child's layout, in DSH's
|
||||
* native right Sidebar. This one is an explicit user gesture, so it always
|
||||
* takes the host's expansion (no narrow-viewport parking). Only this node
|
||||
* click arms the flag, so switching to a subagent session by any other means
|
||||
* keeps that session's own layout untouched.
|
||||
*/
|
||||
const subagentJumpRef = useRef<string | undefined>(undefined)
|
||||
useEffect(() => {
|
||||
const pending = subagentJumpRef.current
|
||||
if (pending === undefined || sessionId !== pending) return
|
||||
subagentJumpRef.current = undefined
|
||||
ctx.get('betterSidebar')?.openTab({ type: 'subagent', title: t('subagent'), target: 'bottom' })
|
||||
activateTasksPage(ctx, sessionId, { background: false })
|
||||
}, [sessionId, store, ctx])
|
||||
|
||||
return { subagentJumpRef }
|
||||
|
||||
Reference in New Issue
Block a user