diff --git a/docs/floating-ai-integration-guide.md b/docs/floating-ai-integration-guide.md
index e8d2cda..0a2ee73 100644
--- a/docs/floating-ai-integration-guide.md
+++ b/docs/floating-ai-integration-guide.md
@@ -32,7 +32,7 @@ Steps MUST be implemented in order. Each step lists its prerequisites.
| Step | Title | Status |
|------|-------|--------|
| 1 | Extract shared `useAIChat` hook | [x] 2026-02-27 |
-| 2 | Create section registry + `FloatingChatContext` | [ ] |
+| 2 | Create section registry + `FloatingChatContext` | [x] 2026-02-27 |
| 3 | Create double-click hook | [ ] |
| 4 | Build `FloatingChat` component | [ ] |
| 5 | Add `ai:action` IPC side-channel | [ ] |
@@ -142,7 +142,7 @@ Refactor `AIChatPanel.tsx` to consume `useAIChat` instead of managing chat state
## Step 2: Create Section Registry + `FloatingChatContext`
-**Status**: [ ]
+**Status**: [x] 2026-02-27
**Prerequisites**: Step 1 completed
**Creates**: `src/renderer/context/FloatingChatContext.tsx`
**Modifies**: `src/renderer/components/layout/AppShell.tsx`
diff --git a/src/renderer/components/layout/AppShell.tsx b/src/renderer/components/layout/AppShell.tsx
index ff23edd..9822937 100644
--- a/src/renderer/components/layout/AppShell.tsx
+++ b/src/renderer/components/layout/AppShell.tsx
@@ -56,6 +56,7 @@ import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { AIChatPanel } from '@/components/ai/AIChatPanel';
import { useTheme } from '@/components/theme-provider';
+import { FloatingChatProvider } from '@/context/FloatingChatContext';
const NAV_ITEMS = [
{ to: '/', icon: House, label: 'Home' },
@@ -208,6 +209,7 @@ export function AppShell({ children }: AppShellProps) {
}, [openCurtain, closeCurtain]);
return (
+
<>
>
+
);
}
diff --git a/src/renderer/context/FloatingChatContext.tsx b/src/renderer/context/FloatingChatContext.tsx
new file mode 100644
index 0000000..05978be
--- /dev/null
+++ b/src/renderer/context/FloatingChatContext.tsx
@@ -0,0 +1,176 @@
+import {
+ createContext,
+ useContext,
+ useCallback,
+ useEffect,
+ useState,
+ useRef,
+ type ReactNode,
+ type RefObject,
+} from 'react';
+
+// ---------- Types ----------
+
+export interface AISection {
+ id: string; // e.g. "project-tasks", "tasks-list", "timeline-chart"
+ label: string; // Human-readable, e.g. "Tasks", "Project Timeline"
+ ref: RefObject;
+ projectId?: string; // If section is project-scoped
+}
+
+interface FloatingChatState {
+ isOpen: boolean;
+ activeSectionId: string | null;
+ position: { x: number; y: number; width: number };
+ morphTargetId: string | null;
+ projectId?: string;
+}
+
+interface FloatingChatContextValue {
+ // State
+ state: FloatingChatState;
+ sections: Map;
+
+ // Section registry
+ registerSection: (section: AISection) => void;
+ unregisterSection: (id: string) => void;
+
+ // Actions
+ openAtSection: (sectionId: string) => void;
+ moveToSection: (sectionId: string) => void;
+ close: () => void;
+ setMorphTarget: (id: string | null) => void;
+}
+
+// ---------- Constants ----------
+
+const CHAT_WIDTH = 380;
+const CHAT_HEIGHT = 420;
+const PADDING = 16;
+
+// ---------- Position computation ----------
+
+function computeAnchorPosition(
+ sectionRef: RefObject,
+): { x: number; y: number; width: number } {
+ const el = sectionRef.current;
+ if (!el) return { x: PADDING, y: PADDING, width: CHAT_WIDTH };
+
+ const rect = el.getBoundingClientRect();
+
+ // Anchor to top-right of section, offset inward
+ let x = rect.right - CHAT_WIDTH - PADDING;
+ let y = rect.top + PADDING;
+
+ // Edge-collision clamping
+ x = Math.max(PADDING, Math.min(x, window.innerWidth - CHAT_WIDTH - PADDING));
+ y = Math.max(
+ PADDING,
+ Math.min(y, window.innerHeight - CHAT_HEIGHT - PADDING),
+ );
+
+ return { x, y, width: CHAT_WIDTH };
+}
+
+// ---------- Context ----------
+
+const FloatingChatCtx = createContext(null);
+
+export function useFloatingChat(): FloatingChatContextValue {
+ const ctx = useContext(FloatingChatCtx);
+ if (!ctx)
+ throw new Error('useFloatingChat must be used within FloatingChatProvider');
+ return ctx;
+}
+
+// Convenience hook for pages to register a section
+export function useAISection(section: AISection): void {
+ const { registerSection, unregisterSection } = useFloatingChat();
+
+ useEffect(() => {
+ registerSection(section);
+ return () => unregisterSection(section.id);
+ }, [section.id, registerSection, unregisterSection]);
+}
+
+// ---------- Provider ----------
+
+export function FloatingChatProvider({ children }: { children: ReactNode }) {
+ const sectionsRef = useRef