Add Contextual chat

This commit is contained in:
Roberto
2026-05-15 22:30:34 +02:00
parent fd700c29be
commit 1cd7a59dfc
5 changed files with 17478 additions and 4291 deletions

View File

@@ -0,0 +1,967 @@
# Floating Chat Deprecation Sweep Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Delete every floating-chat code path across the adiuvAI Electron app — components, context, hooks, DOM attributes, stream branches, main-process methods, and shared-type artefacts — in five clean commits.
**Architecture:** The contextual sidebar (M4) fully replaces floating chat; floating is dead code with no live consumers. Each task targets one layer: (1) renderer components/context/hooks, (2) DOM data-attributes, (3) renderer hook logic, (4) main-process IPC/orchestrator, (5) store/localStorage keys sweep.
**Tech Stack:** TypeScript, React 19, Electron (main + preload + renderer), tRPC v11, Zod, electron-store.
---
## Pre-flight: baseline tsc check
- [ ] **Run tsc before touching anything**
```bash
cd /c/Users/PC-Roby/Documents/_adiuvai_workspace/adiuvAI
source ~/.nvm/nvm.sh && npx tsc --noEmit 2>&1 | tail -20
```
Record the number of pre-existing errors (expected: 0). Any errors here are pre-existing and not your fault.
---
## Task M6.1 — Delete FloatingChat component, context, hook + AppShell cleanup
**Files:**
- Delete: `src/renderer/components/ai/FloatingChat.tsx`
- Delete: `src/renderer/context/FloatingChatContext.tsx`
- Delete: `src/renderer/hooks/useDoubleClickAI.ts`
- Modify: `src/renderer/components/layout/AppShell.tsx`
- Modify: `src/renderer/components/projects/ProjectDetail.tsx`
- Modify: `src/renderer/components/timeline/TimelineGanttView.tsx`
- Modify: `src/renderer/routes/notes.$noteId.tsx`
- Modify: `src/renderer/routes/tasks.tsx`
### Context
Six files import from `FloatingChatContext` or `FloatingChat`. After deleting the three source files, all six must be cleaned up.
`ProjectDetail.tsx`, `TimelineGanttView.tsx`, `notes.$noteId.tsx`, and `tasks.tsx` import `useFloatingChat()` and call `registerSection`/`unregisterSection`. These are pure floating-chat wiring — they register DOM regions with the floating panel so it could anchor itself. With floating chat gone, these calls become dead code and must be removed entirely (no replacement needed; the contextual sidebar uses `useContextualScope` for scope awareness, which is already imported in tasks.tsx and ProjectDetail.tsx).
`AppShell.tsx` wraps the tree in `<FloatingChatProvider>` (line 151) and calls `useDoubleClickAI()` inside `AppShellInner` (line 166).
### Steps
- [ ] **Step 1: Confirm all consumers before deletion**
```bash
grep -rn "FloatingChat\|useFloatingChat\|FloatingChatProvider\|useDoubleClickAI" src/renderer/
```
Expected output matches: `AppShell.tsx`, `FloatingChat.tsx`, `FloatingChatContext.tsx`, `useDoubleClickAI.ts`, `ProjectDetail.tsx`, `TimelineGanttView.tsx`, `notes.$noteId.tsx`, `tasks.tsx`. If any unexpected file appears, stop and report before proceeding.
- [ ] **Step 2: Delete the three source files**
```bash
git rm src/renderer/components/ai/FloatingChat.tsx
git rm src/renderer/context/FloatingChatContext.tsx
git rm src/renderer/hooks/useDoubleClickAI.ts
```
- [ ] **Step 3: Clean AppShell.tsx**
Open `src/renderer/components/layout/AppShell.tsx`.
Remove line 28:
```ts
import { useDoubleClickAI } from '@/hooks/useDoubleClickAI';
```
Remove lines 7071:
```ts
import { FloatingChatPortal } from '@/components/ai/FloatingChat';
import { FloatingChatProvider } from '@/context/FloatingChatContext';
```
In `AppShell` (around line 149163), change:
```tsx
export function AppShell({ children }: AppShellProps) {
return (
<FloatingChatProvider>
<ExpandedClientsProvider>
<TaskBriefingProvider>
<HeaderProvider>
<div className="flex w-full h-full">
<AppShellInner>{children}</AppShellInner>
</div>
</HeaderProvider>
</TaskBriefingProvider>
</ExpandedClientsProvider>
</FloatingChatProvider>
);
}
```
to:
```tsx
export function AppShell({ children }: AppShellProps) {
return (
<ExpandedClientsProvider>
<TaskBriefingProvider>
<HeaderProvider>
<div className="flex w-full h-full">
<AppShellInner>{children}</AppShellInner>
</div>
</HeaderProvider>
</TaskBriefingProvider>
</ExpandedClientsProvider>
);
}
```
In `AppShellInner` (around line 166), remove:
```ts
useDoubleClickAI();
```
Search for `<FloatingChatPortal />` in the file (around line 288) and delete that line.
- [ ] **Step 4: Clean ProjectDetail.tsx**
Open `src/renderer/components/projects/ProjectDetail.tsx`.
Remove line 24:
```ts
import { useFloatingChat } from '@/context/FloatingChatContext';
```
Remove line 63:
```ts
const { registerSection, unregisterSection } = useFloatingChat();
```
Remove the `useEffect` block that calls `registerSection`/`unregisterSection` (lines ~91101):
```ts
useEffect(() => {
if (isLoading || !project) return;
registerSection({ id: 'project-summary', label: 'Project Summary', ref: summaryRef, projectId });
registerSection({ id: 'project-tasks', label: 'Tasks', ref: tasksRef, projectId });
registerSection({ id: 'project-notes', label: 'Notes', ref: notesRef, projectId });
return () => {
unregisterSection('project-summary');
unregisterSection('project-tasks');
unregisterSection('project-notes');
};
}, [projectId, isLoading, project, registerSection, unregisterSection]);
```
- [ ] **Step 5: Clean TimelineGanttView.tsx**
Open `src/renderer/components/timeline/TimelineGanttView.tsx`.
Remove line 31:
```ts
import { useFloatingChat } from '@/context/FloatingChatContext';
```
Remove line 95:
```ts
const { registerSection, unregisterSection } = useFloatingChat();
```
Remove the `useEffect` that calls `registerSection`/`unregisterSection` (lines ~107110):
```ts
useEffect(() => {
registerSection({ id: sectionId, label: sectionLabel, ref: sectionRef, projectId });
return () => unregisterSection(sectionId);
}, [sectionId, sectionLabel, projectId, registerSection, unregisterSection]);
```
- [ ] **Step 6: Clean notes.$noteId.tsx**
Open `src/renderer/routes/notes.$noteId.tsx`.
Remove line 29:
```ts
import { useFloatingChat } from '@/context/FloatingChatContext';
```
Remove line 117:
```ts
const { registerSection, unregisterSection } = useFloatingChat();
```
Remove the `useEffect` that calls `registerSection`/`unregisterSection` (lines ~119128):
```ts
useEffect(() => {
registerSection({
id: 'note-editor',
label: 'Note Editor',
ref: editorRef,
projectId: noteProjectId,
anchorMode: 'right-margin',
});
return () => unregisterSection('note-editor');
}, [noteId, noteProjectId, registerSection, unregisterSection]);
```
- [ ] **Step 7: Clean tasks.tsx**
Open `src/renderer/routes/tasks.tsx`.
Remove line 5:
```ts
import { useFloatingChat } from '@/context/FloatingChatContext';
```
Remove line 18:
```ts
const { registerSection, unregisterSection } = useFloatingChat();
```
Remove the `useEffect` that calls `registerSection`/`unregisterSection` (lines ~2027):
```ts
useEffect(() => {
registerSection({ id: 'tasks-overview', label: 'Tasks Overview', ref: overviewRef });
registerSection({ id: 'tasks-list', label: 'Task List', ref: listRef });
return () => {
unregisterSection('tasks-overview');
unregisterSection('tasks-list');
};
}, [registerSection, unregisterSection]);
```
Also remove the `overviewRef` and `listRef` ref declarations if they are now unused (check whether they are still referenced by the JSX `ref=` attributes after M6.2 strips `data-ai-section`):
```ts
const overviewRef = useRef<HTMLDivElement>(null);
const listRef = useRef<HTMLDivElement>(null);
```
Note: After M6.2 removes `data-ai-section` attributes, `ref={overviewRef}` and `ref={listRef}` in the JSX will also be gone, making the refs fully dead. Remove them here (or in M6.2 — just do it in whichever task you're in when you notice them).
- [ ] **Step 8: Type check**
```bash
source ~/.nvm/nvm.sh && npx tsc --noEmit 2>&1 | grep -i "error" | head -30
```
Expected: zero errors related to FloatingChat, FloatingChatContext, useDoubleClickAI. If any appear, locate the offending file and remove the import/usage.
- [ ] **Step 9: Commit**
```bash
git add src/renderer/components/layout/AppShell.tsx \
src/renderer/components/projects/ProjectDetail.tsx \
src/renderer/components/timeline/TimelineGanttView.tsx \
src/renderer/routes/notes.\$noteId.tsx \
src/renderer/routes/tasks.tsx
git commit -m "$(cat <<'EOF'
refactor(contextual): delete FloatingChat, FloatingChatContext, useDoubleClickAI
Replaced by ContextualChatProvider + AdiuvaTriggerButton in M4.
Pre-1.0 clean removal — no deprecation period.
EOF
)"
```
---
## Task M6.2 — Strip all `data-ai-section` attributes
**Files:**
- Modify: `src/renderer/components/projects/ProjectDetail.tsx`
- Modify: `src/renderer/components/timeline/TimelineGanttView.tsx`
- Modify: `src/renderer/routes/notes.$noteId.tsx`
- Modify: `src/renderer/routes/tasks.tsx`
### Context
`data-ai-section` was used by `useDoubleClickAI` to walk the DOM and find the nearest section anchor to open floating chat. Now that floating chat is gone, these attributes are purely dead markup. The contextual sidebar uses `scope` payloads, not DOM attributes.
Current occurrences (7 total):
- `ProjectDetail.tsx:538``data-ai-section="project-summary"`
- `ProjectDetail.tsx:620``data-ai-section="project-tasks"`
- `ProjectDetail.tsx:631``data-ai-section="project-notes"`
- `TimelineGanttView.tsx:239``data-ai-section={sectionId}`
- `notes.$noteId.tsx:291``data-ai-section="note-editor"`
- `tasks.tsx:42``data-ai-section="tasks-overview"`
- `tasks.tsx:61``data-ai-section="tasks-list"`
### Steps
- [ ] **Step 1: List all occurrences**
```bash
grep -rn "data-ai-section" src/renderer/
```
Confirm you see exactly the 7 occurrences listed above. If more appear, handle them too.
- [ ] **Step 2: Remove from ProjectDetail.tsx**
For each JSX element with a `data-ai-section` prop, delete only that prop (keep `className` and all others). Example:
Find:
```tsx
<div
ref={summaryRef}
data-ai-section="project-summary"
className="..."
>
```
Change to:
```tsx
<div
ref={summaryRef}
className="..."
>
```
Repeat for `project-tasks` and `project-notes`.
- [ ] **Step 3: Remove from TimelineGanttView.tsx**
Find the element at line ~239 and remove only the `data-ai-section={sectionId}` prop. Keep `ref={sectionRef}` and any other props.
Also check: after M6.1 removed `registerSection`/`unregisterSection`, there may now be `sectionRef`, `sectionId`, `sectionLabel` variables declared but unused. Remove any that are now dead. Check with:
```bash
grep -n "sectionRef\|sectionId\|sectionLabel" src/renderer/components/timeline/TimelineGanttView.tsx
```
If these variables are only used by the removed `useEffect` and the now-removed `data-ai-section` attribute, delete their declarations too.
- [ ] **Step 4: Remove from notes.$noteId.tsx**
Find the `<ScrollArea>` at line ~291:
```tsx
<ScrollArea ref={editorRef} data-ai-section="note-editor" className="flex-1 min-h-0">
```
Change to:
```tsx
<ScrollArea ref={editorRef} className="flex-1 min-h-0">
```
Note: `editorRef` is still used by the existing scroll area ref, so keep it.
- [ ] **Step 5: Remove from tasks.tsx**
Remove `data-ai-section="tasks-overview"` from the div at line ~42 and `data-ai-section="tasks-list"` from the div at line ~61. Also remove `ref={overviewRef}` and `ref={listRef}` from those elements if the refs were removed in M6.1.
If `overviewRef` and `listRef` are now unused (no JSX `ref=` and no other use), delete their `useRef` declarations too.
- [ ] **Step 6: Verify clean**
```bash
grep -rn "data-ai-section" src/renderer/
echo "exit=$?"
```
Expected: no output, `exit=1` (grep found nothing).
- [ ] **Step 7: Type check + commit**
```bash
source ~/.nvm/nvm.sh && npx tsc --noEmit 2>&1 | grep -i "error" | head -20
```
Expected: zero errors.
```bash
git add src/renderer/components/projects/ProjectDetail.tsx \
src/renderer/components/timeline/TimelineGanttView.tsx \
src/renderer/routes/notes.\$noteId.tsx \
src/renderer/routes/tasks.tsx
git commit -m "$(cat <<'EOF'
refactor(contextual): strip all data-ai-section attributes
Section-anchoring obsolete now that there is no floating chat.
The contextual sidebar uses scope payload, not DOM attributes.
EOF
)"
```
---
## Task M6.3 — Drop `'floating'` from `useAIChat` + `ChatInputBox` + `useChatStream`
**Files:**
- Modify: `src/renderer/hooks/useAIChat.ts`
- Modify: `src/renderer/hooks/useChatStream.ts`
- Modify: `src/renderer/components/ai/ChatInputBox.tsx`
- Modify: `src/renderer/components/brief/TaskBriefChat.tsx`
### Context
`useAIChat.ts` has `FloatingDomainSignal` type, `'floating'` in `UIChatContext.type` union, a `scope` field, a `'floating'` cache-key branch, `isFloating` logic in `handleSend`, `floating_domain` switch case, and `onDomainSignalRef`. All dead after M6.1 deleted `FloatingChat.tsx` which was the only consumer.
`useChatStream.ts` has a `floating_domain` case and the `onDomainSignal` option — kept during M2.1 explicitly "until M6 removes floating." Now is the time.
`ChatInputBox.tsx` has `'floating'` as a variant in `ChatInputBoxVariant` with its own style entry. This is a visual variant only — the type can be removed if nothing still passes `variant="floating"`. After `FloatingChat.tsx` is gone, nothing does.
`TaskBriefChat.tsx` passes `mode: 'floating'` to `chatMutation.mutate(...)`. After removing the floating mode from the tRPC schema (M6.4), this becomes a type error. Fix it here by removing `mode: 'floating'` and `scope` from the mutation call — task briefs will fall through to the default home orchestrator, or better: use `mode: 'contextual'` with the scope. Since task brief already passes `scope: { type: 'task', id: taskId }`, change `mode: 'floating'` to `mode: 'contextual'`.
`parseMutationsToEntityTags` and `TABLE_TO_ENTITY` in `useAIChat.ts` are still needed — `useChatStream.ts` imports `parseMutationsToEntityTags` from `useAIChat.ts`. Keep both exports.
### Steps
- [ ] **Step 1: Edit useAIChat.ts**
Open `src/renderer/hooks/useAIChat.ts`.
**Delete lines 413** (the `FloatingDomainSignal` type):
```ts
export type FloatingDomainSignal =
| 'tasks'
| 'notes'
| 'timelines'
| 'projects'
| {
type: 'task' | 'timeline' | 'project' | 'note' | 'node';
id?: string | null;
section?: 'task' | 'timeline' | 'note' | null;
};
```
**Replace the `UIChatContext` interface** (lines 2331):
```ts
export interface UIChatContext {
type: 'global' | 'project' | 'floating';
projectId?: string;
/** For floating mode — the entity scope to pass to the backend. */
scope?: {
type: 'task' | 'project' | 'note' | 'timeline';
id?: string;
};
}
```
with:
```ts
export interface UIChatContext {
type: 'global' | 'project';
projectId?: string;
}
```
**Delete the `UseAIChatOptions` interface** (lines 5052):
```ts
interface UseAIChatOptions {
onDomainSignal?: (domain: FloatingDomainSignal) => void;
}
```
**Update the `useAIChat` function signature** — remove the `options` parameter:
```ts
export function useAIChat(defaultContext: UIChatContext, options?: UseAIChatOptions): UseAIChatReturn {
```
becomes:
```ts
export function useAIChat(defaultContext: UIChatContext): UseAIChatReturn {
```
**Update `getContextCacheKey`** — replace the entire function body:
```ts
function getContextCacheKey(ctx: UIChatContext): string {
if (ctx.type === 'global') return 'global';
if (ctx.type === 'project') return `project:${ctx.projectId ?? ''}`;
// Floating chat should keep a single continuous session while the panel is open,
// even when route/section context changes due floating-domain navigation.
return 'floating';
}
```
with:
```ts
function getContextCacheKey(ctx: UIChatContext): string {
if (ctx.type === 'global') return 'global';
return `project:${ctx.projectId ?? ''}`;
}
```
**Update the `useMemo` for `contextCacheKey`** — remove `defaultContext.scope?.type` and `defaultContext.scope?.id` from deps:
```ts
const contextCacheKey = useMemo(
() => getContextCacheKey(defaultContext),
[defaultContext.type, defaultContext.projectId, defaultContext.scope?.type, defaultContext.scope?.id],
);
```
becomes:
```ts
const contextCacheKey = useMemo(
() => getContextCacheKey(defaultContext),
[defaultContext.type, defaultContext.projectId],
);
```
**Remove `onDomainSignalRef`** (lines 151152):
```ts
const onDomainSignalRef = useRef(options?.onDomainSignal);
onDomainSignalRef.current = options?.onDomainSignal;
```
**Remove the `floating_domain` case** from the `switch (event.type)` block (lines 237239):
```ts
case 'floating_domain':
onDomainSignalRef.current?.(event.domain);
break;
```
**Remove `isFloating` and the conditional spread** in `handleSend` (lines 249259):
```ts
const isFloating = ctx.type === 'floating';
chatMutationRef.current.mutate(
{
requestId,
message: trimmed,
conversationHistory,
sessionId: sessionIdRef.current,
...(isFloating && ctx.scope
? { mode: 'floating' as const, scope: ctx.scope }
: {}),
},
```
becomes:
```ts
chatMutationRef.current.mutate(
{
requestId,
message: trimmed,
conversationHistory,
sessionId: sessionIdRef.current,
},
```
- [ ] **Step 2: Edit useChatStream.ts**
Open `src/renderer/hooks/useChatStream.ts`.
Remove `onDomainSignal` from `UseChatStreamArgs` interface (lines 1718):
```ts
/** Optional: legacy floating_domain pivot signal. Kept until M6 removes floating. */
onDomainSignal?: (domain: unknown) => void;
```
Remove `onDomainSignal` from the destructuring of `useChatStream`'s argument (line 25):
```ts
onDomainSignal,
```
Remove `domainRef` declarations (lines 3334):
```ts
const domainRef = useRef(onDomainSignal);
domainRef.current = onDomainSignal;
```
Remove the `floating_domain` case from the switch block (lines 7072):
```ts
case 'floating_domain':
domainRef.current?.(event.domain);
break;
```
- [ ] **Step 3: Edit ChatInputBox.tsx**
Open `src/renderer/components/ai/ChatInputBox.tsx`.
The `'floating'` variant in `ChatInputBoxVariant` (line 12) and its entry in `VARIANT_STYLES` (lines 3035) should be removed since no live code passes `variant="floating"` any more.
Change:
```ts
type ChatInputBoxVariant = 'panel' | 'floating' | 'comment';
```
to:
```ts
type ChatInputBoxVariant = 'panel' | 'comment';
```
Remove the `floating` entry from `VARIANT_STYLES` (lines 3035):
```ts
floating: {
container: 'flex items-center gap-2 px-3 py-2.5',
textarea: 'flex-1 resize-none bg-transparent text-sm placeholder:text-muted-foreground/60 outline-none max-h-20 overflow-y-auto',
button: 'flex h-7 w-7 shrink-0 items-center justify-center rounded-xl bg-primary text-primary-foreground shadow-sm transition-all hover:bg-primary/90 active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed',
iconSize: 14,
},
```
Also remove the comment about FloatingChat on line 52:
```ts
// Re-init when the cache key changes (context switches in FloatingChat).
```
Replace with a neutral comment or just remove the comment line.
- [ ] **Step 4: Fix TaskBriefChat.tsx**
Open `src/renderer/components/brief/TaskBriefChat.tsx`.
At line ~174184, change:
```ts
chatMutation.mutate(
{
requestId,
message: trimmed,
conversationHistory,
sessionId,
mode: 'floating',
scope: { type: 'task', id: taskId },
briefMode: true,
briefingContext: briefingText || undefined,
},
```
to:
```ts
chatMutation.mutate(
{
requestId,
message: trimmed,
conversationHistory,
sessionId,
mode: 'contextual',
scope: { type: 'task', id: taskId },
briefMode: true,
briefingContext: briefingText || undefined,
},
```
- [ ] **Step 5: Type check**
```bash
source ~/.nvm/nvm.sh && npx tsc --noEmit 2>&1 | grep -i "error" | head -30
```
Expected: zero errors. If tsc reports that `'floating'` is no longer valid for some type (from M6.4 not yet done), note it — the router schema still allows `'floating'` until M6.4. If tsc reports that `useAIChat` call sites are broken, check them:
```bash
grep -rn "useAIChat" src/renderer/
```
All call sites should be passing only `type: 'global'` or `type: 'project'` contexts after FloatingChat.tsx was deleted.
- [ ] **Step 6: Commit**
```bash
git add src/renderer/hooks/useAIChat.ts \
src/renderer/hooks/useChatStream.ts \
src/renderer/components/ai/ChatInputBox.tsx \
src/renderer/components/brief/TaskBriefChat.tsx
git commit -m "$(cat <<'EOF'
refactor(contextual): drop 'floating' branch from useAIChat and useChatStream
UIChatContext is now 'global' | 'project' only. Floating domain
signal, scope field, and onDomainSignal callback removed. ChatInputBox
no longer defines floating variant. TaskBriefChat migrated to contextual mode.
EOF
)"
```
---
## Task M6.4 — Main process: drop `sendFloatingRequest`, `orchestrateFloating`, floating mode
**Files:**
- Modify: `src/main/api/backend-client.ts`
- Modify: `src/main/ai/orchestrator.ts`
- Modify: `src/main/router/index.ts`
- Modify: `src/preload/trpc.ts`
- Modify: `src/renderer/lib/ipcLink.ts`
- Modify: `src/shared/api-types.ts`
### Context
`backend-client.ts` has `sendFloatingRequest` (lines 398458) and a `floating_domain` case in its WS message handler (lines 10601063).
`orchestrator.ts` has `OrchestrateFloatingInput` interface (lines 4049) and `orchestrateFloating` function (lines 142173). It also imports `WsFloatingRequest` from shared types.
`router/index.ts` imports `orchestrateFloating` (line 16) and uses it at lines 950960. The `mode` enum must change from `z.enum(['home', 'floating', 'contextual'])` to `z.enum(['contextual'])`.
`preload/trpc.ts` defines the `V3StreamEvent` union which includes `floating_domain` (lines 2942).
`renderer/lib/ipcLink.ts` defines a duplicate `V3StreamEvent` type which also includes `floating_domain` (lines 1633).
`shared/api-types.ts` defines `WsFloatingDomainSchema` (lines 217228) and `WsFloatingDomain` type.
Check also whether `WsFloatingRequest` type is still needed by anything after removing `orchestrateFloating`:
```bash
grep -rn "WsFloatingRequest\|WsFloatingDomain" src/
```
### Steps
- [ ] **Step 1: Delete sendFloatingRequest from backend-client.ts**
Open `src/main/api/backend-client.ts`.
Delete the entire `sendFloatingRequest` method (lines ~394458), from the JSDoc comment through the closing `}`.
Also delete the `floating_domain` case in the WS message handler (lines ~10601063):
```ts
case 'floating_domain': {
const listener = this.streamListeners.get(frame.data.requestId);
listener?.onDomain(frame.data.domain);
break;
}
```
Also check whether `onDomain` is still used in `StreamListener` type or elsewhere. If `onDomain` callback is only referenced by `sendFloatingRequest` and the `floating_domain` case, delete the `onDomain` field from `StreamListener` too (search for `onDomain` to confirm all uses).
- [ ] **Step 2: Delete orchestrateFloating from orchestrator.ts**
Open `src/main/ai/orchestrator.ts`.
Delete lines 4049 (`OrchestrateFloatingInput` interface):
```ts
interface OrchestrateFloatingInput {
message: string;
requestId?: string;
sessionId?: string;
scope: WsFloatingRequest['scope'];
conversationHistory?: WsFloatingRequest['conversationHistory'];
briefMode?: boolean;
briefingContext?: string;
sender?: Electron.WebContents;
}
```
Delete lines 116 of the import section's `WsFloatingRequest` import. Change:
```ts
import type { WsFloatingRequest } from '../../shared/api-types';
```
to nothing (remove the line entirely), since `WsFloatingRequest` is only used by `OrchestrateFloatingInput`.
Delete the entire `orchestrateFloating` function (lines 142173):
```ts
export async function orchestrateFloating(input: OrchestrateFloatingInput): Promise<OrchestrateResult> {
...
}
```
Update the docstring at the top of the file (lines 110) — remove the reference to `sendFloatingRequest()`:
```
* 2. Delegates to BackendClient.sendHomeRequest() / sendFloatingRequest()
```
becomes:
```
* 2. Delegates to BackendClient.sendHomeRequest() / sendContextualRequest()
```
- [ ] **Step 3: Update router/index.ts**
Open `src/main/router/index.ts`.
Change line 16 — remove `orchestrateFloating` from the import:
```ts
import { orchestrate, orchestrateFloating, orchestrateContextual, orchestrateTaskBriefResearch, dailyBrief, getCachedBrief, invalidateBriefCache } from '../ai/orchestrator';
```
becomes:
```ts
import { orchestrate, orchestrateContextual, orchestrateTaskBriefResearch, dailyBrief, getCachedBrief, invalidateBriefCache } from '../ai/orchestrator';
```
Change line 933 — update the `mode` enum:
```ts
mode: z.enum(['home', 'floating', 'contextual']).optional(),
```
becomes:
```ts
mode: z.enum(['contextual']).optional(),
```
Delete the floating branch in the mutation handler (lines 950960):
```ts
if (input.mode === 'floating' && input.scope) {
return await orchestrateFloating({
message: input.message,
requestId: input.requestId,
sessionId: input.sessionId,
scope: input.scope as Parameters<typeof orchestrateFloating>[0]['scope'],
conversationHistory: input.conversationHistory,
briefMode: input.briefMode,
briefingContext: input.briefingContext,
sender: ctx.sender,
});
}
```
The resulting mutation handler should flow: `if contextual → orchestrateContextual`, else `→ orchestrate`.
- [ ] **Step 4: Update preload/trpc.ts**
Open `src/preload/trpc.ts`.
Remove the `floating_domain` case from the `V3StreamEvent` union (lines 2942):
```ts
| {
type: 'floating_domain';
requestId: string;
domain:
| 'tasks'
| 'notes'
| 'timelines'
| 'projects'
| {
type: 'task' | 'timeline' | 'project' | 'note' | 'node';
id?: string | null;
section?: 'task' | 'timeline' | 'note' | null;
};
};
```
The `V3StreamEvent` union becomes:
```ts
type V3StreamEvent =
| { type: 'stream_start'; requestId: string }
| { type: 'stream_text'; requestId: string; chunk: string }
| { type: 'stream_end'; requestId: string; mutations?: unknown[] };
```
- [ ] **Step 5: Update renderer/lib/ipcLink.ts**
Open `src/renderer/lib/ipcLink.ts`.
Remove the `floating_domain` case from the `V3StreamEvent` union (lines 1633) the same way as done in preload. The type becomes:
```ts
type V3StreamEvent =
| { type: 'stream_start'; requestId: string }
| { type: 'stream_text'; requestId: string; chunk: string }
| { type: 'stream_end'; requestId: string; mutations?: unknown[] };
```
- [ ] **Step 6: Update shared/api-types.ts**
Open `src/shared/api-types.ts`.
Check whether `WsFloatingDomainSchema` / `WsFloatingDomain` are imported anywhere:
```bash
grep -rn "WsFloatingDomain\|WsFloatingRequest" src/
```
If `WsFloatingRequest` is imported only by the now-deleted orchestrator import, and `WsFloatingDomain` is imported nowhere, delete both from `shared/api-types.ts`:
- Delete `WsFloatingDomainSchema` Zod object (lines 217228)
- Delete `export type WsFloatingDomain = z.infer<typeof WsFloatingDomainSchema>;` (line 229)
- If `WsFloatingRequest` type/schema exists, delete it too (search for it in the file)
- [ ] **Step 7: Sweep for any remaining floating references in main**
```bash
grep -rn "floating\|Floating" src/main/ src/preload/ | grep -v node_modules | grep -v "\.md:"
```
Any remaining references to floating in these directories should be removed. Common residuals:
- `onDomain` callback type in `StreamListener` (if `sendFloatingRequest` was its only consumer)
- Stale comments in `backend-client.ts` mentioning floating
- [ ] **Step 8: Type check**
```bash
source ~/.nvm/nvm.sh && npx tsc --noEmit 2>&1 | grep -i "error" | head -30
```
Expected: zero errors. The `TaskBriefChat.tsx` now sends `mode: 'contextual'` which is valid per the updated schema.
- [ ] **Step 9: Commit**
```bash
git add src/main/api/backend-client.ts \
src/main/ai/orchestrator.ts \
src/main/router/index.ts \
src/preload/trpc.ts \
src/renderer/lib/ipcLink.ts \
src/shared/api-types.ts
git commit -m "$(cat <<'EOF'
refactor(contextual): main process drops sendFloatingRequest and floating mode
ai.chat tRPC procedure now accepts mode='contextual' (or unset for home).
Orchestrator loses the floating delegation branch. Backend client method
and WsFloatingDomain shared type removed.
EOF
)"
```
---
## Task M6.8 — Sweep electron-store and localStorage `'floating'` keys
**Files:**
- Inspect: `src/main/store.ts`
- Inspect: `src/renderer/` (any localStorage usage)
### Context
`src/main/store.ts` defines the electron-store schema — no `floating.*` keys exist in the current schema (verified: `AppSettings` has `sidebarCollapsed`, `encryptedTokens`, `backendUrl`, `deviceId`, `dailyBriefCache`, `localAgents`, `formatPrefs`, `uiLanguage`, `timelineZoom`). No floating keys.
This task is a verification sweep. If nothing is found, the commit is skipped.
### Steps
- [ ] **Step 1: Search for any floating.* key usage**
```bash
grep -rn "floating\." src/main/ src/preload/ src/renderer/ | grep -v node_modules | grep -v "\.md:"
```
Also check localStorage:
```bash
grep -rn "localStorage" src/renderer/ | grep -v node_modules | grep "float"
```
- [ ] **Step 2: Decision point**
If Step 1 returns NO results (or only results already cleaned by M6.1M6.4), this task is a no-op. Skip the commit and note in the final report: "M6.8: no floating.* store or localStorage keys found — no commit needed."
If Step 1 returns results with actual floating key reads/writes in store.ts or localStorage calls:
- Delete the key from `AppSettings` interface and the `defaults` object in `store.ts`
- Delete any `localStorage.getItem('floating.*')` or `setItem('floating.*', ...)` calls
- [ ] **Step 3: Conditional commit**
Only run this if changes were made in Step 2:
```bash
git add -A
git status --short # confirm what's staged
git commit -m "$(cat <<'EOF'
chore(contextual): purge residual 'floating' keys from store and renderer
EOF
)"
```
---
## Final Self-Review Checklist
After all commits, run these verification checks and paste the output into your report:
- [ ] **FloatingChat imports gone**
```bash
grep -rn "FloatingChat\|useFloatingChat\|FloatingChatProvider\|useDoubleClickAI" src/renderer/
```
Expected: no output.
- [ ] **data-ai-section gone**
```bash
grep -rn "data-ai-section" src/renderer/
```
Expected: no output.
- [ ] **floating string in renderer hooks**
```bash
grep -rn "'floating'" src/renderer/hooks/
```
Expected: no output (sidebar.tsx uses `"floating"` as a layout variant — that is unrelated and harmless).
- [ ] **UIChatContext type**
```bash
grep -n "type:" src/renderer/hooks/useAIChat.ts | head -5
```
Expected: `'global' | 'project'` only.
- [ ] **tRPC schema**
```bash
grep -n "floating" src/main/router/index.ts
```
Expected: no output.
- [ ] **sendFloatingRequest gone**
```bash
grep -rn "sendFloatingRequest\|orchestrateFloating" src/main/
```
Expected: no output.
- [ ] **Final tsc**
```bash
source ~/.nvm/nvm.sh && npx tsc --noEmit 2>&1 | grep -c "error TS"
```
Expected: `0`