From 62580c93a5a1d3c9be8b9e3b533586a6701654ed Mon Sep 17 00:00:00 2001 From: Claas Date: Sat, 11 Jul 2026 16:36:00 +0200 Subject: [PATCH] Format using oxfmt --- CLAUDE.md | 1 + package.json | 2 +- src/App.tsx | 8 +++----- src/components/Markdown.tsx | 6 ++---- src/db/cards.ts | 4 ++-- src/db/client.ts | 4 ++-- src/db/connection.ts | 4 ++-- src/db/decks.ts | 3 ++- src/db/reviews.ts | 9 +++++---- src/lib/db-file.ts | 9 ++++----- src/lib/deck-json.ts | 23 ++++++++++++++++++----- src/lib/time.ts | 5 ++--- src/pages/DeckPage.tsx | 13 +++++++++---- src/pages/SettingsPage.tsx | 4 +--- src/pages/StatsPage.tsx | 5 ++++- src/pages/StudyPage.tsx | 3 +-- src/srs/scheduler.ts | 7 +------ tsconfig.json | 5 +---- 18 files changed, 61 insertions(+), 54 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4c47bbc..cce2cf2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,6 +22,7 @@ Always use **pnpm** (never npm/npx; use `pnpm dlx` instead of npx). **Storage:** `@tursodatabase/database-wasm` — SQLite in the browser, persisted in OPFS. It needs SharedArrayBuffer, so COOP/COEP headers are mandatory in dev AND production (`vite.config.ts` sets them for dev/preview; `public/_headers` for Netlify/Cloudflare; plain GitHub Pages cannot host this app). Only one tab can hold the database; `App.tsx`'s ErrorBoundary shows the multi-tab error screen. Import from `@tursodatabase/database-wasm/vite` (dev-server workaround baked into the export map). **Layering** (UI → db, with srs as pure functions in between): + - `src/db/client.ts` — lazy connection singleton (`getDb`) + `closeDb` (used around OPFS file import/export). Runs migrations on open. - `src/db/migrations.ts` — append-only SQL migrations, tracked via `PRAGMA user_version`. Cascading deletes happen in repository code, not via FK enforcement. - `src/db/{decks,cards,reviews}.ts` — repository functions. All take a `DbConnection` parameter so tests can inject `@tursodatabase/database` (the Node build with an identical async API — this is why tests run in plain Node, no browser needed). diff --git a/package.json b/package.json index 0103f1a..5d67438 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "flashcut", - "private": true, "version": "0.0.0", + "private": true, "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.tsx b/src/App.tsx index 0178fa5..7b4a477 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,8 @@ import { A } from "@solidjs/router"; import { ErrorBoundary, type ParentProps, Show, Suspense } from "solid-js"; -import { colorScheme, toggleColorScheme } from "./stores/theme"; import { btnGhost, btnPrimary } from "./lib/ui"; +import { colorScheme, toggleColorScheme } from "./stores/theme"; function navLink(active?: boolean) { return `rounded-lg px-3 py-1.5 text-sm font-medium transition-colors hover:bg-stone-200 dark:hover:bg-stone-800 ${ @@ -19,7 +19,7 @@ function ErrorScreen(props: { error: unknown; reset: () => void }) { The local database could not be opened. This usually means Flashcut is already open in another tab — the database can only be used by one tab at a time.

-

{message()}

+

{message()}

@@ -55,9 +55,7 @@ export default function App(props: ParentProps) {
}> - Loading…

} - > + Loading…

}> {props.children}
diff --git a/src/components/Markdown.tsx b/src/components/Markdown.tsx index dbfbe66..544d04f 100644 --- a/src/components/Markdown.tsx +++ b/src/components/Markdown.tsx @@ -3,12 +3,10 @@ import { marked } from "marked"; import { createMemo } from "solid-js"; export function Markdown(props: { source: string; class?: string }) { - const html = createMemo(() => - DOMPurify.sanitize(marked.parse(props.source, { async: false })), - ); + const html = createMemo(() => DOMPurify.sanitize(marked.parse(props.source, { async: false }))); return (
); diff --git a/src/db/cards.ts b/src/db/cards.ts index 5c672c8..8b4085a 100644 --- a/src/db/cards.ts +++ b/src/db/cards.ts @@ -127,8 +127,8 @@ export async function listCards(db: DbConnection, deckId: number): Promise | undefined; /** - * Lazily opens the OPFS-backed database. Rejects (and stays rejected for - * retry) when another tab already holds the file lock. + * Lazily opens the OPFS-backed database. Rejects (and stays rejected for retry) when another tab + * already holds the file lock. */ export function getDb(): Promise { dbPromise ??= open().catch((error: unknown) => { diff --git a/src/db/connection.ts b/src/db/connection.ts index 79b30b4..a5b17d6 100644 --- a/src/db/connection.ts +++ b/src/db/connection.ts @@ -3,8 +3,8 @@ export type Row = Record; /** * The subset of the Turso database API used by the repositories. Both - * `@tursodatabase/database-wasm` (browser) and `@tursodatabase/database` - * (Node, used in tests) satisfy this interface. + * `@tursodatabase/database-wasm` (browser) and `@tursodatabase/database` (Node, used in tests) + * satisfy this interface. */ export interface DbConnection { exec(sql: string): Promise; diff --git a/src/db/decks.ts b/src/db/decks.ts index 74a292f..412e9aa 100644 --- a/src/db/decks.ts +++ b/src/db/decks.ts @@ -1,6 +1,7 @@ -import { type DbConnection, type Row, withTransaction } from "./connection"; import { State } from "ts-fsrs"; +import { type DbConnection, type Row, withTransaction } from "./connection"; + export interface Deck { id: number; name: string; diff --git a/src/db/reviews.ts b/src/db/reviews.ts index f5131bb..5016f3a 100644 --- a/src/db/reviews.ts +++ b/src/db/reviews.ts @@ -46,7 +46,10 @@ export async function recordReview( }); } -/** Instants of all reviews since `sinceIso`, ascending. Day-bucketing happens in the UI (user timezone). */ +/** + * Instants of all reviews since `sinceIso`, ascending. Day-bucketing happens in the UI (user + * timezone). + */ export async function reviewTimesSince(db: DbConnection, sinceIso: string): Promise { const rows = await db.all( "SELECT review FROM review_logs WHERE review >= ? ORDER BY review", @@ -62,8 +65,6 @@ export async function totalReviewCount(db: DbConnection): Promise { /** Due instants of all scheduled (non-new) cards, for the forecast chart. */ export async function scheduledDueTimes(db: DbConnection): Promise { - const rows = await db.all( - `SELECT due FROM cards WHERE state != ${State.New} ORDER BY due`, - ); + const rows = await db.all(`SELECT due FROM cards WHERE state != ${State.New} ORDER BY due`); return rows.map((row) => String(row["due"])); } diff --git a/src/lib/db-file.ts b/src/lib/db-file.ts index 059411a..2ddd8cf 100644 --- a/src/lib/db-file.ts +++ b/src/lib/db-file.ts @@ -2,9 +2,8 @@ import { closeDb, DB_FILE, getDb } from "../db/client"; import { downloadBlob } from "./download"; /** - * Downloads the raw SQLite file from OPFS. The WAL is checkpointed and the - * connection closed first so the copied file is complete and consistent; the - * next query lazily reopens the database. + * Downloads the raw SQLite file from OPFS. The WAL is checkpointed and the connection closed first + * so the copied file is complete and consistent; the next query lazily reopens the database. */ export async function exportDatabaseFile(): Promise { const db = await getDb(); @@ -19,8 +18,8 @@ export async function exportDatabaseFile(): Promise { } /** - * Replaces the OPFS database with the given SQLite file and reloads the app. - * Destructive — callers must confirm with the user first. + * Replaces the OPFS database with the given SQLite file and reloads the app. Destructive — callers + * must confirm with the user first. */ export async function importDatabaseFile(file: File): Promise { const bytes = await file.arrayBuffer(); diff --git a/src/lib/deck-json.ts b/src/lib/deck-json.ts index 7a789bc..ec2ff86 100644 --- a/src/lib/deck-json.ts +++ b/src/lib/deck-json.ts @@ -1,9 +1,10 @@ +import { State } from "ts-fsrs"; + import { type FsrsColumns, createCard, listCards } from "../db/cards"; import { type DbConnection, withTransaction } from "../db/connection"; import { createDeck, getDeck } from "../db/decks"; import { newCardFsrs } from "../srs/scheduler"; import { isoNow } from "./time"; -import { State } from "ts-fsrs"; export interface DeckExport { version: 1; @@ -39,7 +40,10 @@ export async function exportDeckJson(db: DbConnection, deckId: number): Promise< }; } -/** Creates a new deck from parsed JSON; cards without valid FSRS state start as new. Returns the deck id. */ +/** + * Creates a new deck from parsed JSON; cards without valid FSRS state start as new. Returns the + * deck id. + */ export async function importDeckJson(db: DbConnection, data: unknown): Promise { const parsed = parseDeckExport(data); const now = isoNow(); @@ -68,7 +72,11 @@ export function parseDeckExport(data: unknown): DeckExport { throw new Error("Not a valid Flashcut deck file: unsupported version"); } const deck = record["deck"]; - if (typeof deck !== "object" || deck === null || typeof (deck as Record)["name"] !== "string") { + if ( + typeof deck !== "object" || + deck === null || + typeof (deck as Record)["name"] !== "string" + ) { throw new Error("Not a valid Flashcut deck file: missing deck name"); } const deckRecord = deck as Record; @@ -77,11 +85,16 @@ export function parseDeckExport(data: unknown): DeckExport { throw new Error("Not a valid Flashcut deck file: missing cards array"); } const cards = cardsRaw.map((item, index) => { - const cardRecord = (typeof item === "object" && item !== null ? item : {}) as Record; + const cardRecord = (typeof item === "object" && item !== null ? item : {}) as Record< + string, + unknown + >; const front = cardRecord["front"]; const back = cardRecord["back"]; if (typeof front !== "string" || typeof back !== "string") { - throw new Error(`Not a valid Flashcut deck file: card ${index + 1} needs front and back strings`); + throw new Error( + `Not a valid Flashcut deck file: card ${index + 1} needs front and back strings`, + ); } const fsrs = parseFsrs(cardRecord["fsrs"]); return fsrs ? { front, back, fsrs } : { front, back }; diff --git a/src/lib/time.ts b/src/lib/time.ts index a1580fe..77addcb 100644 --- a/src/lib/time.ts +++ b/src/lib/time.ts @@ -1,7 +1,6 @@ /** - * Stored timestamps are ISO-8601 UTC with exactly 3 fractional digits - * (matching Date#toISOString), so lexicographic order == chronological order - * in SQL string comparisons. + * Stored timestamps are ISO-8601 UTC with exactly 3 fractional digits (matching Date#toISOString), + * so lexicographic order == chronological order in SQL string comparisons. */ export function toIso(instant: Temporal.Instant): string { return instant.toString({ fractionalSecondDigits: 3 }); diff --git a/src/pages/DeckPage.tsx b/src/pages/DeckPage.tsx index 4efc65e..67e38f8 100644 --- a/src/pages/DeckPage.tsx +++ b/src/pages/DeckPage.tsx @@ -42,7 +42,14 @@ export default function DeckPage() { const db = await getDb(); const id = editingId(); if (id == null) { - await createCard(db, deckId(), front(), back(), isoNow(), newCardFsrs(Temporal.Now.instant())); + await createCard( + db, + deckId(), + front(), + back(), + isoNow(), + newCardFsrs(Temporal.Now.instant()), + ); } else { await updateCardContent(db, id, front(), back()); } @@ -77,9 +84,7 @@ export default function DeckPage() {
-

- {editingId() == null ? "Add card" : "Edit card"} -

+

{editingId() == null ? "Add card" : "Edit card"}