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

Submodule adiuvAI updated: 6aa7cb3d22...c1b1b289c1

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`

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff