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:
2026-04-19 20:05:54 +01:00
parent 6605266587
commit d6bf9ed245
48 changed files with 1693 additions and 1156 deletions

View File

@@ -1,4 +1,5 @@
<script>
<script lang="ts">
import type { FontFamily, ReduceMotion } from "$lib/types/app";
import { getPreferences, updateAccessibility } from '$lib/stores/preferences.svelte.js';
import Toggle from '$lib/components/Toggle.svelte';
import SegmentedButton from '$lib/components/SegmentedButton.svelte';
@@ -9,7 +10,7 @@
{ id: 'lexend', label: 'Lexend' },
{ id: 'atkinson', label: 'Atkinson' },
{ id: 'opendyslexic', label: 'OpenDyslexic' },
];
] satisfies { id: FontFamily; label: string }[];
let bionicChecked = $state(prefs.accessibility.bionicReading || false);
let motionValue = $state(
@@ -25,11 +26,15 @@
});
$effect(() => {
const mapped = motionValue === 'On' ? 'on' : motionValue === 'Off' ? 'off' : 'system';
const mapped: ReduceMotion = motionValue === 'On' ? 'on' : motionValue === 'Off' ? 'off' : 'system';
if (mapped !== prefs.accessibility.reduceMotion) {
updateAccessibility({ reduceMotion: mapped });
}
});
function rangeValue(event: Event): number {
return Number((event.currentTarget as HTMLInputElement).value);
}
</script>
<div class="flex flex-col gap-5">
@@ -59,7 +64,7 @@
<span class="text-[11px] text-text-tertiary">{prefs.accessibility.fontSize}px</span>
</div>
<input type="range" min="16" max="24" step="1" value={prefs.accessibility.fontSize}
oninput={(e) => updateAccessibility({ fontSize: Number(e.target.value) })}
oninput={(e) => updateAccessibility({ fontSize: rangeValue(e) })}
class="w-full accent-accent" />
</div>
@@ -70,7 +75,7 @@
<span class="text-[11px] text-text-tertiary">{prefs.accessibility.letterSpacing.toFixed(2)}em</span>
</div>
<input type="range" min="0" max="0.15" step="0.01" value={prefs.accessibility.letterSpacing}
oninput={(e) => updateAccessibility({ letterSpacing: Number(e.target.value) })}
oninput={(e) => updateAccessibility({ letterSpacing: rangeValue(e) })}
class="w-full accent-accent" />
</div>
@@ -81,7 +86,7 @@
<span class="text-[11px] text-text-tertiary">{prefs.accessibility.lineHeight.toFixed(1)}</span>
</div>
<input type="range" min="1.3" max="2.2" step="0.1" value={prefs.accessibility.lineHeight}
oninput={(e) => updateAccessibility({ lineHeight: Number(e.target.value) })}
oninput={(e) => updateAccessibility({ lineHeight: rangeValue(e) })}
class="w-full accent-accent" />
</div>
@@ -92,7 +97,7 @@
<span class="text-[11px] text-text-tertiary">{prefs.accessibility.transcriptSize}px</span>
</div>
<input type="range" min="16" max="24" step="1" value={prefs.accessibility.transcriptSize}
oninput={(e) => updateAccessibility({ transcriptSize: Number(e.target.value) })}
oninput={(e) => updateAccessibility({ transcriptSize: rangeValue(e) })}
class="w-full accent-accent" />
</div>