diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/components/HealthChart.svelte | 4 | ||||
| -rw-r--r-- | src/lib/server/db/schema.ts | 3 | ||||
| -rw-r--r-- | src/lib/server/services/grocery-actions.ts | 6 | ||||
| -rw-r--r-- | src/lib/server/services/routine-actions.ts | 11 | ||||
| -rw-r--r-- | src/lib/server/services/routines.ts | 19 | ||||
| -rw-r--r-- | src/lib/server/services/validation.ts | 2 | ||||
| -rw-r--r-- | src/lib/shared/routine-metrics.test.ts | 2 | ||||
| -rw-r--r-- | src/lib/shared/routine-metrics.ts | 30 | ||||
| -rw-r--r-- | src/lib/shared/temporal.ts | 3 | ||||
| -rw-r--r-- | src/routes/groceries/+page.svelte | 2 | ||||
| -rw-r--r-- | src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts | 3 | ||||
| -rw-r--r-- | src/temporal-ambient.d.ts | 12 |
12 files changed, 27 insertions, 70 deletions
diff --git a/src/lib/components/HealthChart.svelte b/src/lib/components/HealthChart.svelte index c6121e3..6de0891 100644 --- a/src/lib/components/HealthChart.svelte +++ b/src/lib/components/HealthChart.svelte @@ -20,9 +20,7 @@ markers.map((t) => ({ date: new Date(t), health: 100 })), ); - // Up to 5 x ticks spread across the series, skipping repeats (two samples - // the same day would print the same label twice). Dates stay Date objects - // because LayerChart's time scale consumes them; labels go through Temporal. + // Up to 5 x ticks across the series, skipping same-day repeats. const xTicks = $derived.by(() => { if (data.length < 2) return undefined; const step = Math.max(1, Math.floor((data.length - 1) / 4)); diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index c5daac5..a7fcd8f 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -40,8 +40,7 @@ export const projects = sqliteTable('projects', { archived: integer('archived', { mode: 'boolean' }).notNull().default(false), }); -// Append-only snapshot table: an edit inserts a new row with version+1, the -// current routine is the row with the max version for its id. +// Append-only snapshot table: current routine = max version per id. export const routines = sqliteTable( 'routines', { diff --git a/src/lib/server/services/grocery-actions.ts b/src/lib/server/services/grocery-actions.ts index c8974e9..2138f2d 100644 --- a/src/lib/server/services/grocery-actions.ts +++ b/src/lib/server/services/grocery-actions.ts @@ -17,11 +17,7 @@ export async function toggleGroceryAction(request: Request) { export type DeleteRowResult = ActionFailure<{ error: string }> | { success: boolean; type?: 'delete' }; -/** - * Shared 'delete a row by its id form field' action, used by the category - * list and the reference list (both submit one form whose buttons carry the - * row id). - */ +/** Shared 'delete by id form field' action for category and reference lists. */ export async function deleteByIdAction( request: Request, deleteFn: (id: number) => Promise<unknown>, diff --git a/src/lib/server/services/routine-actions.ts b/src/lib/server/services/routine-actions.ts index 64d0a1b..28a25cc 100644 --- a/src/lib/server/services/routine-actions.ts +++ b/src/lib/server/services/routine-actions.ts @@ -2,13 +2,7 @@ import { fail, type ActionFailure } from '@sveltejs/kit'; import { archive, complete, edit, loadCurrent } from './routines'; import { parseFormString, parseFrequencyDays } from './validation'; -/** - * Shared implementations of the routine edit/complete form actions, used by - * both the project dashboard (/projects/[id]) and the routine page - * (/projects/[projectId]/routine/[routineId]). Guarding and failure messages - * are the strictest common denominator: a missing or archived routine is - * rejected before any write. - */ +/** Shared routine edit/complete actions; rejects missing/archived routines. */ export type EditRoutineResult = ActionFailure<{ error: string }> | { success: boolean; type?: 'edit' }; @@ -38,8 +32,7 @@ export async function editRoutineAction( return fail(400, { error: 'Fréquence invalide' }); } - // The dashboard row form has no description field; only touch it when the - // form actually sends one, so list edits never wipe a description. + // Only touch description when the form sends one; list edits never wipe it. const fields: { name: string; frequencyDays: number; description?: string } = { name, frequencyDays }; const description = data.get('description'); diff --git a/src/lib/server/services/routines.ts b/src/lib/server/services/routines.ts index a29872a..57611d6 100644 --- a/src/lib/server/services/routines.ts +++ b/src/lib/server/services/routines.ts @@ -11,8 +11,7 @@ import { type HealthPoint, } from '$lib/shared/routine-metrics'; -// ponytail: next routine id via MAX(id)+1, safe enough for a single-user app; -// switch to a dedicated id table or transaction if concurrent writes appear. +// ponytail: next routine id via MAX(id)+1; fine while single-user. export async function nextRoutineId(db: AppDatabase): Promise<number> { const [{ maxId }] = await db @@ -44,12 +43,7 @@ export async function add( return inserted; } -/** - * Edit = insert a new snapshot row with version+1; history stays intact. - * Only the given fields change; the others are copied from the current - * snapshot so a name/frequency edit never touches the description and - * vice-versa. - */ +/** Edit = insert a version+1 snapshot, copying unchanged fields forward. */ export async function edit( id: number, fields: Partial<Pick<Routine, 'name' | 'frequencyDays' | 'description'>>, @@ -117,8 +111,7 @@ export async function loadArchived( .from(routines) .where(eq(routines.projectId, projectId)); - // Keep only the max-version row per routine id (same rule as the - // dashboard): archived = its current snapshot is archived. + // Max-version row per routine id; archived = max snapshot archived. const current = new Map<number, Routine>(); for (const r of versions) { const existing = current.get(r.id); @@ -127,8 +120,7 @@ export async function loadArchived( return ( [...current.values()] .filter((r) => r.archived) - // Legacy rows archived before archived_at existed fall back to their - // creation date. + // Rows archived before archived_at existed fall back to creation date. .sort((a, b) => ((a.archivedAt ?? '') < (b.archivedAt ?? '') ? 1 : -1)) .map((r) => ({ id: r.id, @@ -218,8 +210,7 @@ export async function loadProjectDashboard( .from(routines) .where(eq(routines.projectId, projectId)); - // Keep only the max-version row per routine id, then drop archived ones: - // an archived routine's max version is archived, so it disappears. + // Max-version row per routine id, then drop archived ones. const current = new Map<number, Routine>(); for (const r of versions) { const existing = current.get(r.id); diff --git a/src/lib/server/services/validation.ts b/src/lib/server/services/validation.ts index 69820ea..b0a515c 100644 --- a/src/lib/server/services/validation.ts +++ b/src/lib/server/services/validation.ts @@ -45,7 +45,7 @@ export function parseOptionalNumber( return n; } -/** Parses an interval like '2w' into days; throws when the format is invalid. */ +/** Parses an interval like '2w' into days; throws on invalid format. */ export function parseFrequencyDays(value: FormDataEntryValue | null): number { const days = parseIntervalToDays(value?.toString() ?? ''); if (days === null) { diff --git a/src/lib/shared/routine-metrics.test.ts b/src/lib/shared/routine-metrics.test.ts index 64b5da9..5bc0a78 100644 --- a/src/lib/shared/routine-metrics.test.ts +++ b/src/lib/shared/routine-metrics.test.ts @@ -49,7 +49,7 @@ describe('computeRoutineState', () => { it('is due the calendar day after completion for a daily routine', () => { const daily = { ...routine, frequencyDays: 1 }; const doneAt = '2026-09-07T20:00:00.000Z'; // yesterday evening - const now = Temporal.Instant.from('2026-09-08T08:00:00.000Z'); // this morning, <24h later + const now = Temporal.Instant.from('2026-09-08T08:00:00.000Z'); // <24h later const state = computeRoutineState(daily, [{ routineId: 1, doneAt }], now); expect(state.status).toBe('due'); }); diff --git a/src/lib/shared/routine-metrics.ts b/src/lib/shared/routine-metrics.ts index 7758f27..4cff240 100644 --- a/src/lib/shared/routine-metrics.ts +++ b/src/lib/shared/routine-metrics.ts @@ -1,7 +1,4 @@ -// Pure routine metrics, shared by server services and UI. No db access. -// All timestamps are ISO strings; calendar math uses Temporal in UTC: due -// dates anchor on UTC calendar days, not exact 24h intervals: a daily -// routine done yesterday is due today, whatever the time of day. +// Pure routine metrics shared by server and UI; ISO timestamps, UTC calendar. const UTC = 'UTC'; @@ -78,7 +75,7 @@ export interface RoutineState { /** Time past due, in units of the routine's own frequency (0 if not due). */ overdueRatio: number; status: RoutineStatus; - /** 100 on schedule, 50 when a full period overdue, 0 at two periods, floor at 0. */ + /** 100 on schedule, 50 one period overdue, 0 at two periods. */ health: number; } @@ -90,10 +87,7 @@ export function computeRoutineState( const lastDone = maxInstant(parseCompletions(routine.id, completions)); const reference = lastDone ?? parseInstant(routine.createdAt, now); const period = periodMs(routine.frequencyDays); - // Calendar-day anchor: the period starts at midnight of the reference day, - // so any new calendar day past the frequency flips the routine to due. - // A never-done routine has no completed period: its creation day is its - // due date, so it ages exactly like a done one whose anchor is creation. + // Calendar-day anchor: the period starts at the reference day's midnight. const due = reference .toZonedDateTimeISO(UTC) .startOfDay() @@ -138,7 +132,7 @@ export interface HealthPoint { health: number; } -/** Health at instant t given the reference anchor (creation or last completion). */ +/** Health at t given the reference anchor (creation or last completion). */ function healthAtPoint( reference: Temporal.Instant, frequencyDays: number, @@ -155,14 +149,7 @@ function healthAtPoint( return Math.max(0, Math.round(100 - Math.max(0, rawRatio) * 50)); } -/** - * Daily health samples from the routine's creation to now: 100 right after a - * completion, then a linear decay to 0 once two full periods are overdue - * (sawtooth). - * Each sample anchors on the latest completion at or before it (or creation, - * which is then itself the due date for a never-done routine). - * Uses the routine's current frequency across the whole history. - */ +/** Daily health samples: 100 at completion, decaying while overdue. */ export function computeHealthSeries( routine: RoutineInput, completions: CompletionInput[], @@ -210,12 +197,7 @@ export function computeHealthSeries( return points; } -/** - * Project health series: daily mean of per-routine health across the routines - * alive that day (createdAt <= t). Same sawtooth as the per-routine series, - * so the project curve ends at the current health score. Empty when no - * routines. - */ +/** Project health: daily mean of alive routines' health, same sawtooth. */ export function computeProjectHealthSeries( routines: RoutineInput[], completions: CompletionInput[], diff --git a/src/lib/shared/temporal.ts b/src/lib/shared/temporal.ts index 7097ba3..103f209 100644 --- a/src/lib/shared/temporal.ts +++ b/src/lib/shared/temporal.ts @@ -1,5 +1,4 @@ -// Shared Temporal display helpers. Storage and logic stay in ISO instants; -// these format one for the viewer in their own timezone. +// Temporal display helpers: format ISO instants for the viewer's timezone. /** Format an ISO instant in the viewer's timezone. */ export function formatInstant( diff --git a/src/routes/groceries/+page.svelte b/src/routes/groceries/+page.svelte index 5d88e49..616dc75 100644 --- a/src/routes/groceries/+page.svelte +++ b/src/routes/groceries/+page.svelte @@ -14,7 +14,7 @@ // eslint-disable-next-line svelte/prefer-writable-derived let localItems = $state<EnrichedGrocery[]>([]); let addError = $state(''); - // Sync local list with server data when it changes externally (toggle, navigation) + // Sync local list when server data changes (toggle, navigation). $effect(() => { localItems = data?.items ?? []; }); diff --git a/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts b/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts index 891a7b6..cb14e0d 100644 --- a/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts +++ b/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts @@ -25,8 +25,7 @@ export const load: PageServerLoad = async ({ params }) => { } const completions = await loadCompletions(routineId); - // An archived routine is frozen at its archive instant: state and chart - // stop there instead of now. + // Archived routines are frozen at their archive instant, not now. const at = routine.archivedAt ? Temporal.Instant.from(routine.archivedAt) : Temporal.Now.instant(); diff --git a/src/temporal-ambient.d.ts b/src/temporal-ambient.d.ts index fbd721e..87933b9 100644 --- a/src/temporal-ambient.d.ts +++ b/src/temporal-ambient.d.ts @@ -1,6 +1,4 @@ -// Minimal ambient Temporal declarations (native runtime, no polyfill). -// Covers only what this app uses; extend as needed. If a future TS lib -// ships Temporal types, these merge with them. +// Minimal ambient Temporal declarations (no polyfill); extend as needed. declare global { namespace Temporal { @@ -51,9 +49,11 @@ declare global { toZonedDateTime(timeZone: TimeZoneId): ZonedDateTime; } namespace PlainDate { - function from( - plainDateLike: { year: number; month: number; day: number }, - ): PlainDate; + function from(plainDateLike: { + year: number; + month: number; + day: number; + }): PlainDate; } namespace Now { function instant(): Instant; |
