From 116f3e73b830294b421f9368daceee68aeab6ab4 Mon Sep 17 00:00:00 2001 From: codebude Date: Sat, 22 Aug 2026 19:57:53 +0200 Subject: [PATCH 01/23] Update .gitignore --- .gitignore | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index edabd7dd..87c957de 100644 --- a/.gitignore +++ b/.gitignore @@ -216,8 +216,6 @@ __marimo__/ # Streamlit .streamlit/secrets.toml -/.memsearch -/.playwright-mcp /ideas.txt /backend/data/ @@ -227,7 +225,14 @@ __marimo__/ !frontend/src/lib cookies.txt profile-snapshot -.plan/ + node_modules/ /*.png -*-snapshot.md \ No newline at end of file +*-snapshot.md + +# AI tools +/.memsearch +/.playwright-mcp +/.sverklo +.plan/ +/.opencode \ No newline at end of file From 2d7b29a935b025e086e628f2d814fd4f497ee568 Mon Sep 17 00:00:00 2001 From: codebude Date: Sat, 22 Aug 2026 19:58:02 +0200 Subject: [PATCH 02/23] Fix star chart in readme --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5ef60260..78d9115e 100644 --- a/README.md +++ b/README.md @@ -128,10 +128,12 @@ MIT ## Star History +## Star History + - - - Star History Chart + + + Star History Chart From 1767690854925a5d76c626aab6359f1457a1ce3d Mon Sep 17 00:00:00 2001 From: codebude Date: Sat, 22 Aug 2026 21:32:48 +0200 Subject: [PATCH 03/23] Add notifications and handler for reading progress changes to keep cards and drawer in sync --- .../lib/components/BookDetailDialog.svelte | 22 +++++++++++++++++-- frontend/src/routes/dashboard/+page.svelte | 12 +++++++++- frontend/src/routes/library/+page.svelte | 12 +++++++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/components/BookDetailDialog.svelte b/frontend/src/lib/components/BookDetailDialog.svelte index f2d08a5b..d0e40772 100644 --- a/frontend/src/lib/components/BookDetailDialog.svelte +++ b/frontend/src/lib/components/BookDetailDialog.svelte @@ -22,12 +22,14 @@ book = $bindable(null), open = $bindable(false), onEdit, - onDelete + onDelete, + onProgress }: { book?: Book | null; open?: boolean; onEdit?: (book: Book) => void; onDelete?: (id: number) => void; + onProgress?: (bookId: number, currentPage: number) => void; } = $props(); let blurbExpanded = $state(false); @@ -96,6 +98,8 @@ const entry = await api.books.progress.create(book.id, currentPage); latestDbPage = currentPage; progressEntries = [entry, ...progressEntries.filter((e) => e.id !== entry.id)]; + chartRenderKey += 1; + onProgress?.(book.id, currentPage); } catch (e: unknown) { toasts.add( e instanceof Error ? e.message : $_('common.actionFailed', { values: { action: $_('common.save') } }), @@ -126,6 +130,7 @@ currentPage = 0; latestDbPage = 0; } + onProgress?.(book.id, currentPage); } catch (e: unknown) { toasts.add( e instanceof Error ? e.message : $_('common.actionFailed', { values: { action: $_('common.delete') } }), @@ -244,6 +249,7 @@ let lineChart = $state | null>(null); let _themeSignal = $state(0); + let chartRenderKey = $state(0); onMount(() => { return themeApplyCount.subscribe((n: number) => { @@ -251,6 +257,16 @@ }); }); + // Force a full re-mount of the chart after the progress log modal closes so the + // graph is painted from scratch the same way it is when the detail drawer opens. + $effect(() => { + if (logModalOpen) { + return () => { + chartRenderKey += 1; + }; + } + }); + const lineChartConfig = $derived.by>(() => { void _themeSignal; return { @@ -487,7 +503,9 @@
{$_('book.progressGraph')}
- + {#key chartRenderKey} + + {/key}
{/if} diff --git a/frontend/src/routes/dashboard/+page.svelte b/frontend/src/routes/dashboard/+page.svelte index 5b69c2a8..14cc8b14 100644 --- a/frontend/src/routes/dashboard/+page.svelte +++ b/frontend/src/routes/dashboard/+page.svelte @@ -177,6 +177,10 @@ import { Search, X } from '@lucide/svelte'; void loadDashboard(); } + function handleProgressChange(bookId: number, currentPage: number) { + progressMap = { ...progressMap, [bookId]: currentPage }; + } + function handleDelete(id: number) { detailOpen = false; drawerOpen = false; @@ -491,7 +495,13 @@ import { Search, X } from '@lucide/svelte'; {/if} - + - + Date: Sat, 22 Aug 2026 21:47:56 +0200 Subject: [PATCH 04/23] Fix timezone handling in progress chart --- frontend/src/lib/chartjs/register.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/chartjs/register.ts b/frontend/src/lib/chartjs/register.ts index c28b11e0..997e11bc 100644 --- a/frontend/src/lib/chartjs/register.ts +++ b/frontend/src/lib/chartjs/register.ts @@ -31,13 +31,16 @@ Chart.register( import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import advancedFormat from 'dayjs/plugin/advancedFormat'; import localizedFormat from 'dayjs/plugin/localizedFormat'; import quarterOfYear from 'dayjs/plugin/quarterOfYear'; import weekday from 'dayjs/plugin/weekday'; +import { getTimezone } from '$lib/stores/timezone'; dayjs.extend(utc); +dayjs.extend(timezone); dayjs.extend(customParseFormat); dayjs.extend(advancedFormat); dayjs.extend(localizedFormat); @@ -69,16 +72,17 @@ _adapters._date.override({ const d = dayjs.utc(value as string | number | Date); return d.isValid() ? d.valueOf() : null; }, - format: (time: unknown, format: string) => dayjs.utc(time as number).format(format), - add: (time: unknown, amount: number, unit: string) => dayjs.utc(time as number).add(amount, unit as dayjs.ManipulateType).valueOf(), - diff: (max: unknown, min: unknown, unit: string) => dayjs.utc(max as number).diff(dayjs.utc(min as number), unit as dayjs.OpUnitType), + format: (time: unknown, format: string) => dayjs.utc(time as number).tz(getTimezone()).format(format), + add: (time: unknown, amount: number, unit: string) => dayjs.utc(time as number).tz(getTimezone()).add(amount, unit as dayjs.ManipulateType).valueOf(), + diff: (max: unknown, min: unknown, unit: string) => dayjs.utc(max as number).tz(getTimezone()).diff(dayjs.utc(min as number).tz(getTimezone()), unit as dayjs.OpUnitType), startOf: (time: unknown, unit: string, weekday?: number) => { + const date = dayjs.utc(time as number).tz(getTimezone()); if (unit === 'isoWeek') { - return (dayjs.utc(time as number) as unknown as { weekday: (w: number) => { valueOf: () => number } }).weekday(weekday ?? 1).valueOf(); + return (date as unknown as { weekday: (w: number) => { valueOf: () => number } }).weekday(weekday ?? 1).valueOf(); } - return dayjs.utc(time as number).startOf(unit as dayjs.OpUnitType).valueOf(); + return date.startOf(unit as dayjs.OpUnitType).valueOf(); }, - endOf: (time: unknown, unit: string) => dayjs.utc(time as number).endOf(unit as dayjs.OpUnitType).valueOf(), + endOf: (time: unknown, unit: string) => dayjs.utc(time as number).tz(getTimezone()).endOf(unit as dayjs.OpUnitType).valueOf(), } as unknown as Parameters[0]); export { Chart as ChartJS }; From b5509a151609b8a9d20fcfb47f8b89fb5680539e Mon Sep 17 00:00:00 2001 From: codebude Date: Sat, 22 Aug 2026 21:55:23 +0200 Subject: [PATCH 05/23] Add ui dialog to ask for start date when transitioning to "read" status and start date isn't set --- frontend/src/lib/components/BookDrawer.svelte | 49 ++++++++++++++++++- frontend/src/lib/i18n/locales/de.json | 4 ++ frontend/src/lib/i18n/locales/en.json | 4 ++ frontend/src/lib/i18n/locales/es.json | 4 ++ frontend/src/lib/i18n/locales/fr.json | 4 ++ frontend/src/lib/i18n/locales/zh.json | 4 ++ 6 files changed, 68 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/components/BookDrawer.svelte b/frontend/src/lib/components/BookDrawer.svelte index 6e8dd2a4..295727d6 100644 --- a/frontend/src/lib/components/BookDrawer.svelte +++ b/frontend/src/lib/components/BookDrawer.svelte @@ -35,6 +35,8 @@ let pendingStatus = $state(null); let pendingPayload = $state | null>(null); let pendingProgressBook = $state(null); + let startDatePromptOpen = $state(false); + let promptedStartDate = $state(tzToday(tz)); let autoSearchOpen = $state(false); let autoSearchLoading = $state(false); let autoSearchError = $state(null); @@ -188,7 +190,7 @@ pendingPayload = null; } - async function save() { + async function save({ skipAutoDateStarted = false } = {}) { if (!book) return; if (!author.trim()) { toasts.add($_('error.authorRequired'), 'error'); @@ -205,6 +207,17 @@ return; } const statusChanged = reading_status !== book.reading_status; + if ( + !skipAutoDateStarted && + book.reading_status === 'want_to_read' && + reading_status === 'read' && + !book.date_started && + !ds + ) { + promptedStartDate = today; + startDatePromptOpen = true; + return; + } const dfCleared = !df && !!book.date_finished; if (dfCleared && reading_status === 'read' && !statusChanged) { toasts.add($_('error.dateFinishedRequiredForRead'), 'error'); @@ -224,6 +237,7 @@ const transition = await api.books.transitionStatus(book.id, { new_status: reading_status, + ...(skipAutoDateStarted ? { skip_auto_date_started: true } : {}), ...(dfCleared ? { clear_date_finished: true } : {}), ...(dateStartedChanged && ds ? { force_date_started: fromDateInputValue(date_started, tz) } : {}), ...(dateFinishedChanged && df ? { force_date_finished: fromDateInputValue(date_finished, tz) } : {}) @@ -544,6 +558,39 @@ }} /> + {#if startDatePromptOpen} + + {/if} + {#if pendingProgressBook} {@const pbook = pendingProgressBook}