Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 8 additions & 5 deletions web/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,18 +518,21 @@ the doc-title row. Desktop layout is unchanged.
| File | Role |
|---|---|
| `web/src/components/KnowledgeBase/MobileDocSheet.tsx` | Backdrop overlay + right-side slide-in panel (reuses `card-panel` and `animate-panel-slide-in` CSS classes). Renders `KnowledgeBaseSidebar` inside. Accepts all sidebar props plus `onClose: () => void`. Calls `onClose` after a doc is selected (intercepts `onSelect`). |
| `web/src/components/KnowledgeBase/KnowledgeBase.tsx` | Owns `isSheetOpen: boolean` state. Applies `hidden md:flex` to the sidebar wrapper. Renders `<MobileDocSheet>` when `isSheetOpen` is true. Passes `onOpenSelector={() => setIsSheetOpen(true)}` to `KnowledgeDocViewer`. |
| `web/src/components/KnowledgeBase/KnowledgeDocViewer.tsx` | Accepts `onOpenSelector?: () => void`. Renders a `md:hidden` trigger button at the top of the component showing the current doc name + chevron. Clicking it calls `onOpenSelector?.()`. Also forwards `onOpenSelector` to `KnowledgeDocEditor` when editing. |
| `web/src/components/KnowledgeBase/KnowledgeDocEditor.tsx` | Accepts `onOpenSelector?: () => void`. Renders the same `md:hidden` trigger button at the top. When no doc is provided, shows "Choose a document ›" as the prompt. |
| `web/src/components/KnowledgeBase/MobileDocTrigger.tsx` | Shared `md:hidden` trigger row: open-book icon + doc name (or "Choose a document" in `--grey1` when undefined) + chevron-right. Props: `docName?: string; onClick: () => void`. Used by `KnowledgeBase.tsx` fallback branches and by `KnowledgeDocViewer`/`KnowledgeDocEditor`. |
| `web/src/components/KnowledgeBase/KnowledgeBase.tsx` | Owns `isSheetOpen: boolean` state. Applies `hidden md:flex` to the sidebar wrapper. Renders `<MobileDocSheet>` when `isSheetOpen` is true. Passes `onOpenSelector={() => setIsSheetOpen(true)}` to `KnowledgeDocViewer`. Also renders `<MobileDocTrigger>` in the "No KB docs yet" and "Select a doc." fallback branches so mobile users always have a way to open the sheet. |
| `web/src/components/KnowledgeBase/KnowledgeDocViewer.tsx` | Accepts `onOpenSelector?: () => void`. Renders `<MobileDocTrigger docName={doc}>` at the top when `onOpenSelector` is set. Forwards `onOpenSelector` to `KnowledgeDocEditor` when editing. |
| `web/src/components/KnowledgeBase/KnowledgeDocEditor.tsx` | Accepts `onOpenSelector?: () => void`. Renders `<MobileDocTrigger docName={doc}>` at the top when `onOpenSelector` is set. |

### Behaviour

- **Desktop (≥ `md`):** Two-column flex layout. Sidebar always visible on the
left (`w-72`). Trigger row hidden (`md:hidden`).
- **Mobile (< `md`):** Viewer/editor takes full width. Sidebar hidden
(`hidden md:flex`). Trigger row visible at the top showing current doc name +
chevron, or "Choose a document ›" when no doc is selected. Tapping the trigger
sets `isSheetOpen = true`.
chevron, or "Choose a document" when no doc is selected. Tapping the trigger
sets `isSheetOpen = true`. The trigger is also rendered in the "No KB docs
yet" and "Select a doc." fallback branches, so mobile users always have a way
to open the sheet regardless of selection state.

### Closing the sheet

Expand Down
37 changes: 22 additions & 15 deletions web/src/components/KnowledgeBase/KnowledgeBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { api, errorMessage } from '../../api/client';
import { KnowledgeBaseSidebar } from './KnowledgeBaseSidebar';
import { KnowledgeDocViewer } from './KnowledgeDocViewer';
import { MobileDocSheet } from './MobileDocSheet';
import { MobileDocTrigger } from './MobileDocTrigger';
import { useUnsavedGuard } from './useUnsavedGuard';
import { useKnowledgeBaseData } from './useKnowledgeBaseData';
import { useKnowledgeRefreshStatus } from './useKnowledgeRefreshStatus';
Expand Down Expand Up @@ -198,23 +199,29 @@ export function KnowledgeBase({ project }: { project: string }) {
}
/>
) : summary.repos.every((r) => r.docs.length === 0) ? (
<div className="p-6" style={{ color: 'var(--grey1)' }}>
<p>No knowledge base docs yet for this project.</p>
<p className="mt-2 text-sm">
Click the Refresh button next to a repo on the left to build the KB. Alternatively
run{' '}
<code
className="px-1 rounded"
style={{ backgroundColor: 'var(--bg1)', color: 'var(--fg)' }}
>
/contextmatrix:refresh-knowledge --project {project}
</code>{' '}
in your Claude Code session.
</p>
<div className="flex flex-col h-full">
<MobileDocTrigger onClick={() => setIsSheetOpen(true)} />
<div className="p-6" style={{ color: 'var(--grey1)' }}>
<p>No knowledge base docs yet for this project.</p>
<p className="mt-2 text-sm">
Click the Refresh button next to a repo on the left to build the KB. Alternatively
run{' '}
<code
className="px-1 rounded"
style={{ backgroundColor: 'var(--bg1)', color: 'var(--fg)' }}
>
/contextmatrix:refresh-knowledge --project {project}
</code>{' '}
in your Claude Code session.
</p>
</div>
</div>
) : (
<div className="p-6" style={{ color: 'var(--grey1)' }}>
Select a doc.
<div className="flex flex-col h-full">
<MobileDocTrigger onClick={() => setIsSheetOpen(true)} />
<div className="p-6" style={{ color: 'var(--grey1)' }}>
Select a doc.
</div>
</div>
)}
</div>
Expand Down
26 changes: 2 additions & 24 deletions web/src/components/KnowledgeBase/KnowledgeDocEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { lazy, Suspense, useEffect, useRef, useState } from 'react';
import { errorMessage } from '../../api/client';
import { useTheme } from '../../hooks/useTheme';
import { MobileDocTrigger } from './MobileDocTrigger';

const MDEditor = lazy(() => import('@uiw/react-md-editor'));

Expand Down Expand Up @@ -79,30 +80,7 @@ export function KnowledgeDocEditor({ initialContent, doc, onCancel, onSave, onDi

return (
<section className="flex flex-col h-full" data-color-mode={theme}>
{onOpenSelector && (
<button
type="button"
onClick={onOpenSelector}
aria-label="Open document selector"
className="md:hidden px-4 py-2 flex items-center gap-2 text-sm w-full text-left"
style={{ borderBottom: '1px solid var(--bg3)', color: 'var(--fg)', backgroundColor: 'var(--bg0)' }}
>
{doc ? (
<>
<svg className="w-4 h-4 flex-shrink-0" style={{ color: 'var(--grey1)' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
</svg>
<span className="flex-1 truncate" aria-hidden="true">{doc}</span>
<svg className="w-4 h-4 flex-shrink-0" style={{ color: 'var(--grey1)' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<polyline points="9 18 15 12 9 6" />
</svg>
</>
) : (
<span style={{ color: 'var(--grey1)' }}>Choose a document ›</span>
)}
</button>
)}
{onOpenSelector && <MobileDocTrigger docName={doc} onClick={onOpenSelector} />}
<header
className="flex items-center justify-between gap-3 px-6 py-3"
style={{
Expand Down
20 changes: 2 additions & 18 deletions web/src/components/KnowledgeBase/KnowledgeDocViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { api } from '../../api/client';
import { useTheme } from '../../hooks/useTheme';
import type { KnowledgeDocResponse, KnowledgeRepoSummary } from '../../types';
import { KnowledgeDocEditor } from './KnowledgeDocEditor';
import { MobileDocTrigger } from './MobileDocTrigger';
import { formatRelativeTime } from '../CardPanel/utils';

const MarkdownPreview = lazy(() => import('@uiw/react-markdown-preview'));
Expand Down Expand Up @@ -102,24 +103,7 @@ export function KnowledgeDocViewer({

return (
<div className="flex flex-col h-full">
{onOpenSelector && (
<button
type="button"
onClick={onOpenSelector}
aria-label="Open document selector"
className="md:hidden px-4 py-2 flex items-center gap-2 text-sm w-full text-left"
style={{ borderBottom: '1px solid var(--bg3)', color: 'var(--fg)', backgroundColor: 'var(--bg0)' }}
>
<svg className="w-4 h-4 flex-shrink-0" style={{ color: 'var(--grey1)' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
</svg>
<span className="flex-1 truncate" aria-hidden="true">{doc}</span>
<svg className="w-4 h-4 flex-shrink-0" style={{ color: 'var(--grey1)' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<polyline points="9 18 15 12 9 6" />
</svg>
</button>
)}
{onOpenSelector && <MobileDocTrigger docName={doc} onClick={onOpenSelector} />}
<header
className="flex flex-wrap items-start gap-x-4 gap-y-3 px-7 py-5"
style={{ borderBottom: '1px solid var(--bg3)', backgroundColor: 'var(--bg0)' }}
Expand Down
53 changes: 53 additions & 0 deletions web/src/components/KnowledgeBase/MobileDocTrigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
interface MobileDocTriggerProps {
docName?: string;
onClick: () => void;
}

export function MobileDocTrigger({ docName, onClick }: MobileDocTriggerProps) {
return (
<button
type="button"
onClick={onClick}
aria-label="Open document selector"
className="md:hidden px-4 py-2 flex items-center gap-2 text-sm w-full text-left"
style={{ borderBottom: '1px solid var(--bg3)', color: 'var(--fg)', backgroundColor: 'var(--bg0)' }}
>
<svg
className="w-4 h-4 flex-shrink-0"
style={{ color: 'var(--grey1)' }}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
</svg>
{docName ? (
<span className="flex-1 truncate" aria-hidden="true">
{docName}
</span>
) : (
<span className="flex-1" style={{ color: 'var(--grey1)' }}>
Choose a document
</span>
)}
<svg
className="w-4 h-4 flex-shrink-0"
style={{ color: 'var(--grey1)' }}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<polyline points="9 18 15 12 9 6" />
</svg>
</button>
);
}
Loading