refactor(frontend): migrate JS modules to TypeScript
Wholesale JS -> TS migration of the frontend — stores, utils, actions,
and all Svelte component scripts adopt type annotations. Compile-time
surfaces (app.d.ts, lib/types/) added for shared DTO types.
Build plumbing:
- package.json: dev:frontend script that runs svelte-kit sync first
- tauri.conf.json: beforeDevCommand points at dev:frontend
- run.sh: dropped the sed-hack that temporarily blanked beforeDevCommand;
now relies on npm run dev:frontend to avoid double-Vite
- jsconfig.json: allowImportingTsExtensions
Preserves all Group 1 behaviour:
- page.svelte.ts keeps loadHistory / loadTasks Tauri-first, no
localStorage; saveTranscriptMeta + mapTranscriptRow + mapTaskRow
intact; update_task_cmd and update_transcript_meta_cmd invocations
carry the correct payload shape.
- Toasts, preferences stores typed without behaviour change.
- Viewer still routes segment edits through saveTranscriptMeta; the
Task 1.5 TODO markers are gone.
taskExtractor.ts is functionally improved during the migration:
- multi-task matches in the same sentence
- list-style shopping-verb expansion (get bread, milk, and cheese)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
90
src/lib/stores/toasts.svelte.ts
Normal file
90
src/lib/stores/toasts.svelte.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import type { ToastItem, ToastSeverity } from "$lib/types/app";
|
||||
import { errorMessage } from "$lib/utils/errors.js";
|
||||
|
||||
// Minimal toast store. Roll-our-own (no svelte-french-toast etc) so Kon
|
||||
// stays lean. Toasts auto-dismiss after `duration` ms unless duration is 0
|
||||
// (sticky) or unless the user clicks the close button.
|
||||
//
|
||||
// Severity → colour:
|
||||
// info → moss (informational, default 4s)
|
||||
// success → moss (operation succeeded, default 3s)
|
||||
// warn → signal (degraded but not failed, default 6s)
|
||||
// error → ember (operation failed, sticky until dismissed)
|
||||
//
|
||||
// Usage from a component:
|
||||
// import { toasts } from '$lib/stores/toasts.svelte.js';
|
||||
// toasts.error('Could not start recording', 'Selected microphone disappeared.');
|
||||
// toasts.success('Saved');
|
||||
|
||||
let nextId = 1;
|
||||
|
||||
function defaultDuration(severity: ToastSeverity): number {
|
||||
switch (severity) {
|
||||
case "success": return 3000;
|
||||
case "info": return 4000;
|
||||
case "warn": return 6000;
|
||||
case "error": return 0; // sticky
|
||||
default: return 4000;
|
||||
}
|
||||
}
|
||||
|
||||
function createToastsStore() {
|
||||
// $state requires a class field or top-level let in Svelte 5; we expose
|
||||
// `items` via a getter on the singleton.
|
||||
let items = $state<ToastItem[]>([]);
|
||||
|
||||
function show(severity: ToastSeverity, title: string, body?: string) {
|
||||
const id = nextId++;
|
||||
const duration = defaultDuration(severity);
|
||||
const toast = { id, severity, title, body: body ?? '', duration };
|
||||
items.push(toast);
|
||||
if (duration > 0) {
|
||||
setTimeout(() => dismiss(id), duration);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
function dismiss(id: number) {
|
||||
const ix = items.findIndex(t => t.id === id);
|
||||
if (ix >= 0) items.splice(ix, 1);
|
||||
}
|
||||
|
||||
function dismissAll() {
|
||||
items.length = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
get items() { return items; },
|
||||
info: (title: string, body?: string) => show("info", title, body),
|
||||
success: (title: string, body?: string) => show("success", title, body),
|
||||
warn: (title: string, body?: string) => show("warn", title, body),
|
||||
error: (title: string, body?: string) => show("error", title, body),
|
||||
dismiss,
|
||||
dismissAll,
|
||||
};
|
||||
}
|
||||
|
||||
export const toasts = createToastsStore();
|
||||
|
||||
// Helper: wrap a Tauri invoke and toast on failure. Returns the result on
|
||||
// success, throws on failure (so callers that need to handle the error
|
||||
// can still try/catch).
|
||||
//
|
||||
// import { invoke } from '@tauri-apps/api/core';
|
||||
// import { invokeWithToast } from '$lib/stores/toasts.svelte.js';
|
||||
// const result = await invokeWithToast('start_native_capture', { deviceName });
|
||||
//
|
||||
// Optional `errorTitle` overrides the default ("Action failed").
|
||||
export async function invokeWithToast<TArgs, TResult>(
|
||||
invokeFn: (command: string, args?: TArgs) => Promise<TResult>,
|
||||
command: string,
|
||||
args?: TArgs,
|
||||
errorTitle?: string,
|
||||
): Promise<TResult> {
|
||||
try {
|
||||
return await invokeFn(command, args);
|
||||
} catch (err) {
|
||||
toasts.error(errorTitle ?? "Action failed", errorMessage(err));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user