From d90fa101d1f0d4754b69cc1c6e0e1b75f83e5336 Mon Sep 17 00:00:00 2001 From: Li-Yu Yu Date: Wed, 20 May 2026 09:07:51 +0000 Subject: [PATCH 1/2] feat: implement in-browser AECDump Web Viewer V1 - Added client-side binary parser in TS to decode and demux aecdump streams. - Added WAV exporter utility to compile raw PCM into playable WAV blobs. - Added Lit + Wavesurfer.js synced multi-track audio player component. - Configured Vite dev server & bundler with proxy support. - Implemented Playwright automated smoke tests for dev and prod page loads. - Integrated with main dashboard page and updated GitHub Actions CI/CD workflows. - Added local .gitignore to clean up workspace tracks. refactor: move protobuf compilation to build-time - Copied debug.proto locally to src/proto/debug.proto. - Untracked and ignored generated JS/TS protobuf files (debug.js and debug.d.ts) so reviewers do not have to audit auto-generated build output. - Configured package.json scripts (start and build) to compile the local protobuf schema automatically at startup/build time. --- .github/workflows/actions.yml | 5 + src/aecdump-viewer/.editorconfig | 29 + src/aecdump-viewer/.eslintrc.json | 40 + src/aecdump-viewer/.gitignore | 34 + src/aecdump-viewer/index.html | 27 + src/aecdump-viewer/package.json | 29 + src/aecdump-viewer/playwright.config.ts | 35 + src/aecdump-viewer/src/aecdump-viewer.ts | 567 ++++++++++++ src/aecdump-viewer/src/decoder.ts | 206 +++++ src/aecdump-viewer/src/proto/debug.proto | 115 +++ src/aecdump-viewer/src/wav-helper.ts | 110 +++ src/aecdump-viewer/tests/page-load.spec.ts | 69 ++ src/aecdump-viewer/tsconfig.json | 21 + src/aecdump-viewer/vite.config.ts | 16 + src/aecdump-viewer/yarn.lock | 957 +++++++++++++++++++++ src/index.html | 1 + 16 files changed, 2261 insertions(+) create mode 100644 src/aecdump-viewer/.editorconfig create mode 100644 src/aecdump-viewer/.eslintrc.json create mode 100644 src/aecdump-viewer/.gitignore create mode 100644 src/aecdump-viewer/index.html create mode 100644 src/aecdump-viewer/package.json create mode 100644 src/aecdump-viewer/playwright.config.ts create mode 100644 src/aecdump-viewer/src/aecdump-viewer.ts create mode 100644 src/aecdump-viewer/src/decoder.ts create mode 100644 src/aecdump-viewer/src/proto/debug.proto create mode 100644 src/aecdump-viewer/src/wav-helper.ts create mode 100644 src/aecdump-viewer/tests/page-load.spec.ts create mode 100644 src/aecdump-viewer/tsconfig.json create mode 100644 src/aecdump-viewer/vite.config.ts create mode 100644 src/aecdump-viewer/yarn.lock diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 086fae4..6104535 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -16,6 +16,11 @@ jobs: - run: npm ci && npm run build working-directory: src/ds-playground - run: cp -r src/ds-playground/dist out/ds-playground + - name: Build AECDump Viewer + run: yarn install --frozen-lockfile --registry https://registry.npmjs.org && yarn run build + working-directory: src/aecdump-viewer + - name: Copy AECDump Viewer to out + run: cp -r src/aecdump-viewer/dist out/aecdump-viewer - uses: actions/upload-artifact@v4 with: name: out diff --git a/src/aecdump-viewer/.editorconfig b/src/aecdump-viewer/.editorconfig new file mode 100644 index 0000000..c8c2d2a --- /dev/null +++ b/src/aecdump-viewer/.editorconfig @@ -0,0 +1,29 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.json] +indent_size = 2 + +[*.{html,js,md}] +block_comment_start = /** +block_comment = * +block_comment_end = */ diff --git a/src/aecdump-viewer/.eslintrc.json b/src/aecdump-viewer/.eslintrc.json new file mode 100644 index 0000000..9b36e27 --- /dev/null +++ b/src/aecdump-viewer/.eslintrc.json @@ -0,0 +1,40 @@ +{ + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "env": { + "browser": true + }, + "rules": { + "no-prototype-builtins": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_" + } + ] + }, + "overrides": [ + { + "files": ["rollup.config.js", "web-dev-server.config.mjs"], + "env": { + "node": true + } + } + ] +} diff --git a/src/aecdump-viewer/.gitignore b/src/aecdump-viewer/.gitignore new file mode 100644 index 0000000..48de0cd --- /dev/null +++ b/src/aecdump-viewer/.gitignore @@ -0,0 +1,34 @@ +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Production builds +dist/ +out-tsc/ + +# Generated Protobuf files +src/proto/debug.js +src/proto/debug.d.ts + +# Playwright test artifacts +test-results/ +playwright-report/ +blob-report/ +playwright/.cache/ + +# Editor configs and OS files +.DS_Store +*.local +.env +.env.local +.env.*.local + +# IDEs +.idea/ +.vscode/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.swp diff --git a/src/aecdump-viewer/index.html b/src/aecdump-viewer/index.html new file mode 100644 index 0000000..ad1dc5b --- /dev/null +++ b/src/aecdump-viewer/index.html @@ -0,0 +1,27 @@ + + + + + + + + + + AECDump Web Viewer + + + + + + + + + diff --git a/src/aecdump-viewer/package.json b/src/aecdump-viewer/package.json new file mode 100644 index 0000000..aac8477 --- /dev/null +++ b/src/aecdump-viewer/package.json @@ -0,0 +1,29 @@ +{ + "name": "aecdump-viewer", + "description": "Web-based AECDump Viewer", + "license": "Apache-2.0", + "author": "aaronyu", + "version": "0.1.0", + "type": "module", + "scripts": { + "build": "yarn run proto:generate && tsc && vite build", + "start": "yarn run proto:generate && vite", + "serve:dist": "vite preview --port 8080", + "serve:dev": "vite --port 8000", + "test": "playwright test", + "test:dev": "TEST_ENV=dev playwright test", + "proto:generate": "pbjs -t static-module -w es6 -o src/proto/debug.js src/proto/debug.proto && pbts -o src/proto/debug.d.ts src/proto/debug.js" + }, + "dependencies": { + "lit": "^3.1.1", + "protobufjs": "^7.2.4", + "wavesurfer.js": "^7.7.15" + }, + "devDependencies": { + "@playwright/test": "^1.41.2", + "protobufjs-cli": "^1.1.1", + "tslib": "^2.6.2", + "typescript": "^5.3.3", + "vite": "^5.0.12" + } +} diff --git a/src/aecdump-viewer/playwright.config.ts b/src/aecdump-viewer/playwright.config.ts new file mode 100644 index 0000000..34fdbe9 --- /dev/null +++ b/src/aecdump-viewer/playwright.config.ts @@ -0,0 +1,35 @@ +import { defineConfig, devices } from '@playwright/test'; + +const isDev = process.env.TEST_ENV === 'dev'; +const port = isDev ? 8000 : 8080; +const command = isDev ? 'yarn run serve:dev' : 'yarn run serve:dist'; + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'list', + use: { + baseURL: `http://localhost:${port}`, + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + /* Run local dev server before starting tests */ + webServer: { + command: command, + url: `http://localhost:${port}`, + reuseExistingServer: !process.env.CI, + timeout: 20 * 1000, // Dev server compilation might take slightly longer + }, +}); diff --git a/src/aecdump-viewer/src/aecdump-viewer.ts b/src/aecdump-viewer/src/aecdump-viewer.ts new file mode 100644 index 0000000..fccf6f0 --- /dev/null +++ b/src/aecdump-viewer/src/aecdump-viewer.ts @@ -0,0 +1,567 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import WaveSurfer from 'wavesurfer.js'; +import { parseAecDump, DecoderResult } from './decoder.js'; +import { audioBufferToWav } from './wav-helper.js'; + +interface Track { + id: 'ref' | 'mic' | 'out'; + name: string; + ws: WaveSurfer | null; + muted: boolean; + soloed: boolean; + volume: number; + url: string | null; +} + +@customElement('aecdump-viewer') +export class AecDumpViewer extends LitElement { + @state() private loading = false; + @state() private loadingStatus = ''; + @state() private isPlaying = false; + @state() private duration = 0; + @state() private currentTime = 0; + + @state() private tracks: Record = { + ref: { id: 'ref', name: 'Reference (Playout)', ws: null, muted: false, soloed: false, volume: 1.0, url: null }, + mic: { id: 'mic', name: 'Microphone Input', ws: null, muted: false, soloed: false, volume: 1.0, url: null }, + out: { id: 'out', name: 'Processed Output', ws: null, muted: false, soloed: false, volume: 1.0, url: null }, + }; + + private audioCtx: AudioContext | null = null; + private syncSeeking = false; + + static override styles = css` + :host { + display: block; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + color: #333; + max-width: 1200px; + margin: 0 auto; + padding: 20px; + } + + header { + margin-bottom: 30px; + border-bottom: 1px solid #eee; + padding-bottom: 20px; + } + + h1 { + margin: 0 0 10px 0; + font-size: 24px; + color: #1a73e8; + } + + .description { + margin: 0; + color: #666; + font-size: 14px; + } + + .dropzone { + border: 2px dashed #ccc; + border-radius: 8px; + padding: 40px 20px; + text-align: center; + background: #fafafa; + cursor: pointer; + transition: border-color 0.2s, background-color 0.2s; + margin-bottom: 20px; + } + + .dropzone:hover, .dropzone.dragover { + border-color: #1a73e8; + background: #f1f3f4; + } + + .dropzone p { + margin: 0; + font-size: 16px; + color: #5f6368; + } + + .dropzone input { + display: none; + } + + .status { + padding: 10px 15px; + border-radius: 4px; + background: #e8f0fe; + color: #1a73e8; + margin-bottom: 20px; + font-size: 14px; + } + + .controls { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 25px; + background: #f8f9fa; + padding: 15px; + border-radius: 8px; + border: 1px solid #e0e0e0; + } + + button { + background: #1a73e8; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; + font-size: 14px; + } + + button:hover { + background: #1557b0; + } + + button:disabled { + background: #ccc; + cursor: not-allowed; + } + + button.secondary { + background: #f1f3f4; + color: #3c4043; + border: 1px solid #dadce0; + } + + button.secondary:hover { + background: #e8eaed; + } + + button.active { + background: #d93025; + } + + button.active:hover { + background: #b0251a; + } + + .time-display { + font-family: monospace; + font-size: 14px; + color: #5f6368; + margin-left: auto; + } + + .tracks-container { + display: flex; + flex-direction: column; + gap: 20px; + } + + .track-card { + border: 1px solid #dadce0; + border-radius: 8px; + background: white; + overflow: hidden; + box-shadow: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15); + } + + .track-header { + background: #f8f9fa; + padding: 10px 15px; + border-bottom: 1px solid #dadce0; + display: flex; + align-items: center; + gap: 15px; + } + + .track-title { + font-weight: 600; + font-size: 14px; + color: #3c4043; + } + + .track-controls { + display: flex; + align-items: center; + gap: 8px; + margin-left: auto; + } + + .track-controls button { + padding: 4px 8px; + font-size: 12px; + } + + .track-controls button.mute.active { + background: #f28b82; + color: #b00020; + border-color: #f28b82; + } + + .track-controls button.solo.active { + background: #fdd663; + color: #875900; + border-color: #fdd663; + } + + .volume-slider { + display: flex; + align-items: center; + gap: 5px; + font-size: 12px; + color: #5f6368; + } + + .volume-slider input { + width: 80px; + } + + .track-body { + padding: 15px; + background: #fafafa; + position: relative; + } + + .waveform-container { + background: white; + border: 1px solid #eee; + border-radius: 4px; + min-height: 80px; + } + `; + + override render() { + const hasTracks = Object.values(this.tracks).some(t => t.url !== null); + + return html` +
+

AECDump Web Viewer (V1)

+

In-browser parser and synchronized waveform player for WebRTC APM audio dumps.

+
+ +
+

${this.loading ? 'Parsing dump...' : 'Drag & drop an aecdump/protobuf file here, or click to select'}

+ +
+ + ${this.loadingStatus ? html`
${this.loadingStatus}
` : ''} + + ${hasTracks ? html` +
+ + + +
+ ${this.formatTime(this.currentTime)} / ${this.formatTime(this.duration)} +
+
+ +
+ ${Object.values(this.tracks).map(track => this.renderTrackCard(track))} +
+ ` : ''} + `; + } + + private renderTrackCard(track: Track) { + if (!track.url) return ''; + + return html` +
+
+ ${track.name} + +
+
+ Vol: + this.onVolumeChange(track.id, e)} + > +
+ + + +
+
+
+
+
+
+ `; + } + + // File selection & drag-drop handling + private triggerFileSelect() { + this.shadowRoot?.getElementById('fileInput')?.click(); + } + + private onDragOver(e: DragEvent) { + e.preventDefault(); + this.shadowRoot?.querySelector('.dropzone')?.classList.add('dragover'); + } + + private onDragLeave() { + this.shadowRoot?.querySelector('.dropzone')?.classList.remove('dragover'); + } + + private onDrop(e: DragEvent) { + e.preventDefault(); + this.onDragLeave(); + const file = e.dataTransfer?.files[0]; + if (file) this.processFile(file); + } + + private onFileSelected(e: Event) { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) this.processFile(file); + } + + private async processFile(file: File) { + this.loading = true; + this.loadingStatus = `Loading file: ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)...`; + this.stopAll(); + this.destroyWaveSurfers(); + + try { + const arrayBuffer = await file.arrayBuffer(); + this.loadingStatus = 'Parsing AECDump protobuf data...'; + + // Small delay to allow UI to update + await new Promise(resolve => setTimeout(resolve, 50)); + + const parsed = parseAecDump(arrayBuffer); + + this.loadingStatus = 'Decoding audio and preparing tracks...'; + await new Promise(resolve => setTimeout(resolve, 50)); + + await this.initializeTracks(parsed); + this.loadingStatus = 'AECDump loaded successfully!'; + } catch (error) { + console.error(error); + this.loadingStatus = `Error: ${(error as Error).message}`; + } finally { + this.loading = false; + } + } + + // Convert parsed raw PCM streams into WAV Blob URLs + private async initializeTracks(parsed: DecoderResult) { + if (!this.audioCtx) { + this.audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)(); + } + + // Helper to convert ParsedAudioStream to Blob URL + const createWavUrl = (stream: typeof parsed.reference) => { + if (stream.channelData.length === 0 || stream.channelData[0].length === 0) { + return null; + } + // Create AudioBuffer + const buffer = this.audioCtx!.createBuffer( + stream.channels, + stream.channelData[0].length, + stream.sampleRate + ); + for (let c = 0; c < stream.channels; c++) { + buffer.copyToChannel(stream.channelData[c] as any, c); + } + // Encode to WAV + const wavBytes = audioBufferToWav(buffer); + const blob = new Blob([wavBytes], { type: 'audio/wav' }); + return URL.createObjectURL(blob); + }; + + // Generate URLs + const refUrl = createWavUrl(parsed.reference); + const micUrl = createWavUrl(parsed.input); + const outUrl = createWavUrl(parsed.output); + + // Update track state + this.tracks = { + ref: { ...this.tracks.ref, url: refUrl }, + mic: { ...this.tracks.mic, url: micUrl }, + out: { ...this.tracks.out, url: outUrl }, + }; + + // Request Lit update so track-cards render, then initialize WaveSurfers on the DOM + this.requestUpdate(); + await this.updateComplete; + + this.initWaveSurfers(); + } + + private initWaveSurfers() { + const wsOptions = { + height: 80, + waveColor: '#a8c7fa', + progressColor: '#1a73e8', + cursorColor: '#3c4043', + cursorWidth: 2, + dragToSeek: true, + normalize: true, + }; + + // Initialize each active track + Object.values(this.tracks).forEach(track => { + if (!track.url) return; + + const container = this.shadowRoot?.getElementById(`waveform-${track.id}`); + if (!container) return; + + const ws = WaveSurfer.create({ + ...wsOptions, + container: container, + url: track.url, + }); + + track.ws = ws; + + // Sync Mute/Volume state + ws.setMuted(track.muted); + ws.setVolume(track.volume); + + // Bind events + if (track.id === 'mic') { + // Use mic as master track for duration/currentTime state updates + ws.on('ready', (duration) => { + this.duration = duration; + }); + ws.on('timeupdate', (time) => { + this.currentTime = time; + }); + ws.on('finish', () => { + this.isPlaying = false; + }); + } + + // Synchronized Seeking + ws.on('interaction', () => { + if (this.syncSeeking) return; + this.syncSeeking = true; + + const time = ws.getCurrentTime(); + Object.values(this.tracks).forEach(t => { + if (t.id !== track.id && t.ws) { + t.ws.setTime(time); + } + }); + + this.syncSeeking = false; + }); + }); + } + + private destroyWaveSurfers() { + Object.values(this.tracks).forEach(track => { + if (track.ws) { + track.ws.destroy(); + track.ws = null; + } + if (track.url) { + URL.revokeObjectURL(track.url); + track.url = null; + } + }); + this.isPlaying = false; + this.duration = 0; + this.currentTime = 0; + } + + // Master controls + private togglePlay() { + const activeWs = Object.values(this.tracks).map(t => t.ws).filter(Boolean) as WaveSurfer[]; + if (activeWs.length === 0) return; + + if (this.isPlaying) { + activeWs.forEach(ws => ws.pause()); + this.isPlaying = false; + } else { + // Play all + activeWs.forEach(ws => ws.play()); + this.isPlaying = true; + } + } + + private stopAll() { + Object.values(this.tracks).forEach(t => { + if (t.ws) { + t.ws.stop(); + } + }); + this.isPlaying = false; + this.currentTime = 0; + } + + // Track controls + private onVolumeChange(id: 'ref' | 'mic' | 'out', e: Event) { + const value = parseFloat((e.target as HTMLInputElement).value); + const track = this.tracks[id]; + track.volume = value; + if (track.ws) { + track.ws.setVolume(value); + } + this.requestUpdate(); + } + + private toggleMute(id: 'ref' | 'mic' | 'out') { + const track = this.tracks[id]; + track.muted = !track.muted; + if (track.ws) { + track.ws.setMuted(track.muted); + } + this.requestUpdate(); + } + + private toggleSolo(id: 'ref' | 'mic' | 'out') { + const track = this.tracks[id]; + track.soloed = !track.soloed; + + const hasSoloedTracks = Object.values(this.tracks).some(t => t.soloed); + + Object.values(this.tracks).forEach(t => { + if (!t.ws) return; + + if (hasSoloedTracks) { + // If there are soloed tracks, mute this track unless it is soloed + t.ws.setMuted(!t.soloed); + } else { + // Otherwise restore the track's own mute state + t.ws.setMuted(t.muted); + } + }); + + this.requestUpdate(); + } + + private formatTime(seconds: number): string { + const min = Math.floor(seconds / 60); + const sec = Math.floor(seconds % 60); + const ms = Math.floor((seconds % 1) * 100); + return `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}.${ms.toString().padStart(2, '0')}`; + } + + override disconnectedCallback() { + super.disconnectedCallback(); + this.destroyWaveSurfers(); + } +} diff --git a/src/aecdump-viewer/src/decoder.ts b/src/aecdump-viewer/src/decoder.ts new file mode 100644 index 0000000..cf88b94 --- /dev/null +++ b/src/aecdump-viewer/src/decoder.ts @@ -0,0 +1,206 @@ +import { webrtc } from './proto/debug.js'; + +const Event = webrtc.audioproc.Event; + +export interface ParsedAudioStream { + sampleRate: number; + channels: number; + channelData: Float32Array[]; +} + +export interface DecoderResult { + reference: ParsedAudioStream; + input: ParsedAudioStream; + output: ParsedAudioStream; +} + +class ChannelAccumulator { + private chunks: Float32Array[] = []; + private totalLength = 0; + + append(chunk: Float32Array) { + this.chunks.push(chunk); + this.totalLength += chunk.length; + } + + get length() { + return this.totalLength; + } + + getMerged(): Float32Array { + const merged = new Float32Array(this.totalLength); + let offset = 0; + for (const chunk of this.chunks) { + merged.set(chunk, offset); + offset += chunk.length; + } + return merged; + } +} + +class StreamAccumulator { + public accumulators: ChannelAccumulator[] = []; + public sampleRate = 0; + public channels = 0; + + init(sampleRate: number, channels: number) { + if (this.sampleRate === 0) { + this.sampleRate = sampleRate; + this.channels = channels; + this.accumulators = Array.from({ length: channels }, () => new ChannelAccumulator()); + } else if (this.sampleRate !== sampleRate || this.channels !== channels) { + console.warn( + `StreamAccumulator: Audio format changed mid-dump! ` + + `Old: ${this.sampleRate}Hz/${this.channels}ch, ` + + `New: ${sampleRate}Hz/${channels}ch. V1 ignores mid-dump format changes.` + ); + } + } + + appendInterleavedInt16(bytes: Uint8Array) { + if (this.channels === 0) return; + const int16 = new Int16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2); + const numSamples = int16.length / this.channels; + + // Temporary chunks for each channel + const chunks = Array.from({ length: this.channels }, () => new Float32Array(numSamples)); + + let index = 0; + for (let i = 0; i < numSamples; i++) { + for (let c = 0; c < this.channels; c++) { + chunks[c][i] = int16[index++] / 32768.0; + } + } + + for (let c = 0; c < this.channels; c++) { + this.accumulators[c].append(chunks[c]); + } + } + + appendDeinterleavedFloat(channelsBytes: Uint8Array[]) { + if (this.channels === 0) return; + const actualChannels = Math.min(this.channels, channelsBytes.length); + for (let c = 0; c < actualChannels; c++) { + const bytes = channelsBytes[c]; + let floatData: Float32Array; + if (bytes.byteOffset % 4 === 0) { + floatData = new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4); + } else { + const copy = new Uint8Array(bytes.byteLength); + copy.set(bytes); + floatData = new Float32Array(copy.buffer, 0, copy.byteLength / 4); + } + this.accumulators[c].append(floatData); + } + } + + toParsedStream(): ParsedAudioStream { + return { + sampleRate: this.sampleRate || 16000, // fallback + channels: this.channels || 1, + channelData: this.accumulators.map((acc) => acc.getMerged()), + }; + } +} + +export function parseAecDump(arrayBuffer: ArrayBuffer): DecoderResult { + const view = new DataView(arrayBuffer); + let offset = 0; + + const refAcc = new StreamAccumulator(); + const inputAcc = new StreamAccumulator(); + const outputAcc = new StreamAccumulator(); + + let eventCount = 0; + + while (offset < arrayBuffer.byteLength) { + if (offset + 4 > arrayBuffer.byteLength) { + console.warn('parseAecDump: Unexpected EOF while reading message size.'); + break; + } + const size = view.getInt32(offset, true); + offset += 4; + + if (offset + size > arrayBuffer.byteLength) { + console.warn('parseAecDump: Unexpected EOF while reading message payload.'); + break; + } + + const eventBytes = new Uint8Array(arrayBuffer, offset, size); + offset += size; + + let event: webrtc.audioproc.Event; + try { + event = Event.decode(eventBytes); + } catch (e) { + console.error(`parseAecDump: Failed to decode event #${eventCount} at offset ${offset - size - 4}:`, e); + continue; + } + + eventCount++; + + switch (event.type) { + case Event.Type.INIT: { + const init = event.init; + if (!init) break; + + const sampleRate = init.sampleRate || 16000; + const reverseSampleRate = init.reverseSampleRate || sampleRate; + const outputSampleRate = init.outputSampleRate || sampleRate; + + const inputChannels = init.numInputChannels || 1; + const outputChannels = init.numOutputChannels || 1; + const reverseChannels = init.numReverseChannels || 1; + + refAcc.init(reverseSampleRate, reverseChannels); + inputAcc.init(sampleRate, inputChannels); + outputAcc.init(outputSampleRate, outputChannels); + break; + } + + case Event.Type.REVERSE_STREAM: { + const rev = event.reverseStream; + if (!rev) break; + + if (rev.data && rev.data.length > 0) { + refAcc.appendInterleavedInt16(rev.data); + } else if (rev.channel && rev.channel.length > 0) { + refAcc.appendDeinterleavedFloat(rev.channel); + } + break; + } + + case Event.Type.STREAM: { + const stream = event.stream; + if (!stream) break; + + // Input + if (stream.inputData && stream.inputData.length > 0) { + inputAcc.appendInterleavedInt16(stream.inputData); + } else if (stream.inputChannel && stream.inputChannel.length > 0) { + inputAcc.appendDeinterleavedFloat(stream.inputChannel); + } + + // Output + if (stream.outputData && stream.outputData.length > 0) { + outputAcc.appendInterleavedInt16(stream.outputData); + } else if (stream.outputChannel && stream.outputChannel.length > 0) { + outputAcc.appendDeinterleavedFloat(stream.outputChannel); + } + break; + } + + default: + // Config, RuntimeSetting, Unknown are ignored in V1 decoder + break; + } + } + + console.log(`parseAecDump: Successfully parsed ${eventCount} events.`); + + return { + reference: refAcc.toParsedStream(), + input: inputAcc.toParsedStream(), + output: outputAcc.toParsedStream(), + }; +} diff --git a/src/aecdump-viewer/src/proto/debug.proto b/src/aecdump-viewer/src/proto/debug.proto new file mode 100644 index 0000000..cc5efbc --- /dev/null +++ b/src/aecdump-viewer/src/proto/debug.proto @@ -0,0 +1,115 @@ +syntax = "proto2"; +option optimize_for = LITE_RUNTIME; +package webrtc.audioproc; + +// Contains the format of input/output/reverse audio. An Init message is added +// when any of the fields are changed. +message Init { + optional int32 sample_rate = 1; + optional int32 device_sample_rate = 2 [deprecated=true]; + optional int32 num_input_channels = 3; + optional int32 num_output_channels = 4; + optional int32 num_reverse_channels = 5; + optional int32 reverse_sample_rate = 6; + optional int32 output_sample_rate = 7; + optional int32 reverse_output_sample_rate = 8; + optional int32 num_reverse_output_channels = 9; + optional int64 timestamp_ms = 10; +} + +// May contain interleaved or deinterleaved data, but don't store both formats. +message ReverseStream { + // int16 interleaved data. + optional bytes data = 1; + + // float deinterleaved data, where each repeated element points to a single + // channel buffer of data. + repeated bytes channel = 2; +} + +// May contain interleaved or deinterleaved data, but don't store both formats. +message Stream { + // int16 interleaved data. + optional bytes input_data = 1; + optional bytes output_data = 2; + + optional int32 delay = 3; + optional sint32 drift = 4; + optional int32 applied_input_volume = 5; + optional bool keypress = 6; + + // float deinterleaved data, where each repeated element points to a single + // channel buffer of data. + repeated bytes input_channel = 7; + repeated bytes output_channel = 8; +} + +// Contains the configurations of various APM component. A Config message is +// added when any of the fields are changed. +message Config { + // Acoustic echo canceler. + optional bool aec_enabled = 1; + optional bool aec_delay_agnostic_enabled = 2; + optional bool aec_drift_compensation_enabled = 3; + optional bool aec_extended_filter_enabled = 4; + optional int32 aec_suppression_level = 5; + // Mobile AEC. + optional bool aecm_enabled = 6; + optional bool aecm_comfort_noise_enabled = 7 [deprecated = true]; + optional int32 aecm_routing_mode = 8 [deprecated = true]; + // Automatic gain controller. + optional bool agc_enabled = 9; + optional int32 agc_mode = 10; + optional bool agc_limiter_enabled = 11; + optional bool noise_robust_agc_enabled = 12; + // High pass filter. + optional bool hpf_enabled = 13; + // Noise suppression. + optional bool ns_enabled = 14; + optional int32 ns_level = 15; + // Transient suppression. + optional bool transient_suppression_enabled = 16; + // Semicolon-separated string containing experimental feature + // descriptions. + optional string experiments_description = 17; + reserved 18; // Intelligibility enhancer enabled (deprecated). + // Pre amplifier. + optional bool pre_amplifier_enabled = 19; + optional float pre_amplifier_fixed_gain_factor = 20; + + // Next field number 21. +} + +message PlayoutAudioDeviceInfo { + optional int32 id = 1; + optional int32 max_volume = 2; +} + +message RuntimeSetting { + optional float capture_pre_gain = 1; + optional float custom_render_processing_setting = 2; + optional float capture_fixed_post_gain = 3; + optional int32 playout_volume_change = 4; + optional PlayoutAudioDeviceInfo playout_audio_device_change = 5; + optional bool capture_output_used = 6; + optional float capture_post_gain = 7; +} + +message Event { + enum Type { + INIT = 0; + REVERSE_STREAM = 1; + STREAM = 2; + CONFIG = 3; + UNKNOWN_EVENT = 4; + RUNTIME_SETTING = 5; + } + + required Type type = 1; + + optional Init init = 2; + optional ReverseStream reverse_stream = 3; + optional Stream stream = 4; + optional Config config = 5; + optional RuntimeSetting runtime_setting = 6; +} diff --git a/src/aecdump-viewer/src/wav-helper.ts b/src/aecdump-viewer/src/wav-helper.ts new file mode 100644 index 0000000..41d8980 --- /dev/null +++ b/src/aecdump-viewer/src/wav-helper.ts @@ -0,0 +1,110 @@ +/** + * Converts an AudioBuffer to a WAV file format ArrayBuffer. + * Adapted from pwa-audio-recorder/wav-utils.mjs + */ +export function audioBufferToWav(buffer: AudioBuffer, opt?: { float32?: boolean }): ArrayBuffer { + opt = opt || {}; + const numChannels = buffer.numberOfChannels; + const sampleRate = buffer.sampleRate; + const format = opt.float32 ? 3 : 1; // 3 = IEEE Float, 1 = LPCM + const bitDepth = format === 3 ? 32 : 16; + + let result: Float32Array; + if (numChannels === 2) { + result = interleave(buffer.getChannelData(0), buffer.getChannelData(1)); + } else if (numChannels > 2) { + // For V1, if more than 2 channels, we just take the first one, or we could interleave all. + // Let's take the first one for simplicity, or we can log a warning. + console.warn(`audioBufferToWav: ${numChannels} channels detected. Downmixing to mono (first channel) for V1.`); + result = buffer.getChannelData(0); + } else { + result = buffer.getChannelData(0); + } + + return encodeWAV(result, format, sampleRate, numChannels, bitDepth); +} + +function encodeWAV( + samples: Float32Array, + format: number, + sampleRate: number, + numChannels: number, + bitDepth: number +): ArrayBuffer { + const bytesPerSample = bitDepth / 8; + const blockAlign = numChannels * bytesPerSample; + + const buffer = new ArrayBuffer(44 + samples.length * bytesPerSample); + const view = new DataView(buffer); + + /* RIFF identifier */ + writeString(view, 0, 'RIFF'); + /* RIFF chunk length */ + view.setUint32(4, 36 + samples.length * bytesPerSample, true); + /* RIFF type */ + writeString(view, 8, 'WAVE'); + /* format chunk identifier */ + writeString(view, 12, 'fmt '); + /* format chunk length */ + view.setUint32(16, 16, true); + /* sample format (raw) */ + view.setUint16(20, format, true); + /* channel count */ + view.setUint16(22, numChannels, true); + /* sample rate */ + view.setUint32(24, sampleRate, true); + /* byte rate (sample rate * block align) */ + view.setUint32(28, sampleRate * blockAlign, true); + /* block align (channel count * bytes per sample) */ + view.setUint16(32, blockAlign, true); + /* bits per sample */ + view.setUint16(34, bitDepth, true); + /* data chunk identifier */ + writeString(view, 36, 'data'); + /* data chunk length */ + view.setUint32(40, samples.length * bytesPerSample, true); + + if (format === 1) { + // PCM 16-bit + floatTo16BitPCM(view, 44, samples); + } else { + // Float 32-bit + writeFloat32(view, 44, samples); + } + + return buffer; +} + +function interleave(inputL: Float32Array, inputR: Float32Array): Float32Array { + const length = inputL.length + inputR.length; + const result = new Float32Array(length); + + let index = 0; + let inputIndex = 0; + + while (index < length) { + result[index++] = inputL[inputIndex]; + result[index++] = inputR[inputIndex]; + inputIndex++; + } + return result; +} + +function writeString(view: DataView, offset: number, string: string) { + for (let i = 0; i < string.length; i++) { + view.setUint8(offset + i, string.charCodeAt(i)); + } +} + +function floatTo16BitPCM(output: DataView, offset: number, input: Float32Array) { + for (let i = 0; i < input.length; i++, offset += 2) { + const s = Math.max(-1, Math.min(1, input[i])); + output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true); + } +} + +function writeFloat32(output: DataView, offset: number, input: Float32Array) { + for (let i = 0; i < input.length; i++, offset += 4) { + output.setFloat32(offset, input[i], true); + } +} diff --git a/src/aecdump-viewer/tests/page-load.spec.ts b/src/aecdump-viewer/tests/page-load.spec.ts new file mode 100644 index 0000000..4a1e802 --- /dev/null +++ b/src/aecdump-viewer/tests/page-load.spec.ts @@ -0,0 +1,69 @@ +import { test, expect } from '@playwright/test'; + +test.describe('AECDump Web Viewer Page Load', () => { + const errors: Error[] = []; + const consoleErrors: string[] = []; + const networkFailures: string[] = []; + + test.beforeEach(({ page }) => { + // Clear error logs before each test + errors.length = 0; + consoleErrors.length = 0; + networkFailures.length = 0; + + // 1. Catch unhandled exceptions + page.on('pageerror', (exception) => { + console.error(`[Page Error] ${exception.stack || exception.message}`); + errors.push(exception); + }); + + // 2. Catch console errors + page.on('console', (msg) => { + if (msg.type() === 'error') { + console.error(`[Console Error] ${msg.text()}`); + consoleErrors.push(msg.text()); + } + }); + + // 3. Catch network failures and incorrect MIME-types + page.on('response', (response) => { + const status = response.status(); + const url = response.url(); + + if (status >= 400) { + const failure = `${url} (HTTP ${status})`; + console.error(`[Network Failure] ${failure}`); + networkFailures.push(failure); + } + + // Verify that Javascript assets are served with correct MIME-types to prevent loading crashes + const contentType = response.headers()['content-type'] || ''; + if (url.endsWith('.js') && !contentType.includes('javascript')) { + const failure = `${url} (Invalid MIME: "${contentType}", expected "application/javascript")`; + console.error(`[MIME Type Error] ${failure}`); + networkFailures.push(failure); + } + }); + }); + + test('should boot successfully with clean logs and correct visuals', async ({ page }) => { + // Navigate to the app root + await page.goto('/'); + + // Assert that the main drag-and-drop area is visible + const dropzone = page.locator('.dropzone'); + await expect(dropzone).toBeVisible(); + + const dropzoneText = await dropzone.locator('p').innerText(); + expect(dropzoneText).toContain('Drag & drop an aecdump/protobuf file here'); + + // Assert there are no unhandled JS exceptions + expect(errors, 'Uncaught exceptions were thrown during load').toHaveLength(0); + + // Assert there are no console errors + expect(consoleErrors, 'Errors were logged to the browser console').toHaveLength(0); + + // Assert there are no network or MIME-type resolution failures + expect(networkFailures, 'Asset requests failed to load').toHaveLength(0); + }); +}); diff --git a/src/aecdump-viewer/tsconfig.json b/src/aecdump-viewer/tsconfig.json new file mode 100644 index 0000000..01fcba4 --- /dev/null +++ b/src/aecdump-viewer/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es2021", + "module": "es2020", + "moduleResolution": "node", + "noEmitOnError": true, + "lib": ["es2021", "dom"], + "strict": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "importHelpers": true, + "outDir": "out-tsc", + "allowJs": true, + "sourceMap": true, + "inlineSources": true, + "rootDir": "./", + "incremental": true + }, + "include": ["**/*.ts", "src/proto/debug.js"] +} diff --git a/src/aecdump-viewer/vite.config.ts b/src/aecdump-viewer/vite.config.ts new file mode 100644 index 0000000..382d9d4 --- /dev/null +++ b/src/aecdump-viewer/vite.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + // Allow any host to respond to proxy requests (e.g. from cloud workspace / corporate proxy) + allowedHosts: true, + + // Listen on all local IP addresses (0.0.0.0) so the proxy can forward requests to Vite + host: true, + }, + preview: { + // Also configure preview server (used for production build testing) + allowedHosts: true, + host: true, + } +}); diff --git a/src/aecdump-viewer/yarn.lock b/src/aecdump-viewer/yarn.lock new file mode 100644 index 0000000..5257b10 --- /dev/null +++ b/src/aecdump-viewer/yarn.lock @@ -0,0 +1,957 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/parser@^7.20.15": + version "7.29.3" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz#116f70a77958307fceac27747573032f8a62f88e" + integrity sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA== + dependencies: + "@babel/types" "^7.29.0" + +"@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + +"@jsdoc/salty@^0.2.1": + version "0.2.12" + resolved "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.12.tgz#6320bb4bd16e98c2da2c666ec97d1a72922cd06a" + integrity sha512-TuB0x50EoAvEX/UEWITd8Mkn3WhiTjSvbTMCLj0BhsQEl5iUzjXdA0bETEVpTk+5TGTLR6QktI9H4hLviVeaAQ== + dependencies: + lodash "^4.18.1" + +"@lit-labs/ssr-dom-shim@^1.5.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.6.0.tgz#693e129b809741fd23e98fcb57e41fd3d082db1a" + integrity sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ== + +"@lit/reactive-element@^2.1.0": + version "2.1.2" + resolved "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz#4c6af9042603c98e61ba90b294607904d51b61cb" + integrity sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A== + dependencies: + "@lit-labs/ssr-dom-shim" "^1.5.0" + +"@playwright/test@^1.41.2": + version "1.60.0" + resolved "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz#e696c31427e8882851235cd556dc2490c3206d97" + integrity sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag== + dependencies: + playwright "1.60.0" + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.5": + version "2.0.5" + resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz#d9315ad7cf3f30aac70bda3c068443dc6f143659" + integrity sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz#4d6fc00c8fb64016a5c81b469d549046350f1065" + integrity sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz#ae64fbc014ff44c8bfad03dd4c93cd2d6a4c82db" + integrity sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz#eaee5900122c110a3dbcb728c0597014a2621774" + integrity sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg== + +"@rollup/rollup-android-arm-eabi@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz#3a04f01e9f01392bbef5920b94aa3b88794be7ab" + integrity sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ== + +"@rollup/rollup-android-arm64@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz#e371b653ceabc900790ae73f5548a0fd7cd63a70" + integrity sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw== + +"@rollup/rollup-darwin-arm64@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz#2a5aa70432e39816d666d79287a7324cfc3b4e72" + integrity sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA== + +"@rollup/rollup-darwin-x64@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz#c3b5b49629379cd9cdc5d841bf00ed44ebf393dd" + integrity sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg== + +"@rollup/rollup-freebsd-arm64@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz#f929d8e0462fae6602fc960beeabd7287d859283" + integrity sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g== + +"@rollup/rollup-freebsd-x64@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz#c01cb58031226f95d0900b1ec847f4fb32c6e809" + integrity sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw== + +"@rollup/rollup-linux-arm-gnueabihf@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz#f29d890c4858c8e0d3be01677eef4f6a359eed9d" + integrity sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA== + +"@rollup/rollup-linux-arm-musleabihf@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz#1ebfc8eb9f66136ed2faae5f44995add5ca3c964" + integrity sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w== + +"@rollup/rollup-linux-arm64-gnu@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz#c1fa823c2c4ce46ba7f61de1a4c3fdadd4fb4e7b" + integrity sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg== + +"@rollup/rollup-linux-arm64-musl@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz#a7f18854d0471b78bda8ea38f0891a4e059b571d" + integrity sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A== + +"@rollup/rollup-linux-loong64-gnu@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz#83658a9a4576bcce8cef85b2c78b9b649d2200c4" + integrity sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ== + +"@rollup/rollup-linux-loong64-musl@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz#fd2af677ae3417bb58d57ae37dd0d84686e40244" + integrity sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw== + +"@rollup/rollup-linux-ppc64-gnu@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz#6481647181c4cf8f1ddbd99f62c84cfc56c1a94a" + integrity sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg== + +"@rollup/rollup-linux-ppc64-musl@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz#18610a1a1550e28a5042ca916f898419540f17f4" + integrity sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A== + +"@rollup/rollup-linux-riscv64-gnu@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz#597bb80465a2621dbe0de0a41c66394a8a7e9a6e" + integrity sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA== + +"@rollup/rollup-linux-riscv64-musl@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz#a2a919a9f927ef7f24a60af77e3cb55f1ad59e4d" + integrity sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw== + +"@rollup/rollup-linux-s390x-gnu@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz#3166f6ceae7df9bbfddf9f36be1937231e13e3c6" + integrity sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ== + +"@rollup/rollup-linux-x64-gnu@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz#23c9bf79771d804fb87415eb0767569f273261e5" + integrity sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ== + +"@rollup/rollup-linux-x64-musl@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz#97941c6b94d67fe25cde0f027c10a19f2d1fdd39" + integrity sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg== + +"@rollup/rollup-openbsd-x64@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz#7aeb7d92e2cd1d399f56daf75c39040b777b6c77" + integrity sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA== + +"@rollup/rollup-openharmony-arm64@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz#925de61ae83bf99aa636e8acea87432e8c0ffaab" + integrity sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg== + +"@rollup/rollup-win32-arm64-msvc@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz#888ab83842721491044c46a7407e1f38f3235bb4" + integrity sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw== + +"@rollup/rollup-win32-ia32-msvc@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz#fa30ac24e3f0232139d2a47500560a28695764d4" + integrity sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA== + +"@rollup/rollup-win32-x64-gnu@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz#223e2bc93f86e0707568e1fadb5b537e50c976c7" + integrity sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw== + +"@rollup/rollup-win32-x64-msvc@4.60.4": + version "4.60.4" + resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz#da4f1676d87e2bdf744291b504b0ab79550c3e61" + integrity sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw== + +"@types/estree@1.0.8": + version "1.0.8" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/linkify-it@^5": + version "5.0.0" + resolved "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76" + integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== + +"@types/markdown-it@^14.1.1": + version "14.1.2" + resolved "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61" + integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog== + dependencies: + "@types/linkify-it" "^5" + "@types/mdurl" "^2" + +"@types/mdurl@^2": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" + integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== + +"@types/node@>=13.7.0": + version "25.9.1" + resolved "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz#3bda556db500ae4319c08e7fc9ab94f19013ba0b" + integrity sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg== + dependencies: + undici-types ">=7.24.0 <7.24.7" + +"@types/trusted-types@^2.0.2": + version "2.0.7" + resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.16.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +brace-expansion@^2.0.1: + version "2.1.0" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz#4f41a41190216ee36067ec381526fe9539c4f0ae" + integrity sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w== + dependencies: + balanced-match "^1.0.0" + +catharsis@^0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121" + integrity sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A== + dependencies: + lodash "^4.17.15" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escodegen@^1.13.0: + version "1.14.3" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-visitor-keys@^3.4.1: + version "3.4.3" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +espree@^9.0.0: + version "9.6.1" + resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +glob@^8.0.0: + version "8.1.0" + resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +graceful-fs@^4.1.9: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +js2xmlparser@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz#2a1fdf01e90585ef2ae872a01bc169c6a8d5e60a" + integrity sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA== + dependencies: + xmlcreate "^2.0.4" + +jsdoc@^4.0.0: + version "4.0.5" + resolved "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.5.tgz#fbed70e04a3abcf2143dad6b184947682bbc7315" + integrity sha512-P4C6MWP9yIlMiK8nwoZvxN84vb6MsnXcHuy7XzVOvQoCizWX5JFCBsWIIWKXBltpoRZXddUOVQmCTOZt9yDj9g== + dependencies: + "@babel/parser" "^7.20.15" + "@jsdoc/salty" "^0.2.1" + "@types/markdown-it" "^14.1.1" + bluebird "^3.7.2" + catharsis "^0.9.0" + escape-string-regexp "^2.0.0" + js2xmlparser "^4.0.2" + klaw "^3.0.0" + markdown-it "^14.1.0" + markdown-it-anchor "^8.6.7" + marked "^4.0.10" + mkdirp "^1.0.4" + requizzle "^0.2.3" + strip-json-comments "^3.1.0" + underscore "~1.13.2" + +klaw@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146" + integrity sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g== + dependencies: + graceful-fs "^4.1.9" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +linkify-it@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" + integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== + dependencies: + uc.micro "^2.0.0" + +lit-element@^4.2.0: + version "4.2.2" + resolved "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz#f74fcbfbea945eae5614ece22a674fa52ca3365b" + integrity sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w== + dependencies: + "@lit-labs/ssr-dom-shim" "^1.5.0" + "@lit/reactive-element" "^2.1.0" + lit-html "^3.3.0" + +lit-html@^3.3.0: + version "3.3.3" + resolved "https://registry.npmjs.org/lit-html/-/lit-html-3.3.3.tgz#a63fd02fb8c1c7b7057ee805ab6c612fdebef0b1" + integrity sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA== + dependencies: + "@types/trusted-types" "^2.0.2" + +lit@^3.1.1: + version "3.3.3" + resolved "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz#93579885e51a20a772c68482a34706fe1636e8f0" + integrity sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw== + dependencies: + "@lit/reactive-element" "^2.1.0" + lit-element "^4.2.0" + lit-html "^3.3.0" + +lodash@^4.17.15, lodash@^4.17.21, lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== + +long@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + +markdown-it-anchor@^8.6.7: + version "8.6.7" + resolved "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz#ee6926daf3ad1ed5e4e3968b1740eef1c6399634" + integrity sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA== + +markdown-it@^14.1.0: + version "14.1.1" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz#856f90b66fc39ae70affd25c1b18b581d7deee1f" + integrity sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA== + dependencies: + argparse "^2.0.1" + entities "^4.4.0" + linkify-it "^5.0.0" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + +marked@^4.0.10: + version "4.3.0" + resolved "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" + integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== + +mdurl@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0" + integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== + +minimatch@^5.0.1: + version "5.1.9" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz#1293ef15db0098b394540e8f9f744f9fda8dee4b" + integrity sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +nanoid@^3.3.12: + version "3.3.12" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz#ab3d912e217a6d0a514f00a72a16543a28982c05" + integrity sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +playwright-core@1.60.0: + version "1.60.0" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz#24e0d9cc4730713db5dffcace29b5e4696b1907a" + integrity sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA== + +playwright@1.60.0: + version "1.60.0" + resolved "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz#89710863a51f21112633ef8b6b182594d3bfd7b5" + integrity sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA== + dependencies: + playwright-core "1.60.0" + optionalDependencies: + fsevents "2.3.2" + +postcss@^8.4.43: + version "8.5.15" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz#d1eaf677a324e9ec02196da2d3fecf4a0b9a735c" + integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A== + dependencies: + nanoid "^3.3.12" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + +protobufjs-cli@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.3.0.tgz#a5849b91421485d2a504206456662ff1e73c8d57" + integrity sha512-vIb5o20PPMDH6dw9jPhO1kbC9UJEQor3ENtMOkNi4AjRp5FsrNtqrWgRRNTUcFcnUNhmil54Y1W3fxGADqu0kA== + dependencies: + chalk "^4.0.0" + escodegen "^1.13.0" + espree "^9.0.0" + estraverse "^5.1.0" + glob "^8.0.0" + jsdoc "^4.0.0" + minimist "^1.2.0" + semver "^7.1.2" + tmp "^0.2.1" + uglify-js "^3.7.7" + +protobufjs@^7.2.4: + version "7.6.0" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.0.tgz#61e42285beec2708a9c84d7abbb5f22e2ddc54d4" + integrity sha512-LtESOsMPTZgyYtwxhvdgdjGL0HmXEaRA/hVD6sol4zA60hVXXXP/SGmxnqDbgGE8gy7pYex7cym+5vYPcmaXBQ== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.5" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.1" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.2" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.1" + "@types/node" ">=13.7.0" + long "^5.3.2" + +punycode.js@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" + integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== + +requizzle@^0.2.3: + version "0.2.4" + resolved "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz#319eb658b28c370f0c20f968fa8ceab98c13d27c" + integrity sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw== + dependencies: + lodash "^4.17.21" + +rollup@^4.20.0: + version "4.60.4" + resolved "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz#ca3814f5900da3ac3981d2e0c61944b7e6e0cb09" + integrity sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g== + dependencies: + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.60.4" + "@rollup/rollup-android-arm64" "4.60.4" + "@rollup/rollup-darwin-arm64" "4.60.4" + "@rollup/rollup-darwin-x64" "4.60.4" + "@rollup/rollup-freebsd-arm64" "4.60.4" + "@rollup/rollup-freebsd-x64" "4.60.4" + "@rollup/rollup-linux-arm-gnueabihf" "4.60.4" + "@rollup/rollup-linux-arm-musleabihf" "4.60.4" + "@rollup/rollup-linux-arm64-gnu" "4.60.4" + "@rollup/rollup-linux-arm64-musl" "4.60.4" + "@rollup/rollup-linux-loong64-gnu" "4.60.4" + "@rollup/rollup-linux-loong64-musl" "4.60.4" + "@rollup/rollup-linux-ppc64-gnu" "4.60.4" + "@rollup/rollup-linux-ppc64-musl" "4.60.4" + "@rollup/rollup-linux-riscv64-gnu" "4.60.4" + "@rollup/rollup-linux-riscv64-musl" "4.60.4" + "@rollup/rollup-linux-s390x-gnu" "4.60.4" + "@rollup/rollup-linux-x64-gnu" "4.60.4" + "@rollup/rollup-linux-x64-musl" "4.60.4" + "@rollup/rollup-openbsd-x64" "4.60.4" + "@rollup/rollup-openharmony-arm64" "4.60.4" + "@rollup/rollup-win32-arm64-msvc" "4.60.4" + "@rollup/rollup-win32-ia32-msvc" "4.60.4" + "@rollup/rollup-win32-x64-gnu" "4.60.4" + "@rollup/rollup-win32-x64-msvc" "4.60.4" + fsevents "~2.3.2" + +semver@^7.1.2: + version "7.8.0" + resolved "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz#ed0661039fcbcda2ce71f01fa6adbefaa77040df" + integrity sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +strip-json-comments@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tmp@^0.2.1: + version "0.2.5" + resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" + integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== + +tslib@^2.6.2: + version "2.8.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + +typescript@^5.3.3: + version "5.9.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + +uglify-js@^3.7.7: + version "3.19.3" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +underscore@~1.13.2: + version "1.13.8" + resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz#a93a21186c049dbf0e847496dba72b7bd8c1e92b" + integrity sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ== + +"undici-types@>=7.24.0 <7.24.7": + version "7.24.6" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz#61275b485d7fd4e9d269c7cf04ec2873c9cc0f91" + integrity sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg== + +vite@^5.0.12: + version "5.4.21" + resolved "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz#84a4f7c5d860b071676d39ba513c0d598fdc7027" + integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +wavesurfer.js@^7.7.15: + version "7.12.7" + resolved "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.12.7.tgz#31f66428a11013d49817a060481d5f82f58ea1da" + integrity sha512-TIe7hB6OCZysNOZ2cn2NR8Qpko22POWel6rauNcqOammFoH65NYQUM35unNLLMIlUMVYvjJ6w/TTl/G/m+w0nA== + +word-wrap@~1.2.3: + version "1.2.5" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +xmlcreate@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz#0c5ab0f99cdd02a81065fa9cd8f8ae87624889be" + integrity sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg== diff --git a/src/index.html b/src/index.html index 958b026..05329f0 100644 --- a/src/index.html +++ b/src/index.html @@ -14,6 +14,7 @@

Audio tests and demos

  • Microphone Testing
  • Volume Tuning
  • Device Selection Playground
  • +
  • AECDump Web Viewer
  • WebAudio Delay (external)
  • PWA Audio Recorder From 9e36fd2db68a9d031e50cb9ac1ca73ce439d8a03 Mon Sep 17 00:00:00 2001 From: Li-Yu Yu Date: Wed, 20 May 2026 09:19:01 +0000 Subject: [PATCH 2/2] fix: update tsconfig to use bundler moduleResolution - Changed module to esnext and moduleResolution to bundler in tsconfig.json to successfully resolve package exports (like rollup/parseAst imported inside vite typings) in CI/CD runners. --- src/aecdump-viewer/tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aecdump-viewer/tsconfig.json b/src/aecdump-viewer/tsconfig.json index 01fcba4..011a79a 100644 --- a/src/aecdump-viewer/tsconfig.json +++ b/src/aecdump-viewer/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "target": "es2021", - "module": "es2020", - "moduleResolution": "node", + "module": "esnext", + "moduleResolution": "bundler", "noEmitOnError": true, "lib": ["es2021", "dom"], "strict": true,