sync: Major UI updates from private repo (Jan 16-24)

Features synced:
- UI Panels Refactor: flexible pane system with ChatPane, NodePane,
  DimensionsPane, WorkflowsPane, ViewsPane, MapPane
- Source Content Reader: content type detection + 4 formatters
  (transcript, book, markdown, raw)
- Source Content Search: Cmd+F search in Source Reader
- Feed Layout Overhaul: LeftToolbar, SplitHandle, CollapsedRail
- Map Panel: promoted to first-class pane
- Edge policy simplification: auto-inference + direction correction

New files:
- src/components/panes/* (pane system)
- src/components/layout/CollapsedRail.tsx
- src/components/layout/LeftToolbar.tsx
- src/components/layout/SplitHandle.tsx
- src/components/focus/source/* (Source Reader + formatters)
- src/components/views/ViewsOverlay.tsx

Updated:
- ThreePanelLayout.tsx (complete refactor)
- FocusPanel.tsx (Source tab integration)
- API routes (dimensions GET, edges auto-inference)
- Database services (nodes, edges, dimensions)

Dependencies added:
- react-markdown
- remark-gfm

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
“BeeRad”
2026-01-24 21:54:21 +11:00
co-authored by Claude Opus 4.5
parent cea1df7f1f
commit 05523b7cb2
34 changed files with 7895 additions and 991 deletions
+56 -11
View File
@@ -12,16 +12,26 @@ interface QuickAddSubmitPayload {
interface QuickAddInputProps {
activeDelegations: AgentDelegation[];
onSubmit: (payload: QuickAddSubmitPayload) => Promise<void>;
// External control (optional - if provided, component is controlled)
isOpen?: boolean;
onClose?: () => void;
}
export default function QuickAddInput({ activeDelegations, onSubmit }: QuickAddInputProps) {
export default function QuickAddInput({ activeDelegations, onSubmit, isOpen, onClose }: QuickAddInputProps) {
const [input, setInput] = useState('');
const [isPosting, setIsPosting] = useState(false);
const [isExpanded, setIsExpanded] = useState(false);
const [isExpandedInternal, setIsExpandedInternal] = useState(false);
const [dragOver, setDragOver] = useState(false);
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
const [uploadError, setUploadError] = useState<string | null>(null);
// Support both controlled (isOpen/onClose) and uncontrolled (internal state) modes
const isControlled = isOpen !== undefined;
const isExpanded = isControlled ? isOpen : isExpandedInternal;
const setIsExpanded = isControlled
? (value: boolean) => { if (!value && onClose) onClose(); }
: setIsExpandedInternal;
const maxConcurrent = 5;
const activeCount = activeDelegations.filter(
(d) => d.status === 'queued' || d.status === 'in_progress'
@@ -149,8 +159,12 @@ export default function QuickAddInput({ activeDelegations, onSubmit }: QuickAddI
const hasContent = input.trim() || uploadedFile;
// Collapsed state - prominent "ADD STUFF" button
// Collapsed state - only show button if NOT controlled externally
if (!isExpanded) {
// In controlled mode, don't render anything when closed
if (isControlled) return null;
// Uncontrolled mode - show the "ADD STUFF" button
return (
<button
onClick={() => setIsExpanded(true)}
@@ -201,14 +215,10 @@ export default function QuickAddInput({ activeDelegations, onSubmit }: QuickAddI
);
}
// Expanded state - full width overlay
return (
// Expanded state - modal overlay (centered if controlled, absolute if uncontrolled)
const modalContent = (
<div
style={{
position: 'absolute',
top: '60px',
left: '20px',
right: '20px',
display: 'flex',
flexDirection: 'column',
gap: '12px',
@@ -216,13 +226,15 @@ export default function QuickAddInput({ activeDelegations, onSubmit }: QuickAddI
padding: '16px',
borderRadius: '12px',
border: dragOver ? '1px solid #22c55e' : '1px solid #2a2a2a',
zIndex: 100,
animation: 'fadeIn 150ms ease-out',
transition: 'border-color 0.15s ease'
transition: 'border-color 0.15s ease',
width: isControlled ? '500px' : 'auto',
maxWidth: isControlled ? '90vw' : 'none',
}}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div style={{
@@ -534,4 +546,37 @@ export default function QuickAddInput({ activeDelegations, onSubmit }: QuickAddI
`}</style>
</div>
);
// In controlled mode, wrap with backdrop
if (isControlled) {
return (
<div
style={{
position: 'fixed',
inset: 0,
background: 'rgba(0, 0, 0, 0.6)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000,
}}
onClick={() => setIsExpanded(false)}
>
{modalContent}
</div>
);
}
// Uncontrolled mode - render with absolute positioning
return (
<div style={{
position: 'absolute',
top: '60px',
left: '20px',
right: '20px',
zIndex: 100,
}}>
{modalContent}
</div>
);
}
+14
View File
@@ -22,10 +22,17 @@ const createVoiceRequestId = () =>
? crypto.randomUUID()
: `voice_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
interface HighlightedPassage {
selectedText: string;
nodeId: number;
nodeTitle: string;
}
interface RAHChatProps {
openTabsData: Node[];
activeTabId: number | null;
activeDimension?: string | null;
onClearDimension?: () => void;
onNodeClick?: (nodeId: number) => void;
delegations?: AgentDelegation[];
messages?: ChatMessage[];
@@ -33,12 +40,16 @@ interface RAHChatProps {
mode?: 'easy' | 'hard';
delegationMode?: boolean;
delegationSessionId?: string;
onQuickAdd?: () => void;
highlightedPassage?: HighlightedPassage | null;
onClearPassage?: () => void;
}
export default function RAHChat({
openTabsData,
activeTabId,
activeDimension,
onClearDimension: _onClearDimension,
onNodeClick,
delegations = [],
messages: externalMessages,
@@ -46,6 +57,9 @@ export default function RAHChat({
mode = 'easy',
delegationMode = false,
delegationSessionId,
onQuickAdd: _onQuickAdd,
highlightedPassage: _highlightedPassage,
onClearPassage: _onClearPassage,
}: RAHChatProps) {
// Use external state if provided (lifted state), otherwise use local state
const [internalMessages, internalSetMessages] = useState<ChatMessage[]>([]);