- Conversations in a versioned IndexedDB store (freezmo-chat v1, zod validated with the same schema backup/sync use, capped at 100 x 300). Settled turns persist (question always; answer when produced, partial stops included); ?c=<id> keeps reloads on the conversation via history.replaceState (no router round-trip mid-chat). - History UI: desktop column, phone sheet (spring, backdrop, Escape), auto titles from the first question, inline rename, delete with an Undo toast, diacritic-folding search over titles and message text. Scope and model are remembered per conversation; history is readable offline. - Assistant actions: Turn into flashcards (New deck with the answer as notes, landing on the options step - deck seed gained step) and Save to notes (appends to the single scoped deck, trimmed at 40k). - Chats travel in the backup export (preview counts them) and in the encrypted sync payload; still never the key. - Motion from lib/motion.ts: message fade-up, sheet springs for the model/context/history sheets, 150ms composer height transition, all skipped under reduced motion. - Tests: chat suite 63 -> 89 checks (history, reload, rename/search/ delete/undo, actions, export, two-device sync round-trip, offline, desktop panel). Full set: 12/12 suites, 513 checks. SW -> freezmo-v33. Also: tests/model-compare.mjs benchmark (diagnostic) for deciding the default text model on numbers; defaults unchanged.
38 lines
1,002 B
TypeScript
38 lines
1,002 B
TypeScript
"use client";
|
|
|
|
/**
|
|
* Hand-off from a homework result to the New deck flow: the content travels in
|
|
* sessionStorage and the wizard picks it up on mount, with the options step
|
|
* pre-filled. One-shot, like the share-target payload.
|
|
*/
|
|
|
|
export type DeckSeed = {
|
|
text: string;
|
|
difficulty?: "easy" | "medium" | "hard";
|
|
count?: number;
|
|
/** Wizard step to land on (chat's "turn into flashcards" opens options). */
|
|
step?: 1 | 2 | 3;
|
|
};
|
|
|
|
const KEY = "freezmo.deck.seed.v1";
|
|
|
|
export function setDeckSeed(seed: DeckSeed): void {
|
|
try {
|
|
window.sessionStorage.setItem(KEY, JSON.stringify(seed));
|
|
} catch {
|
|
/* private mode */
|
|
}
|
|
}
|
|
|
|
export function takeDeckSeed(): DeckSeed | null {
|
|
try {
|
|
const raw = window.sessionStorage.getItem(KEY);
|
|
if (!raw) return null;
|
|
window.sessionStorage.removeItem(KEY);
|
|
const parsed = JSON.parse(raw) as DeckSeed;
|
|
if (typeof parsed.text !== "string" || !parsed.text.trim()) return null;
|
|
return parsed;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|