Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8b4d715
MAESTRO: port agent-inbox shared types from feature/inbox-focus-polish
Feb 26, 2026
8d91745
MAESTRO: add unifiedInbox flag to EncoreFeatureFlags
Feb 26, 2026
10d97be
MAESTRO: add Unified Inbox toggle card in Settings Encore tab
Feb 26, 2026
4cf633c
MAESTRO: port useAgentInbox hook and AgentInbox components from featu…
Feb 26, 2026
c0122f6
MAESTRO: integrate Unified Inbox modal into App.tsx with Encore Featu…
Feb 26, 2026
5585426
MAESTRO: register Option+I keyboard shortcut for Unified Inbox with E…
Feb 26, 2026
9878b48
MAESTRO: add Unified Inbox menu item to hamburger menu in SessionList
Feb 26, 2026
2b050a8
MAESTRO: add Unified Inbox action to command palette (QuickActionsModal)
Feb 26, 2026
8db8aca
MAESTRO: fix unused-var eslint warning in FocusModeView
Feb 26, 2026
2ad1360
MAESTRO: fix Focus Mode auto-scroll bug with proximity-based approach
Feb 26, 2026
3858e60
MAESTRO: fix CodeRabbit bugs — group header a11y, scoped collapse, se…
Feb 26, 2026
cf4d3da
MAESTRO: align Inbox + Focus Mode headers with Director's Notes tokens
Feb 26, 2026
2bf9841
MAESTRO: align Focus Mode content spacing + reply input with Director…
Feb 26, 2026
1db3174
MAESTRO: remove redundant star icons from Inbox + Focus Mode
Feb 26, 2026
2ec48d2
MAESTRO: mark test verification gate as passed — 3 pre-existing Codex…
Feb 26, 2026
9aac1d9
MAESTRO: mark final verification gate as passed — ESLint clean + all …
Feb 26, 2026
b8ee7c3
fix: remove duplicate optimistic user log from handleSendToTab
Feb 26, 2026
41492b8
fix: wire inboxProcessInputRef binding, handler props, and tabId navi…
Feb 26, 2026
a948a2b
fix: resolve resize listener leak, group key collision, and listbox a…
Feb 26, 2026
962155f
fix: address all CodeRabbit/Greptile minor review feedback for Unifie…
Feb 26, 2026
fc186ea
MAESTRO: fix CodeRabbit review round 3 — race condition, ARIA, focus …
Feb 26, 2026
b62b844
MAESTRO: address CodeRabbit nitpicks — perf, timing, docs, typing
Feb 26, 2026
346686e
fix: force AI mode in inbox reply flows and simplify focus restoration
Feb 27, 2026
d5ec10f
fix: resolve remaining Unified Inbox review feedback
Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# ENCORE-INBOX-02: Add `unifiedInbox` flag to EncoreFeatureFlags

## Objective
Register the Unified Inbox as an Encore Feature with a type flag and default value.

## Context
- `EncoreFeatureFlags` interface is at `src/renderer/types/index.ts:906` — currently only has `directorNotes: boolean`
- `DEFAULT_ENCORE_FEATURES` is at `src/renderer/stores/settingsStore.ts:110` — currently `{ directorNotes: false }`
- The settings store hydration merges saved values with defaults at `settingsStore.ts:1669-1673` using spread: `{ ...DEFAULT_ENCORE_FEATURES, ...(saved) }` — so new fields with defaults are safe
- Both type AND default MUST be updated in the same task to avoid runtime `undefined`

## Tasks

- [x] In `src/renderer/types/index.ts`, find the `EncoreFeatureFlags` interface at line 906. Add `unifiedInbox: boolean` below `directorNotes`. Also in `src/renderer/stores/settingsStore.ts`, find `DEFAULT_ENCORE_FEATURES` at line 110. Add `unifiedInbox: false` to the object. Both changes must happen together:
```typescript
// types/index.ts:906
export interface EncoreFeatureFlags {
directorNotes: boolean;
unifiedInbox: boolean;
}

// stores/settingsStore.ts:110
export const DEFAULT_ENCORE_FEATURES: EncoreFeatureFlags = {
directorNotes: false,
unifiedInbox: false,
};
```

- [x] Run `npm run lint` to verify the new field doesn't cause type errors. Existing code spreading `encoreFeatures` will pick up the new field automatically via the default merge at line 1669-1673.

## Gate
- `npm run lint` passes
- `EncoreFeatureFlags` has both `directorNotes` and `unifiedInbox` fields
- `DEFAULT_ENCORE_FEATURES` has `unifiedInbox: false`
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ENCORE-INBOX-04: Port useAgentInbox hook and AgentInbox components

## Objective
Port the data hook and all 3 component files from `feature/inbox-focus-polish` into the current codebase, adapting imports to the new architecture.

## Context
- Source branch: `feature/inbox-focus-polish`
- Files to port:
- `src/renderer/hooks/useAgentInbox.ts` — data aggregation hook
- `src/renderer/components/AgentInbox/index.tsx` — modal shell + view switching
- `src/renderer/components/AgentInbox/InboxListView.tsx` — virtualized list view
- `src/renderer/components/AgentInbox/FocusModeView.tsx` — focus mode detail view
- The new codebase uses `settingsStore.ts` (Zustand) instead of prop-drilling settings
- `useModalLayer` hook and `MODAL_PRIORITIES` still exist in the same locations
- `modalStore` still exists at `src/renderer/stores/modalStore.ts`
- `formatRelativeTime` is at `src/renderer/utils/formatters.ts`

## Tasks

- [x] Create `src/renderer/hooks/useAgentInbox.ts` by extracting from old branch: `git show feature/inbox-focus-polish:src/renderer/hooks/useAgentInbox.ts`. Copy the full content. Verify imports: `Session`, `Group` from `../types`, `InboxItem`, `InboxFilterMode`, `InboxSortMode` from `../types/agent-inbox`. These should resolve since Phase 01 created the types file. Run `npm run lint` to check.

- [x] Create the directory `src/renderer/components/AgentInbox/` and port all 3 component files. For each, extract from old branch and copy:
1. `git show feature/inbox-focus-polish:src/renderer/components/AgentInbox/index.tsx` → `src/renderer/components/AgentInbox/index.tsx`
2. `git show feature/inbox-focus-polish:src/renderer/components/AgentInbox/InboxListView.tsx` → `src/renderer/components/AgentInbox/InboxListView.tsx`
3. `git show feature/inbox-focus-polish:src/renderer/components/AgentInbox/FocusModeView.tsx` → `src/renderer/components/AgentInbox/FocusModeView.tsx`
After copying, verify all imports resolve. Key imports to check:
- `../../types` should export `Theme`, `Session`, `Group`, `ThinkingMode`, `LogEntry`
- `../../types/agent-inbox` should export all inbox types (from Phase 01)
- `../../hooks/useAgentInbox` should resolve (created above)
- `../../hooks/ui/useModalLayer` — verify this exists: `ls src/renderer/hooks/ui/useModalLayer*`
- `../../constants/modalPriorities` — verify: `grep -n "AGENT_INBOX" src/renderer/constants/modalPriorities.ts`. If `AGENT_INBOX` doesn't exist in priorities, add it (use priority 555, same as old branch)
- `../../stores/modalStore` — verify `selectModalData` is exported
- `../../utils/formatters` — verify `formatRelativeTime` is exported
- The ported `InboxListView.tsx` uses `react-window` for virtualization, but this dependency does NOT exist in the project. The codebase already uses `@tanstack/react-virtual` (used by Director's Notes `UnifiedHistoryTab.tsx`). After copying the files, replace ALL `react-window` imports with `@tanstack/react-virtual` equivalents. Specifically: replace `import { FixedSizeList } from 'react-window'` (or similar) with `import { useVirtualizer } from '@tanstack/react-virtual'` and refactor the list rendering to use the `useVirtualizer` hook pattern (see `src/renderer/components/DirectorNotes/UnifiedHistoryTab.tsx` lines 201-221 for reference). This avoids adding an unnecessary dependency.
- `../../utils/markdownConfig` — verify `generateTerminalProseStyles` exists
- `../../components/MarkdownRenderer` — verify exists
**Notes:** react-window → @tanstack/react-virtual migration completed. Added `agentInbox` to ModalId type, AgentInboxModalData interface, ModalDataMap, and getModalActions() in modalStore.ts. AGENT_INBOX priority added at 555 in modalPriorities.ts.

- [x] Run `npm run lint` and fix any import resolution errors. Common issues: renamed types, moved hooks, missing re-exports. Fix each error by finding the new location of the imported symbol.
**Notes:** Fixed 2 type narrowing issues in index.tsx (filterMode/sortMode from ModalData needed explicit casts to InboxFilterMode/InboxSortMode). Lint passes clean.

## Gate
- `src/renderer/hooks/useAgentInbox.ts` exists
- `src/renderer/components/AgentInbox/` directory has 3 files: `index.tsx`, `InboxListView.tsx`, `FocusModeView.tsx`
- `npm run lint` passes (zero errors)
- `AGENT_INBOX` priority registered in `modalPriorities.ts`
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# ENCORE-INBOX-05: Integrate Unified Inbox into App.tsx (modal + state + Encore gating)

## Objective
Wire the Unified Inbox modal into App.tsx with Encore Feature gating, following the Director's Notes pattern exactly.

## Context
- App.tsx is at `src/renderer/App.tsx` (6,563 lines post-refactor)
- Director's Notes pattern to mirror:
- Line 46-47: lazy import `const DirectorNotesModal = lazy(() => import('./components/DirectorNotes')...)`
- Line 356-357: modal state from `useModalActions()` — `directorNotesOpen, setDirectorNotesOpen`
- Line 508: `encoreFeatures` destructured from settings
- Line 5220: conditional setter for SessionList: `setDirectorNotesOpen: encoreFeatures.directorNotes ? setDirectorNotesOpen : undefined`
- Line 5634: conditional handler for QuickActions: `encoreFeatures.directorNotes ? () => setDirectorNotesOpen(true) : undefined`
- Line 6017: gated modal render: `{encoreFeatures.directorNotes && directorNotesOpen && (<Suspense>...</Suspense>)}`
- `useModalActions()` comes from `src/renderer/stores/modalStore.ts` — need to add `agentInboxOpen`/`setAgentInboxOpen` there first
- `modalStore.ts` has `agentInbox` in valid modal IDs (check with `grep "agentInbox" src/renderer/stores/modalStore.ts`) — if not, add it

## Tasks

- [x] In `src/renderer/stores/modalStore.ts`, verify that `agentInbox` is registered as a valid modal ID. Search for `type ModalId` or the array/union of valid modal IDs. If `agentInbox` is NOT present, add it following the pattern used by `directorNotes` or other modals. Also verify `useModalActions()` returns `agentInboxOpen` and `setAgentInboxOpen` — if not, add them following the `directorNotesOpen`/`setDirectorNotesOpen` pattern.
> `agentInbox` was already registered as ModalId (line 230) with `AgentInboxModalData` and `setAgentInboxOpen` action. Added `agentInboxOpen` reactive selector to `useModalActions()` return object.

- [x] In `src/renderer/App.tsx`, add the lazy import for AgentInbox near line 46 (next to Director's Notes import):
```typescript
const AgentInbox = lazy(() => import('./components/AgentInbox'));
```
Then destructure `agentInboxOpen` and `setAgentInboxOpen` from `useModalActions()` (find where `directorNotesOpen` is destructured, around line 356).
> Added lazy import at line 49. Destructured `agentInboxOpen` and `setAgentInboxOpen` from `useModalActions()` at lines 360-361.

- [x] In `src/renderer/App.tsx`, add the gated modal render. Find the Director's Notes modal block (line 6017: `{encoreFeatures.directorNotes && directorNotesOpen && (`). AFTER that block, add:
```tsx
{encoreFeatures.unifiedInbox && agentInboxOpen && (
<Suspense fallback={null}>
<AgentInbox
theme={theme}
sessions={sessions}
groups={groups}
onClose={() => setAgentInboxOpen(false)}
onNavigateToSession={handleNavigateToSession}
/>
</Suspense>
)}
```
Verify `handleNavigateToSession` or equivalent callback exists (search for the function used by Director's Notes `onResumeSession`). If the exact callback doesn't exist, create a minimal one or use `handleDirectorNotesResumeSession` pattern as reference.
> Created `handleAgentInboxNavigateToSession` callback (closes inbox, switches to session). Gated modal render at line 6051 with full props including `enterToSendAI`.

- [x] Wire conditional setters for child components. Find the props assembly area (around line 5220 where Director's Notes does it):
- For SessionList: `setAgentInboxOpen: encoreFeatures.unifiedInbox ? setAgentInboxOpen : undefined`
- For QuickActionsModal (around line 5634): `onOpenUnifiedInbox: encoreFeatures.unifiedInbox ? () => setAgentInboxOpen(true) : undefined`
> Both wired. Also added `setAgentInboxOpen` to keyboard handler ref object for future keybinding support.

- [x] Run `npm run lint` — expect type errors for SessionList/QuickActionsModal props not accepting optional. Note errors for next phases.
> 2 expected type errors:
> 1. `TS2353`: `setAgentInboxOpen` not in `UseSessionListPropsDeps` type (line 5236)
> 2. `TS2322`: `onOpenUnifiedInbox` not in `AppModalsProps` type (line 5652)
> Both will be resolved in phases 06-08 when those component prop interfaces are updated.

## Gate
- `npm run lint` may have type errors (resolved in phases 06-08)
- `grep -n "agentInboxOpen" src/renderer/App.tsx` returns lazy import, state, gated render, and conditional setters
- Modal is gated: `encoreFeatures.unifiedInbox && agentInboxOpen`
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# ENCORE-INBOX-07: Add Unified Inbox to hamburger menu (SessionList)

## Objective

Add the Unified Inbox menu item to the hamburger menu, conditionally rendered based on the Encore gate.

## Context

- SessionList is at `src/renderer/components/SessionList.tsx`
- Director's Notes pattern:
- Props: `setDirectorNotesOpen?: (open: boolean) => void` (optional, line 442)
- Destructured: line 462
- Conditional render: `{setDirectorNotesOpen && (<button .../>)}` (line 702)
- Shortcut badge: `shortcuts.directorNotes` (line 719)
- The `setAgentInboxOpen` prop does NOT exist yet — must be added
- App.tsx passes `undefined` when feature is off (from Phase 05)
- Props may also need updating in `HamburgerMenuContentProps` (inner component) and the outer `SessionListProps`
- Check `src/renderer/hooks/props/useSessionListProps.ts` for prop threading

## Tasks

- [x] In `src/renderer/components/SessionList.tsx`, add `setAgentInboxOpen?: (open: boolean) => void` to ALL relevant prop interfaces. Search for `setDirectorNotesOpen` to find all interfaces (there are at least 2: `HamburgerMenuContentProps` around line 442 and the outer interface around line 1081). Add `setAgentInboxOpen` as optional in each.

- [x] In the hamburger menu render section, find the Director's Notes button (line 702: `{setDirectorNotesOpen && (`). AFTER that block (after its closing `)}` and before the next `<div>` separator), add the Unified Inbox button following the same pattern:

```tsx
{
setAgentInboxOpen && (
<button
onClick={() => {
setAgentInboxOpen(true);
setMenuOpen(false);
}}
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-md hover:bg-white/10 transition-colors text-left"
>
<Inbox className="w-5 h-5" style={{ color: theme.colors.accent }} />
<div className="flex-1">
<div className="text-sm font-medium" style={{ color: theme.colors.textMain }}>
Unified Inbox
</div>
<div className="text-xs" style={{ color: theme.colors.textDim }}>
Cross-session notifications
</div>
</div>
{shortcuts.agentInbox && (
<span
className="text-xs font-mono px-1.5 py-0.5 rounded"
style={{ backgroundColor: theme.colors.bgActivity, color: theme.colors.textDim }}
>
{formatShortcutKeys(shortcuts.agentInbox.keys)}
</span>
)}
</button>
);
}
```

Add `Inbox` to the lucide-react imports if not already there.

- [x] In `src/renderer/hooks/props/useSessionListProps.ts`, add `setAgentInboxOpen` to the returned props if this file threads props to SessionList. Search for `setDirectorNotesOpen` in this file to find the pattern.

- [x] Ensure `setAgentInboxOpen` is properly destructured in all intermediate components that pass it down within SessionList.tsx.

- [x] Run `npm run lint` to verify. (Note: only pre-existing error remains — `onOpenUnifiedInbox` in AppModalsProps from another phase)

## Gate

- `npm run lint` passes (pre-existing `onOpenUnifiedInbox` error in AppModalsProps is from a separate phase and does not block this gate)
- `grep -n "setAgentInboxOpen" src/renderer/components/SessionList.tsx` returns prop definition + conditional render
- Menu button only appears when `setAgentInboxOpen` is defined (i.e., feature enabled)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# ENCORE-INBOX-08: Add Unified Inbox to command palette (QuickActionsModal)

## Objective
Add the Unified Inbox action to the command palette, conditionally rendered based on Encore gate.

## Context
- QuickActionsModal is at `src/renderer/components/QuickActionsModal.tsx`
- Director's Notes pattern:
- Props: `onOpenDirectorNotes?: () => void` (optional, line 119)
- Destructured: line 206
- Conditional action entry: lines 1023-1036 using spread `...(onOpenDirectorNotes ? [{...}] : [])`
- NO agentInbox entry exists — must be created from scratch
- App.tsx passes `undefined` for the handler when feature is off (from Phase 05)

## Tasks

- [x] In `src/renderer/components/QuickActionsModal.tsx`, add `onOpenUnifiedInbox?: () => void` to the props interface (find where `onOpenDirectorNotes` is defined, around line 119). Then destructure it alongside `onOpenDirectorNotes` (around line 206).

- [x] In the actions array, find the Director's Notes entry (line 1023: `...(onOpenDirectorNotes`). AFTER that block, add the Unified Inbox entry:
```typescript
...(onOpenUnifiedInbox
? [
{
id: 'unifiedInbox',
label: 'Unified Inbox',
shortcut: shortcuts.agentInbox,
subtext: 'Cross-session notification center',
action: () => {
onOpenUnifiedInbox();
setQuickActionOpen(false);
},
},
]
: []),
```

- [x] Run `npm run lint` to verify.

> **Note:** Also added `onOpenUnifiedInbox` to both `AppUtilityModalsProps` and `AppModalsProps` interfaces in `AppModals.tsx`, with destructuring and JSX threading to QuickActionsModal, to fix the type error from App.tsx already passing this prop.

## Gate
- `npm run lint` passes
- `grep -n "unifiedInbox" src/renderer/components/QuickActionsModal.tsx` returns action entry
- Action only appears when `onOpenUnifiedInbox` is defined (feature enabled)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ENCORE-INBOX-10: Fix Focus Mode auto-scroll bug

## Objective
Fix the bug where Focus Mode forcefully scrolls to the bottom every time logs update, preventing users from reading earlier messages.

## Context
- Bug location: `src/renderer/components/AgentInbox/FocusModeView.tsx` — search for `Auto-scroll to bottom when logs change`
- Current code: `useEffect` fires on every `visibleLogs` change and sets `scrollTop = scrollHeight`
- Problem: when an agent is actively producing output, `visibleLogs` updates constantly, scroll snaps to bottom — user cannot scroll up
- Fix: proximity-based auto-scroll (only scroll if user is near bottom)
- Note: line numbers are approximate since the file was just ported in Phase 04

## Tasks

- [x] In `src/renderer/components/AgentInbox/FocusModeView.tsx`, find the auto-scroll useEffect (search for `scrollRef.current.scrollTop = scrollRef.current.scrollHeight`). Replace the entire useEffect block with a proximity-based approach:
```typescript
// Auto-scroll to bottom ONLY if user is near bottom (within 150px) or item changed
const scrollRef = useRef<HTMLDivElement>(null);
const prevItemRef = useRef<string>('');
useEffect(() => {
if (!scrollRef.current) return;
const el = scrollRef.current;
const itemKey = `${item.sessionId}:${item.tabId}`;
const isNewItem = prevItemRef.current !== itemKey;
if (isNewItem) {
prevItemRef.current = itemKey;
el.scrollTop = el.scrollHeight;
return;
}
// Only auto-scroll if user is near bottom
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
if (distanceFromBottom < 150) {
el.scrollTop = el.scrollHeight;
}
}, [visibleLogs, item.sessionId, item.tabId]);
```
Keep the `scrollRef` declaration if it already exists (just replace the useEffect body). If `prevItemRef` doesn't exist, add it. Key behaviors:
- **New item (prev/next navigation):** always scroll to bottom (fresh context)
- **Same item, user near bottom (<150px):** auto-scroll (following along)
- **Same item, user scrolled up (>150px):** don't scroll (reading history)

- [x] Run `npm run lint` to verify no type errors.

## Gate
- `npm run lint` passes
- `grep -n "distanceFromBottom" src/renderer/components/AgentInbox/FocusModeView.tsx` returns the proximity check
Loading