Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ada4893
chore(deps-dev): bump @typescript-eslint/eslint-plugin in /src/frontend
dependabot[bot] Apr 27, 2026
433703f
chore(deps-dev): bump vitest from 4.1.4 to 4.1.5 in /src/frontend
dependabot[bot] Apr 27, 2026
c661050
chore(deps-dev): bump @tailwindcss/postcss in /src/frontend
dependabot[bot] Apr 27, 2026
b0b2eb1
chore(deps): bump react-i18next from 17.0.2 to 17.0.4 in /src/frontend
dependabot[bot] Apr 27, 2026
69efc8e
chore(deps): bump @google/genai from 1.48.0 to 1.50.1 in /src/frontend
dependabot[bot] Apr 27, 2026
d820da2
Merge pull request #216 from StableLlamaAI/dependabot/npm_and_yarn/sr…
StableLlama Apr 27, 2026
120f55a
Merge pull request #217 from StableLlamaAI/dependabot/npm_and_yarn/sr…
StableLlama Apr 27, 2026
327b562
chore(deps-dev): bump vite from 8.0.9 to 8.0.10 in /src/frontend
dependabot[bot] Apr 27, 2026
335b868
chore(deps-dev): bump jsdom from 29.0.2 to 29.1.0 in /src/frontend
dependabot[bot] Apr 27, 2026
3c37a9e
Merge pull request #218 from StableLlamaAI/dependabot/npm_and_yarn/sr…
StableLlama Apr 27, 2026
5d33b86
Merge pull request #219 from StableLlamaAI/dependabot/npm_and_yarn/sr…
StableLlama Apr 27, 2026
7adfd17
chore(deps-dev): bump prettier from 3.8.2 to 3.8.3 in /src/frontend
dependabot[bot] Apr 27, 2026
6eb1280
chore(deps): bump lucide-react from 1.8.0 to 1.11.0 in /src/frontend
dependabot[bot] Apr 27, 2026
b90133b
Merge pull request #220 from StableLlamaAI/dependabot/npm_and_yarn/sr…
StableLlama Apr 27, 2026
004ef90
Merge pull request #221 from StableLlamaAI/dependabot/npm_and_yarn/sr…
StableLlama Apr 27, 2026
addb01e
Merge pull request #222 from StableLlamaAI/dependabot/npm_and_yarn/sr…
StableLlama Apr 27, 2026
b609a0a
Merge pull request #223 from StableLlamaAI/dependabot/npm_and_yarn/sr…
StableLlama Apr 27, 2026
dddc833
Merge pull request #224 from StableLlamaAI/dependabot/npm_and_yarn/sr…
StableLlama Apr 27, 2026
a08f471
Prevent highlighting when loading
StableLlama Apr 27, 2026
965150b
Bump release date
StableLlama Apr 27, 2026
e72070d
Fix undo after (re)load
StableLlama Apr 27, 2026
80705c4
try to fix GH complaint
StableLlama Apr 27, 2026
3ae77ae
Merge pull request #225 from StableLlama/last_minute_fixes
StableLlama Apr 27, 2026
8c57248
fix cursor shape at chapters
StableLlama Apr 27, 2026
03b3ec7
Merge pull request #226 from StableLlama/more_tiny_fixes
StableLlama Apr 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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Changelog

## [0.9.0] - 2026-04-26
## [0.9.0] - 2026-04-27

### Added

Expand Down
18 changes: 13 additions & 5 deletions src/frontend/features/app/useAppControlProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,13 @@ type UseAppMainLayoutPropsParams = {
currentChapterId: string | null;
handleChapterSelect: (chapterId: string | null) => void;
deleteChapter: (chapterId: string) => Promise<void>;
updateChapter: (id: string, partial: Record<string, unknown>) => Promise<void>;
updateChapter: (
id: string,
partial: Record<string, unknown>,
sync?: boolean,
pushHistory?: boolean,
isUserEdit?: boolean
) => Promise<void>;
updateBook: (id: string, partial: Record<string, unknown>) => Promise<void>;
addChapter: (title?: string, content?: string, bookId?: string) => Promise<void>;
handleBookCreate: (title: string) => Promise<void>;
Expand Down Expand Up @@ -336,6 +342,7 @@ export function useAppHeaderProps(params: UseAppHeaderPropsParams): AppHeaderPro
);
}

// eslint-disable-next-line max-lines-per-function
export function useAppMainLayoutProps(params: UseAppMainLayoutPropsParams): {
sidebarControls: AppMainLayoutProps['sidebarControls'];
editorControls: AppMainLayoutProps['editorControls'];
Expand Down Expand Up @@ -373,8 +380,6 @@ export function useAppMainLayoutProps(params: UseAppMainLayoutPropsParams): {
refreshStory,
undo,
redo,
canUndo,
canRedo,
searchState,
currentChapter,
isChapterLoading,
Expand Down Expand Up @@ -464,11 +469,14 @@ export function useAppMainLayoutProps(params: UseAppMainLayoutPropsParams): {
]
);
const editorUpdateChapter = useCallback(
(id: string, partial: Record<string, unknown>) => {
(id: string, partial: Record<string, unknown>, isUndoRedo?: boolean) => {
if ('content' in partial) {
searchStateRef.current.notifyContentChanged(Number.parseInt(id, 10));
}
return updateChapter(id, partial);
if (isUndoRedo) {
return updateChapter(id, partial, true, false, false);
}
return updateChapter(id, partial, true, true, true);
},
[updateChapter]
);
Expand Down
10 changes: 5 additions & 5 deletions src/frontend/features/chapters/ChapterList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,8 @@ export const ChapterList: React.FC<ChapterListProps> = React.memo(
}`}
>
<button
className="flex flex-col w-full text-left cursor-row-resize"
style={{ cursor: 'row-resize' }}
type="button"
className="flex flex-col w-full text-left cursor-pointer"
draggable
onDragStart={(e: React.DragEvent<HTMLButtonElement>) =>
handleDragStart(e, 'chapter', chapter.id, index, chapter.book_id)
Expand Down Expand Up @@ -466,6 +466,9 @@ export const ChapterList: React.FC<ChapterListProps> = React.memo(
)}
</div>
</div>
<div className="mt-2 text-xs text-brand-gray-500 line-clamp-2">
{renderSummary()}
</div>
</button>
<div className="absolute top-2 right-2 flex items-center opacity-0 group-hover:opacity-100 transition-opacity">
<button
Expand All @@ -488,9 +491,6 @@ export const ChapterList: React.FC<ChapterListProps> = React.memo(
<Trash2 size={14} />
</button>
</div>
<p className="text-xs text-brand-gray-500 line-clamp-2 pointer-events-none">
{renderSummary()}
</p>
</div>
);
};
Expand Down
27 changes: 27 additions & 0 deletions src/frontend/features/editor/Editor.sync.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,33 @@ describe('Editor diff highlighting', () => {
expect(cmContent?.innerHTML).not.toContain('diff-inserted');
});

it('resets diff baseline when switching to a new chapter with no baseline', async () => {
const firstChapter = { ...mockChapter, content: 'Original content with AI' };
const secondChapter = {
...mockChapter,
id: '2',
title: 'Chapter 2',
content: 'Second chapter text',
};

const { rerender } = render(
<Editor
{...defaultProps}
chapter={firstChapter}
baselineContent="Original content"
/>
);

await act(async () => {
rerender(
<Editor {...defaultProps} chapter={secondChapter} baselineContent={undefined} />
);
});

const cmContent = document.querySelector('.cm-content');
expect(cmContent?.innerHTML).not.toContain('diff-inserted');
});

it('shows streaming content from store slot with correct diff during streaming', async () => {
const { rerender } = render(<Editor {...defaultProps} />);

Expand Down
24 changes: 20 additions & 4 deletions src/frontend/features/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ interface EditorProps {
viewMode: ViewMode;
showWhitespace?: boolean;
onToggleShowWhitespace?: () => void;
onChange: (id: string, updates: Partial<WritingUnit>) => void;
onChange: (id: string, updates: Partial<WritingUnit>, isUndoRedo?: boolean) => void;
baselineContent?: string;
language?: string;
spellCheck?: boolean;
Expand Down Expand Up @@ -153,6 +153,22 @@ export const Editor = React.memo(
const prevBaselineRef = useRef<string | undefined>(baselineContent);
// Keep the last non-undefined baseline so undo can restore the diff view.
const savedBaselineRef = useRef<string | undefined>(baselineContent);
const lastChapterIdRef = useRef(chapter.id);

useEffect(() => {
const isChapterSwitch = chapter.id !== lastChapterIdRef.current;
if (!isChapterSwitch) return;

lastChapterIdRef.current = chapter.id;
prevBaselineRef.current = baselineContent;
setLocalBaseline(baselineContent);
if (baselineContent !== undefined && baselineContent !== chapter.content) {
savedBaselineRef.current = baselineContent;
} else if (baselineContent === undefined) {
savedBaselineRef.current = undefined;
}
}, [chapter.id, baselineContent, chapter.content]);

if (baselineContent !== prevBaselineRef.current) {
prevBaselineRef.current = baselineContent;
setLocalBaseline(baselineContent);
Expand Down Expand Up @@ -181,7 +197,6 @@ export const Editor = React.memo(
// switch, AI update, undo/redo). Use chapter.id as the primary trigger
// for chapter switches; also watch chapter.content so AI insertions and
// undo/redo (which can change content without changing id) are reflected.
const lastChapterIdRef = useRef(chapter.id);
useEffect(() => {
const isChapterSwitch = chapter.id !== lastChapterIdRef.current;
lastChapterIdRef.current = chapter.id;
Expand Down Expand Up @@ -764,10 +779,11 @@ export const Editor = React.memo(
setLocalBaseline(undefined);
}
scheduleCheckContext();
if (contentDebounceRef.current)
if (contentDebounceRef.current) {
clearTimeout(contentDebounceRef.current);
}
contentDebounceRef.current = setTimeout(() => {
onChange(chapter.id, { content: val });
onChange(chapter.id, { content: val }, isUndoRedo);
}, DEBOUNCE_MS);
}}
onSelectionChange={scheduleCheckContext}
Expand Down
16 changes: 14 additions & 2 deletions src/frontend/features/layout/layoutControlTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,13 @@ export type MainSidebarControls = {
currentChapterId: string | null;
handleChapterSelect: (id: string | null) => void;
deleteChapter: (id: string) => Promise<void>;
updateChapter: (id: string, partial: Partial<Chapter>) => Promise<void>;
updateChapter: (
id: string,
partial: Partial<Chapter>,
sync?: boolean,
pushHistory?: boolean,
isUserEdit?: boolean
) => Promise<void>;
updateBook: (
id: string,
partial: { title?: string; summary?: string }
Expand Down Expand Up @@ -223,7 +229,13 @@ export type MainEditorControls = {
v: EditorSettings | ((prev: EditorSettings) => EditorSettings)
) => void;
viewMode: ViewMode;
updateChapter: (id: string, partial: Partial<WritingUnit>) => Promise<void>;
updateChapter: (
id: string,
partial: Partial<WritingUnit>,
sync?: boolean,
pushHistory?: boolean,
isUserEdit?: boolean
) => Promise<void>;
suggestionControls: MainEditorSuggestionControls;
aiControls: MainEditorAiControls;
setActiveFormats: (v: string[]) => void;
Expand Down
Loading
Loading