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
19 changes: 13 additions & 6 deletions apps/desktop/src/renderer/components/sidebar/NotebookItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
GripVertical,
} from 'lucide-react';
import type { NotebookTreeNode } from '../../../preload/index';
import { useNotebookExpandStore } from '../../stores/notebookExpandStore';
import { CommitHistory } from '../git/CommitHistory';
import { sc } from './sc';

Expand Down Expand Up @@ -62,7 +63,9 @@ export const NotebookItem = memo(function NotebookItem({
onReorder,
siblingIds,
}: NotebookItemProps) {
const [isExpanded, setIsExpanded] = useState(true);
const isExpanded = useNotebookExpandStore(s => !s.collapsedIds.includes(node.notebook.id));
const toggleExpanded = useNotebookExpandStore(s => s.toggle);
const expandNotebook = useNotebookExpandStore(s => s.expand);
const [isEditing, setIsEditing] = useState(false);
const [editName, setEditName] = useState(node.notebook.name);
const [isGitEnabled, setIsGitEnabled] = useState(false);
Expand Down Expand Up @@ -103,10 +106,13 @@ export const NotebookItem = memo(function NotebookItem({
[node.notebook.id, onSelect]
);

const handleToggle = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
setIsExpanded(prev => !prev);
}, []);
const handleToggle = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
toggleExpanded(node.notebook.id);
},
[node.notebook.id, toggleExpanded]
);

const handleDoubleClick = useCallback(
(e: React.MouseEvent) => {
Expand Down Expand Up @@ -287,7 +293,7 @@ export const NotebookItem = memo(function NotebookItem({
if (pos === 'inside') {
// Move dragged notebook into this one as a child
onMove?.(draggedId, node.notebook.id);
setIsExpanded(true);
expandNotebook(node.notebook.id);
} else if (pos === 'above' || pos === 'below') {
const fromSameParent =
(draggedParentId === 'root' ? null : draggedParentId) === thisParentId;
Expand All @@ -313,6 +319,7 @@ export const NotebookItem = memo(function NotebookItem({
onMove,
onReorder,
siblingIds,
expandNotebook,
]
);

Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/renderer/data/nowBoard.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Shipped this week:
- Note templates: Meeting, Decision, Daily, Weekly, Reading, Issue (notebook `templates`).
- What’s New is authored (`docs/releases/`). Promotion PRs: `chore(release): promote X.Y.Z`.
- Workspace view: click a notebook to focus the sidebar on that tree. Escape or the breadcrumb home returns to All Notes.
- Collapsed notebooks list notes from their descendants; expanded ones stay direct-only.

CSS path (decided 2026-08-17):
Keep CSS modules + tokens. Do not add Tailwind to desktop.
Expand Down
24 changes: 22 additions & 2 deletions apps/desktop/src/renderer/hooks/useNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
type SortOrder,
} from '../stores/navigationStore';
import { listOptionsFromNav } from '../utils/listOptionsFromNav';
import { collectNotebookSubtreeIds } from '../utils/notebookTree';
import { collectNotebookSubtreeIds, findNotebookNode } from '../utils/notebookTree';
import { useNotebookExpandStore } from '../stores/notebookExpandStore';
import { useNotes, useNoteCounts, useScopedNoteCounts, withExcerpt } from './useNotes';
import { useNotebookTree, getNotebookPath, getAncestorIds } from './useNotebooks';

Expand Down Expand Up @@ -139,6 +140,15 @@ export function useFilteredNotes(): NoteWithExcerpt[] {
const sortOrder = useSortOrder();
const workspaceListAll = useWorkspaceListAll();
const workspaceNotebookIds = useWorkspaceNotebookIds();
const collapsedIds = useNotebookExpandStore(s => s.collapsedIds);
const { data: tree } = useNotebookTree();
const descendantNotebookIds = useMemo(() => {
if (navigation.kind !== 'notebook') return undefined;
if (!collapsedIds.includes(navigation.id)) return undefined;
const node = findNotebookNode(tree ?? [], navigation.id);
if (!node || node.children.length === 0) return undefined;
return collectNotebookSubtreeIds(tree ?? [], navigation.id);
}, [navigation, collapsedIds, tree]);

const options = useMemo(
() =>
Expand All @@ -150,8 +160,18 @@ export function useFilteredNotes(): NoteWithExcerpt[] {
sortOrder,
workspaceNotebookIds,
workspaceListAll,
descendantNotebookIds,
}),
[navigation, statusFilter, tagFilter, sortBy, sortOrder, workspaceNotebookIds, workspaceListAll]
[
navigation,
statusFilter,
tagFilter,
sortBy,
sortOrder,
workspaceNotebookIds,
workspaceListAll,
descendantNotebookIds,
]
);
const { data: notes } = useNotes(options);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, it, beforeEach } from 'vitest';
import { useNotebookExpandStore } from '../notebookExpandStore';

describe('notebookExpandStore', () => {
beforeEach(() => {
useNotebookExpandStore.setState({ collapsedIds: [] });
});

it('treats notebooks as expanded by default', () => {
expect(useNotebookExpandStore.getState().isExpanded('work')).toBe(true);
});

it('toggles collapse', () => {
useNotebookExpandStore.getState().toggle('work');
expect(useNotebookExpandStore.getState().isExpanded('work')).toBe(false);
useNotebookExpandStore.getState().toggle('work');
expect(useNotebookExpandStore.getState().isExpanded('work')).toBe(true);
});
});
24 changes: 24 additions & 0 deletions apps/desktop/src/renderer/stores/notebookExpandStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { create } from 'zustand';

/** Notebooks default to expanded. This set is the collapsed ones. */
interface NotebookExpandStore {
collapsedIds: string[];
isExpanded: (id: string) => boolean;
toggle: (id: string) => void;
expand: (id: string) => void;
}

export const useNotebookExpandStore = create<NotebookExpandStore>((set, get) => ({
collapsedIds: [],
isExpanded: id => !get().collapsedIds.includes(id),
toggle: id =>
set(state => ({
collapsedIds: state.collapsedIds.includes(id)
? state.collapsedIds.filter(item => item !== id)
: [...state.collapsedIds, id],
})),
expand: id =>
set(state => ({
collapsedIds: state.collapsedIds.filter(item => item !== id),
})),
}));
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ describe('listOptionsFromNav', () => {
});
});

it('includes descendant notebooks when the selected parent is collapsed', () => {
expect(
listOptionsFromNav({
navigation: { kind: 'notebook', id: 'work' },
statusFilter: null,
tagFilter: null,
descendantNotebookIds: ['work', 'api', 'web'],
})
).toEqual({
notebookIds: ['work', 'api', 'web'],
archived: 'active',
isDeleted: false,
limit: 10000,
});
});

it('scopes sidebar counts to the workspace tree', () => {
expect(
listOptionsFromNav({
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop/src/renderer/utils/listOptionsFromNav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export function listOptionsFromNav(input: {
workspaceListAll?: boolean;
/** Sidebar counts: always the workspace tree when focused. */
scopeToWorkspaceTree?: boolean;
/** Selected notebook subtree when that notebook is collapsed. */
descendantNotebookIds?: string[];
}): ListOptions {
const options: ListOptions = {
...optionsForNavigation(input.navigation, input),
Expand Down Expand Up @@ -51,6 +53,7 @@ function optionsForNavigation(
workspaceNotebookIds?: string[];
workspaceListAll?: boolean;
scopeToWorkspaceTree?: boolean;
descendantNotebookIds?: string[];
}
): ListOptions {
switch (navigation.kind) {
Expand Down Expand Up @@ -80,6 +83,14 @@ function optionsForNavigation(
isDeleted: false,
};
}
const descendants = input.descendantNotebookIds;
if (descendants && descendants.length > 1) {
return {
notebookIds: descendants,
archived: 'active',
isDeleted: false,
};
}
return {
notebookId: navigation.id,
archived: 'active',
Expand Down
1 change: 1 addition & 0 deletions docs/NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Shipped this week:
- Note templates: Meeting, Decision, Daily, Weekly, Reading, Issue (notebook `templates`).
- What’s New is authored (`docs/releases/`). Promotion PRs: `chore(release): promote X.Y.Z`. See `docs/WHATS_NEW.md`.
- Workspace view: click a notebook to focus the sidebar on that tree. Escape or the breadcrumb home returns to All Notes.
- Collapsed notebooks list notes from their descendants; expanded ones stay direct-only.

CSS path (decided 2026-08-17):
Keep CSS modules + tokens. Do not add Tailwind to desktop.
Expand Down
Loading