fix(frontend): resolve 26 svelte-check type errors

- add @types/node for hooks.server process/env types
- fix katex auto-render import path (contrib/auto-render)
- declare global Web Bluetooth API types in ble-deployer
- guard simApi null in CircuitEditor autosave interval
- default-type imports for CodeEditor/CircuitEditor
- make lesson slug getter non-nullable
- add expected_flowchart to LessonContent type
- key lesson workspace by slug; drop phantom onReset prop
- guard CodeTab render when lesson data is null
This commit is contained in:
a2nr 2026-08-09 12:28:17 +07:00
parent 9ee92cfaaf
commit c356e234fa
8 changed files with 120 additions and 8 deletions

View File

@ -21,6 +21,7 @@
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@types/katex": "^0.16.7",
"@types/node": "^26.2.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"typescript": "^5.0.0",
@ -1287,6 +1288,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "26.2.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
"integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~8.3.0"
}
},
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
@ -2279,6 +2290,13 @@
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
"dev": true,
"license": "MIT"
},
"node_modules/vite": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",

View File

@ -15,6 +15,7 @@
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@types/katex": "^0.16.7",
"@types/node": "^26.2.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"typescript": "^5.0.0",

View File

@ -5,7 +5,7 @@ export async function autoRenderMath(node: HTMLElement) {
if (!browser) return;
try {
const renderMathInElement = (await import('katex/dist/contrib/auto-render')).default;
const renderMathInElement = (await import('katex/contrib/auto-render')).default;
await import('katex/dist/katex.min.css');
// 1. First, handle the standard delimiters

View File

@ -127,6 +127,7 @@
$effect(() => {
if (simApi && ready && storageKey) {
const interval = setInterval(() => {
if (!simApi) return;
const currentText = simApi.exportCircuit();
const saved = localStorage.getItem(storageKey);
if (currentText && currentText !== saved && currentText.trim().length > 10) {

View File

@ -8,6 +8,98 @@ import {
type DeployProgress, type BLEACKResponse
} from '$types/deployer';
/* Ambient declarations for Web Bluetooth API (not in TS lib.dom.d.ts). */
/* Minimal subset needed by BLEHardwareDeployer. */
/* Wrapped in declare global because the file is a module (has imports) */
/* and we need these types to be globally visible. */
declare global {
interface Navigator {
readonly bluetooth: Bluetooth;
}
interface Bluetooth {
requestDevice(options: RequestDeviceOptions): Promise<BluetoothDevice>;
}
interface RequestDeviceOptions {
filters?: BluetoothLEScanFilter[];
optionalServices?: BluetoothServiceUUID[];
acceptAllDevices?: boolean;
}
interface BluetoothLEScanFilter {
name?: string;
namePrefix?: string;
services?: BluetoothServiceUUID[];
manufacturerId?: number;
}
type BluetoothServiceUUID = string | number;
interface BluetoothDevice {
readonly id: string;
readonly name?: string;
readonly gatt?: BluetoothRemoteGATTServer;
addEventListener(
type: 'gattserverdisconnected',
listener: (this: BluetoothDevice, ev: Event) => void
): void;
removeEventListener(
type: 'gattserverdisconnected',
listener: (this: BluetoothDevice, ev: Event) => void
): void;
}
interface BluetoothRemoteGATTServer {
readonly connected: boolean;
readonly device: BluetoothDevice;
readonly mtu: number;
connect(): Promise<BluetoothRemoteGATTServer>;
disconnect(): void;
requestMTU(size: number): Promise<BluetoothRemoteGATTServer>;
getPrimaryService(service: BluetoothServiceUUID): Promise<BluetoothRemoteGATTService>;
getPrimaryServices(service?: BluetoothServiceUUID): Promise<BluetoothRemoteGATTService[]>;
}
interface BluetoothRemoteGATTService {
readonly device: BluetoothDevice;
readonly uuid: string;
readonly isPrimary: boolean;
getCharacteristic(characteristic: BluetoothServiceUUID): Promise<BluetoothRemoteGATTCharacteristic>;
getCharacteristics(characteristic?: BluetoothServiceUUID): Promise<BluetoothRemoteGATTCharacteristic[]>;
}
interface BluetoothRemoteGATTCharacteristic extends EventTarget {
readonly service: BluetoothRemoteGATTService;
readonly uuid: string;
readonly properties: BluetoothCharacteristicProperties;
readonly value: DataView | null;
readValue(): Promise<DataView>;
writeValue(value: BufferSource): Promise<void>;
writeValueWithResponse(value: BufferSource): Promise<void>;
writeValueWithoutResponse(value: BufferSource): Promise<void>;
startNotifications(): Promise<BluetoothRemoteGATTCharacteristic>;
stopNotifications(): Promise<BluetoothRemoteGATTCharacteristic>;
addEventListener(
type: 'characteristicvaluechanged',
listener: (this: BluetoothRemoteGATTCharacteristic, ev: Event) => void
): void;
removeEventListener(
type: 'characteristicvaluechanged',
listener: (this: BluetoothRemoteGATTCharacteristic, ev: Event) => void
): void;
}
interface BluetoothCharacteristicProperties {
readonly read: boolean;
readonly write: boolean;
readonly notify: boolean;
readonly indicate: boolean;
readonly writeWithoutResponse: boolean;
readonly broadcast: boolean;
}
}
let _ackNotificationCount = 0;
function logAck(tag: string, msg: string) {
console.log(`[BLE-ACK#${++_ackNotificationCount}] ${tag}: ${msg}`);

View File

@ -22,6 +22,7 @@ export interface LessonContent {
initial_python: string;
initial_circuit: string;
initial_flowchart?: any;
expected_flowchart?: string;
initial_quiz: string;
initial_code_arduino: string;
velxio_circuit: string;

View File

@ -186,7 +186,7 @@ import QuizQuestionView from './QuizQuestionView.svelte';
</svelte:head>
{#if pageData.lesson}
{#key pageData.lesson.filename}
{#key mgr.slug}
<div class="lesson-layout" class:single-col={float.floating || mgr.isMobile}>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="lesson-content" bind:this={contentEl} use:noSelect use:renderMath
@ -326,7 +326,6 @@ import QuizQuestionView from './QuizQuestionView.svelte';
storageKey={mgr.flowchartStorageKey}
initialData={mgr.data?.initial_flowchart}
onRun={mgr.handleRun.bind(mgr)}
onReset={mgr.handleReset.bind(mgr)}
compiling={mgr.compiling}
/>
</div>
@ -343,7 +342,7 @@ import QuizQuestionView from './QuizQuestionView.svelte';
</div>
{/if}
{#if !mgr.data?.active_tabs?.length || mgr.data.active_tabs.includes('c') || mgr.data.active_tabs.includes('python')}
{#if mgr.data && (!mgr.data.active_tabs?.length || mgr.data.active_tabs.includes('c') || mgr.data.active_tabs.includes('python'))}
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'editor'}>
<CodeTab
data={mgr.data}

View File

@ -11,8 +11,8 @@ import { evaluateCircuitSubmission, processLanguageEvaluation } from '$services/
import { getVelxioState, initVelxioBridge } from '$services/velxio-manager';
import { VelxioBridge } from '$services/velxio-bridge';
import type { LessonContent } from '$types/lesson';
import type { CodeEditor } from '$components/CodeEditor.svelte';
import type { CircuitEditor } from '$components/CircuitEditor.svelte';
import type CodeEditor from '$components/CodeEditor.svelte';
import type CircuitEditor from '$components/CircuitEditor.svelte';
import type { DeployState } from '$types/deployer';
import type { QuizQuestion, QuizAnswer } from '$types/quiz';
import {
@ -71,7 +71,7 @@ export class LessonManager {
circuitEditor = $state<CircuitEditor | null>(null);
flowchartTab = $state<any>(null);
get slug() { return get(page).params.slug; }
get slug(): string { return get(page).params.slug ?? ''; }
isVelxio = $derived(this.data?.active_tabs?.includes('velxio') ?? false);
isFlowchart = $derived(this.data?.active_tabs?.includes('flowchart') ?? false);
@ -379,7 +379,7 @@ export class LessonManager {
this.activeTab = 'output';
try {
const circuitText = this.circuitEditor.getCircuitText();
const res = evaluateCircuitSubmission(simApi, circuitText, this.isHybrid, this.data, () => this.checkAllPassed());
const res = evaluateCircuitSubmission(simApi, circuitText, this.isHybrid ?? false, this.data, () => this.checkAllPassed());
if (res.error) {
Object.assign(this.circuitOut, { error: res.error, success: false, loading: false });
return;