diff --git a/firmware/libs/ble_mgmt/Kconfig b/firmware/libs/ble_mgmt/Kconfig index 133d919..90c074b 100644 --- a/firmware/libs/ble_mgmt/Kconfig +++ b/firmware/libs/ble_mgmt/Kconfig @@ -8,6 +8,12 @@ menuconfig BLE_MGMT Library for initializing and managing Bluetooth functionality. if BLE_MGMT + config BLE_MGMT_TX_QUEUE_DEPTH + int "BLE TX queue depth" + default 32 + help + Number of notification payloads that can be queued in the BLE transport. + config BLE_MGMT_DEFAULT_DEVICE_NAME string "Default Bluetooth Device Name" default "Edis Buzzer" @@ -22,7 +28,6 @@ if BLE_MGMT help Maximal advertising interval. 160 equals to 100ms. -# 1. MTU und Data Length (Maximale Paketgrößen) config BT_L2CAP_TX_MTU default 247 config BT_BUF_ACL_RX_SIZE @@ -33,14 +38,12 @@ if BLE_MGMT default 251 config BT_USER_DATA_LEN_UPDATE default y - - # 2. Physical Layer (Erlaubt 2M PHY) config BT_USER_PHY_UPDATE default y - - # 3. Flow-Control und Queues (High Throughput, Host + SDC Controller synchronisiert) config BT_HCI_ACL_FLOW_CONTROL default y + config BT_BUF_CMD_TX_COUNT + default 24 config BT_BUF_EVT_RX_COUNT default 22 config BT_BUF_ACL_TX_COUNT @@ -49,13 +52,13 @@ if BLE_MGMT default 20 config BT_CONN_TX_MAX default 20 - - # 4. SDC Controller Buffering (an Host-Tiefen angeglichen) config BT_CTLR_SDC_TX_PACKET_COUNT default 20 config BT_CTLR_SDC_RX_PACKET_COUNT default 20 - + config BT_MAX_CONN + default 2 + module = BLE_MGMT module-str = ble_mgmt source "subsys/logging/Kconfig.template.log_config" diff --git a/firmware/libs/buzz_proto/src/buzz_proto.c b/firmware/libs/buzz_proto/src/buzz_proto.c index ea8b9c9..a794cb1 100644 --- a/firmware/libs/buzz_proto/src/buzz_proto.c +++ b/firmware/libs/buzz_proto/src/buzz_proto.c @@ -74,7 +74,7 @@ void buzz_proto_buf_free(uint8_t **buf) { if (buf && *buf) { - k_mem_slab_free(&buzz_proto_slabs, (void **)*buf); + k_mem_slab_free(&buzz_proto_slabs, *buf); *buf = NULL; } } @@ -84,6 +84,8 @@ int buzz_proto_submit_frame(struct buzz_frame_msg *msg) return k_msgq_put(&buzz_proto_msgq, msg, K_NO_WAIT); } +static void send_stream_error(buzz_transport_reply_fn reply_cb, uint16_t error_code); + static void send_error_frame(struct buzz_frame_msg *msg, uint16_t error_code) { struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr; @@ -99,6 +101,18 @@ static void send_error_frame(struct buzz_frame_msg *msg, uint16_t error_code) } } +static void send_stream_error(buzz_transport_reply_fn reply_cb, uint16_t error_code) +{ + uint8_t *buf = NULL; + if (reply_cb == NULL || buzz_proto_buf_alloc(&buf) != 0) + { + return; + } + struct buzz_frame_msg err_msg = {.data_ptr = buf, .reply_cb = reply_cb}; + send_error_frame(&err_msg, error_code); + buzz_proto_buf_free(&buf); +} + static void handle_proto_version_request(struct buzz_frame_msg *msg) { struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr; @@ -284,6 +298,8 @@ static void handle_file_get_request(struct buzz_frame_msg *msg) fs_mgmt_pm_close(&get_file_state.file); get_file_state.active = false; current_stream = STREAM_IDLE; + k_sleep(K_MSEC(10)); + send_error_frame(msg, EIO); return; } } @@ -363,16 +379,20 @@ static void process_file_get_stream(void) return; } - // Daten gelesen -> CRC aktualisieren und Chunk senden - get_file_state.crc32 = crc32_ieee_update(get_file_state.crc32, payload_ptr, read_len); - get_file_state.offset += read_len; - + // Chunk senden; CRC/Offset erst nach erfolgreichem Enqueue aktualisieren hdr->frame_type = BUZZ_FRAME_FILE_CHUNK; hdr->payload_length = sys_cpu_to_le16(read_len); if (get_file_state.reply_cb) { int send_rc = get_file_state.reply_cb(buf, sizeof(*hdr) + read_len); + if (send_rc == -ENOMEM) + { + // BLE TX queue voll - Datei zurücksetzen, nächster Zyklus wiederholt den Chunk + fs_seek(&get_file_state.file, -(off_t)read_len, FS_SEEK_CUR); + buzz_proto_buf_free(&buf); + return; + } if (send_rc) { LOG_ERR("Failed to send FILE_CHUNK (err %d)", send_rc); @@ -380,10 +400,15 @@ static void process_file_get_stream(void) get_file_state.active = false; current_stream = STREAM_IDLE; buzz_proto_buf_free(&buf); + k_sleep(K_MSEC(10)); + send_stream_error(get_file_state.reply_cb, EIO); return; } } + // Erfolgreich eingereiht: State aktualisieren + get_file_state.crc32 = crc32_ieee_update(get_file_state.crc32, payload_ptr, read_len); + get_file_state.offset += read_len; get_file_state.credits--; get_file_state.retry_counter = 0; buzz_proto_buf_free(&buf); @@ -590,6 +615,7 @@ static void buzz_proto_thread_fn(void *p1, void *p2, void *p3) { LOG_WRN("LS timeout waiting for ACK"); fs_mgmt_pm_closedir(&ls_state.dir); + send_stream_error(ls_state.reply_cb, ETIMEDOUT); ls_state.active = false; current_stream = STREAM_IDLE; } @@ -608,6 +634,7 @@ static void buzz_proto_thread_fn(void *p1, void *p2, void *p3) { LOG_WRN("FILE_GET timeout waiting for ACK"); fs_close(&get_file_state.file); + send_stream_error(get_file_state.reply_cb, ETIMEDOUT); get_file_state.active = false; current_stream = STREAM_IDLE; } diff --git a/firmware/prj.conf b/firmware/prj.conf index 9c957b1..620efaf 100644 --- a/firmware/prj.conf +++ b/firmware/prj.conf @@ -3,22 +3,13 @@ CONFIG_LOG=y ### File System CONFIG_FS_MGMT=y -CONFIG_FS_MGMT_LOG_LEVEL_DBG=y +# CONFIG_FS_MGMT_LOG_LEVEL_DBG=y CONFIG_FS_LOG_LEVEL_WRN=y ### Bluetooth CONFIG_BLE_MGMT=y # CONFIG_BLE_MGMT_LOG_LEVEL_DBG=y -# Explicit throughput tuning in project config (wins over competing defaults) -CONFIG_BT_HCI_ACL_FLOW_CONTROL=y -CONFIG_BT_BUF_CMD_TX_COUNT=24 -CONFIG_BT_BUF_ACL_TX_COUNT=20 -CONFIG_BT_L2CAP_TX_BUF_COUNT=20 -CONFIG_BT_CONN_TX_MAX=20 -CONFIG_BT_CTLR_SDC_TX_PACKET_COUNT=20 -CONFIG_BT_CTLR_SDC_RX_PACKET_COUNT=20 - # Advertising 500ms - 1s CONFIG_BLE_MGMT_ADV_INT_MIN=160 CONFIG_BLE_MGMT_ADV_INT_MAX=320 diff --git a/webpage/package-lock.json b/webpage/package-lock.json index 7ac3fd7..1b3baaa 100644 --- a/webpage/package-lock.json +++ b/webpage/package-lock.json @@ -8,9 +8,9 @@ "name": "webpage", "version": "0.0.1", "dependencies": { - "@astrojs/svelte": "^7.2.5", + "@astrojs/svelte": "^8.0.0", "@tailwindcss/vite": "^4.2.1", - "astro": "^5.17.1", + "astro": "^6.0.3", "prettier-plugin-svelte": "^3.5.1", "svelte": "^5.53.7", "tailwindcss": "^4.2.1", @@ -26,26 +26,29 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==", + "dev": true, "license": "MIT" }, "node_modules/@astrojs/internal-helpers": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz", - "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==", - "license": "MIT" - }, - "node_modules/@astrojs/markdown-remark": { - "version": "6.3.10", - "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.10.tgz", - "integrity": "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.8.0.tgz", + "integrity": "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w==", "license": "MIT", "dependencies": { - "@astrojs/internal-helpers": "0.7.5", - "@astrojs/prism": "3.3.0", + "picomatch": "^4.0.3" + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.0.0.tgz", + "integrity": "sha512-jTAXHPy45L7o1ljH4jYV+ShtOHtyQUa1mGp3a5fJp1soX8lInuTJQ6ihmldHzVM4Q7QptU4SzIDIcKbBJO7sXQ==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.8.0", + "@astrojs/prism": "4.0.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", - "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", @@ -54,43 +57,43 @@ "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", - "shiki": "^3.19.0", - "smol-toml": "^1.5.2", + "shiki": "^4.0.0", + "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", - "unist-util-visit": "^5.0.0", + "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "node_modules/@astrojs/prism": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", - "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.0.tgz", + "integrity": "sha512-NndtNPpxaGinRpRytljGBvYHpTOwHycSZ/c+lQi5cHvkqqrHKWdkPEhImlODBNmbuB+vyQUNUDXyjzt66CihJg==", "license": "MIT", "dependencies": { "prismjs": "^1.30.0" }, "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" + "node": "^20.19.1 || >=22.12.0" } }, "node_modules/@astrojs/svelte": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/@astrojs/svelte/-/svelte-7.2.5.tgz", - "integrity": "sha512-Tl5aF/dYbzzd7sLpxMBX6pRz3yJ1B4pilt9G3GJbj0I0/doJHIEmerNQsnlxX0/InNKUhMXXN8wyyet9VhA+Zw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/svelte/-/svelte-8.0.0.tgz", + "integrity": "sha512-jX5t7BLAzS72An/1ci1gl5pliyMCjoC2KgTJrw3jbMmZgCaeZQtkzMy7jARjME5bDutPZX2WeaII0C7NOC2SSA==", "license": "MIT", "dependencies": { - "@sveltejs/vite-plugin-svelte": "^5.1.1", - "svelte2tsx": "^0.7.46", - "vite": "^6.4.1" + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "svelte2tsx": "^0.7.51", + "vite": "^7.3.1" }, "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" + "node": "^20.19.1 || >=22.12.0" }, "peerDependencies": { - "astro": "^5.0.0", - "svelte": "^5.1.16", + "astro": "^6.0.0-alpha.0", + "svelte": "^5.43.6", "typescript": "^5.3.3" } }, @@ -170,6 +173,25 @@ "node": ">=18" } }, + "node_modules/@clack/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.1.0.tgz", + "integrity": "sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA==", + "license": "MIT", + "dependencies": { + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.1.0.tgz", + "integrity": "sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.1.0", + "sisteransi": "^1.0.5" + } + }, "node_modules/@emnapi/runtime": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", @@ -1467,64 +1489,97 @@ ] }, "node_modules/@shikijs/core": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", - "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", + "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0", + "@shikijs/primitive": "4.0.2", + "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/engine-javascript": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", - "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", + "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0", + "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", - "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", + "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0", + "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/langs": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", - "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz", + "integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0" + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz", + "integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/themes": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", - "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz", + "integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0" + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/types": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", - "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", + "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/vscode-textmate": { @@ -1543,50 +1598,40 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", - "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "license": "MIT", "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", - "debug": "^4.4.1", + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.17", - "vitefu": "^1.0.6" + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.1" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { "svelte": "^5.0.0", - "vite": "^6.0.0" + "vite": "^6.3.0 || ^7.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", - "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", + "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", "license": "MIT", "dependencies": { - "debug": "^4.3.7" + "obug": "^2.1.0" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", - "vite": "^6.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte/node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" + "vite": "^6.3.0 || ^7.0.0" } }, "node_modules/@tailwindcss/node": { @@ -1924,80 +1969,6 @@ "node": ">=0.4.0" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2049,80 +2020,72 @@ } }, "node_modules/astro": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.18.0.tgz", - "integrity": "sha512-CHiohwJIS4L0G6/IzE1Fx3dgWqXBCXus/od0eGUfxrZJD2um2pE7ehclMmgL/fXqbU7NfE1Ze2pq34h2QaA6iQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/astro/-/astro-6.0.3.tgz", + "integrity": "sha512-M0s9KMmGeaLQPB0shVNqr3ZypEXBDFE/VsexBoA0nWVFwr84BnGxffXH8LG0KPxnVTRwpC3/zPjllVAlRLYJuw==", "license": "MIT", "dependencies": { - "@astrojs/compiler": "^2.13.0", - "@astrojs/internal-helpers": "0.7.5", - "@astrojs/markdown-remark": "6.3.10", + "@astrojs/compiler": "^3.0.0", + "@astrojs/internal-helpers": "0.8.0", + "@astrojs/markdown-remark": "7.0.0", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.0.1", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", - "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", - "boxen": "8.0.1", - "ci-info": "^4.3.1", + "ci-info": "^4.4.0", "clsx": "^2.1.1", - "common-ancestor-path": "^1.0.1", + "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", - "cssesc": "^3.0.0", - "debug": "^4.4.3", - "deterministic-object-hash": "^2.0.2", - "devalue": "^5.6.2", + "devalue": "^5.6.3", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", - "es-module-lexer": "^1.7.0", + "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", - "estree-walker": "^3.0.3", "flattie": "^1.1.1", - "fontace": "~0.4.0", + "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", - "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", - "magicast": "^0.5.1", + "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", - "p-limit": "^6.2.0", - "p-queue": "^8.1.1", + "obug": "^2.1.1", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", - "prompts": "^2.4.2", "rehype": "^13.0.2", - "semver": "^7.7.3", - "shiki": "^3.21.0", + "semver": "^7.7.4", + "shiki": "^4.0.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", + "tinyclip": "^0.1.6", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", - "unifont": "~0.7.3", - "unist-util-visit": "^5.0.0", + "unifont": "~0.7.4", + "unist-util-visit": "^5.1.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", - "vite": "^6.4.1", - "vitefu": "^1.1.1", + "vite": "^7.3.1", + "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", - "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.3", - "zod": "^3.25.76", - "zod-to-json-schema": "^3.25.1", - "zod-to-ts": "^1.2.0" + "yargs-parser": "^22.0.0", + "zod": "^4.3.6" }, "bin": { - "astro": "astro.js" + "astro": "bin/astro.mjs" }, "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "node": "^20.19.1 || >=22.12.0", "npm": ">=9.6.5", "pnpm": ">=7.1.0" }, @@ -2134,6 +2097,12 @@ "sharp": "^0.34.0" } }, + "node_modules/astro/node_modules/@astrojs/compiler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-3.0.0.tgz", + "integrity": "sha512-MwAbDE5mawZ1SS+D8qWiHdprdME5Tlj2e0YjxnEICvcOpbSukNS7Sa7hA5PK+6RrmUr/t6Gi5YgrdZKjbO/WPQ==", + "license": "MIT" + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -2153,52 +2122,12 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "license": "MIT" - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, - "node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -2209,18 +2138,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -2281,18 +2198,6 @@ "node": ">=8" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2322,10 +2227,13 @@ } }, "node_modules/common-ancestor-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", - "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", - "license": "ISC" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } }, "node_modules/cookie": { "version": "1.1.1", @@ -2396,18 +2304,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/csso": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", @@ -2516,18 +2412,6 @@ "node": ">=8" } }, - "node_modules/deterministic-object-hash": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", - "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", - "license": "MIT", - "dependencies": { - "base-64": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/devalue": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", @@ -2638,12 +2522,6 @@ "node": ">=4" } }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, "node_modules/enhanced-resolve": { "version": "5.20.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", @@ -2670,9 +2548,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "license": "MIT" }, "node_modules/esbuild": { @@ -2747,6 +2625,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -2825,18 +2704,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", @@ -3065,16 +2932,6 @@ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/iron-webcrypto": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", @@ -3099,15 +2956,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -3183,15 +3031,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/lightningcss": { "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", @@ -4378,6 +4217,16 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/ofetch": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", @@ -4413,43 +4262,43 @@ } }, "node_modules/p-limit": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", - "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", + "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", "license": "MIT", "dependencies": { - "yocto-queue": "^1.1.1" + "yocto-queue": "^1.2.1" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-queue": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", - "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz", + "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", "license": "MIT", "dependencies": { "eventemitter3": "^5.0.1", - "p-timeout": "^6.1.2" + "p-timeout": "^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-timeout": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", - "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", "license": "MIT", "engines": { - "node": ">=14.16" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4612,19 +4461,6 @@ "node": ">=6" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -5015,19 +4851,22 @@ } }, "node_modules/shiki": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", - "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz", + "integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==", "license": "MIT", "dependencies": { - "@shikijs/core": "3.23.0", - "@shikijs/engine-javascript": "3.23.0", - "@shikijs/engine-oniguruma": "3.23.0", - "@shikijs/langs": "3.23.0", - "@shikijs/themes": "3.23.0", - "@shikijs/types": "3.23.0", + "@shikijs/core": "4.0.2", + "@shikijs/engine-javascript": "4.0.2", + "@shikijs/engine-oniguruma": "4.0.2", + "@shikijs/langs": "4.0.2", + "@shikijs/themes": "4.0.2", + "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/sisteransi": { @@ -5067,23 +4906,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -5098,21 +4920,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/suf-log": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz", @@ -5223,6 +5030,15 @@ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "license": "MIT" }, + "node_modules/tinyclip": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.12.tgz", + "integrity": "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, "node_modules/tinyexec": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", @@ -5295,18 +5111,6 @@ "license": "0BSD", "optional": true }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -5630,23 +5434,23 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -5655,14 +5459,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -5703,463 +5507,6 @@ } } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, "node_modules/vitefu": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", @@ -6198,38 +5545,6 @@ "node": ">=4" } }, - "node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/xxhash-wasm": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", @@ -6237,12 +5552,12 @@ "license": "MIT" }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yocto-queue": { @@ -6257,33 +5572,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yocto-spinner": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", - "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zimmerframe": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", @@ -6291,32 +5579,14 @@ "license": "MIT" }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25 || ^4" - } - }, - "node_modules/zod-to-ts": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", - "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", - "peerDependencies": { - "typescript": "^4.9.4 || ^5.0.2", - "zod": "^3" - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/webpage/package.json b/webpage/package.json index 99e2225..3db221f 100644 --- a/webpage/package.json +++ b/webpage/package.json @@ -9,9 +9,9 @@ "astro": "astro" }, "dependencies": { - "@astrojs/svelte": "^7.2.5", + "@astrojs/svelte": "^8.0.0", "@tailwindcss/vite": "^4.2.1", - "astro": "^5.17.1", + "astro": "^6.0.3", "prettier-plugin-svelte": "^3.5.1", "svelte": "^5.53.7", "tailwindcss": "^4.2.1", diff --git a/webpage/src/components/App.svelte b/webpage/src/components/App.svelte new file mode 100644 index 0000000..0744642 --- /dev/null +++ b/webpage/src/components/App.svelte @@ -0,0 +1,31 @@ + + +
+
+ +
+ +
+

Dateiverarbeitung

+
+ +
+

Buzzer Status

+
+ +
+

Lokale Sounds

+
+ +
+

Buzzer Sounds

+
+ +
+ + +
\ No newline at end of file diff --git a/webpage/src/components/AppGuard.svelte b/webpage/src/components/AppGuard.svelte index 94ac96b..9a678d6 100644 --- a/webpage/src/components/AppGuard.svelte +++ b/webpage/src/components/AppGuard.svelte @@ -1,61 +1,48 @@ {#if $isInitializing} -
-

SYSTEM_CHECK_RUNNING...

+
+

+ Browserkompatibilität wird geprüft... +

-{:else if !$isBluetoothSupported && !$isSerialSupported} -
-
-

Inkompatibler Browser

+{:else if !$isBluetoothSupported} +
+
+

Dein Browser ist... suboptimal

+
🥺
-

- Du nutzt aktuell {browserName} - . Dieser Browser unterstützt weder Bluetooth noch serielle USB-Verbindungen. -

- -
-
- Web Bluetooth: - NICHT UNTERSTÜTZT -
-
- Web Serial: - NICHT UNTERSTÜTZT -
+
+

+ Leider unterstützt dein Browser die benötigten Bluetooth-Funktionen nicht. Bitte versuche + es mit einem aktuellen Chrome + oder einem andern Chromium-basierten Browser. + Winzigweich Kante soll gerüchteweise auch Chromium-basiert sein... +

+

+ Rundreise auf iOS unterstützt Bluetooth leider nicht, aber du kannst es mit einem vernünftigen Gerät oder Browser versuchen. +

- -

- (Info: Firefox und Safari blockieren diese Hardware-Schnittstellen aus Prinzip.) -

- - - Googles Glanzeisen installieren - - -

- Gerüchten zufolge soll Winzigweichs Kante - -Browser diese Technologien auch unterstützen. Aber wer nutzt schon diese Weichware? -

{:else} - + {/if} diff --git a/webpage/src/components/BuzzerControl.svelte b/webpage/src/components/BuzzerControl.svelte index e4ff4e4..247519c 100644 --- a/webpage/src/components/BuzzerControl.svelte +++ b/webpage/src/components/BuzzerControl.svelte @@ -9,9 +9,11 @@ fsInfo, loadConnectionState, availableDevices, + transferStats, + resetTransferStats, } from "../lib/store"; import { refreshRemote } from "../lib/sync"; - import { fetchFileThroughputTest } from "../lib/transport"; + import { getFile } from "../lib/transport"; onMount(() => { restoreSession(); @@ -127,11 +129,40 @@ {/if}
diff --git a/webpage/src/components/DeviceInfo.svelte b/webpage/src/components/DeviceInfo.svelte new file mode 100644 index 0000000..1c4cab7 --- /dev/null +++ b/webpage/src/components/DeviceInfo.svelte @@ -0,0 +1,72 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ Modell + + nrf52840dk-prototyp +
+ Version + + 2.3.22-debug +
+ HW-ID + + DEAD-BEAF-0102-3456 +
+ Batterie + + 85% 1200mAh +
+ Speicher + +
+ +
+
+
+ + diff --git a/webpage/src/components/FileList.svelte b/webpage/src/components/FileList.svelte new file mode 100644 index 0000000..a055969 --- /dev/null +++ b/webpage/src/components/FileList.svelte @@ -0,0 +1,13 @@ + + +
+ {#each $buzzerAudioFiles as file, index(index)} + + {/each} +
\ No newline at end of file diff --git a/webpage/src/components/FileListItem.svelte b/webpage/src/components/FileListItem.svelte new file mode 100644 index 0000000..615e985 --- /dev/null +++ b/webpage/src/components/FileListItem.svelte @@ -0,0 +1,35 @@ + +
+ +
+ + \ No newline at end of file diff --git a/webpage/src/components/FileTransfer.svelte b/webpage/src/components/FileTransfer.svelte deleted file mode 100644 index 047674d..0000000 --- a/webpage/src/components/FileTransfer.svelte +++ /dev/null @@ -1,27 +0,0 @@ - - -
-
- -
- {#if $storageUsage} -
-
- - Rate: {($storageUsage.systemBytes / 1048576).toFixed(2)} MB - -
-
- - {($storageUsage.freeBytes / 1048576).toFixed(2)} Sekunden - -
- {:else} -
Kein Transfer aktiv
- {/if} -
\ No newline at end of file diff --git a/webpage/src/components/FlashUsage.svelte b/webpage/src/components/FlashUsage.svelte index d230b4e..4007aec 100644 --- a/webpage/src/components/FlashUsage.svelte +++ b/webpage/src/components/FlashUsage.svelte @@ -2,39 +2,37 @@ import { storageUsage } from "../lib/store"; -
+
-
+
{#if $storageUsage}
System: - {($storageUsage.systemBytes / 1048576).toFixed(2)} MB -
-
+ {($storageUsage.systemBytes / 1048576).toFixed(2)} MB Audio: - {($storageUsage.audioBytes / 1048576).toFixed(2)} MB + {($storageUsage.audioBytes / 1048576).toFixed(2)} MB
Frei: - {($storageUsage.freeBytes / 1048576).toFixed(2)} MB + {($storageUsage.freeBytes / 1048576).toFixed(2)} MB
{:else}
Speicherdaten nicht verfügbar
diff --git a/webpage/src/components/Header.svelte b/webpage/src/components/Header.svelte new file mode 100644 index 0000000..d79f9b8 --- /dev/null +++ b/webpage/src/components/Header.svelte @@ -0,0 +1,205 @@ + + +
+
+ + Edis Buzzer +   + CONTROL + +
+ +
(showDropdown = false)}> +
+ + + +
+ + {#if showDropdown} +
+ {#each $pairedDevices as dev (dev.id)} + { connectBuzzer(e.detail); showDropdown = false; }} + on:forget={(e) => forgetDevice(e.detail)} + /> + {/each} + + {#if $pairedDevices.length === 0} +
+ Keine Geräte gekoppelt +
+ {/if} + + + + +
+ {/if} +
+
+ + \ No newline at end of file diff --git a/webpage/src/components/HeaderDeviceListItem.svelte b/webpage/src/components/HeaderDeviceListItem.svelte new file mode 100644 index 0000000..5cb3f42 --- /dev/null +++ b/webpage/src/components/HeaderDeviceListItem.svelte @@ -0,0 +1,66 @@ + + + +
+ + +
diff --git a/webpage/src/components/MainGrid.svelte b/webpage/src/components/MainGrid.svelte new file mode 100644 index 0000000..067eb6d --- /dev/null +++ b/webpage/src/components/MainGrid.svelte @@ -0,0 +1,301 @@ + + +
+
+
+

Dateiverarbeitung

+ +
+
+
+
+ 16kHz 16bit MONO | NORMALIZER ON + | + COMPRESSOR OFF +
+
+
+ +
+
+
+ +
+
+

+ {#if $isConnected && currentDevice?.name} + Geräteinfos: {currentDevice.name} + {/if} +

+ +
+ +
+
+ +
+ + {#if showOverlay} +
+ {#if isTransferFinished} + + {/if} + +
+
+
+
+
+ {$transferDetails.filePercent}% +
+
+ {$transferDetails.filePercent}% +
+
+
+ + {$transferStats.currentFileName || "Lade..."} + + {formatTime($transferDetails.fileEta)} +
+
+ +
+
+
+
+ {$transferDetails.totalPercent}% +
+
+ {$transferDetails.totalPercent}% +
+
+
+ {$transferDetails.speedKbs} kB/s + {formatTime($transferDetails.totalEta)} +
+
+ +
+ +
+
+
+ {/if} +
+
+ +
+
+

Lokale Bibliothek

+ +
+
+
+ Bibliothek leer +
+
+
+ +
+
+
+

+ Gerätebibliothek + {$fsInfo?.audioPath} + +

+
+
+
+ + + + + +
+
+
+
+ +
+
+
+ + diff --git a/webpage/src/components/TransferProgress.svelte b/webpage/src/components/TransferProgress.svelte new file mode 100644 index 0000000..c838d2e --- /dev/null +++ b/webpage/src/components/TransferProgress.svelte @@ -0,0 +1,61 @@ + + +
+{#if $transferStats.bytesTotal > 0} +
+ +
+
+ Datei: {$transferStats.currentFileName || 'Übertragung...'} + {formatTime($transferDetails.fileEta)} +
+
+
+
+
+ +
+
+ Gesamtfortschritt + {formatTime($transferDetails.totalEta)} +
+
+
+
+
+ +
+ Rate: {$transferDetails.speedKbs.toFixed(0)}kBps + {$transferDetails.totalPercent}% abgeschlossen +
+
+{:else} +
+ Kein Transfer aktiv +
+{/if} +
\ No newline at end of file diff --git a/webpage/src/layouts/MainLayout.astro b/webpage/src/layouts/MainLayout.astro index 0b4f417..0ed7715 100644 --- a/webpage/src/layouts/MainLayout.astro +++ b/webpage/src/layouts/MainLayout.astro @@ -1,29 +1,15 @@ + --- -import "../styles/global.css"; -const year = new Date().getFullYear(); +import "../styles/app.css"; --- + Edis Buzzer - - - - - -
- -
- -
-
- © 2026-{year} iten engineering. Alle Rechte vorbehalten. -
-
- + + - + \ No newline at end of file diff --git a/webpage/src/lib/bluetooth.ts b/webpage/src/lib/bluetooth.ts index 7a20cbd..ad02a8f 100644 --- a/webpage/src/lib/bluetooth.ts +++ b/webpage/src/lib/bluetooth.ts @@ -1,5 +1,5 @@ import { get } from 'svelte/store'; -import { injectDummyDevices, isConnected, isPaired, isConnecting, pairedDevices, availableDevices, activeDeviceId, saveConnectionState, loadConnectionState, targetDeviceId, resetLocal, resetRemote } from './store'; +import { isConnected, isPaired, isConnecting, pairedDevices, availableDevices, activeDeviceId, saveConnectionState, loadConnectionState, targetDeviceId, resetLocal, resetRemote, autoConnect } from './store'; import { BLE } from './protocol/constants'; import { parseIncomingFrame } from './protocol'; import { registerTransport, handleTransportDisconnect, handleTransportConnect } from './transport'; @@ -9,27 +9,25 @@ import { SETTINGS } from './settings'; let rxCharacteristic: BluetoothRemoteGATTCharacteristic | null = null; let txCharacteristic: BluetoothRemoteGATTCharacteristic | null = null; let device: BluetoothDevice | null = null; +let writeQueue = Promise.resolve(); export async function restoreSession() { try { const devices = await getPairedDevices(); if (devices.length > 0) { isPaired.set(true); - startScanningAdvertisements(devices); + // Zuerst das Zielgerät definieren const savedState = loadConnectionState(); - if (savedState && savedState.autoConnect && savedState.transport === 'ble') { - const targetDev = devices.find(d => d.id === savedState.deviceId); - if (targetDev) { - addToast("Versuche automatische Wiederverbindung...", "info"); - await connectBuzzer(targetDev); - } - } else if (savedState) { + if (savedState) { targetDeviceId.set(savedState.deviceId); device = devices.find(d => d.id === savedState.deviceId) || devices[0]; } else { device = devices[0]; } + + // Danach das Scanning starten (die Auto-Connect-Logik liegt nun in den Callbacks) + startScanningAdvertisements(devices); } } catch (error) { console.error("Session-Wiederherstellung fehlgeschlagen:", error); @@ -38,19 +36,24 @@ export async function restoreSession() { async function startScanningAdvertisements(devices: BluetoothDevice[]) { for (const dev of devices) { - // Sicherheits-Check für Mock-Objekte if (typeof dev.addEventListener !== 'function') continue; - dev.addEventListener('advertisementreceived', () => { + dev.addEventListener('advertisementreceived', async () => { + // Gerät als verfügbar markieren availableDevices.update(set => { const newSet = new Set(set); newSet.add(dev.id); return newSet; }); + + // Auto-Connect ausführen, sobald das Gerät funkt und falls die Voraussetzungen stimmen + if (get(autoConnect) && get(targetDeviceId) === dev.id && !get(isConnected) && !get(isConnecting)) { + console.debug("Auto-Connect: Gerät in Reichweite, starte Verbindung."); + await connectBuzzer(dev); + } }); try { - // Auch hier vorher prüfen if (typeof dev.watchAdvertisements === 'function') { await dev.watchAdvertisements(); } @@ -201,22 +204,17 @@ export async function forgetDevice(targetDevice: BluetoothDevice) { export async function getPairedDevices() { let rawDevices: BluetoothDevice[] = []; - // 1. Physische Geräte abrufen, falls die API verfügbar ist if ('bluetooth' in navigator && 'getDevices' in navigator.bluetooth) { try { rawDevices = await navigator.bluetooth.getDevices(); } catch (error) { console.error("Fehler beim Abrufen der gekoppelten Geräte:", error); } + console.log("Bluetooth-Devices", rawDevices); } - // 2. Physische Geräte in den Store schreiben pairedDevices.set(rawDevices); - // 3. Testdaten anfügen - injectDummyDevices(); - - // 4. Den aktualisierten Store-Inhalt (inkl. Dummies) für die weiterverarbeitenden Funktionen zurückgeben return get(pairedDevices); } @@ -226,7 +224,7 @@ function handleDisconnect() { if (get(isConnected)) { addToast("Verbindung zu Buzzer verloren", "warning"); } - + writeQueue = Promise.resolve(); resetRemote(); registerTransport(null); rxCharacteristic = null; @@ -240,8 +238,15 @@ function handleIncomingData(event: Event) { } } -export async function sendBleFrame(buffer: ArrayBuffer) { +export function sendBleFrame(buffer: ArrayBuffer): Promise { // TODO: MTU Check einfügen! - if (!rxCharacteristic) return; - await rxCharacteristic.writeValueWithoutResponse(buffer); + if (!rxCharacteristic) return Promise.resolve(); + + writeQueue = writeQueue.then(() => + rxCharacteristic!.writeValueWithoutResponse(buffer) + ).catch(error => { + console.error("BLE Sende-Fehler:", error); + }); + + return writeQueue; } diff --git a/webpage/src/lib/protocol/crc32.ts b/webpage/src/lib/protocol/crc32.ts new file mode 100644 index 0000000..120eca6 --- /dev/null +++ b/webpage/src/lib/protocol/crc32.ts @@ -0,0 +1,16 @@ +const CRC32_TABLE = new Int32Array(256); +for (let i = 0; i < 256; i++) { + let c = i; + for (let j = 0; j < 8; j++) { + c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); + } + CRC32_TABLE[i] = c; +} + +export function crc32(buffer: Uint8Array, previousCrc = 0): number { + let crc = previousCrc ^ -1; + for (let i = 0; i < buffer.length; i++) { + crc = (crc >>> 8) ^ CRC32_TABLE[(crc ^ buffer[i]) & 0xFF]; + } + return (crc ^ -1) >>> 0; // Rückgabe als unsigned 32-bit +} \ No newline at end of file diff --git a/webpage/src/lib/protocol/parser.ts b/webpage/src/lib/protocol/parser.ts index bec5f37..75fa6d5 100644 --- a/webpage/src/lib/protocol/parser.ts +++ b/webpage/src/lib/protocol/parser.ts @@ -1,6 +1,11 @@ import { FRAME, DATA, ZEPHYR_ERRORS } from './constants'; -import { protocolInfo, fsInfo } from '../store'; +import { protocolInfo, fsInfo, transferStats, resetTransferStats, transferDetails } from '../store'; import { addToast } from '../toast'; +import { SETTINGS } from '../settings'; +import { crc32 } from './crc32'; + +let lastUiUpdate = 0; +let currentFileCrc32 = 0; export type FrameSender = (buffer: ArrayBuffer) => Promise; @@ -90,30 +95,42 @@ export function parseIncomingFrame(view: DataView, sender: FrameSender) { break; case FRAME.FILE_START: - fileTransfer.totalBytes = view.getUint32(3, true); + currentFileCrc32 = 0; + const totalBytes = view.getUint32(3, true); + const nowStart = performance.now(); + + transferStats.update(s => ({ + ...s, + bytesTotal: totalBytes, + bytesDone: 0, + currentFileName: s.pendingFileName || s.currentFileName, + fileStartTime: nowStart, + bulkStartTime: s.bulkStartTime === 0 ? nowStart : s.bulkStartTime + })); + + // Parser-interne Metriken (Watchdog etc.) + fileTransfer.totalBytes = totalBytes; fileTransfer.receivedBytes = 0; - fileTransfer.lastReceivedBytes = 0; - fileTransfer.stalledSeconds = 0; fileTransfer.active = true; - fileTransfer.startTime = performance.now(); - + fileTransfer.startTime = nowStart; + lastUiUpdate = 0; + console.log(`[FILE_GET] Stream gestartet. Erwartete Größe: ${fileTransfer.totalBytes} Bytes.`); fileTransfer.metricsTimer = setInterval(() => { if (!fileTransfer.active) return; - + // Watchdog-Logik: Prüfen ob seit der letzten Sekunde Daten kamen if (fileTransfer.receivedBytes === fileTransfer.lastReceivedBytes) { fileTransfer.stalledSeconds++; - + if (fileTransfer.stalledSeconds >= 5) { // 5 Sekunden Timeout console.warn("[FILE_GET] Übertragung abgebrochen: Timeout (Keine Daten empfangen)."); - + if (fileTransfer.metricsTimer) clearInterval(fileTransfer.metricsTimer); fileTransfer.active = false; - - // Hier optional einen Toast anzeigen lassen, falls importiert: - // addToast("Dateitransfer abgebrochen (Timeout)", "error"); + + addToast("Dateitransfer abgebrochen (Timeout)", "error"); if (fileGetReject) { fileGetReject(new Error("Timeout beim Dateitransfer")); @@ -123,18 +140,9 @@ case FRAME.FILE_START: return; } } else { - // Daten fließen -> Watchdog zurücksetzen fileTransfer.stalledSeconds = 0; fileTransfer.lastReceivedBytes = fileTransfer.receivedBytes; } - - const elapsedSec = (performance.now() - fileTransfer.startTime) / 1000; - const speedKB = (fileTransfer.receivedBytes / 1024) / elapsedSec; - const percent = fileTransfer.totalBytes > 0 - ? ((fileTransfer.receivedBytes / fileTransfer.totalBytes) * 100).toFixed(1) - : "0.0"; - - console.log(`[FILE_GET] Fortschritt: ${percent}% | Speed: ${speedKB.toFixed(2)} KB/s`); }, 1000); // Initiale Credits (z.B. 64) @@ -145,32 +153,61 @@ case FRAME.FILE_START: case FRAME.FILE_CHUNK: if (!fileTransfer.active) break; + const chunkData = new Uint8Array(view.buffer, 3, payloadLength); + currentFileCrc32 = crc32(chunkData, currentFileCrc32); + + const previousReceived = fileTransfer.receivedBytes; fileTransfer.receivedBytes += payloadLength; fileTransfer.credits--; - // Nachladen, sobald die Credits auf 32 fallen (Dein Vorschlag) + const nowChunk = performance.now(); + if (nowChunk - lastUiUpdate > SETTINGS.ui.transferUpdateIntervalMs) { + const delta = fileTransfer.receivedBytes - previousReceived; // Das Delta seit dem letzten Paket + + transferStats.update(s => ({ + ...s, + bytesDone: fileTransfer.receivedBytes, + overallDone: s.overallDone + (fileTransfer.receivedBytes - s.bytesDone) + })); + console.log("[FILE_GET] Fortschritt: " + ((fileTransfer.receivedBytes / fileTransfer.totalBytes) * 100).toFixed(2) + "%"); + lastUiUpdate = nowChunk; + } + if (fileTransfer.credits <= 64) { fileTransfer.credits = 128; sendCredits(fileTransfer.credits, sender); - } break; case FRAME.FILE_END: - if (fileTransfer.metricsTimer) { - clearInterval(fileTransfer.metricsTimer); - fileTransfer.metricsTimer = null; - } + transferStats.update(s => { + return { + ...s, + bytesDone: s.bytesTotal, + }; + }); + // Watchdog stoppen + if (fileTransfer.metricsTimer) clearInterval(fileTransfer.metricsTimer); fileTransfer.active = false; + const buzzerCrc32 = view.getUint32(3, true); - const crc32 = view.getUint32(3, true); + console.log(`[CRC] Lokal: 0x${currentFileCrc32.toString(16).toUpperCase()}`); + console.log(`[CRC] Buzzer: 0x${buzzerCrc32.toString(16).toUpperCase()}`); const totalElapsed = (performance.now() - fileTransfer.startTime) / 1000; const avgSpeed = (fileTransfer.receivedBytes / 1024) / totalElapsed; console.log(`[FILE_GET] Stream beendet.`); console.log(`[FILE_GET] Empfangen: ${fileTransfer.receivedBytes} Bytes in ${totalElapsed.toFixed(2)}s.`); console.log(`[FILE_GET] Durchschnitt: ${avgSpeed.toFixed(2)} KB/s`); - console.log(`[FILE_GET] Zephyr CRC32: 0x${crc32.toString(16).toUpperCase().padStart(8, '0')}`); + + if (currentFileCrc32 === buzzerCrc32) { + console.log("%c[CRC] Match! Datei ist integer.", "color: green; font-weight: bold;"); + } else { + console.error("[CRC] Mismatch! Datei beschädigt."); + addToast("CRC Fehler: Datei wurde fehlerhaft übertragen.", "error"); + if (fileGetReject) fileGetReject(new Error("CRC Mismatch")); + break; + } if (fileGetResolve) { fileGetResolve(true); @@ -281,8 +318,8 @@ const fileTransfer = { startTime: 0, totalBytes: 0, receivedBytes: 0, - lastReceivedBytes: 0, // NEU: Für die Timeout-Berechnung - stalledSeconds: 0, // NEU: Zähler für Stillstand + lastReceivedBytes: 0, + stalledSeconds: 0, credits: 0, metricsTimer: null as ReturnType | null }; @@ -301,7 +338,7 @@ export function buildFileGetRequest(path: string): ArrayBuffer { view.setUint8(0, FRAME.REQUEST); view.setUint16(1, 1 + pathBytes.length, true); view.setUint8(3, DATA.FILE_GET); - + const uint8Buffer = new Uint8Array(buffer); uint8Buffer.set(pathBytes, 4); diff --git a/webpage/src/lib/settings.ts b/webpage/src/lib/settings.ts index d30cace..16f95b0 100644 --- a/webpage/src/lib/settings.ts +++ b/webpage/src/lib/settings.ts @@ -6,6 +6,9 @@ export const SETTINGS = { connectionTimeoutMs: 10000 // Timeout für den Verbindungsaufbau }, ui: { - toastDurationMs: 5000 + toastDurationMs: 5000, + transferUpdateIntervalMs: 100, + kbpsCalculationWindowMs: 10000, + transferOverlayPersistMs: 4000, } }; \ No newline at end of file diff --git a/webpage/src/lib/store.ts b/webpage/src/lib/store.ts index da1bc58..d55e622 100644 --- a/webpage/src/lib/store.ts +++ b/webpage/src/lib/store.ts @@ -1,5 +1,8 @@ import { writable, derived } from 'svelte/store'; import type { BuzzerFile } from './types'; +import { SETTINGS } from './settings'; + +const CONNECTION_STATE_KEY = 'buzzer_connection_state'; // Fallback-Typ fuer Build-Umgebungen ohne DOM-Library. interface BluetoothDevice { @@ -46,8 +49,6 @@ export interface StorageUsage { freePercent: number; } -const CONNECTION_STATE_KEY = 'buzzer_connection_state'; - // App-Status: Initialisierung und Feature-Support export const isInitializing = writable(true); export const isBluetoothSupported = writable(null); @@ -120,46 +121,66 @@ export const storageUsage = derived( }, ); -// Nur für Entwicklungszwecke: lokale Dummy-Geräte für UI-Tests -export function injectDummyDevices(): void { - const dummy1 = { - id: 'dummy-1', - name: 'Dev Buzzer (Erreichbar)', - forget: async () => { - console.log('Forget dummy-1'); - }, - addEventListener: () => {}, - removeEventListener: () => {}, - watchAdvertisements: async () => {}, - gatt: { connected: false, disconnect: () => {} }, - } as unknown as BluetoothDevice; +// Für die Anzeige der Transferdetails (Dateiname, Fortschritt, Geschwindigkeit, ETA) +export const transferStats = writable({ + currentFileName: '', + pendingFileName: '', + bytesDone: 0, + bytesTotal: 0, + overallDone: 0, + overallTotal: 0, + bulkStartTime: 0, + fileStartTime: 0 +}); - const dummy2 = { - id: 'dummy-2', - name: 'Dev Buzzer (Offline)', - forget: async () => { - console.log('Forget dummy-2'); - }, - addEventListener: () => {}, - removeEventListener: () => {}, - watchAdvertisements: async () => {}, - gatt: { connected: false, disconnect: () => {} }, - } as unknown as BluetoothDevice; - - pairedDevices.update((devices) => { - if (!devices.find((d) => d.id === 'dummy-1')) { - return [...devices, dummy1, dummy2]; - } - return devices; +export const resetTransferStats = () => { + transferStats.set({ + currentFileName: '', + pendingFileName: '', + bytesDone: 0, + bytesTotal: 0, + overallDone: 0, + overallTotal: 0, + bulkStartTime: 0, + fileStartTime: 0 }); +}; - availableDevices.update((set) => { - const newSet = new Set(set); - newSet.add('dummy-1'); - return newSet; - }); -} +let speedHistory: { bytes: number, time: number }[] = []; +export const transferDetails = derived(transferStats, ($s) => { + const now = performance.now(); + + if ($s.overallTotal === 0) { + speedHistory = []; + return { filePercent: 0, totalPercent: 0, speedKbs: 0, fileEta: Infinity, totalEta: Infinity }; + } + + speedHistory.push({ bytes: $s.overallDone, time: now }); + speedHistory = speedHistory.filter(p => now - p.time < SETTINGS.ui.kbpsCalculationWindowMs); + + let speedKbs = 0; + if (speedHistory.length > 1) { + const first = speedHistory[0]; + const last = speedHistory[speedHistory.length - 1]; + const timeDiff = (last.time - first.time) / 1000; + const bytesDiff = last.bytes - first.bytes; + if (timeDiff > 0) speedKbs = (bytesDiff / 1024) / timeDiff; + } + + const speedBytesPerSec = speedKbs * 1024; + + return { + filePercent: Math.round(($s.bytesTotal > 0 ? $s.bytesDone / $s.bytesTotal : 0) * 100), + totalPercent: Math.round(($s.overallTotal > 0 ? $s.overallDone / $s.overallTotal : 0) * 100), + speedKbs: parseFloat(speedKbs.toFixed(2)), + // Wenn Speed zu gering, direkt Infinity für das ∞ Symbol + fileEta: speedBytesPerSec > 100 ? ($s.bytesTotal - $s.bytesDone) / speedBytesPerSec : Infinity, + totalEta: speedBytesPerSec > 100 ? ($s.overallTotal - $s.overallDone) / speedBytesPerSec : Infinity + }; +}); + +// Reset-Funktionen für verschiedene Anwendungsfälle export function resetRemote(): void { isConnected.set(false); isConnecting.set(false); @@ -169,6 +190,7 @@ export function resetRemote(): void { buzzerAudioFiles.set([]); buzzerSysFiles.set([]); isFetchingRemote.set(false); + resetTransferStats(); } export function resetLocal(): void { @@ -179,4 +201,27 @@ export function resetLocal(): void { export function resetAll(): void { resetRemote(); resetLocal(); -} \ No newline at end of file +} + +// Initialisierung aus dem bestehenden LocalStorage-Eintrag +const initialState = loadConnectionState(); +export const autoConnect = writable(initialState?.autoConnect ?? true); + +// Automatische Speicherung bei Änderungen +autoConnect.subscribe(value => { + // Verhindert Fehler beim serverseitigen Rendern (Astro) + if (typeof window !== 'undefined') { + const currentState = loadConnectionState() || { transport: 'ble', deviceId: '', autoConnect: true }; + saveConnectionState({ ...currentState, autoConnect: value }); + } +}); + +// Abgeleitete Stores für die Anzahl der ausgewählten Dateien und der Dateien +export const buzzerFilesCount = derived( + buzzerAudioFiles, + ($files) => $files.length +); +export const selectedBuzzerFilesCount = derived( + buzzerAudioFiles, + ($files) => $files.filter(f => f.selected).length +); \ No newline at end of file diff --git a/webpage/src/lib/sync.ts b/webpage/src/lib/sync.ts index e8ba464..5600835 100644 --- a/webpage/src/lib/sync.ts +++ b/webpage/src/lib/sync.ts @@ -1,7 +1,9 @@ import { get } from 'svelte/store'; -import { isConnected, fsInfo, buzzerAudioFiles, buzzerSysFiles, isFetchingRemote, isFetchingLocal, storageUsage} from './store'; +import { isConnected, fsInfo, buzzerAudioFiles, buzzerSysFiles, isFetchingRemote, isFetchingLocal, storageUsage, transferDetails, transferStats } from './store'; import { requestProtocolInfo, requestFSInfo, fetchDirectory } from './transport'; import type { BuzzerFile } from './types'; +import { getFile } from './transport'; +import { addToast } from './toast'; function mapToBuzzerFile(rawFile: any): BuzzerFile { return { @@ -10,7 +12,8 @@ function mapToBuzzerFile(rawFile: any): BuzzerFile { type: rawFile.type, tagsLoaded: false, sysTags: { format: null, crc32: null }, - metaTags: {} + metaTags: {}, + selected: false, }; } @@ -21,12 +24,12 @@ export async function refreshRemote() { try { await requestProtocolInfo(); await requestFSInfo(); - + // Kurze Verzögerung für Store-Propagation await new Promise(r => setTimeout(r, 100)); const currentFsInfo = get(fsInfo); - + // Sequenzielle Abfrage via Transport-Layer const sysFiles = await fetchDirectory(currentFsInfo?.sysPath || "/lfs/sys"); buzzerSysFiles.set(sysFiles.map(mapToBuzzerFile)); @@ -56,4 +59,54 @@ export async function refreshLocal() { } finally { isFetchingLocal.set(false); } +} + +export async function downloadSelectedFiles() { + const files = get(buzzerAudioFiles).filter(f => f.selected); + const pathPrefix = get(fsInfo)?.audioPath || "/lfs/a"; + + if (files.length === 0) { + addToast("Keine Dateien zum Herunterladen ausgewählt.", "warning"); + return; + } + + const totalBytes = files.reduce((sum, f) => sum + f.size, 0); + const bulkStart = performance.now(); // Startzeitpunkt exakt erfassen + + transferStats.update(s => ({ + ...s, + overallTotal: totalBytes, + overallDone: 0, + bulkStartTime: bulkStart + })); + + isFetchingRemote.set(true); + + try { +for (const file of files) { + console.log(`Starte Download von: ${file.name}`); + + transferStats.update(s => ({ ...s, pendingFileName: file.name })); + + const fullPath = `${pathPrefix}/${file.name}`; + await new Promise(r => setTimeout(r, 10)); + await getFile(fullPath); + } + + // Echte Durchschnittsgeschwindigkeit für den gesamten Bulk-Transfer berechnen + const totalTimeSec = (performance.now() - bulkStart) / 1000; + const avgSpeedKbs = ((totalBytes / 1024) / totalTimeSec).toFixed(1); + + addToast(`${files.length} ${files.length === 1 ? "Datei" : "Dateien"} erfolgreich heruntergeladen. (${avgSpeedKbs} kB/s)`, "success"); + } catch (error) { + console.error("Bulk-Download Fehler:", error); + addToast("Download abgebrochen: " + (error instanceof Error ? error.message : "Unbekannter Fehler"), "error"); + } finally { + transferStats.update(s => ({ + ...s, + overallDone: s.overallTotal, // Schließt den Ladebalken visuell sauber ab + })); + // Das console.log für den Live-Speed wurde entfernt, da es hier falsche Werte liefert + isFetchingRemote.set(false); + } } \ No newline at end of file diff --git a/webpage/src/lib/transport.ts b/webpage/src/lib/transport.ts index 35b8cc1..e4d82d8 100644 --- a/webpage/src/lib/transport.ts +++ b/webpage/src/lib/transport.ts @@ -70,7 +70,7 @@ export function handleTransportDisconnect() { let isFileTransferring = false; -export async function fetchFileThroughputTest(path: string): Promise { +export async function getFile(path: string): Promise { if (isFileTransferring) { throw new Error("Ein Dateitransfer läuft bereits."); } diff --git a/webpage/src/lib/types.ts b/webpage/src/lib/types.ts index 441afc2..5897795 100644 --- a/webpage/src/lib/types.ts +++ b/webpage/src/lib/types.ts @@ -26,4 +26,5 @@ export interface BuzzerFile { tagsLoaded: boolean; sysTags: SystemTags; metaTags: MetadataTags; + selected: boolean; } \ No newline at end of file diff --git a/webpage/src/pages/index.astro b/webpage/src/pages/index.astro index 00b3c9b..ec47c96 100644 --- a/webpage/src/pages/index.astro +++ b/webpage/src/pages/index.astro @@ -1,41 +1,13 @@ + --- -import MainLayout from "../layouts/MainLayout.astro"; -import BuzzerControl from "../components/BuzzerControl.svelte"; -import BLEList from "../components/BLEList.svelte"; import AppGuard from "../components/AppGuard.svelte"; -import FlashUsage from "../components/FlashUsage.svelte"; +import MainLayout from "../layouts/MainLayout.astro"; +import Header from "../components/Header.svelte"; +import MainGrid from "../components/MainGrid.svelte"; --- - -
-
-

- Buzzer Management -

-

- Verbinde dich mit dem nRF52840 Buzzer, um Audio-Dateien zu übertragen und - Systemparameter auszulesen. -

-
- -
-
- -
- -
- -
-
-
-
- -
- -
-
-
-
+
+ - + \ No newline at end of file diff --git a/webpage/src/styles/app.css b/webpage/src/styles/app.css new file mode 100644 index 0000000..38752f8 --- /dev/null +++ b/webpage/src/styles/app.css @@ -0,0 +1,73 @@ +/* app.css */ +@import "tailwindcss"; +@slot base; +@slot components; +@slot utilities; + +@theme { + --color-surface: var(--color-slate-50); + --color-on-surface: var(--color-slate-900); + --color-light-on-surface: var(--color-slate-700); + --color-accent: var(--color-blue-600); + --color-accent-bg: var(--color-blue-50); + --color-accent-hover: var(--color-blue-100); + --color-accent-border-separator: var(--color-blue-200); + + --color-surface-card: var(--color-white); + --color-surface-hover: var(--color-slate-100); + + --color-border-card: var(--color-slate-200); + --color-border-separator: var(--color-slate-100); + + --color-text-muted: var(--color-slate-400); + + --color-border-selected: var(--color-blue-500); + --color-bg-selected: var(--color-blue-50); + --color-bg-selected-hover: var(--color-blue-200); +} + +@layer base { + body { + @apply bg-surface text-on-surface; + } +} + +@layer components { + .main-layout { + @apply grid grid-cols-1 lg:grid-cols-2 gap-0 lg:gap-6 p-0 lg:p-6 w-full max-w-7xl mx-auto; + } + + + .disconnected { + @apply grayscale opacity-30 blur-[1px]; + } + + .buzzer-card { + @apply bg-surface-card border-border-card transition-all duration-300; + @apply border-b lg:border lg:rounded-xl lg:shadow-sm overflow-hidden; + } + + .card-header { + @apply px-3 py-2 flex justify-between items-center bg-gradient-to-b from-white to-slate-100 border-b border-border-card h-12; + } + + .card-title { + @apply font-light font-features-['smcp'] tracking-tight; + } + + /* .card-body { + @apply; + } */ + + .buzzer-card .icon { + @apply w-7 h-7 p-1; + } + + .card-header .btn { + @apply border rounded border-transparent hover:bg-slate-200 hover:border-border-card hover:shadow-sm; + } + + .text-2xs { + @apply text-[0.625rem]; + } +} \ No newline at end of file