From 16f17f1da72432a7a76c02e8c045fa2807523ac8 Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 13:37:37 -0400 Subject: [PATCH 01/13] feat(webapp): add session recording log parser package Adds a reusable @devolutions/session-recording-log package for .slog NDJSON parsing, models, ordering, and bounded in-memory search.\n\nIncludes ADR-aligned warning codes and review-driven hardening for large sequence gaps, deep nesting, malformed-heavy input bounds, unknown field caps, and empty-log classification.\n\nUpdates package docs and workspace/publish wiring for consumption.\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/publish-libraries.yml | 4 +- webapp/README.md | 4 + .../packages/session-recording-log/README.md | 92 ++ .../packages/session-recording-log/build.ps1 | 19 + .../session-recording-log/package.dist.json | 30 + .../session-recording-log/package.json | 27 + .../session-recording-log/src/helpers.test.ts | 74 ++ .../session-recording-log/src/index.ts | 21 + .../session-recording-log/src/manifest.ts | 8 + .../session-recording-log/src/model.ts | 109 ++ .../session-recording-log/src/ordering.ts | 14 + .../session-recording-log/src/parser.test.ts | 182 ++++ .../session-recording-log/src/parser.ts | 429 ++++++++ .../session-recording-log/src/search.ts | 111 ++ .../tsconfig.declaration.json | 11 + .../session-recording-log/tsconfig.json | 22 + .../session-recording-log/vite.config.ts | 31 + webapp/pnpm-lock.yaml | 970 +++++++++++++++++- 18 files changed, 2106 insertions(+), 52 deletions(-) create mode 100644 webapp/packages/session-recording-log/README.md create mode 100644 webapp/packages/session-recording-log/build.ps1 create mode 100644 webapp/packages/session-recording-log/package.dist.json create mode 100644 webapp/packages/session-recording-log/package.json create mode 100644 webapp/packages/session-recording-log/src/helpers.test.ts create mode 100644 webapp/packages/session-recording-log/src/index.ts create mode 100644 webapp/packages/session-recording-log/src/manifest.ts create mode 100644 webapp/packages/session-recording-log/src/model.ts create mode 100644 webapp/packages/session-recording-log/src/ordering.ts create mode 100644 webapp/packages/session-recording-log/src/parser.test.ts create mode 100644 webapp/packages/session-recording-log/src/parser.ts create mode 100644 webapp/packages/session-recording-log/src/search.ts create mode 100644 webapp/packages/session-recording-log/tsconfig.declaration.json create mode 100644 webapp/packages/session-recording-log/tsconfig.json create mode 100644 webapp/packages/session-recording-log/vite.config.ts diff --git a/.github/workflows/publish-libraries.yml b/.github/workflows/publish-libraries.yml index 4a2d4a53a..c267366f6 100644 --- a/.github/workflows/publish-libraries.yml +++ b/.github/workflows/publish-libraries.yml @@ -95,7 +95,7 @@ jobs: strategy: fail-fast: false matrix: - library: [ ts-angular-client, multi-video-player, shadow-player, web-recorder ] + library: [ ts-angular-client, multi-video-player, shadow-player, web-recorder, session-recording-log ] include: - library: ts-angular-client libpath: ./devolutions-gateway/openapi/ts-angular-client @@ -105,6 +105,8 @@ jobs: libpath: ./webapp/packages/shadow-player - library: web-recorder libpath: ./webapp/packages/web-recorder + - library: session-recording-log + libpath: ./webapp/packages/session-recording-log steps: - name: Check out ${{ github.repository }} diff --git a/webapp/README.md b/webapp/README.md index ec0ecaafc..5c641d654 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -47,6 +47,10 @@ The workspace is organized into three main categories: - Web component for real-time session streaming - Used by recording-player for live shadowing +**@devolutions/session-recording-log** - Session recording log parser/model package +- Framework-agnostic parser, models, ordering, and in-file search for `.slog` NDJSON +- Intended for host reuse (DVLS, Gateway UI sandbox, legacy recording-player) + #### Tools (`tools/`) **recording-player-tester** - Testing utility diff --git a/webapp/packages/session-recording-log/README.md b/webapp/packages/session-recording-log/README.md new file mode 100644 index 000000000..a28555520 --- /dev/null +++ b/webapp/packages/session-recording-log/README.md @@ -0,0 +1,92 @@ +# @devolutions/session-recording-log + +Framework-agnostic models, parser, ordering, and in-file search for Session Recording Log (`.slog`) files. + +## Host contract + +- Hosts detect `.slog` artifacts from `recording.json` file names (for example `recording-0.slog`). +- Hosts keep original bytes for download scenarios. +- Hosts decode UTF-8 text and pass the decoded NDJSON text to the parser. +- This package does not reconstruct downloadable `.slog` content. + +## ADR-2 canonical contract + +- `parseSessionRecordingLog` returns: + - `entries: ParsedSessionRecordingLogEntry[]` in original file order. + - `warnings: SessionRecordingLogWarning[]` with structured `code`. + - `completionState: 'complete' | 'ended-unexpectedly'`. +- `ParsedSessionRecordingLogEntry` exposes `entry`, `sourceLineNumber`, and `sourceIndex`. +- Public warning codes follow ADR-2 categories: + - `malformed-line` + - `missing-session-start` + - `missing-session-end` + - `duplicate-sequence` + - `missing-sequence` + - `sequence-order-mismatch` + - `unknown-event-type` + - `invalid-field` + - `entry-limit-exceeded` + - `string-truncated` +- Additive warning: + - `unterminated-final-line` (informational; valid final line without newline is not semantically invalid). + - Intended for ADR addendum documentation rather than replacement of canonical malformed handling. + +Schema-limit options: + +- `maxLineLengthBytes` +- `maxStringLength` +- `maxParameterCount` +- `maxObjectDepth` +- `maxParsedEntries` +- `maxScannedLines` +- `maxMissingSequenceWarnings` +- `maxUnknownFieldCount` + +## Review-driven hardening updates (2026-07-17) + +- Added bounded missing-sequence emission to prevent unbounded gap loops on sparse large `seq` ranges. +- Replaced recursive depth traversal with iterative depth checks to prevent stack-overflow failure on deeply nested JSON. +- Added malformed-heavy input bounding with `maxScannedLines`, and finite default `maxParsedEntries`. +- Fixed empty-log lifecycle classification (`completionState: 'complete'` when no valid entries). +- Added top-level unknown field cap (`maxUnknownFieldCount`) with `entry-limit-exceeded` warning when truncated. +- Split unknown future events into explicit `unknown-event-type` warning code. +- Clarified alias intent: `SessionRecordingLogRecord` is retained for AD historical naming compatibility. + +## Ratified v1 defaults (review decision) + +Ratified as v1 canonical defaults for cross-host behavior: + +- `maxLineLengthBytes`: `262144` (256 KiB) +- `maxStringLength`: `4096` +- `maxParameterCount`: `200` +- `maxObjectDepth`: `8` +- `maxParsedEntries`: `10000` +- `maxScannedLines`: `20000` +- `maxMissingSequenceWarnings`: `1000` +- `maxUnknownFieldCount`: `100` +- search default limit: `100` +- search max limit: `1000` + +Callout: these values were elevated from implementation defaults to ratified policy as a direct follow-up to review-driven hardening. + +## Search behavior + +- Searches visible fields only: + - `description` + - `object` + - `parameters` keys and values + - `actor` + - `host` + - `sessionType` + - `timestamp` +- Does not search `sourceText` or `unknownFields`. +- Supports optional filters: + - `eventTypes` for lifecycle filtering + - `warnings` + `onlyWithWarnings` for warning-linked filtering + +## API + +- `parseSessionRecordingLog(text, options?)` +- `getSessionRecordingLogDisplayEntries(parseResult)` +- `searchSessionRecordingLogEntries(entries, query, options?)` +- `isSessionRecordingLogFileName(fileName)` diff --git a/webapp/packages/session-recording-log/build.ps1 b/webapp/packages/session-recording-log/build.ps1 new file mode 100644 index 000000000..26e9012f9 --- /dev/null +++ b/webapp/packages/session-recording-log/build.ps1 @@ -0,0 +1,19 @@ +#!/usr/bin/env pwsh + +$ErrorActionPreference = "Stop" + +Push-Location -Path $PSScriptRoot + +try +{ + pnpm install + + pnpm --filter @devolutions/session-recording-log... build + + Set-Location -Path ./dist/ + npm pack +} +finally +{ + Pop-Location +} diff --git a/webapp/packages/session-recording-log/package.dist.json b/webapp/packages/session-recording-log/package.dist.json new file mode 100644 index 000000000..1c8211b61 --- /dev/null +++ b/webapp/packages/session-recording-log/package.dist.json @@ -0,0 +1,30 @@ +{ + "name": "@devolutions/session-recording-log", + "version": "0.1.0", + "description": "Framework-agnostic parser and models for Devolutions Gateway Session Recording Log NDJSON files", + "type": "module", + "main": "./index.js", + "module": "./index.js", + "types": "./index.d.ts", + "exports": { + ".": { + "import": "./index.js", + "types": "./index.d.ts" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/Devolutions/devolutions-gateway.git" + }, + "keywords": [ + "recording", + "session-recording", + "ndjson", + "slog", + "parser" + ], + "license": "MIT OR Apache-2.0", + "publishConfig": { + "access": "public" + } +} diff --git a/webapp/packages/session-recording-log/package.json b/webapp/packages/session-recording-log/package.json new file mode 100644 index 000000000..79012c7b2 --- /dev/null +++ b/webapp/packages/session-recording-log/package.json @@ -0,0 +1,27 @@ +{ + "name": "@devolutions/session-recording-log", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "build": "tsc && vite build", + "check": "biome check ./src", + "check:write": "biome check --write ./src", + "test": "vitest run" + }, + "devDependencies": { + "typescript": "~5.6.2", + "vite": "8.0.5", + "vite-plugin-dts": "^4.3.0", + "vite-plugin-static-copy": "^2.3.0", + "vitest": "^3.1.1" + } +} diff --git a/webapp/packages/session-recording-log/src/helpers.test.ts b/webapp/packages/session-recording-log/src/helpers.test.ts new file mode 100644 index 000000000..2d16a9401 --- /dev/null +++ b/webapp/packages/session-recording-log/src/helpers.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, it } from 'vitest'; +import { isSessionRecordingLogFileName } from './manifest'; +import type { ParsedSessionRecordingLogEntry, SessionRecordingLogEntry, SessionRecordingLogWarning } from './model'; +import { getSessionRecordingLogDisplayEntries } from './ordering'; +import { searchSessionRecordingLogEntries } from './search'; + +function makeEntry( + sourceIndex: number, + seq: number, + description: string, + overrides: Partial = {}, +): ParsedSessionRecordingLogEntry { + return { + sourceIndex, + sourceLineNumber: sourceIndex + 1, + sourceText: '{}', + entry: { + timestamp: `2026-07-16T10:00:0${seq}.000Z`, + seq, + event: 'session.action', + description, + ...overrides, + }, + }; +} + +describe('helpers', () => { + it('detects slog files by filename extension only', () => { + expect(isSessionRecordingLogFileName('recording-0.slog')).toBe(true); + expect(isSessionRecordingLogFileName('recording-0.SLOG')).toBe(true); + expect(isSessionRecordingLogFileName('recording-0.webm')).toBe(false); + }); + + it('derives display order by seq then sourceIndex without mutating source', () => { + const entries = [makeEntry(2, 2, 'third'), makeEntry(1, 1, 'second'), makeEntry(0, 1, 'first tie')]; + const original = entries.map((entry) => entry.sourceIndex); + + const sorted = getSessionRecordingLogDisplayEntries({ entries }); + + expect(sorted.map((entry) => `${entry.entry.seq}:${entry.sourceIndex}`)).toEqual(['1:0', '1:1', '2:2']); + expect(entries.map((entry) => entry.sourceIndex)).toEqual(original); + }); + + it('searches visible fields only and supports event/warning filters', () => { + const entries = [ + makeEntry(0, 0, 'Renamed Object', { + event: 'session.action', + object: 'Help Desk Ottawa', + actor: 'Administrator', + host: 'IT-HELP-DC', + sessionType: 'ADConsole', + parameters: { + 'Members added': "Sarah O'Connor, Bob Smith", + }, + unknownFields: { + hidden: 'do-not-match', + }, + }), + makeEntry(1, 1, 'Session started', { + event: 'session.start', + }), + ]; + + const warnings: SessionRecordingLogWarning[] = [ + { code: 'invalid-field', sourceLineNumber: 1, seq: 0, message: 'x' }, + ]; + + expect(searchSessionRecordingLogEntries(entries, "o'connor")).toHaveLength(1); + expect(searchSessionRecordingLogEntries(entries, 'adconsole')).toHaveLength(1); + expect(searchSessionRecordingLogEntries(entries, 'do-not-match')).toHaveLength(0); + expect(searchSessionRecordingLogEntries(entries, 'session', { eventTypes: ['session.start'] })).toHaveLength(1); + expect(searchSessionRecordingLogEntries(entries, '', { onlyWithWarnings: true, warnings })).toHaveLength(1); + }); +}); diff --git a/webapp/packages/session-recording-log/src/index.ts b/webapp/packages/session-recording-log/src/index.ts new file mode 100644 index 000000000..fd98b7d42 --- /dev/null +++ b/webapp/packages/session-recording-log/src/index.ts @@ -0,0 +1,21 @@ +export { isSessionRecordingLogFileName } from './manifest'; +export type { + ParsedSessionRecordingLog, + ParsedSessionRecordingLogEntry, + ParseSessionRecordingLogOptions, + SearchableSessionRecordingLogField, + SearchSessionRecordingLogOptions, + SessionRecordingArtifact, + SessionRecordingArtifactContent, + SessionRecordingLogCompletionState, + SessionRecordingLogEntry, + SessionRecordingLogKnownEvent, + SessionRecordingLogParseResult, + SessionRecordingLogRecord, + SessionRecordingLogSearchHit, + SessionRecordingLogWarning, + SessionRecordingLogWarningCode, +} from './model'; +export { getSessionRecordingLogDisplayEntries } from './ordering'; +export { parseSessionRecordingLog } from './parser'; +export { searchSessionRecordingLogEntries } from './search'; diff --git a/webapp/packages/session-recording-log/src/manifest.ts b/webapp/packages/session-recording-log/src/manifest.ts new file mode 100644 index 000000000..3c72a81f1 --- /dev/null +++ b/webapp/packages/session-recording-log/src/manifest.ts @@ -0,0 +1,8 @@ +export function isSessionRecordingLogFileName(fileName: string): boolean { + if (!fileName) { + return false; + } + + const lowerCased = fileName.toLowerCase(); + return lowerCased.endsWith('.slog'); +} diff --git a/webapp/packages/session-recording-log/src/model.ts b/webapp/packages/session-recording-log/src/model.ts new file mode 100644 index 000000000..7e7a3291b --- /dev/null +++ b/webapp/packages/session-recording-log/src/model.ts @@ -0,0 +1,109 @@ +export type SessionRecordingLogKnownEvent = 'session.start' | 'session.action' | 'session.end'; + +export interface SessionRecordingLogEntry { + timestamp: string; + seq: number; + event: string; + description: string; + actor?: string; + locale?: string; + host?: string; + sessionType?: string; + object?: string; + parameters?: Record; + unknownFields?: Record; +} + +export interface ParsedSessionRecordingLogEntry { + entry: SessionRecordingLogEntry; + sourceLineNumber: number; + sourceIndex: number; + sourceText: string; +} + +export type SessionRecordingLogWarningCode = + | 'malformed-line' + | 'missing-session-start' + | 'missing-session-end' + | 'duplicate-sequence' + | 'missing-sequence' + | 'sequence-order-mismatch' + | 'unknown-event-type' + | 'invalid-field' + | 'entry-limit-exceeded' + | 'string-truncated' + | 'unterminated-final-line'; + +export interface SessionRecordingLogWarning { + code: SessionRecordingLogWarningCode; + sourceLineNumber?: number; + seq?: number; + message: string; +} + +export interface SessionRecordingLogParseResult { + entries: ParsedSessionRecordingLogEntry[]; + warnings: SessionRecordingLogWarning[]; + completionState: 'complete' | 'ended-unexpectedly'; +} + +export type SessionRecordingLogCompletionState = SessionRecordingLogParseResult['completionState']; + +export interface ParseSessionRecordingLogOptions { + warnOnInvalidTimestamp?: boolean; + maxLineLengthBytes?: number; + maxStringLength?: number; + maxParameterCount?: number; + maxObjectDepth?: number; + maxParsedEntries?: number; + maxScannedLines?: number; + maxMissingSequenceWarnings?: number; + maxUnknownFieldCount?: number; +} + +export interface SearchSessionRecordingLogOptions { + limit?: number; + caseSensitive?: boolean; + eventTypes?: SessionRecordingLogKnownEvent[]; + warnings?: SessionRecordingLogWarning[]; + onlyWithWarnings?: boolean; +} + +export type SearchableSessionRecordingLogField = + | 'timestamp' + | 'description' + | 'object' + | 'actor' + | 'host' + | 'sessionType' + | 'parameter-key' + | 'parameter-value'; + +export interface SessionRecordingLogSearchHit { + entry: ParsedSessionRecordingLogEntry; + sourceIndex: number; + sourceLineNumber: number; + matchedFields: SearchableSessionRecordingLogField[]; +} + +export interface SessionRecordingArtifact { + recordingId: string; + artifactId: string; + fileName: string; + /** Derived from fileName extension, not read from recording.json. */ + artifactType: string; + contentType?: string; + displayName: string; + startTime?: number; + duration?: number; +} + +export interface SessionRecordingArtifactContent { + artifact: SessionRecordingArtifact; + blob: Blob; + text: string; +} + +// Alias preserved to match AD producer historical naming. +export type SessionRecordingLogRecord = SessionRecordingLogEntry; +export type ParsedSessionRecordingLog = SessionRecordingLogParseResult; diff --git a/webapp/packages/session-recording-log/src/ordering.ts b/webapp/packages/session-recording-log/src/ordering.ts new file mode 100644 index 000000000..e83847e82 --- /dev/null +++ b/webapp/packages/session-recording-log/src/ordering.ts @@ -0,0 +1,14 @@ +import type { ParsedSessionRecordingLogEntry, SessionRecordingLogParseResult } from './model'; + +export function getSessionRecordingLogDisplayEntries( + parseResult: Pick, +): ParsedSessionRecordingLogEntry[] { + return [...parseResult.entries].sort((left, right) => { + const bySeq = left.entry.seq - right.entry.seq; + if (bySeq !== 0) { + return bySeq; + } + + return left.sourceIndex - right.sourceIndex; + }); +} diff --git a/webapp/packages/session-recording-log/src/parser.test.ts b/webapp/packages/session-recording-log/src/parser.test.ts new file mode 100644 index 000000000..b3d4e7c65 --- /dev/null +++ b/webapp/packages/session-recording-log/src/parser.test.ts @@ -0,0 +1,182 @@ +import { describe, expect, it } from 'vitest'; +import { parseSessionRecordingLog } from './parser'; + +describe('parseSessionRecordingLog', () => { + it('parses completed AD-like slog and preserves source metadata', () => { + const text = [ + '{"timestamp":"2026-07-15T16:15:35.140Z","seq":0,"actor":"Administrator","locale":"en-US","host":"IT-HELP-DC","sessionType":"ADConsole","event":"session.start","description":"Session started"}', + '{"timestamp":"2026-07-15T16:15:41.054Z","seq":1,"event":"session.action","description":"Created User","object":"asdasdasd"}', + '{"timestamp":"2026-07-15T16:16:32.759Z","seq":2,"event":"session.end","description":"Session ended"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text); + + expect(result.completionState).toBe('complete'); + expect(result.entries).toHaveLength(3); + expect(result.entries[0].sourceLineNumber).toBe(1); + expect(result.entries[1].sourceLineNumber).toBe(2); + expect(result.entries[0].entry.sessionType).toBe('ADConsole'); + }); + + it('marks missing session.end as ended unexpectedly while keeping valid entries', () => { + const text = [ + '{"timestamp":"2026-07-15T21:17:49.777Z","seq":0,"actor":"Administrator","host":"IT-HELP-DC","sessionType":"ADConsole","event":"session.start","description":"Session started"}', + '{"timestamp":"2026-07-15T21:19:14.351Z","seq":1,"event":"session.action","description":"Renamed Object","object":"Help Desk Ottawa","parameters":{"New name":"Help Desk Ottawa/Hull"}}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text); + + expect(result.entries).toHaveLength(2); + expect(result.completionState).toBe('ended-unexpectedly'); + expect(result.warnings.some((warning) => warning.code === 'missing-session-end')).toBe(true); + }); + + it('maps malformed/invalid conditions to canonical warning codes', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start","producer":"custom-host"}', + '{not valid json}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":2,"event":"session.action","description":"Action"}', + '{"timestamp":"not-a-date","seq":2,"event":"session.unknown","description":"Future event"}', + '{"timestamp":"2026-07-16T10:00:03.000Z","seq":4,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text); + + expect(result.entries).toHaveLength(4); + expect(result.warnings.some((warning) => warning.code === 'malformed-line')).toBe(true); + expect(result.warnings.some((warning) => warning.code === 'invalid-field')).toBe(true); + expect(result.warnings.some((warning) => warning.code === 'duplicate-sequence')).toBe(true); + expect(result.warnings.some((warning) => warning.code === 'missing-sequence')).toBe(true); + expect(result.warnings.some((warning) => warning.code === 'unknown-event-type')).toBe(true); + expect(result.warnings.some((warning) => warning.code === 'sequence-order-mismatch')).toBe(false); + }); + + it('reports unterminated final line as additive informational warning', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start"}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"session.end","description":"End"}', + ].join('\n'); + + const result = parseSessionRecordingLog(text); + expect(result.warnings.some((warning) => warning.code === 'unterminated-final-line')).toBe(true); + }); + + it('applies schema limits using canonical warning categories', () => { + const deepObject = + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start","x":{"y":{"z":{"k":1}}}}'; + const hugeLine = `{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"session.action","description":"${'a'.repeat(600)}"}`; + const tooManyParameters = + '{"timestamp":"2026-07-16T10:00:02.000Z","seq":2,"event":"session.action","description":"Action","parameters":{"a":"1","b":"2","c":"3"}}'; + const endLine = '{"timestamp":"2026-07-16T10:00:03.000Z","seq":3,"event":"session.end","description":"End"}'; + const text = [deepObject, hugeLine, tooManyParameters, endLine, ''].join('\n'); + + const result = parseSessionRecordingLog(text, { + maxLineLengthBytes: 2000, + maxObjectDepth: 3, + maxParameterCount: 2, + maxStringLength: 64, + }); + + expect(result.warnings.some((warning) => warning.code === 'entry-limit-exceeded')).toBe(true); + expect(result.warnings.some((warning) => warning.code === 'invalid-field')).toBe(true); + expect(result.warnings.some((warning) => warning.code === 'string-truncated')).toBe(true); + expect(result.entries.some((entry) => entry.entry.event === 'session.end')).toBe(true); + }); + + it('caps missing sequence warnings for very large sequence gaps', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start"}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1000000000,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text, { + maxMissingSequenceWarnings: 3, + }); + + expect(result.entries).toHaveLength(2); + expect(result.warnings.filter((warning) => warning.code === 'missing-sequence')).toHaveLength(3); + expect( + result.warnings.some( + (warning) => + warning.code === 'entry-limit-exceeded' && warning.message.startsWith('missing sequence warnings truncated'), + ), + ).toBe(true); + }); + + it('handles deeply nested records without crashing parse flow', () => { + const depth = 1000; + let nested = '1'; + for (let index = 0; index < depth; index += 1) { + nested = `{"k":${nested}}`; + } + + const text = [ + `{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start","deep":${nested}}`, + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text, { maxObjectDepth: 8 }); + + expect(result.warnings.some((warning) => warning.code === 'invalid-field')).toBe(true); + expect(result.entries).toHaveLength(1); + expect(result.entries[0].entry.event).toBe('session.end'); + }); + + it('stops scanning after configured non-empty line limit even when lines are malformed', () => { + const malformed = Array.from({ length: 20 }, () => '{not valid json}'); + const text = `${malformed.join('\n')}\n`; + + const result = parseSessionRecordingLog(text, { maxScannedLines: 5 }); + + expect(result.entries).toHaveLength(0); + expect(result.warnings.some((warning) => warning.code === 'entry-limit-exceeded')).toBe(true); + }); + + it('treats an empty file as complete and emits no missing-session warnings', () => { + const result = parseSessionRecordingLog(''); + + expect(result.completionState).toBe('complete'); + expect(result.warnings.some((warning) => warning.code === 'missing-session-start')).toBe(false); + expect(result.warnings.some((warning) => warning.code === 'missing-session-end')).toBe(false); + }); + + it('emits unknown-event-type warning for unrecognized events', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start"}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"session.future","description":"Future"}', + '{"timestamp":"2026-07-16T10:00:02.000Z","seq":2,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text); + + expect(result.entries).toHaveLength(3); + expect(result.warnings.some((warning) => warning.code === 'unknown-event-type')).toBe(true); + }); + + it('caps unknown top-level field collection per record', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start","a":1,"b":2,"c":3}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text, { maxUnknownFieldCount: 2 }); + const startEntry = result.entries.find((entry) => entry.entry.event === 'session.start'); + + expect(startEntry).toBeDefined(); + expect(Object.keys(startEntry?.entry.unknownFields ?? {})).toHaveLength(2); + expect( + result.warnings.some( + (warning) => + warning.code === 'entry-limit-exceeded' && + warning.message === 'unknown top-level field count exceeded max limit', + ), + ).toBe(true); + }); +}); diff --git a/webapp/packages/session-recording-log/src/parser.ts b/webapp/packages/session-recording-log/src/parser.ts new file mode 100644 index 000000000..7d92a6032 --- /dev/null +++ b/webapp/packages/session-recording-log/src/parser.ts @@ -0,0 +1,429 @@ +import type { + ParsedSessionRecordingLogEntry, + ParseSessionRecordingLogOptions, + SessionRecordingLogEntry, + SessionRecordingLogKnownEvent, + SessionRecordingLogParseResult, + SessionRecordingLogWarning, + SessionRecordingLogWarningCode, +} from './model'; + +const KNOWN_EVENTS = new Set(['session.start', 'session.action', 'session.end']); +const KNOWN_FIELDS = new Set([ + 'timestamp', + 'seq', + 'event', + 'description', + 'actor', + 'locale', + 'host', + 'sessionType', + 'object', + 'parameters', +]); + +const DEFAULT_MAX_LINE_LENGTH_BYTES = 256 * 1024; +const DEFAULT_MAX_STRING_LENGTH = 4096; +const DEFAULT_MAX_PARAMETER_COUNT = 200; +const DEFAULT_MAX_OBJECT_DEPTH = 8; +const DEFAULT_MAX_PARSED_ENTRIES = 10_000; +const DEFAULT_MAX_SCANNED_LINES = 20_000; +const DEFAULT_MAX_MISSING_SEQUENCE_WARNINGS = 1_000; +const DEFAULT_MAX_UNKNOWN_FIELD_COUNT = 100; +const textEncoder = new TextEncoder(); + +function createWarning( + code: SessionRecordingLogWarningCode, + message: string, + details?: Pick, +): SessionRecordingLogWarning { + return { + code, + message, + ...details, + }; +} + +function isPlainObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function exceedsMaxObjectDepth(value: unknown, maxObjectDepth: number): boolean { + const stack: Array<{ value: unknown; depth: number }> = [{ value, depth: 1 }]; + + while (stack.length > 0) { + const current = stack.pop(); + if (!current) { + continue; + } + + if (current.depth > maxObjectDepth) { + return true; + } + + if (Array.isArray(current.value)) { + for (const item of current.value) { + if (isPlainObject(item) || Array.isArray(item)) { + stack.push({ value: item, depth: current.depth + 1 }); + } + } + continue; + } + + if (isPlainObject(current.value)) { + for (const item of Object.values(current.value)) { + if (isPlainObject(item) || Array.isArray(item)) { + stack.push({ value: item, depth: current.depth + 1 }); + } + } + } + } + + return false; +} + +function normalizeString( + value: unknown, + field: string, + warnings: SessionRecordingLogWarning[], + sourceLineNumber: number, + maxStringLength: number, +): string | null { + if (typeof value !== 'string') { + warnings.push(createWarning('invalid-field', `${field} must be a string`, { sourceLineNumber })); + return null; + } + + if (value.length > maxStringLength) { + warnings.push( + createWarning('string-truncated', `${field} exceeded max string length and was truncated`, { sourceLineNumber }), + ); + return value.slice(0, maxStringLength); + } + + return value; +} + +function parseParameters( + value: unknown, + warnings: SessionRecordingLogWarning[], + sourceLineNumber: number, + maxStringLength: number, + maxParameterCount: number, +): Record | undefined { + if (value === undefined) { + return undefined; + } + + if (!isPlainObject(value)) { + warnings.push(createWarning('invalid-field', 'parameters must be an object', { sourceLineNumber })); + return undefined; + } + + const entries = Object.entries(value); + if (entries.length > maxParameterCount) { + warnings.push(createWarning('entry-limit-exceeded', 'parameter count exceeded max limit', { sourceLineNumber })); + } + + const output: Record = {}; + for (const [rawKey, rawValue] of entries.slice(0, maxParameterCount)) { + let key = rawKey; + if (rawKey.length > maxStringLength) { + warnings.push( + createWarning('string-truncated', 'parameter key exceeded max string length and was truncated', { + sourceLineNumber, + }), + ); + key = rawKey.slice(0, maxStringLength); + } + + if (typeof rawValue !== 'string') { + warnings.push(createWarning('invalid-field', `parameter ${key} must be a string`, { sourceLineNumber })); + continue; + } + + if (rawValue.length > maxStringLength) { + warnings.push( + createWarning('string-truncated', `parameter ${key} exceeded max string length and was truncated`, { + sourceLineNumber, + }), + ); + output[key] = rawValue.slice(0, maxStringLength); + continue; + } + + output[key] = rawValue; + } + + return output; +} + +function parseLineRecord( + parsed: Record, + warnings: SessionRecordingLogWarning[], + sourceLineNumber: number, + options: Required< + Pick< + ParseSessionRecordingLogOptions, + 'maxStringLength' | 'maxParameterCount' | 'warnOnInvalidTimestamp' | 'maxUnknownFieldCount' + > + >, +): SessionRecordingLogEntry | null { + const timestamp = normalizeString(parsed.timestamp, 'timestamp', warnings, sourceLineNumber, options.maxStringLength); + const description = normalizeString( + parsed.description, + 'description', + warnings, + sourceLineNumber, + options.maxStringLength, + ); + const event = normalizeString(parsed.event, 'event', warnings, sourceLineNumber, options.maxStringLength); + const seq = parsed.seq; + + if (timestamp === null || description === null || event === null) { + return null; + } + + if (typeof seq !== 'number' || !Number.isInteger(seq) || seq < 0) { + warnings.push(createWarning('invalid-field', 'seq must be a non-negative integer', { sourceLineNumber })); + return null; + } + + if (options.warnOnInvalidTimestamp && Number.isNaN(Date.parse(timestamp))) { + warnings.push(createWarning('invalid-field', 'timestamp is not a valid date string', { sourceLineNumber, seq })); + } + + if (!KNOWN_EVENTS.has(event as SessionRecordingLogKnownEvent)) { + warnings.push( + createWarning('unknown-event-type', 'event is not one of the known lifecycle events', { sourceLineNumber, seq }), + ); + } + + const actor = + parsed.actor === undefined + ? undefined + : normalizeString(parsed.actor, 'actor', warnings, sourceLineNumber, options.maxStringLength); + const locale = + parsed.locale === undefined + ? undefined + : normalizeString(parsed.locale, 'locale', warnings, sourceLineNumber, options.maxStringLength); + const host = + parsed.host === undefined + ? undefined + : normalizeString(parsed.host, 'host', warnings, sourceLineNumber, options.maxStringLength); + const sessionType = + parsed.sessionType === undefined + ? undefined + : normalizeString(parsed.sessionType, 'sessionType', warnings, sourceLineNumber, options.maxStringLength); + const object = + parsed.object === undefined + ? undefined + : normalizeString(parsed.object, 'object', warnings, sourceLineNumber, options.maxStringLength); + const parameters = parseParameters( + parsed.parameters, + warnings, + sourceLineNumber, + options.maxStringLength, + options.maxParameterCount, + ); + + const unknownEntries = Object.entries(parsed).filter(([key]) => !KNOWN_FIELDS.has(key)); + if (unknownEntries.length > options.maxUnknownFieldCount) { + warnings.push( + createWarning('entry-limit-exceeded', 'unknown top-level field count exceeded max limit', { sourceLineNumber }), + ); + } + + const unknownFields: Record = {}; + for (const [key, value] of unknownEntries.slice(0, options.maxUnknownFieldCount)) { + unknownFields[key] = value; + } + + return { + timestamp, + seq, + event, + description, + ...(actor === null || actor === undefined ? {} : { actor }), + ...(locale === null || locale === undefined ? {} : { locale }), + ...(host === null || host === undefined ? {} : { host }), + ...(sessionType === null || sessionType === undefined ? {} : { sessionType }), + ...(object === null || object === undefined ? {} : { object }), + ...(parameters ? { parameters } : {}), + ...(Object.keys(unknownFields).length === 0 ? {} : { unknownFields }), + }; +} + +export function parseSessionRecordingLog( + text: string, + options?: ParseSessionRecordingLogOptions, +): SessionRecordingLogParseResult { + const warnings: SessionRecordingLogWarning[] = []; + const entries: ParsedSessionRecordingLogEntry[] = []; + const maxLineLengthBytes = options?.maxLineLengthBytes ?? DEFAULT_MAX_LINE_LENGTH_BYTES; + const maxStringLength = options?.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH; + const maxParameterCount = options?.maxParameterCount ?? DEFAULT_MAX_PARAMETER_COUNT; + const maxObjectDepth = options?.maxObjectDepth ?? DEFAULT_MAX_OBJECT_DEPTH; + const maxParsedEntries = options?.maxParsedEntries ?? DEFAULT_MAX_PARSED_ENTRIES; + const maxScannedLines = options?.maxScannedLines ?? DEFAULT_MAX_SCANNED_LINES; + const maxMissingSequenceWarnings = options?.maxMissingSequenceWarnings ?? DEFAULT_MAX_MISSING_SEQUENCE_WARNINGS; + const maxUnknownFieldCount = options?.maxUnknownFieldCount ?? DEFAULT_MAX_UNKNOWN_FIELD_COUNT; + const warnOnInvalidTimestamp = options?.warnOnInvalidTimestamp ?? true; + let scannedNonEmptyLines = 0; + + const lines = text.split(/\r?\n/); + const hasTrailingNewline = text.endsWith('\n'); + if (!hasTrailingNewline && text.length > 0) { + warnings.push( + createWarning('unterminated-final-line', 'final line does not end with newline', { + sourceLineNumber: lines.length, + }), + ); + } + + for (const [lineIndex, line] of lines.entries()) { + const sourceLineNumber = lineIndex + 1; + if (hasTrailingNewline && lineIndex === lines.length - 1 && line.length === 0) { + continue; + } + + if (line.trim().length === 0) { + continue; + } + + scannedNonEmptyLines += 1; + if (scannedNonEmptyLines > maxScannedLines) { + warnings.push(createWarning('entry-limit-exceeded', 'scanned line limit exceeded', { sourceLineNumber })); + break; + } + + if (entries.length >= maxParsedEntries) { + warnings.push(createWarning('entry-limit-exceeded', 'parsed entry limit exceeded', { sourceLineNumber })); + break; + } + + if (textEncoder.encode(line).length > maxLineLengthBytes) { + warnings.push(createWarning('entry-limit-exceeded', 'line exceeds max byte size', { sourceLineNumber })); + continue; + } + + let parsed: unknown; + try { + parsed = JSON.parse(line); + } catch { + warnings.push(createWarning('malformed-line', 'line is not valid JSON', { sourceLineNumber })); + continue; + } + + if (!isPlainObject(parsed)) { + warnings.push(createWarning('malformed-line', 'line JSON must be an object', { sourceLineNumber })); + continue; + } + + if (exceedsMaxObjectDepth(parsed, maxObjectDepth)) { + warnings.push(createWarning('invalid-field', 'line object exceeds max depth', { sourceLineNumber })); + continue; + } + + const record = parseLineRecord(parsed, warnings, sourceLineNumber, { + maxStringLength, + maxParameterCount, + maxUnknownFieldCount, + warnOnInvalidTimestamp, + }); + if (!record) { + continue; + } + + entries.push({ + entry: record, + sourceLineNumber, + sourceIndex: entries.length, + sourceText: line, + }); + } + + const seenSequences = new Set(); + let previousSeq = -1; + + for (const parsedEntry of entries) { + const seq = parsedEntry.entry.seq; + + if (seenSequences.has(seq)) { + warnings.push( + createWarning('duplicate-sequence', 'sequence number is duplicated', { + sourceLineNumber: parsedEntry.sourceLineNumber, + seq, + }), + ); + } else { + seenSequences.add(seq); + } + + if (seq < previousSeq) { + warnings.push( + createWarning('sequence-order-mismatch', 'file order does not match sequence order', { + sourceLineNumber: parsedEntry.sourceLineNumber, + seq, + }), + ); + } + previousSeq = seq; + } + + if (seenSequences.size > 1) { + const sortedSeq = Array.from(seenSequences).sort((left, right) => left - right); + let missingTotal = 0; + + for (let index = 1; index < sortedSeq.length; index += 1) { + const gap = sortedSeq[index] - sortedSeq[index - 1] - 1; + if (gap > 0) { + missingTotal += gap; + } + } + + let emittedMissingWarnings = 0; + if (missingTotal > 0 && maxMissingSequenceWarnings > 0) { + for (let index = 1; index < sortedSeq.length; index += 1) { + const start = sortedSeq[index - 1] + 1; + const end = sortedSeq[index]; + for (let seq = start; seq < end; seq += 1) { + warnings.push(createWarning('missing-sequence', 'sequence number is missing', { seq })); + emittedMissingWarnings += 1; + if (emittedMissingWarnings >= maxMissingSequenceWarnings) { + break; + } + } + + if (emittedMissingWarnings >= maxMissingSequenceWarnings) { + break; + } + } + } + + if (missingTotal > emittedMissingWarnings) { + warnings.push( + createWarning( + 'entry-limit-exceeded', + `missing sequence warnings truncated: ${missingTotal - emittedMissingWarnings} additional gaps`, + ), + ); + } + } + + const hasSessionStart = entries.some((parsedEntry) => parsedEntry.entry.event === 'session.start'); + const hasSessionEnd = entries.some((parsedEntry) => parsedEntry.entry.event === 'session.end'); + + if (entries.length > 0 && !hasSessionStart) { + warnings.push(createWarning('missing-session-start', 'session.start is missing')); + } + if (entries.length > 0 && !hasSessionEnd) { + warnings.push(createWarning('missing-session-end', 'session.end is missing')); + } + + return { + entries, + warnings, + completionState: entries.length === 0 || hasSessionEnd ? 'complete' : 'ended-unexpectedly', + }; +} diff --git a/webapp/packages/session-recording-log/src/search.ts b/webapp/packages/session-recording-log/src/search.ts new file mode 100644 index 000000000..e15cd7767 --- /dev/null +++ b/webapp/packages/session-recording-log/src/search.ts @@ -0,0 +1,111 @@ +import type { + ParsedSessionRecordingLogEntry, + SearchableSessionRecordingLogField, + SearchSessionRecordingLogOptions, + SessionRecordingLogSearchHit, +} from './model'; + +const DEFAULT_SEARCH_LIMIT = 100; +const MAX_SEARCH_LIMIT = 1000; + +function normalizeLimit(limit: number | undefined): number { + if (!Number.isFinite(limit)) { + return DEFAULT_SEARCH_LIMIT; + } + + return Math.max(1, Math.min(MAX_SEARCH_LIMIT, Math.trunc(limit ?? DEFAULT_SEARCH_LIMIT))); +} + +function maybeNormalize(value: string, caseSensitive: boolean): string { + return caseSensitive ? value : value.toLowerCase(); +} + +function hasWarningAssociation( + entry: ParsedSessionRecordingLogEntry, + warnings: SearchSessionRecordingLogOptions['warnings'], +): boolean { + if (!warnings || warnings.length === 0) { + return false; + } + + return warnings.some( + (warning) => + warning.sourceLineNumber === entry.sourceLineNumber || + (warning.seq !== undefined && warning.seq === entry.entry.seq), + ); +} + +function tryMatchField( + value: string | undefined, + query: string, + caseSensitive: boolean, + field: SearchableSessionRecordingLogField, + matchedFields: Set, +): void { + if (!value) { + return; + } + + const candidate = maybeNormalize(value, caseSensitive); + if (candidate.includes(query)) { + matchedFields.add(field); + } +} + +export function searchSessionRecordingLogEntries( + entries: ParsedSessionRecordingLogEntry[], + query: string, + options?: SearchSessionRecordingLogOptions, +): SessionRecordingLogSearchHit[] { + const trimmed = query.trim(); + const limit = normalizeLimit(options?.limit); + const caseSensitive = options?.caseSensitive ?? false; + const normalizedQuery = maybeNormalize(trimmed, caseSensitive); + const eventTypes = options?.eventTypes; + const onlyWithWarnings = options?.onlyWithWarnings ?? false; + const hits: SessionRecordingLogSearchHit[] = []; + + for (const parsedEntry of entries) { + if (eventTypes && eventTypes.length > 0 && !eventTypes.includes(parsedEntry.entry.event as never)) { + continue; + } + + if (onlyWithWarnings && !hasWarningAssociation(parsedEntry, options?.warnings)) { + continue; + } + + const matchedFields = new Set(); + const entry = parsedEntry.entry; + if (normalizedQuery.length > 0) { + tryMatchField(entry.timestamp, normalizedQuery, caseSensitive, 'timestamp', matchedFields); + tryMatchField(entry.description, normalizedQuery, caseSensitive, 'description', matchedFields); + tryMatchField(entry.object, normalizedQuery, caseSensitive, 'object', matchedFields); + tryMatchField(entry.actor, normalizedQuery, caseSensitive, 'actor', matchedFields); + tryMatchField(entry.host, normalizedQuery, caseSensitive, 'host', matchedFields); + tryMatchField(entry.sessionType, normalizedQuery, caseSensitive, 'sessionType', matchedFields); + + const parameters = entry.parameters ?? {}; + for (const [key, value] of Object.entries(parameters)) { + tryMatchField(key, normalizedQuery, caseSensitive, 'parameter-key', matchedFields); + tryMatchField(value, normalizedQuery, caseSensitive, 'parameter-value', matchedFields); + } + } + + if (normalizedQuery.length > 0 && matchedFields.size === 0) { + continue; + } + + hits.push({ + entry: parsedEntry, + sourceIndex: parsedEntry.sourceIndex, + sourceLineNumber: parsedEntry.sourceLineNumber, + matchedFields: [...matchedFields], + }); + + if (hits.length >= limit) { + break; + } + } + + return hits; +} diff --git a/webapp/packages/session-recording-log/tsconfig.declaration.json b/webapp/packages/session-recording-log/tsconfig.declaration.json new file mode 100644 index 000000000..c28817bc3 --- /dev/null +++ b/webapp/packages/session-recording-log/tsconfig.declaration.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowImportingTsExtensions": false, + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "rootDir": "src", + "outDir": "dist" + } +} diff --git a/webapp/packages/session-recording-log/tsconfig.json b/webapp/packages/session-recording-log/tsconfig.json new file mode 100644 index 000000000..eda521452 --- /dev/null +++ b/webapp/packages/session-recording-log/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "exclude": ["dist", "node_modules"] +} diff --git a/webapp/packages/session-recording-log/vite.config.ts b/webapp/packages/session-recording-log/vite.config.ts new file mode 100644 index 000000000..e5ddeafbb --- /dev/null +++ b/webapp/packages/session-recording-log/vite.config.ts @@ -0,0 +1,31 @@ +import path from 'node:path'; +import {defineConfig} from 'vite'; +import dts from 'vite-plugin-dts'; +import {viteStaticCopy} from 'vite-plugin-static-copy'; + +const staticCopyPlugin = viteStaticCopy({ + targets: [ + { + src: './package.dist.json', + dest: './', + rename: 'package.json', + }, + ], +}); + +export default defineConfig({ + build: { + lib: { + entry: path.resolve(__dirname, 'src/index.ts'), + name: 'SessionRecordingLog', + fileName: 'index', + formats: ['es'], + }, + rollupOptions: { + output: { + globals: {}, + }, + } + }, + plugins: [dts({tsconfigPath: './tsconfig.declaration.json'}), staticCopyPlugin], +}); diff --git a/webapp/pnpm-lock.yaml b/webapp/pnpm-lock.yaml index 76c231983..7e2f0f90e 100644 --- a/webapp/pnpm-lock.yaml +++ b/webapp/pnpm-lock.yaml @@ -138,7 +138,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: 20.3.21 - version: 20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.19.27)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.30.2)(tailwindcss@4.1.18)(typescript@5.9.3) + version: 20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.19.27)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(tailwindcss@4.1.18)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) '@angular-devkit/core': specifier: 20.3.21 version: 20.3.21(chokidar@4.0.3) @@ -190,14 +190,14 @@ importers: version: 24.2.3(typescript@5.6.3) vite-plugin-static-copy: specifier: ^2.3.0 - version: 2.3.2(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 2.3.2(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) devDependencies: typescript: specifier: ~5.6.2 version: 5.6.3 vite: specifier: ^6.0.1 - version: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + version: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) packages/multi-video-player: dependencies: @@ -234,16 +234,34 @@ importers: version: 5.7.3 vite: specifier: ^6.2.0 - version: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + version: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) vite-plugin-bundle-css: specifier: ^0.1.1 - version: 0.1.1(rollup@4.59.0)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 0.1.1(rollup@4.59.0)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) vite-plugin-dts: specifier: ^4.5.3 - version: 4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.7.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.7.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) vite-plugin-static-copy: specifier: ^2.3.0 - version: 2.3.2(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 2.3.2(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + + packages/session-recording-log: + devDependencies: + typescript: + specifier: ~5.6.2 + version: 5.6.3 + vite: + specifier: 8.0.5 + version: 8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1) + vite-plugin-dts: + specifier: ^4.3.0 + version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)) + vite-plugin-static-copy: + specifier: ^2.3.0 + version: 2.3.2(vite@8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)) + vitest: + specifier: ^3.1.1 + version: 3.2.7(@types/node@22.19.3)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) packages/shadow-player: devDependencies: @@ -255,13 +273,13 @@ importers: version: 5.6.3 vite: specifier: ^5.4.9 - version: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + version: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) vite-plugin-dts: specifier: ^4.3.0 - version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) vite-plugin-static-copy: specifier: ^2.3.0 - version: 2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) packages/web-recorder: devDependencies: @@ -270,19 +288,19 @@ importers: version: 5.6.3 vite: specifier: ^5.4.9 - version: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + version: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) vite-plugin-dts: specifier: ^4.3.0 - version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) vite-plugin-static-copy: specifier: ^2.3.0 - version: 2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) tools/recording-player-tester: dependencies: '@tailwindcss/vite': specifier: ^4.0.13 - version: 4.1.18(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 4.1.18(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) express: specifier: ^4.21.0 version: 4.22.1 @@ -301,7 +319,7 @@ importers: version: 19.2.3(@types/react@19.2.7) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) globals: specifier: ^16.0.0 version: 16.5.0 @@ -313,7 +331,7 @@ importers: version: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3) vite: specifier: ^6.2.1 - version: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + version: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) packages: @@ -1386,6 +1404,15 @@ packages: resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} engines: {node: '>=14.17.0'} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} @@ -2411,6 +2438,12 @@ packages: resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} engines: {node: '>= 10'} + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@ngtools/webpack@20.3.21': resolution: {integrity: sha512-qO6tit1mm4bGTMT1ZL6RTQMiQvck1aQymTiuUSa9lYg5ZRGL/v9WhKVx9+ncf9yrq2kj28LEAVJcAtrEEd/YJA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -2468,6 +2501,9 @@ packages: resolution: {integrity: sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==} engines: {node: ^20.17.0 || >=22.9.0} + '@oxc-project/types@0.122.0': + resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -2574,9 +2610,107 @@ packages: resolution: {integrity: sha512-pmEbSfP0Phf9W9RweiM66zXnkn73ZeKyYINElbX3uZ2+stzzaba2svLAl3B1pHVcRw5t43O0VciaGe4ye2EXKw==} engines: {node: '>=12.11.0'} + '@rolldown/binding-android-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': + resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + '@rolldown/pluginutils@1.0.0-rc.12': + resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -3030,6 +3164,9 @@ packages: resolution: {integrity: sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==} engines: {node: ^20.17.0 || >=22.9.0} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} @@ -3051,12 +3188,18 @@ packages: '@types/bonjour@3.5.13': resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect-history-api-fallback@1.5.4': resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -3257,6 +3400,35 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/expect@3.2.7': + resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==} + + '@vitest/mocker@3.2.7': + resolution: {integrity: sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.7': + resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==} + + '@vitest/runner@3.2.7': + resolution: {integrity: sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==} + + '@vitest/snapshot@3.2.7': + resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==} + + '@vitest/spy@3.2.7': + resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==} + + '@vitest/utils@3.2.7': + resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==} + '@volar/language-core@2.4.27': resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==} @@ -3527,6 +3699,10 @@ packages: asciinema-player@3.8.2: resolution: {integrity: sha512-Lgcnj9u/H6sRpGRX1my7Azcay6llLmB/GVkCGcDbPwdTVTisS1ir8SQ9jRWRvjlLUjpSJkN0euruvy3sLRM8tw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -3667,6 +3843,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + cacache@20.0.4: resolution: {integrity: sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==} engines: {node: ^20.17.0 || >=22.9.0} @@ -3694,6 +3874,10 @@ packages: caniuse-lite@1.0.30001760: resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -3709,6 +3893,10 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -3963,6 +4151,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -4124,6 +4316,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} @@ -4229,6 +4424,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -4263,6 +4461,10 @@ packages: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4990,6 +5192,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -5109,30 +5314,60 @@ packages: cpu: [arm64] os: [android] + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + lightningcss-darwin-arm64@1.30.2: resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + lightningcss-darwin-x64@1.30.2: resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + lightningcss-freebsd-x64@1.30.2: resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + lightningcss-linux-arm-gnueabihf@1.30.2: resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + lightningcss-linux-arm64-gnu@1.30.2: resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} engines: {node: '>= 12.0.0'} @@ -5140,6 +5375,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} @@ -5147,6 +5389,13 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} @@ -5154,6 +5403,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} @@ -5161,22 +5417,45 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + lightningcss-win32-x64-msvc@1.30.2: resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + lightningcss@1.30.2: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -5232,6 +5511,9 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@11.2.4: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} @@ -5462,6 +5744,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -5698,6 +5985,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -5709,6 +6000,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -5786,6 +6081,10 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.5.19: + resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -5994,6 +6293,11 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rolldown@1.0.0-rc.12: + resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.53.5: resolution: {integrity: sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -6169,6 +6473,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -6264,6 +6571,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -6272,6 +6582,9 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} @@ -6322,6 +6635,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -6387,6 +6703,12 @@ packages: thunky@1.1.0: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} @@ -6395,6 +6717,18 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -6651,6 +6985,11 @@ packages: videojs-vtt.js@0.15.5: resolution: {integrity: sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite-plugin-bundle-css@0.1.1: resolution: {integrity: sha512-CGiZlMym2bkvbe6zbMcoQh7ZVZd+vkdiWc/vsVT61czQWn8w+ysI6zOHqsKbyXfpCBtzmUHFSdZo5krXncjEcQ==} engines: {node: '>=16'} @@ -6789,6 +7128,77 @@ packages: yaml: optional: true + vite@8.0.5: + resolution: {integrity: sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + 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 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.7: + resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.7 + '@vitest/ui': 3.2.7 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} @@ -6901,6 +7311,11 @@ packages: engines: {node: ^20.17.0 || >=22.9.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wildcard@2.0.1: resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} @@ -7108,13 +7523,13 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.19.27)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.30.2)(tailwindcss@4.1.18)(typescript@5.9.3)': + '@angular-devkit/build-angular@20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.19.27)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(tailwindcss@4.1.18)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.21(chokidar@4.0.3) '@angular-devkit/build-webpack': 0.2003.21(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.105.0))(webpack@5.105.0(esbuild@0.25.9)) '@angular-devkit/core': 20.3.21(chokidar@4.0.3) - '@angular/build': 20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(postcss@8.5.6)(tailwindcss@4.1.18)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3) + '@angular/build': 20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(postcss@8.5.6)(tailwindcss@4.1.18)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) '@angular/compiler-cli': 20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3) '@babel/core': 7.28.3 '@babel/generator': 7.28.3 @@ -7231,7 +7646,7 @@ snapshots: '@angular/core': 20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0) tslib: 2.6.1 - '@angular/build@20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(postcss@8.5.6)(tailwindcss@4.1.18)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)': + '@angular/build@20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(postcss@8.5.6)(tailwindcss@4.1.18)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.21(chokidar@4.0.3) @@ -7241,7 +7656,7 @@ snapshots: '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 '@inquirer/confirm': 5.1.14(@types/node@20.19.27) - '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) beasties: 0.3.5 browserslist: 4.28.1 esbuild: 0.25.9 @@ -7261,7 +7676,7 @@ snapshots: tinyglobby: 0.2.14 tslib: 2.8.1 typescript: 5.9.3 - vite: 7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) watchpack: 2.4.4 optionalDependencies: '@angular/core': 20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0) @@ -7270,6 +7685,7 @@ snapshots: lmdb: 3.4.2 postcss: 8.5.6 tailwindcss: 4.1.18 + vitest: 3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - chokidar @@ -8323,6 +8739,22 @@ snapshots: '@discoveryjs/json-ext@0.6.3': {} + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.6.1 + optional: true + + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.6.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.6.1 + optional: true + '@epic-web/invariant@1.0.0': {} '@esbuild/aix-ppc64@0.21.5': @@ -9244,6 +9676,13 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.1.1 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 + optional: true + '@ngtools/webpack@20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.0(esbuild@0.25.9))': dependencies: '@angular/compiler-cli': 20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3) @@ -9320,6 +9759,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@oxc-project/types@0.122.0': {} + '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -9397,8 +9838,60 @@ snapshots: '@primeuix/utils@0.7.2': {} + '@rolldown/binding-android-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + optional: true + '@rolldown/pluginutils@1.0.0-beta.27': {} + '@rolldown/pluginutils@1.0.0-rc.12': {} + '@rollup/pluginutils@5.3.0(rollup@4.59.0)': dependencies: '@types/estree': 1.0.8 @@ -9732,12 +10225,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 - '@tailwindcss/vite@4.1.18(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': + '@tailwindcss/vite@4.1.18(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': dependencies: '@tailwindcss/node': 4.1.18 '@tailwindcss/oxide': 4.1.18 tailwindcss: 4.1.18 - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) '@tootallnate/once@2.0.0': {} @@ -9756,6 +10249,11 @@ snapshots: '@tufjs/canonical-json': 2.0.0 minimatch: 10.1.1 + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.6.1 + optional: true + '@types/argparse@1.0.38': {} '@types/babel__core@7.20.5': @@ -9788,6 +10286,11 @@ snapshots: dependencies: '@types/node': 20.19.27 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 4.19.7 @@ -9797,14 +10300,16 @@ snapshots: dependencies: '@types/node': 20.19.27 + '@types/deep-eql@4.0.2': {} + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/eslint@9.6.1': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@types/estree@1.0.8': {} @@ -10046,11 +10551,11 @@ snapshots: global: 4.4.0 is-function: 1.0.2 - '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': + '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': dependencies: - vite: 7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) - '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -10058,10 +10563,61 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - supports-color + '@vitest/expect@3.2.7': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.7(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': + dependencies: + '@vitest/spy': 3.2.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + optional: true + + '@vitest/mocker@3.2.7(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': + dependencies: + '@vitest/spy': 3.2.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + + '@vitest/pretty-format@3.2.7': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.7': + dependencies: + '@vitest/utils': 3.2.7 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.7': + dependencies: + '@vitest/pretty-format': 3.2.7 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.7': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.7': + dependencies: + '@vitest/pretty-format': 3.2.7 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@volar/language-core@2.4.27': dependencies: '@volar/source-map': 2.4.27 @@ -10395,6 +10951,8 @@ snapshots: '@babel/runtime': 7.28.4 solid-js: 1.9.10 + assertion-error@2.0.1: {} + asynckit@0.4.0: {} autoprefixer@10.4.21(postcss@8.5.6): @@ -10603,6 +11161,8 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + cacache@20.0.4: dependencies: '@npmcli/fs': 5.0.0 @@ -10634,6 +11194,14 @@ snapshots: caniuse-lite@1.0.30001760: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -10645,6 +11213,8 @@ snapshots: chardet@2.1.1: {} + check-error@2.1.3: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -10905,6 +11475,8 @@ snapshots: dedent@1.7.0: {} + deep-eql@5.0.2: {} + deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -11024,6 +11596,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} es-object-atoms@1.1.1: @@ -11216,6 +11790,10 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + esutils@2.0.3: {} etag@1.8.1: {} @@ -11246,6 +11824,8 @@ snapshots: exit@0.1.2: {} + expect-type@1.4.0: {} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -12260,6 +12840,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@3.14.2: dependencies: argparse: 1.0.10 @@ -12385,36 +12967,69 @@ snapshots: lightningcss-android-arm64@1.30.2: optional: true + lightningcss-android-arm64@1.32.0: + optional: true + lightningcss-darwin-arm64@1.30.2: optional: true + lightningcss-darwin-arm64@1.32.0: + optional: true + lightningcss-darwin-x64@1.30.2: optional: true + lightningcss-darwin-x64@1.32.0: + optional: true + lightningcss-freebsd-x64@1.30.2: optional: true + lightningcss-freebsd-x64@1.32.0: + optional: true + lightningcss-linux-arm-gnueabihf@1.30.2: optional: true + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + lightningcss-linux-arm64-gnu@1.30.2: optional: true + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + lightningcss-linux-arm64-musl@1.30.2: optional: true + lightningcss-linux-arm64-musl@1.32.0: + optional: true + lightningcss-linux-x64-gnu@1.30.2: optional: true + lightningcss-linux-x64-gnu@1.32.0: + optional: true + lightningcss-linux-x64-musl@1.30.2: optional: true + lightningcss-linux-x64-musl@1.32.0: + optional: true + lightningcss-win32-arm64-msvc@1.30.2: optional: true + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + lightningcss-win32-x64-msvc@1.30.2: optional: true + lightningcss-win32-x64-msvc@1.32.0: + optional: true + lightningcss@1.30.2: dependencies: detect-libc: 2.1.2 @@ -12431,6 +13046,22 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + lines-and-columns@1.2.4: {} listr2@9.0.1: @@ -12504,6 +13135,8 @@ snapshots: strip-ansi: 7.1.2 wrap-ansi: 9.0.2 + loupe@3.2.1: {} + lru-cache@11.2.4: {} lru-cache@5.1.1: @@ -12750,6 +13383,8 @@ snapshots: nanoid@3.3.11: {} + nanoid@3.3.16: {} + natural-compare@1.4.0: {} needle@3.3.1: @@ -13015,12 +13650,16 @@ snapshots: pathe@2.0.3: {} + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.3: {} + picomatch@4.0.5: {} + pify@4.0.1: optional: true @@ -13093,6 +13732,12 @@ snapshots: postcss-value-parser@4.2.0: {} + postcss@8.5.19: + dependencies: + nanoid: 3.3.16 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -13292,6 +13937,30 @@ snapshots: rfdc@1.4.1: {} + rolldown@1.0.0-rc.12(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2): + dependencies: + '@oxc-project/types': 0.122.0 + '@rolldown/pluginutils': 1.0.0-rc.12 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-x64': 1.0.0-rc.12 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + rollup@4.53.5: dependencies: '@types/estree': 1.0.8 @@ -13546,6 +14215,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -13664,10 +14335,14 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + stackback@0.0.2: {} + statuses@1.5.0: {} statuses@2.0.2: {} + std-env@3.10.0: {} + stdin-discarder@0.2.2: {} streamsearch@1.1.0: {} @@ -13713,6 +14388,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -13775,6 +14454,10 @@ snapshots: thunky@1.1.0: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinyglobby@0.2.14: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -13785,6 +14468,12 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -14043,14 +14732,57 @@ snapshots: dependencies: global: 4.4.0 - vite-plugin-bundle-css@0.1.1(rollup@4.59.0)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): + vite-node@3.2.4(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + optional: true + + vite-node@3.2.4(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite-plugin-bundle-css@0.1.1(rollup@4.59.0)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.59.0) optionalDependencies: rollup: 4.59.0 - vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) - vite-plugin-dts@4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.7.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-dts@4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.7.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): dependencies: '@microsoft/api-extractor': 7.55.2(@types/node@20.19.27) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) @@ -14063,13 +14795,32 @@ snapshots: magic-string: 0.30.21 typescript: 5.7.3 optionalDependencies: - vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + transitivePeerDependencies: + - '@types/node' + - rollup + - supports-color + + vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): + dependencies: + '@microsoft/api-extractor': 7.55.2(@types/node@22.19.3) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + '@volar/typescript': 2.4.27 + '@vue/language-core': 2.2.0(typescript@5.6.3) + compare-versions: 6.1.1 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 1.1.2 + magic-string: 0.30.21 + typescript: 5.6.3 + optionalDependencies: + vite: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)): dependencies: '@microsoft/api-extractor': 7.55.2(@types/node@22.19.3) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) @@ -14082,53 +14833,62 @@ snapshots: magic-string: 0.30.21 typescript: 5.6.3 optionalDependencies: - vite: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-static-copy@2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-static-copy@2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): + dependencies: + chokidar: 3.6.0 + fast-glob: 3.3.3 + fs-extra: 11.3.2 + p-map: 7.0.4 + picocolors: 1.1.1 + vite: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + + vite-plugin-static-copy@2.3.2(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): dependencies: chokidar: 3.6.0 fast-glob: 3.3.3 fs-extra: 11.3.2 p-map: 7.0.4 picocolors: 1.1.1 - vite: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) - vite-plugin-static-copy@2.3.2(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-static-copy@2.3.2(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): dependencies: chokidar: 3.6.0 fast-glob: 3.3.3 fs-extra: 11.3.2 p-map: 7.0.4 picocolors: 1.1.1 - vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) - vite-plugin-static-copy@2.3.2(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-static-copy@2.3.2(vite@8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)): dependencies: chokidar: 3.6.0 fast-glob: 3.3.3 fs-extra: 11.3.2 p-map: 7.0.4 picocolors: 1.1.1 - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1) - vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): + vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): dependencies: esbuild: 0.21.5 postcss: 8.5.6 - rollup: 4.53.5 + rollup: 4.59.0 optionalDependencies: '@types/node': 22.19.3 fsevents: 2.3.3 less: 4.4.0 - lightningcss: 1.30.2 + lightningcss: 1.32.0 sass: 1.90.0 terser: 5.43.1 - vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): + vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -14141,11 +14901,11 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 less: 4.4.0 - lightningcss: 1.30.2 + lightningcss: 1.32.0 sass: 1.90.0 terser: 5.43.1 - vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): + vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -14158,11 +14918,11 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 less: 4.4.0 - lightningcss: 1.30.2 + lightningcss: 1.32.0 sass: 1.90.0 terser: 5.43.1 - vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): + vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -14175,9 +14935,112 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 less: 4.4.0 - lightningcss: 1.30.2 + lightningcss: 1.32.0 + sass: 1.90.0 + terser: 5.43.1 + + vite@8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.5 + postcss: 8.5.19 + rolldown: 1.0.0-rc.12(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.19.3 + fsevents: 2.3.3 + jiti: 2.6.1 + less: 4.4.0 sass: 1.90.0 terser: 5.43.1 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.7 + '@vitest/mocker': 3.2.7(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + '@vitest/pretty-format': 3.2.7 + '@vitest/runner': 3.2.7 + '@vitest/snapshot': 3.2.7 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.4.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite-node: 3.2.4(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.27 + jsdom: 20.0.3 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + optional: true + + vitest@3.2.7(@types/node@22.19.3)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.7 + '@vitest/mocker': 3.2.7(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + '@vitest/pretty-format': 3.2.7 + '@vitest/runner': 3.2.7 + '@vitest/snapshot': 3.2.7 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.4.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite-node: 3.2.4(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.3 + jsdom: 20.0.3 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml vscode-uri@3.1.0: {} @@ -14275,7 +15138,7 @@ snapshots: webpack@5.105.0(esbuild@0.25.9): dependencies: '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 @@ -14331,6 +15194,11 @@ snapshots: dependencies: isexe: 4.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wildcard@2.0.1: {} word-wrap@1.2.5: {} From 865a2de946113e04358a719efa80d5cf0c39f0d4 Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 13:47:07 -0400 Subject: [PATCH 02/13] fix(webapp): address copilot parser review feedback Harden parser handling for untrusted input and align package tooling versions:\n\n- reject non-safe integer sequence values\n- preserve __proto__ keys safely with null-prototype maps\n- parse lines lazily without pre-splitting the full file\n- align vite dependency to v6 peer ecosystem\n- add regression tests for unsafe seq and prototype-key preservation\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../session-recording-log/package.json | 2 +- .../session-recording-log/src/parser.test.ts | 33 + .../session-recording-log/src/parser.ts | 71 ++- webapp/pnpm-lock.yaml | 588 +++--------------- 4 files changed, 166 insertions(+), 528 deletions(-) diff --git a/webapp/packages/session-recording-log/package.json b/webapp/packages/session-recording-log/package.json index 79012c7b2..182f67c25 100644 --- a/webapp/packages/session-recording-log/package.json +++ b/webapp/packages/session-recording-log/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "typescript": "~5.6.2", - "vite": "8.0.5", + "vite": "^6.2.1", "vite-plugin-dts": "^4.3.0", "vite-plugin-static-copy": "^2.3.0", "vitest": "^3.1.1" diff --git a/webapp/packages/session-recording-log/src/parser.test.ts b/webapp/packages/session-recording-log/src/parser.test.ts index b3d4e7c65..bc6ba30d9 100644 --- a/webapp/packages/session-recording-log/src/parser.test.ts +++ b/webapp/packages/session-recording-log/src/parser.test.ts @@ -179,4 +179,37 @@ describe('parseSessionRecordingLog', () => { ), ).toBe(true); }); + + it('rejects unsafe sequence integers', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start"}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":9007199254740993,"event":"session.action","description":"Unsafe seq"}', + '{"timestamp":"2026-07-16T10:00:02.000Z","seq":1,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text); + + expect(result.entries).toHaveLength(2); + expect( + result.warnings.some( + (warning) => warning.code === 'invalid-field' && warning.message === 'seq must be a safe integer', + ), + ).toBe(true); + }); + + it('preserves __proto__ keys in parameters and unknown fields as data', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start","parameters":{"__proto__":"kept"},"__proto__":{"safe":"value"}}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text); + const startEntry = result.entries.find((entry) => entry.entry.event === 'session.start'); + + expect(startEntry).toBeDefined(); + expect(startEntry?.entry.parameters?.__proto__).toBe('kept'); + expect((startEntry?.entry.unknownFields?.__proto__ as { safe?: string } | undefined)?.safe).toBe('value'); + }); }); diff --git a/webapp/packages/session-recording-log/src/parser.ts b/webapp/packages/session-recording-log/src/parser.ts index 7d92a6032..2aa66fd8e 100644 --- a/webapp/packages/session-recording-log/src/parser.ts +++ b/webapp/packages/session-recording-log/src/parser.ts @@ -48,6 +48,59 @@ function isPlainObject(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } +function createLineIterator(text: string): Iterable<{ line: string; sourceLineNumber: number }> { + return { + [Symbol.iterator](): Iterator<{ line: string; sourceLineNumber: number }> { + let offset = 0; + let sourceLineNumber = 1; + let finished = false; + + return { + next(): IteratorResult<{ line: string; sourceLineNumber: number }> { + if (finished) { + return { done: true, value: undefined }; + } + + if (offset >= text.length) { + finished = true; + return { done: true, value: undefined }; + } + + const nextNewLine = text.indexOf('\n', offset); + if (nextNewLine === -1) { + const line = text.slice(offset).replace(/\r$/, ''); + finished = true; + return { + done: false, + value: { line, sourceLineNumber }, + }; + } + + const line = text.slice(offset, nextNewLine).replace(/\r$/, ''); + const currentLineNumber = sourceLineNumber; + sourceLineNumber += 1; + offset = nextNewLine + 1; + return { + done: false, + value: { line, sourceLineNumber: currentLineNumber }, + }; + }, + }; + }, + }; +} + +function getFinalLineNumber(text: string): number { + let lineNumber = 1; + for (const character of text) { + if (character === '\n') { + lineNumber += 1; + } + } + + return lineNumber; +} + function exceedsMaxObjectDepth(value: unknown, maxObjectDepth: number): boolean { const stack: Array<{ value: unknown; depth: number }> = [{ value, depth: 1 }]; @@ -125,7 +178,7 @@ function parseParameters( warnings.push(createWarning('entry-limit-exceeded', 'parameter count exceeded max limit', { sourceLineNumber })); } - const output: Record = {}; + const output: Record = Object.create(null) as Record; for (const [rawKey, rawValue] of entries.slice(0, maxParameterCount)) { let key = rawKey; if (rawKey.length > maxStringLength) { @@ -188,6 +241,10 @@ function parseLineRecord( warnings.push(createWarning('invalid-field', 'seq must be a non-negative integer', { sourceLineNumber })); return null; } + if (!Number.isSafeInteger(seq)) { + warnings.push(createWarning('invalid-field', 'seq must be a safe integer', { sourceLineNumber })); + return null; + } if (options.warnOnInvalidTimestamp && Number.isNaN(Date.parse(timestamp))) { warnings.push(createWarning('invalid-field', 'timestamp is not a valid date string', { sourceLineNumber, seq })); @@ -234,7 +291,7 @@ function parseLineRecord( ); } - const unknownFields: Record = {}; + const unknownFields: Record = Object.create(null) as Record; for (const [key, value] of unknownEntries.slice(0, options.maxUnknownFieldCount)) { unknownFields[key] = value; } @@ -271,22 +328,16 @@ export function parseSessionRecordingLog( const warnOnInvalidTimestamp = options?.warnOnInvalidTimestamp ?? true; let scannedNonEmptyLines = 0; - const lines = text.split(/\r?\n/); const hasTrailingNewline = text.endsWith('\n'); if (!hasTrailingNewline && text.length > 0) { warnings.push( createWarning('unterminated-final-line', 'final line does not end with newline', { - sourceLineNumber: lines.length, + sourceLineNumber: getFinalLineNumber(text), }), ); } - for (const [lineIndex, line] of lines.entries()) { - const sourceLineNumber = lineIndex + 1; - if (hasTrailingNewline && lineIndex === lines.length - 1 && line.length === 0) { - continue; - } - + for (const { line, sourceLineNumber } of createLineIterator(text)) { if (line.trim().length === 0) { continue; } diff --git a/webapp/pnpm-lock.yaml b/webapp/pnpm-lock.yaml index 7e2f0f90e..ec737ebb7 100644 --- a/webapp/pnpm-lock.yaml +++ b/webapp/pnpm-lock.yaml @@ -138,7 +138,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: 20.3.21 - version: 20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.19.27)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(tailwindcss@4.1.18)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.19.27)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.30.2)(tailwindcss@4.1.18)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) '@angular-devkit/core': specifier: 20.3.21 version: 20.3.21(chokidar@4.0.3) @@ -190,14 +190,14 @@ importers: version: 24.2.3(typescript@5.6.3) vite-plugin-static-copy: specifier: ^2.3.0 - version: 2.3.2(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 2.3.2(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) devDependencies: typescript: specifier: ~5.6.2 version: 5.6.3 vite: specifier: ^6.0.1 - version: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + version: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) packages/multi-video-player: dependencies: @@ -234,16 +234,16 @@ importers: version: 5.7.3 vite: specifier: ^6.2.0 - version: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + version: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) vite-plugin-bundle-css: specifier: ^0.1.1 - version: 0.1.1(rollup@4.59.0)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 0.1.1(rollup@4.59.0)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) vite-plugin-dts: specifier: ^4.5.3 - version: 4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.7.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.7.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) vite-plugin-static-copy: specifier: ^2.3.0 - version: 2.3.2(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 2.3.2(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) packages/session-recording-log: devDependencies: @@ -251,17 +251,17 @@ importers: specifier: ~5.6.2 version: 5.6.3 vite: - specifier: 8.0.5 - version: 8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1) + specifier: ^6.2.1 + version: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) vite-plugin-dts: specifier: ^4.3.0 - version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)) + version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) vite-plugin-static-copy: specifier: ^2.3.0 - version: 2.3.2(vite@8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)) + version: 2.3.2(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) vitest: specifier: ^3.1.1 - version: 3.2.7(@types/node@22.19.3)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + version: 3.2.7(@types/node@22.19.3)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) packages/shadow-player: devDependencies: @@ -273,13 +273,13 @@ importers: version: 5.6.3 vite: specifier: ^5.4.9 - version: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + version: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) vite-plugin-dts: specifier: ^4.3.0 - version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) vite-plugin-static-copy: specifier: ^2.3.0 - version: 2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) packages/web-recorder: devDependencies: @@ -288,19 +288,19 @@ importers: version: 5.6.3 vite: specifier: ^5.4.9 - version: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + version: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) vite-plugin-dts: specifier: ^4.3.0 - version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) vite-plugin-static-copy: specifier: ^2.3.0 - version: 2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) tools/recording-player-tester: dependencies: '@tailwindcss/vite': specifier: ^4.0.13 - version: 4.1.18(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 4.1.18(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) express: specifier: ^4.21.0 version: 4.22.1 @@ -319,7 +319,7 @@ importers: version: 19.2.3(@types/react@19.2.7) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) globals: specifier: ^16.0.0 version: 16.5.0 @@ -331,7 +331,7 @@ importers: version: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.7.3) vite: specifier: ^6.2.1 - version: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + version: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) packages: @@ -1404,15 +1404,6 @@ packages: resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} engines: {node: '>=14.17.0'} - '@emnapi/core@1.11.2': - resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} - - '@emnapi/runtime@1.11.2': - resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} - - '@emnapi/wasi-threads@1.2.2': - resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} @@ -2438,12 +2429,6 @@ packages: resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} engines: {node: '>= 10'} - '@napi-rs/wasm-runtime@1.1.6': - resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - '@ngtools/webpack@20.3.21': resolution: {integrity: sha512-qO6tit1mm4bGTMT1ZL6RTQMiQvck1aQymTiuUSa9lYg5ZRGL/v9WhKVx9+ncf9yrq2kj28LEAVJcAtrEEd/YJA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -2501,9 +2486,6 @@ packages: resolution: {integrity: sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==} engines: {node: ^20.17.0 || >=22.9.0} - '@oxc-project/types@0.122.0': - resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} - '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -2610,107 +2592,9 @@ packages: resolution: {integrity: sha512-pmEbSfP0Phf9W9RweiM66zXnkn73ZeKyYINElbX3uZ2+stzzaba2svLAl3B1pHVcRw5t43O0VciaGe4ye2EXKw==} engines: {node: '>=12.11.0'} - '@rolldown/binding-android-arm64@1.0.0-rc.12': - resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-rc.12': - resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-rc.12': - resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-rc.12': - resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': - resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': - resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': - resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': - resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': - resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': - resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': - resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': - resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': - resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': - resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': - resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - '@rolldown/pluginutils@1.0.0-rc.12': - resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} - '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -3164,9 +3048,6 @@ packages: resolution: {integrity: sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==} engines: {node: ^20.17.0 || >=22.9.0} - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} @@ -5314,60 +5195,30 @@ packages: cpu: [arm64] os: [android] - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - lightningcss-darwin-arm64@1.30.2: resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - lightningcss-darwin-x64@1.30.2: resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - lightningcss-freebsd-x64@1.30.2: resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.30.2: resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - lightningcss-linux-arm64-gnu@1.30.2: resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} engines: {node: '>= 12.0.0'} @@ -5375,13 +5226,6 @@ packages: os: [linux] libc: [glibc] - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} @@ -5389,13 +5233,6 @@ packages: os: [linux] libc: [musl] - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} @@ -5403,13 +5240,6 @@ packages: os: [linux] libc: [glibc] - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} @@ -5417,45 +5247,22 @@ packages: os: [linux] libc: [musl] - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - lightningcss-win32-x64-msvc@1.30.2: resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - lightningcss@1.30.2: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -5744,11 +5551,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -6000,10 +5802,6 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} - engines: {node: '>=12'} - pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -6081,10 +5879,6 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.19: - resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -6293,11 +6087,6 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rolldown@1.0.0-rc.12: - resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - rollup@4.53.5: resolution: {integrity: sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -7128,49 +6917,6 @@ packages: yaml: optional: true - vite@8.0.5: - resolution: {integrity: sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.0 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - 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 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vitest@3.2.7: resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -7523,13 +7269,13 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.19.27)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(tailwindcss@4.1.18)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': + '@angular-devkit/build-angular@20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.19.27)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.30.2)(tailwindcss@4.1.18)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.21(chokidar@4.0.3) '@angular-devkit/build-webpack': 0.2003.21(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.105.0))(webpack@5.105.0(esbuild@0.25.9)) '@angular-devkit/core': 20.3.21(chokidar@4.0.3) - '@angular/build': 20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(postcss@8.5.6)(tailwindcss@4.1.18)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + '@angular/build': 20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(postcss@8.5.6)(tailwindcss@4.1.18)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) '@angular/compiler-cli': 20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3) '@babel/core': 7.28.3 '@babel/generator': 7.28.3 @@ -7646,7 +7392,7 @@ snapshots: '@angular/core': 20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0) tslib: 2.6.1 - '@angular/build@20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(postcss@8.5.6)(tailwindcss@4.1.18)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': + '@angular/build@20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@20.19.27)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(postcss@8.5.6)(tailwindcss@4.1.18)(terser@5.43.1)(tslib@2.8.1)(typescript@5.9.3)(vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.21(chokidar@4.0.3) @@ -7656,7 +7402,7 @@ snapshots: '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 '@inquirer/confirm': 5.1.14(@types/node@20.19.27) - '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) beasties: 0.3.5 browserslist: 4.28.1 esbuild: 0.25.9 @@ -7676,7 +7422,7 @@ snapshots: tinyglobby: 0.2.14 tslib: 2.8.1 typescript: 5.9.3 - vite: 7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) watchpack: 2.4.4 optionalDependencies: '@angular/core': 20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0) @@ -7685,7 +7431,7 @@ snapshots: lmdb: 3.4.2 postcss: 8.5.6 tailwindcss: 4.1.18 - vitest: 3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vitest: 3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - chokidar @@ -8739,22 +8485,6 @@ snapshots: '@discoveryjs/json-ext@0.6.3': {} - '@emnapi/core@1.11.2': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.6.1 - optional: true - - '@emnapi/runtime@1.11.2': - dependencies: - tslib: 2.6.1 - optional: true - - '@emnapi/wasi-threads@1.2.2': - dependencies: - tslib: 2.6.1 - optional: true - '@epic-web/invariant@1.0.0': {} '@esbuild/aix-ppc64@0.21.5': @@ -9676,13 +9406,6 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.1.1 optional: true - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': - dependencies: - '@emnapi/core': 1.11.2 - '@emnapi/runtime': 1.11.2 - '@tybys/wasm-util': 0.10.3 - optional: true - '@ngtools/webpack@20.3.21(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.0(esbuild@0.25.9))': dependencies: '@angular/compiler-cli': 20.3.18(@angular/compiler@20.3.18)(typescript@5.9.3) @@ -9759,8 +9482,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@oxc-project/types@0.122.0': {} - '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -9838,60 +9559,8 @@ snapshots: '@primeuix/utils@0.7.2': {} - '@rolldown/binding-android-arm64@1.0.0-rc.12': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.0-rc.12': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-rc.12': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-rc.12': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': - dependencies: - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': - optional: true - '@rolldown/pluginutils@1.0.0-beta.27': {} - '@rolldown/pluginutils@1.0.0-rc.12': {} - '@rollup/pluginutils@5.3.0(rollup@4.59.0)': dependencies: '@types/estree': 1.0.8 @@ -10225,12 +9894,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 - '@tailwindcss/vite@4.1.18(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': + '@tailwindcss/vite@4.1.18(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': dependencies: '@tailwindcss/node': 4.1.18 '@tailwindcss/oxide': 4.1.18 tailwindcss: 4.1.18 - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) '@tootallnate/once@2.0.0': {} @@ -10249,11 +9918,6 @@ snapshots: '@tufjs/canonical-json': 2.0.0 minimatch: 10.1.1 - '@tybys/wasm-util@0.10.3': - dependencies: - tslib: 2.6.1 - optional: true - '@types/argparse@1.0.38': {} '@types/babel__core@7.20.5': @@ -10551,11 +10215,11 @@ snapshots: global: 4.4.0 is-function: 1.0.2 - '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': + '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': dependencies: - vite: 7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) - '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -10563,7 +10227,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - supports-color @@ -10575,22 +10239,22 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.7(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': + '@vitest/mocker@3.2.7(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': dependencies: '@vitest/spy': 3.2.7 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) optional: true - '@vitest/mocker@3.2.7(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1))': + '@vitest/mocker@3.2.7(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': dependencies: '@vitest/spy': 3.2.7 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) '@vitest/pretty-format@3.2.7': dependencies: @@ -12967,69 +12631,36 @@ snapshots: lightningcss-android-arm64@1.30.2: optional: true - lightningcss-android-arm64@1.32.0: - optional: true - lightningcss-darwin-arm64@1.30.2: optional: true - lightningcss-darwin-arm64@1.32.0: - optional: true - lightningcss-darwin-x64@1.30.2: optional: true - lightningcss-darwin-x64@1.32.0: - optional: true - lightningcss-freebsd-x64@1.30.2: optional: true - lightningcss-freebsd-x64@1.32.0: - optional: true - lightningcss-linux-arm-gnueabihf@1.30.2: optional: true - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - lightningcss-linux-arm64-gnu@1.30.2: optional: true - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - lightningcss-linux-arm64-musl@1.30.2: optional: true - lightningcss-linux-arm64-musl@1.32.0: - optional: true - lightningcss-linux-x64-gnu@1.30.2: optional: true - lightningcss-linux-x64-gnu@1.32.0: - optional: true - lightningcss-linux-x64-musl@1.30.2: optional: true - lightningcss-linux-x64-musl@1.32.0: - optional: true - lightningcss-win32-arm64-msvc@1.30.2: optional: true - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - lightningcss-win32-x64-msvc@1.30.2: optional: true - lightningcss-win32-x64-msvc@1.32.0: - optional: true - lightningcss@1.30.2: dependencies: detect-libc: 2.1.2 @@ -13046,22 +12677,6 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - lines-and-columns@1.2.4: {} listr2@9.0.1: @@ -13383,8 +12998,6 @@ snapshots: nanoid@3.3.11: {} - nanoid@3.3.16: {} - natural-compare@1.4.0: {} needle@3.3.1: @@ -13658,8 +13271,6 @@ snapshots: picomatch@4.0.3: {} - picomatch@4.0.5: {} - pify@4.0.1: optional: true @@ -13732,12 +13343,6 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.5.19: - dependencies: - nanoid: 3.3.16 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -13937,30 +13542,6 @@ snapshots: rfdc@1.4.1: {} - rolldown@1.0.0-rc.12(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2): - dependencies: - '@oxc-project/types': 0.122.0 - '@rolldown/pluginutils': 1.0.0-rc.12 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.12 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.12 - '@rolldown/binding-darwin-x64': 1.0.0-rc.12 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.12 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - rollup@4.53.5: dependencies: '@types/estree': 1.0.8 @@ -14732,13 +14313,13 @@ snapshots: dependencies: global: 4.4.0 - vite-node@3.2.4(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + vite-node@3.2.4(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - jiti @@ -14754,13 +14335,13 @@ snapshots: - yaml optional: true - vite-node@3.2.4(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + vite-node@3.2.4(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - jiti @@ -14775,14 +14356,14 @@ snapshots: - tsx - yaml - vite-plugin-bundle-css@0.1.1(rollup@4.59.0)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-bundle-css@0.1.1(rollup@4.59.0)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.59.0) optionalDependencies: rollup: 4.59.0 - vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) - vite-plugin-dts@4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.7.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-dts@4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.7.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: '@microsoft/api-extractor': 7.55.2(@types/node@20.19.27) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) @@ -14795,13 +14376,13 @@ snapshots: magic-string: 0.30.21 typescript: 5.7.3 optionalDependencies: - vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: '@microsoft/api-extractor': 7.55.2(@types/node@22.19.3) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) @@ -14814,13 +14395,13 @@ snapshots: magic-string: 0.30.21 typescript: 5.6.3 optionalDependencies: - vite: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: '@microsoft/api-extractor': 7.55.2(@types/node@22.19.3) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) @@ -14833,49 +14414,40 @@ snapshots: magic-string: 0.30.21 typescript: 5.6.3 optionalDependencies: - vite: 8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-static-copy@2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): - dependencies: - chokidar: 3.6.0 - fast-glob: 3.3.3 - fs-extra: 11.3.2 - p-map: 7.0.4 - picocolors: 1.1.1 - vite: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) - - vite-plugin-static-copy@2.3.2(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-static-copy@2.3.2(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: chokidar: 3.6.0 fast-glob: 3.3.3 fs-extra: 11.3.2 p-map: 7.0.4 picocolors: 1.1.1 - vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) - vite-plugin-static-copy@2.3.2(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-static-copy@2.3.2(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: chokidar: 3.6.0 fast-glob: 3.3.3 fs-extra: 11.3.2 p-map: 7.0.4 picocolors: 1.1.1 - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) - vite-plugin-static-copy@2.3.2(vite@8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-static-copy@2.3.2(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: chokidar: 3.6.0 fast-glob: 3.3.3 fs-extra: 11.3.2 p-map: 7.0.4 picocolors: 1.1.1 - vite: 8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) - vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): dependencies: esbuild: 0.21.5 postcss: 8.5.6 @@ -14884,11 +14456,11 @@ snapshots: '@types/node': 22.19.3 fsevents: 2.3.3 less: 4.4.0 - lightningcss: 1.32.0 + lightningcss: 1.30.2 sass: 1.90.0 terser: 5.43.1 - vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -14901,11 +14473,11 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 less: 4.4.0 - lightningcss: 1.32.0 + lightningcss: 1.30.2 sass: 1.90.0 terser: 5.43.1 - vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -14918,11 +14490,11 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 less: 4.4.0 - lightningcss: 1.32.0 + lightningcss: 1.30.2 sass: 1.90.0 terser: 5.43.1 - vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + vite@7.1.11(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -14935,33 +14507,15 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 less: 4.4.0 - lightningcss: 1.32.0 - sass: 1.90.0 - terser: 5.43.1 - - vite@8.0.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(sass@1.90.0)(terser@5.43.1): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.5 - postcss: 8.5.19 - rolldown: 1.0.0-rc.12(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 22.19.3 - fsevents: 2.3.3 - jiti: 2.6.1 - less: 4.4.0 + lightningcss: 1.30.2 sass: 1.90.0 terser: 5.43.1 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + vitest@3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.7 - '@vitest/mocker': 3.2.7(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + '@vitest/mocker': 3.2.7(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) '@vitest/pretty-format': 3.2.7 '@vitest/runner': 3.2.7 '@vitest/snapshot': 3.2.7 @@ -14979,8 +14533,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) - vite-node: 3.2.4(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite-node: 3.2.4(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.27 @@ -15000,11 +14554,11 @@ snapshots: - yaml optional: true - vitest@3.2.7(@types/node@22.19.3)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1): + vitest@3.2.7(@types/node@22.19.3)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.7 - '@vitest/mocker': 3.2.7(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1)) + '@vitest/mocker': 3.2.7(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) '@vitest/pretty-format': 3.2.7 '@vitest/runner': 3.2.7 '@vitest/snapshot': 3.2.7 @@ -15022,8 +14576,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) - vite-node: 3.2.4(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite-node: 3.2.4(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.19.3 From af383d7ebdef7acf7758b1d52b445cbee9195061 Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 13:58:48 -0400 Subject: [PATCH 03/13] fix(webapp): harden parser option handling and search scaling Addresses latest Copilot review feedback by normalizing parser numeric limits, pre-indexing warning associations in search, and clarifying truncated missing-sequence wording.\n\nAlso adds regression coverage for invalid numeric option inputs.\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../session-recording-log/src/parser.test.ts | 28 ++++++++++++++++ .../session-recording-log/src/parser.ts | 32 +++++++++++++------ .../session-recording-log/src/search.ts | 32 ++++++++++++------- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/webapp/packages/session-recording-log/src/parser.test.ts b/webapp/packages/session-recording-log/src/parser.test.ts index bc6ba30d9..2fdb516c0 100644 --- a/webapp/packages/session-recording-log/src/parser.test.ts +++ b/webapp/packages/session-recording-log/src/parser.test.ts @@ -107,6 +107,34 @@ describe('parseSessionRecordingLog', () => { ).toBe(true); }); + it('normalizes invalid numeric limit options to safe finite defaults', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start"}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":2000,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text, { + maxLineLengthBytes: Number.NaN, + maxStringLength: Number.POSITIVE_INFINITY, + maxParameterCount: Number.NaN, + maxObjectDepth: Number.POSITIVE_INFINITY, + maxParsedEntries: Number.NaN, + maxScannedLines: Number.NaN, + maxMissingSequenceWarnings: Number.POSITIVE_INFINITY, + maxUnknownFieldCount: Number.NaN, + }); + + expect(result.entries).toHaveLength(2); + expect(result.warnings.filter((warning) => warning.code === 'missing-sequence').length).toBeLessThanOrEqual(1000); + expect( + result.warnings.some( + (warning) => + warning.code === 'entry-limit-exceeded' && warning.message.includes('additional missing sequences'), + ), + ).toBe(true); + }); + it('handles deeply nested records without crashing parse flow', () => { const depth = 1000; let nested = '1'; diff --git a/webapp/packages/session-recording-log/src/parser.ts b/webapp/packages/session-recording-log/src/parser.ts index 2aa66fd8e..d7c818132 100644 --- a/webapp/packages/session-recording-log/src/parser.ts +++ b/webapp/packages/session-recording-log/src/parser.ts @@ -101,6 +101,14 @@ function getFinalLineNumber(text: string): number { return lineNumber; } +function normalizeNonNegativeLimit(value: number | undefined, fallback: number): number { + if (value === undefined || !Number.isFinite(value) || value < 0) { + return fallback; + } + + return Math.trunc(value); +} + function exceedsMaxObjectDepth(value: unknown, maxObjectDepth: number): boolean { const stack: Array<{ value: unknown; depth: number }> = [{ value, depth: 1 }]; @@ -317,14 +325,20 @@ export function parseSessionRecordingLog( ): SessionRecordingLogParseResult { const warnings: SessionRecordingLogWarning[] = []; const entries: ParsedSessionRecordingLogEntry[] = []; - const maxLineLengthBytes = options?.maxLineLengthBytes ?? DEFAULT_MAX_LINE_LENGTH_BYTES; - const maxStringLength = options?.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH; - const maxParameterCount = options?.maxParameterCount ?? DEFAULT_MAX_PARAMETER_COUNT; - const maxObjectDepth = options?.maxObjectDepth ?? DEFAULT_MAX_OBJECT_DEPTH; - const maxParsedEntries = options?.maxParsedEntries ?? DEFAULT_MAX_PARSED_ENTRIES; - const maxScannedLines = options?.maxScannedLines ?? DEFAULT_MAX_SCANNED_LINES; - const maxMissingSequenceWarnings = options?.maxMissingSequenceWarnings ?? DEFAULT_MAX_MISSING_SEQUENCE_WARNINGS; - const maxUnknownFieldCount = options?.maxUnknownFieldCount ?? DEFAULT_MAX_UNKNOWN_FIELD_COUNT; + const maxLineLengthBytes = normalizeNonNegativeLimit(options?.maxLineLengthBytes, DEFAULT_MAX_LINE_LENGTH_BYTES); + const maxStringLength = normalizeNonNegativeLimit(options?.maxStringLength, DEFAULT_MAX_STRING_LENGTH); + const maxParameterCount = normalizeNonNegativeLimit(options?.maxParameterCount, DEFAULT_MAX_PARAMETER_COUNT); + const maxObjectDepth = normalizeNonNegativeLimit(options?.maxObjectDepth, DEFAULT_MAX_OBJECT_DEPTH); + const maxParsedEntries = normalizeNonNegativeLimit(options?.maxParsedEntries, DEFAULT_MAX_PARSED_ENTRIES); + const maxScannedLines = normalizeNonNegativeLimit(options?.maxScannedLines, DEFAULT_MAX_SCANNED_LINES); + const maxMissingSequenceWarnings = normalizeNonNegativeLimit( + options?.maxMissingSequenceWarnings, + DEFAULT_MAX_MISSING_SEQUENCE_WARNINGS, + ); + const maxUnknownFieldCount = normalizeNonNegativeLimit( + options?.maxUnknownFieldCount, + DEFAULT_MAX_UNKNOWN_FIELD_COUNT, + ); const warnOnInvalidTimestamp = options?.warnOnInvalidTimestamp ?? true; let scannedNonEmptyLines = 0; @@ -456,7 +470,7 @@ export function parseSessionRecordingLog( warnings.push( createWarning( 'entry-limit-exceeded', - `missing sequence warnings truncated: ${missingTotal - emittedMissingWarnings} additional gaps`, + `missing sequence warnings truncated: ${missingTotal - emittedMissingWarnings} additional missing sequences`, ), ); } diff --git a/webapp/packages/session-recording-log/src/search.ts b/webapp/packages/session-recording-log/src/search.ts index e15cd7767..fbd7d386a 100644 --- a/webapp/packages/session-recording-log/src/search.ts +++ b/webapp/packages/session-recording-log/src/search.ts @@ -20,19 +20,22 @@ function maybeNormalize(value: string, caseSensitive: boolean): string { return caseSensitive ? value : value.toLowerCase(); } -function hasWarningAssociation( - entry: ParsedSessionRecordingLogEntry, - warnings: SearchSessionRecordingLogOptions['warnings'], -): boolean { - if (!warnings || warnings.length === 0) { - return false; +function buildWarningAssociationIndex(warnings: SearchSessionRecordingLogOptions['warnings']): { + lineNumbers: Set; + sequences: Set; +} { + const lineNumbers = new Set(); + const sequences = new Set(); + for (const warning of warnings ?? []) { + if (warning.sourceLineNumber !== undefined) { + lineNumbers.add(warning.sourceLineNumber); + } + if (warning.seq !== undefined) { + sequences.add(warning.seq); + } } - return warnings.some( - (warning) => - warning.sourceLineNumber === entry.sourceLineNumber || - (warning.seq !== undefined && warning.seq === entry.entry.seq), - ); + return { lineNumbers, sequences }; } function tryMatchField( @@ -63,6 +66,7 @@ export function searchSessionRecordingLogEntries( const normalizedQuery = maybeNormalize(trimmed, caseSensitive); const eventTypes = options?.eventTypes; const onlyWithWarnings = options?.onlyWithWarnings ?? false; + const warningAssociations = buildWarningAssociationIndex(options?.warnings); const hits: SessionRecordingLogSearchHit[] = []; for (const parsedEntry of entries) { @@ -70,7 +74,11 @@ export function searchSessionRecordingLogEntries( continue; } - if (onlyWithWarnings && !hasWarningAssociation(parsedEntry, options?.warnings)) { + if ( + onlyWithWarnings && + !warningAssociations.lineNumbers.has(parsedEntry.sourceLineNumber) && + !warningAssociations.sequences.has(parsedEntry.entry.seq) + ) { continue; } From 262fe452d48736b4a2da6dec90052e64b0fc1ec0 Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 14:20:18 -0400 Subject: [PATCH 04/13] docs(webapp): clarify ADR acronym in session recording log readme Expand ADR references for first-time readers by spelling out Architecture Decision Record in README headings/text.\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- webapp/packages/session-recording-log/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/packages/session-recording-log/README.md b/webapp/packages/session-recording-log/README.md index a28555520..a08161382 100644 --- a/webapp/packages/session-recording-log/README.md +++ b/webapp/packages/session-recording-log/README.md @@ -9,7 +9,7 @@ Framework-agnostic models, parser, ordering, and in-file search for Session Reco - Hosts decode UTF-8 text and pass the decoded NDJSON text to the parser. - This package does not reconstruct downloadable `.slog` content. -## ADR-2 canonical contract +## Architecture Decision Record 2 (ADR-2) canonical contract - `parseSessionRecordingLog` returns: - `entries: ParsedSessionRecordingLogEntry[]` in original file order. @@ -29,7 +29,7 @@ Framework-agnostic models, parser, ordering, and in-file search for Session Reco - `string-truncated` - Additive warning: - `unterminated-final-line` (informational; valid final line without newline is not semantically invalid). - - Intended for ADR addendum documentation rather than replacement of canonical malformed handling. + - Intended for Architecture Decision Record (ADR) addendum documentation rather than replacement of canonical malformed handling. Schema-limit options: From 092821cd4f6f43f4b3ec9400405641c3f26fa57e Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 14:51:38 -0400 Subject: [PATCH 05/13] docs(test): add library-only slog MVP examples and fixtures Implements the library-only MVP scope inside @devolutions/session-recording-log:\n\n- add centralized src/fixtures sample .slog files\n- add fixture-driven tests for parse/order/warnings/search flows\n- add noUncheckedSideEffectImports in package tsconfig\n- restructure README to sibling-style sections and runnable usage examples\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../packages/session-recording-log/README.md | 154 ++++++++++++++---- .../src/fixtures/sample-complete.slog | 3 + .../src/fixtures/sample-missing-end.slog | 2 + .../src/fixtures/sample-warning-linked.slog | 3 + .../session-recording-log/src/helpers.test.ts | 27 +++ .../session-recording-log/src/parser.test.ts | 24 +++ .../session-recording-log/tsconfig.json | 3 +- 7 files changed, 179 insertions(+), 37 deletions(-) create mode 100644 webapp/packages/session-recording-log/src/fixtures/sample-complete.slog create mode 100644 webapp/packages/session-recording-log/src/fixtures/sample-missing-end.slog create mode 100644 webapp/packages/session-recording-log/src/fixtures/sample-warning-linked.slog diff --git a/webapp/packages/session-recording-log/README.md b/webapp/packages/session-recording-log/README.md index a08161382..b9b7a79f0 100644 --- a/webapp/packages/session-recording-log/README.md +++ b/webapp/packages/session-recording-log/README.md @@ -2,14 +2,85 @@ Framework-agnostic models, parser, ordering, and in-file search for Session Recording Log (`.slog`) files. -## Host contract +## Features + +- NDJSON parser that keeps valid entries even when other lines are malformed. +- Structured warning codes for parser behavior and host branching. +- Source metadata (`sourceLineNumber`, `sourceIndex`, `sourceText`) preserved for each parsed entry. +- Non-mutating display-order helper (sort by `seq`, tie-break on `sourceIndex`). +- Bounded in-memory search over visible fields with optional event/warning-linked filtering. +- Hardened bounds for large/malformed inputs (line size, string length, parameter count, depth, entry/scan caps). + +## Usage + +### Basic usage + +```ts +import { parseSessionRecordingLog } from '@devolutions/session-recording-log'; + +const result = parseSessionRecordingLog(slogText); +console.log(result.completionState); +console.log(result.entries.length); +console.log(result.warnings.map((warning) => warning.code)); +``` + +### Full runnable example + +```ts +import { + getSessionRecordingLogDisplayEntries, + parseSessionRecordingLog, + searchSessionRecordingLogEntries, +} from '@devolutions/session-recording-log'; + +const slogText = [ + '{"timestamp":"2026-07-15T21:17:49.777Z","seq":0,"event":"session.start","description":"Session started","actor":"Administrator","host":"IT-HELP-DC","sessionType":"ADConsole"}', + '{"timestamp":"2026-07-15T21:19:14.351Z","seq":1,"event":"session.action","description":"Renamed Object","object":"Help Desk Ottawa","parameters":{"Members added":"Sarah O\'Connor, Bob Smith"}}', + // Missing session.end on purpose. +].join('\n'); + +const parseResult = parseSessionRecordingLog(slogText); + +// Parse status + warning surfacing. +console.log(parseResult.completionState); // 'ended-unexpectedly' +for (const warning of parseResult.warnings) { + console.log(`${warning.code} line=${warning.sourceLineNumber ?? '-'} seq=${warning.seq ?? '-'}`); +} + +// Non-mutating display order by seq/sourceIndex. +const displayEntries = getSessionRecordingLogDisplayEntries(parseResult); +console.log(displayEntries.map((entry) => `${entry.entry.seq}:${entry.entry.event}`)); + +// Basic search/filter. +const actionHits = searchSessionRecordingLogEntries(parseResult.entries, "o'connor", { + eventTypes: ['session.action'], +}); +const warningLinked = searchSessionRecordingLogEntries(parseResult.entries, '', { + onlyWithWarnings: true, + warnings: parseResult.warnings, +}); + +console.log(actionHits.length); // 1 +console.log(warningLinked.length); // entries linked to parser warnings +``` + +## API Reference + +Public API: + +- `parseSessionRecordingLog(text, options?)` +- `getSessionRecordingLogDisplayEntries(parseResult)` +- `searchSessionRecordingLogEntries(entries, query, options?)` +- `isSessionRecordingLogFileName(fileName)` + +Host contract: - Hosts detect `.slog` artifacts from `recording.json` file names (for example `recording-0.slog`). - Hosts keep original bytes for download scenarios. - Hosts decode UTF-8 text and pass the decoded NDJSON text to the parser. - This package does not reconstruct downloadable `.slog` content. -## Architecture Decision Record 2 (ADR-2) canonical contract +Architecture Decision Record 2 (ADR-2) canonical contract: - `parseSessionRecordingLog` returns: - `entries: ParsedSessionRecordingLogEntry[]` in original file order. @@ -42,19 +113,7 @@ Schema-limit options: - `maxMissingSequenceWarnings` - `maxUnknownFieldCount` -## Review-driven hardening updates (2026-07-17) - -- Added bounded missing-sequence emission to prevent unbounded gap loops on sparse large `seq` ranges. -- Replaced recursive depth traversal with iterative depth checks to prevent stack-overflow failure on deeply nested JSON. -- Added malformed-heavy input bounding with `maxScannedLines`, and finite default `maxParsedEntries`. -- Fixed empty-log lifecycle classification (`completionState: 'complete'` when no valid entries). -- Added top-level unknown field cap (`maxUnknownFieldCount`) with `entry-limit-exceeded` warning when truncated. -- Split unknown future events into explicit `unknown-event-type` warning code. -- Clarified alias intent: `SessionRecordingLogRecord` is retained for AD historical naming compatibility. - -## Ratified v1 defaults (review decision) - -Ratified as v1 canonical defaults for cross-host behavior: +Ratified v1 defaults (review decision): - `maxLineLengthBytes`: `262144` (256 KiB) - `maxStringLength`: `4096` @@ -67,26 +126,49 @@ Ratified as v1 canonical defaults for cross-host behavior: - search default limit: `100` - search max limit: `1000` -Callout: these values were elevated from implementation defaults to ratified policy as a direct follow-up to review-driven hardening. +## Project Structure + +```txt +session-recording-log/ + src/ + fixtures/ # sample .slog fixtures used by package tests + parser.ts + ordering.ts + search.ts + manifest.ts + model.ts + index.ts + parser.test.ts + helpers.test.ts + README.md + package.json + package.dist.json + tsconfig.json + vite.config.ts +``` + +## Dependencies + +- Runtime dependencies: none +- Dev dependencies: TypeScript, Vite, Vitest, Biome, `vite-plugin-dts`, `vite-plugin-static-copy` + +## For Developers + +From `webapp/`: + +```bash +pnpm --filter @devolutions/session-recording-log check:write +pnpm --filter @devolutions/session-recording-log check +pnpm --filter @devolutions/session-recording-log test +pnpm --filter @devolutions/session-recording-log build +``` -## Search behavior - -- Searches visible fields only: - - `description` - - `object` - - `parameters` keys and values - - `actor` - - `host` - - `sessionType` - - `timestamp` -- Does not search `sourceText` or `unknownFields`. -- Supports optional filters: - - `eventTypes` for lifecycle filtering - - `warnings` + `onlyWithWarnings` for warning-linked filtering - -## API +## Review-driven hardening updates (2026-07-17) -- `parseSessionRecordingLog(text, options?)` -- `getSessionRecordingLogDisplayEntries(parseResult)` -- `searchSessionRecordingLogEntries(entries, query, options?)` -- `isSessionRecordingLogFileName(fileName)` +- Added bounded missing-sequence emission to prevent unbounded gap loops on sparse large `seq` ranges. +- Replaced recursive depth traversal with iterative depth checks to prevent stack-overflow failure on deeply nested JSON. +- Added malformed-heavy input bounding with `maxScannedLines`, and finite default `maxParsedEntries`. +- Fixed empty-log lifecycle classification (`completionState: 'complete'` when no valid entries). +- Added top-level unknown field cap (`maxUnknownFieldCount`) with `entry-limit-exceeded` warning when truncated. +- Split unknown future events into explicit `unknown-event-type` warning code. +- Clarified alias intent: `SessionRecordingLogRecord` is retained for AD historical naming compatibility. diff --git a/webapp/packages/session-recording-log/src/fixtures/sample-complete.slog b/webapp/packages/session-recording-log/src/fixtures/sample-complete.slog new file mode 100644 index 000000000..acc49b34d --- /dev/null +++ b/webapp/packages/session-recording-log/src/fixtures/sample-complete.slog @@ -0,0 +1,3 @@ +{"timestamp":"2026-07-15T16:15:35.140Z","seq":0,"actor":"Administrator","locale":"en-US","host":"IT-HELP-DC","sessionType":"ADConsole","event":"session.start","description":"Session started"} +{"timestamp":"2026-07-15T16:15:41.054Z","seq":1,"event":"session.action","description":"Created User","object":"Help Desk Ottawa","parameters":{"New name":"Help Desk Ottawa/Hull"}} +{"timestamp":"2026-07-15T16:16:32.759Z","seq":2,"event":"session.end","description":"Session ended"} diff --git a/webapp/packages/session-recording-log/src/fixtures/sample-missing-end.slog b/webapp/packages/session-recording-log/src/fixtures/sample-missing-end.slog new file mode 100644 index 000000000..065ebe938 --- /dev/null +++ b/webapp/packages/session-recording-log/src/fixtures/sample-missing-end.slog @@ -0,0 +1,2 @@ +{"timestamp":"2026-07-15T21:17:49.777Z","seq":0,"actor":"Administrator","host":"IT-HELP-DC","sessionType":"ADConsole","event":"session.start","description":"Session started"} +{"timestamp":"2026-07-15T21:19:14.351Z","seq":1,"event":"session.action","description":"Renamed Object","object":"Help Desk Ottawa","parameters":{"New name":"Help Desk Ottawa/Hull","Members added":"Sarah O'Connor, Bob Smith"}} diff --git a/webapp/packages/session-recording-log/src/fixtures/sample-warning-linked.slog b/webapp/packages/session-recording-log/src/fixtures/sample-warning-linked.slog new file mode 100644 index 000000000..8672fb6d8 --- /dev/null +++ b/webapp/packages/session-recording-log/src/fixtures/sample-warning-linked.slog @@ -0,0 +1,3 @@ +{"timestamp":"2026-07-15T21:17:49.777Z","seq":0,"event":"session.start","description":"Session started"} +{"timestamp":"2026-07-15T21:19:14.351Z","seq":1,"event":"session.future","description":"Future action","parameters":{"Members added":"Sarah O'Connor, Bob Smith"}} +{"timestamp":"2026-07-15T21:20:00.000Z","seq":2,"event":"session.end","description":"Session ended"} diff --git a/webapp/packages/session-recording-log/src/helpers.test.ts b/webapp/packages/session-recording-log/src/helpers.test.ts index 2d16a9401..cd7514934 100644 --- a/webapp/packages/session-recording-log/src/helpers.test.ts +++ b/webapp/packages/session-recording-log/src/helpers.test.ts @@ -1,9 +1,18 @@ +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; import { isSessionRecordingLogFileName } from './manifest'; import type { ParsedSessionRecordingLogEntry, SessionRecordingLogEntry, SessionRecordingLogWarning } from './model'; import { getSessionRecordingLogDisplayEntries } from './ordering'; +import { parseSessionRecordingLog } from './parser'; import { searchSessionRecordingLogEntries } from './search'; +function readFixture(name: string): string { + const fixturePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'fixtures', name); + return readFileSync(fixturePath, 'utf8'); +} + function makeEntry( sourceIndex: number, seq: number, @@ -71,4 +80,22 @@ describe('helpers', () => { expect(searchSessionRecordingLogEntries(entries, 'session', { eventTypes: ['session.start'] })).toHaveLength(1); expect(searchSessionRecordingLogEntries(entries, '', { onlyWithWarnings: true, warnings })).toHaveLength(1); }); + + it('supports fixture-driven ordering and search usage flow', () => { + const parseResult = parseSessionRecordingLog(readFixture('sample-warning-linked.slog')); + const orderedEntries = getSessionRecordingLogDisplayEntries(parseResult); + + expect(orderedEntries).toHaveLength(3); + expect(orderedEntries.map((entry) => entry.entry.seq)).toEqual([0, 1, 2]); + + const warningOnly = searchSessionRecordingLogEntries(parseResult.entries, '', { + onlyWithWarnings: true, + warnings: parseResult.warnings, + }); + const queryHits = searchSessionRecordingLogEntries(parseResult.entries, "o'connor"); + + expect(parseResult.warnings.some((warning) => warning.code === 'unknown-event-type')).toBe(true); + expect(warningOnly).toHaveLength(1); + expect(queryHits).toHaveLength(1); + }); }); diff --git a/webapp/packages/session-recording-log/src/parser.test.ts b/webapp/packages/session-recording-log/src/parser.test.ts index 2fdb516c0..c06ec8b01 100644 --- a/webapp/packages/session-recording-log/src/parser.test.ts +++ b/webapp/packages/session-recording-log/src/parser.test.ts @@ -1,7 +1,31 @@ +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; import { parseSessionRecordingLog } from './parser'; +function readFixture(name: string): string { + const fixturePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'fixtures', name); + return readFileSync(fixturePath, 'utf8'); +} + describe('parseSessionRecordingLog', () => { + it('parses sample-complete fixture as complete with no warnings', () => { + const result = parseSessionRecordingLog(readFixture('sample-complete.slog')); + + expect(result.completionState).toBe('complete'); + expect(result.entries).toHaveLength(3); + expect(result.warnings).toHaveLength(0); + }); + + it('parses sample-missing-end fixture and surfaces missing-session-end warning', () => { + const result = parseSessionRecordingLog(readFixture('sample-missing-end.slog')); + + expect(result.completionState).toBe('ended-unexpectedly'); + expect(result.entries).toHaveLength(2); + expect(result.warnings.some((warning) => warning.code === 'missing-session-end')).toBe(true); + }); + it('parses completed AD-like slog and preserves source metadata', () => { const text = [ '{"timestamp":"2026-07-15T16:15:35.140Z","seq":0,"actor":"Administrator","locale":"en-US","host":"IT-HELP-DC","sessionType":"ADConsole","event":"session.start","description":"Session started"}', diff --git a/webapp/packages/session-recording-log/tsconfig.json b/webapp/packages/session-recording-log/tsconfig.json index eda521452..792dc76c2 100644 --- a/webapp/packages/session-recording-log/tsconfig.json +++ b/webapp/packages/session-recording-log/tsconfig.json @@ -15,7 +15,8 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true }, "include": ["src"], "exclude": ["dist", "node_modules"] From e21964a76330848359da72546288b22fb9db0300 Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 15:12:54 -0400 Subject: [PATCH 06/13] chore(webapp): enforce LF endings for new package files Prevents CRLF regressions on Windows by tightening repository/editor line-ending configuration for webapp and common package file types.\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .gitattributes | 40 ++++++++++++++++++++++++---------------- webapp/.editorconfig | 33 +++++++++++++++++---------------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/.gitattributes b/.gitattributes index 3eb52bd81..1f8b44bb4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,16 +1,24 @@ -*.rs text eol=lf -*.toml text eol=lf -*.cs text eol=lf -*.js text eol=lf -*.ps1 text eol=lf -*.sln text eol=crlf -*.md text eol=lf -*.mustache text eol=lf -*.yaml text eol=lf - -devolutions-gateway/openapi/doc/index.adoc linguist-generated merge=binary -devolutions-gateway/openapi/dotnet-client/src/** linguist-generated merge=binary -devolutions-gateway/openapi/dotnet-client/docs/** linguist-generated merge=binary -devolutions-gateway/openapi/dotnet-subscriber/src/** linguist-generated merge=binary -devolutions-gateway/openapi/ts-angular-client/api/** linguist-generated merge=binary -devolutions-gateway/openapi/ts-angular-client/model/** linguist-generated merge=binary +*.rs text eol=lf +*.toml text eol=lf +*.cs text eol=lf +*.js text eol=lf +*.ts text eol=lf +*.tsx text eol=lf +*.json text eol=lf +*.ps1 text eol=lf +*.sln text eol=crlf +*.md text eol=lf +*.mustache text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.css text eol=lf +*.scss text eol=lf +*.html text eol=lf +*.slog text eol=lf + +devolutions-gateway/openapi/doc/index.adoc linguist-generated merge=binary +devolutions-gateway/openapi/dotnet-client/src/** linguist-generated merge=binary +devolutions-gateway/openapi/dotnet-client/docs/** linguist-generated merge=binary +devolutions-gateway/openapi/dotnet-subscriber/src/** linguist-generated merge=binary +devolutions-gateway/openapi/ts-angular-client/api/** linguist-generated merge=binary +devolutions-gateway/openapi/ts-angular-client/model/** linguist-generated merge=binary diff --git a/webapp/.editorconfig b/webapp/.editorconfig index 59d9a3a3e..23760c1d5 100644 --- a/webapp/.editorconfig +++ b/webapp/.editorconfig @@ -1,16 +1,17 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false From 7bdfc694220b3e48f25e56b7f45d2197cd06979c Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 15:25:33 -0400 Subject: [PATCH 07/13] fix(webapp): enforce biome formatting for vite config Formats session-recording-log vite config to workspace Biome rules and updates package check/check:write scripts to include vite.config.ts so future formatting regressions are caught locally.\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- webapp/packages/session-recording-log/package.json | 4 ++-- webapp/packages/session-recording-log/vite.config.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/webapp/packages/session-recording-log/package.json b/webapp/packages/session-recording-log/package.json index 182f67c25..d8b44d444 100644 --- a/webapp/packages/session-recording-log/package.json +++ b/webapp/packages/session-recording-log/package.json @@ -13,8 +13,8 @@ }, "scripts": { "build": "tsc && vite build", - "check": "biome check ./src", - "check:write": "biome check --write ./src", + "check": "biome check ./src vite.config.ts", + "check:write": "biome check --write ./src vite.config.ts", "test": "vitest run" }, "devDependencies": { diff --git a/webapp/packages/session-recording-log/vite.config.ts b/webapp/packages/session-recording-log/vite.config.ts index e5ddeafbb..886fb9bdb 100644 --- a/webapp/packages/session-recording-log/vite.config.ts +++ b/webapp/packages/session-recording-log/vite.config.ts @@ -1,7 +1,7 @@ import path from 'node:path'; -import {defineConfig} from 'vite'; +import { defineConfig } from 'vite'; import dts from 'vite-plugin-dts'; -import {viteStaticCopy} from 'vite-plugin-static-copy'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; const staticCopyPlugin = viteStaticCopy({ targets: [ @@ -25,7 +25,7 @@ export default defineConfig({ output: { globals: {}, }, - } + }, }, - plugins: [dts({tsconfigPath: './tsconfig.declaration.json'}), staticCopyPlugin], + plugins: [dts({ tsconfigPath: './tsconfig.declaration.json' }), staticCopyPlugin], }); From 663b10bb755779d6ed687a7d8361eac13a908e8b Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 15:48:44 -0400 Subject: [PATCH 08/13] fix(webapp): tighten warning association and parser memory bounds Addresses latest review findings in session-recording-log package:\n\n- make warning-linked search use sourceLine as authoritative and seq only as fallback\n- allow event-type filters to accept forward-compatible string events\n- add aggregate parser warning cap with truncation warning\n- replace TextEncoder line-size check with incremental UTF-8 byte counting\n- add regression tests for warning-association and warning-cap behavior\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../session-recording-log/src/helpers.test.ts | 15 ++ .../session-recording-log/src/model.ts | 3 +- .../session-recording-log/src/parser.test.ts | 16 ++ .../session-recording-log/src/parser.ts | 154 +++++++++++++----- .../session-recording-log/src/search.ts | 3 +- 5 files changed, 147 insertions(+), 44 deletions(-) diff --git a/webapp/packages/session-recording-log/src/helpers.test.ts b/webapp/packages/session-recording-log/src/helpers.test.ts index cd7514934..c754e5b14 100644 --- a/webapp/packages/session-recording-log/src/helpers.test.ts +++ b/webapp/packages/session-recording-log/src/helpers.test.ts @@ -98,4 +98,19 @@ describe('helpers', () => { expect(warningOnly).toHaveLength(1); expect(queryHits).toHaveLength(1); }); + + it('treats sourceLineNumber as authoritative for warning-linked filtering', () => { + const entries = [makeEntry(0, 1, 'Original sequence'), makeEntry(1, 1, 'Duplicate sequence')]; + const warnings: SessionRecordingLogWarning[] = [ + { code: 'duplicate-sequence', sourceLineNumber: 2, seq: 1, message: 'duplicate sequence number' }, + ]; + + const warningOnly = searchSessionRecordingLogEntries(entries, '', { + onlyWithWarnings: true, + warnings, + }); + + expect(warningOnly).toHaveLength(1); + expect(warningOnly[0]?.sourceLineNumber).toBe(2); + }); }); diff --git a/webapp/packages/session-recording-log/src/model.ts b/webapp/packages/session-recording-log/src/model.ts index 7e7a3291b..d8cfe1807 100644 --- a/webapp/packages/session-recording-log/src/model.ts +++ b/webapp/packages/session-recording-log/src/model.ts @@ -59,12 +59,13 @@ export interface ParseSessionRecordingLogOptions { maxScannedLines?: number; maxMissingSequenceWarnings?: number; maxUnknownFieldCount?: number; + maxWarnings?: number; } export interface SearchSessionRecordingLogOptions { limit?: number; caseSensitive?: boolean; - eventTypes?: SessionRecordingLogKnownEvent[]; + eventTypes?: string[]; warnings?: SessionRecordingLogWarning[]; onlyWithWarnings?: boolean; } diff --git a/webapp/packages/session-recording-log/src/parser.test.ts b/webapp/packages/session-recording-log/src/parser.test.ts index c06ec8b01..3260d280c 100644 --- a/webapp/packages/session-recording-log/src/parser.test.ts +++ b/webapp/packages/session-recording-log/src/parser.test.ts @@ -159,6 +159,22 @@ describe('parseSessionRecordingLog', () => { ).toBe(true); }); + it('caps total warning volume and emits one truncation warning', () => { + const invalidParameters = Array.from({ length: 40 }, (_, index) => `"p${index}":${index}`).join(','); + const text = `{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start","parameters":{${invalidParameters}}}\n`; + + const result = parseSessionRecordingLog(text, { maxWarnings: 5 }); + + expect(result.warnings.length).toBeLessThanOrEqual(6); + expect( + result.warnings.some( + (warning) => + warning.code === 'entry-limit-exceeded' && + warning.message === 'warning limit exceeded; additional warnings were truncated', + ), + ).toBe(true); + }); + it('handles deeply nested records without crashing parse flow', () => { const depth = 1000; let nested = '1'; diff --git a/webapp/packages/session-recording-log/src/parser.ts b/webapp/packages/session-recording-log/src/parser.ts index d7c818132..55295cd43 100644 --- a/webapp/packages/session-recording-log/src/parser.ts +++ b/webapp/packages/session-recording-log/src/parser.ts @@ -30,7 +30,7 @@ const DEFAULT_MAX_PARSED_ENTRIES = 10_000; const DEFAULT_MAX_SCANNED_LINES = 20_000; const DEFAULT_MAX_MISSING_SEQUENCE_WARNINGS = 1_000; const DEFAULT_MAX_UNKNOWN_FIELD_COUNT = 100; -const textEncoder = new TextEncoder(); +const DEFAULT_MAX_WARNINGS = 50_000; function createWarning( code: SessionRecordingLogWarningCode, @@ -44,6 +44,35 @@ function createWarning( }; } +interface WarningCollector { + warnings: SessionRecordingLogWarning[]; + add(warning: SessionRecordingLogWarning): void; +} + +function createWarningCollector(maxWarnings: number): WarningCollector { + const warnings: SessionRecordingLogWarning[] = []; + let emittedTruncationWarning = false; + + return { + warnings, + add(warning) { + if (warnings.length < maxWarnings) { + warnings.push(warning); + return; + } + + if (emittedTruncationWarning) { + return; + } + + warnings.push( + createWarning('entry-limit-exceeded', 'warning limit exceeded; additional warnings were truncated'), + ); + emittedTruncationWarning = true; + }, + }; +} + function isPlainObject(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } @@ -109,6 +138,36 @@ function normalizeNonNegativeLimit(value: number | undefined, fallback: number): return Math.trunc(value); } +function exceedsMaxLineLengthBytes(value: string, maxLineLengthBytes: number): boolean { + let byteLength = 0; + + for (let index = 0; index < value.length; index += 1) { + const code = value.charCodeAt(index); + + if (code <= 0x7f) { + byteLength += 1; + } else if (code <= 0x7ff) { + byteLength += 2; + } else if (code >= 0xd800 && code <= 0xdbff) { + const nextCode = value.charCodeAt(index + 1); + if (nextCode >= 0xdc00 && nextCode <= 0xdfff) { + byteLength += 4; + index += 1; + } else { + byteLength += 3; + } + } else { + byteLength += 3; + } + + if (byteLength > maxLineLengthBytes) { + return true; + } + } + + return false; +} + function exceedsMaxObjectDepth(value: unknown, maxObjectDepth: number): boolean { const stack: Array<{ value: unknown; depth: number }> = [{ value, depth: 1 }]; @@ -146,17 +205,17 @@ function exceedsMaxObjectDepth(value: unknown, maxObjectDepth: number): boolean function normalizeString( value: unknown, field: string, - warnings: SessionRecordingLogWarning[], + warningCollector: WarningCollector, sourceLineNumber: number, maxStringLength: number, ): string | null { if (typeof value !== 'string') { - warnings.push(createWarning('invalid-field', `${field} must be a string`, { sourceLineNumber })); + warningCollector.add(createWarning('invalid-field', `${field} must be a string`, { sourceLineNumber })); return null; } if (value.length > maxStringLength) { - warnings.push( + warningCollector.add( createWarning('string-truncated', `${field} exceeded max string length and was truncated`, { sourceLineNumber }), ); return value.slice(0, maxStringLength); @@ -167,7 +226,7 @@ function normalizeString( function parseParameters( value: unknown, - warnings: SessionRecordingLogWarning[], + warningCollector: WarningCollector, sourceLineNumber: number, maxStringLength: number, maxParameterCount: number, @@ -177,20 +236,22 @@ function parseParameters( } if (!isPlainObject(value)) { - warnings.push(createWarning('invalid-field', 'parameters must be an object', { sourceLineNumber })); + warningCollector.add(createWarning('invalid-field', 'parameters must be an object', { sourceLineNumber })); return undefined; } const entries = Object.entries(value); if (entries.length > maxParameterCount) { - warnings.push(createWarning('entry-limit-exceeded', 'parameter count exceeded max limit', { sourceLineNumber })); + warningCollector.add( + createWarning('entry-limit-exceeded', 'parameter count exceeded max limit', { sourceLineNumber }), + ); } const output: Record = Object.create(null) as Record; for (const [rawKey, rawValue] of entries.slice(0, maxParameterCount)) { let key = rawKey; if (rawKey.length > maxStringLength) { - warnings.push( + warningCollector.add( createWarning('string-truncated', 'parameter key exceeded max string length and was truncated', { sourceLineNumber, }), @@ -199,12 +260,12 @@ function parseParameters( } if (typeof rawValue !== 'string') { - warnings.push(createWarning('invalid-field', `parameter ${key} must be a string`, { sourceLineNumber })); + warningCollector.add(createWarning('invalid-field', `parameter ${key} must be a string`, { sourceLineNumber })); continue; } if (rawValue.length > maxStringLength) { - warnings.push( + warningCollector.add( createWarning('string-truncated', `parameter ${key} exceeded max string length and was truncated`, { sourceLineNumber, }), @@ -221,7 +282,7 @@ function parseParameters( function parseLineRecord( parsed: Record, - warnings: SessionRecordingLogWarning[], + warningCollector: WarningCollector, sourceLineNumber: number, options: Required< Pick< @@ -230,15 +291,21 @@ function parseLineRecord( > >, ): SessionRecordingLogEntry | null { - const timestamp = normalizeString(parsed.timestamp, 'timestamp', warnings, sourceLineNumber, options.maxStringLength); + const timestamp = normalizeString( + parsed.timestamp, + 'timestamp', + warningCollector, + sourceLineNumber, + options.maxStringLength, + ); const description = normalizeString( parsed.description, 'description', - warnings, + warningCollector, sourceLineNumber, options.maxStringLength, ); - const event = normalizeString(parsed.event, 'event', warnings, sourceLineNumber, options.maxStringLength); + const event = normalizeString(parsed.event, 'event', warningCollector, sourceLineNumber, options.maxStringLength); const seq = parsed.seq; if (timestamp === null || description === null || event === null) { @@ -246,20 +313,22 @@ function parseLineRecord( } if (typeof seq !== 'number' || !Number.isInteger(seq) || seq < 0) { - warnings.push(createWarning('invalid-field', 'seq must be a non-negative integer', { sourceLineNumber })); + warningCollector.add(createWarning('invalid-field', 'seq must be a non-negative integer', { sourceLineNumber })); return null; } if (!Number.isSafeInteger(seq)) { - warnings.push(createWarning('invalid-field', 'seq must be a safe integer', { sourceLineNumber })); + warningCollector.add(createWarning('invalid-field', 'seq must be a safe integer', { sourceLineNumber })); return null; } if (options.warnOnInvalidTimestamp && Number.isNaN(Date.parse(timestamp))) { - warnings.push(createWarning('invalid-field', 'timestamp is not a valid date string', { sourceLineNumber, seq })); + warningCollector.add( + createWarning('invalid-field', 'timestamp is not a valid date string', { sourceLineNumber, seq }), + ); } if (!KNOWN_EVENTS.has(event as SessionRecordingLogKnownEvent)) { - warnings.push( + warningCollector.add( createWarning('unknown-event-type', 'event is not one of the known lifecycle events', { sourceLineNumber, seq }), ); } @@ -267,26 +336,26 @@ function parseLineRecord( const actor = parsed.actor === undefined ? undefined - : normalizeString(parsed.actor, 'actor', warnings, sourceLineNumber, options.maxStringLength); + : normalizeString(parsed.actor, 'actor', warningCollector, sourceLineNumber, options.maxStringLength); const locale = parsed.locale === undefined ? undefined - : normalizeString(parsed.locale, 'locale', warnings, sourceLineNumber, options.maxStringLength); + : normalizeString(parsed.locale, 'locale', warningCollector, sourceLineNumber, options.maxStringLength); const host = parsed.host === undefined ? undefined - : normalizeString(parsed.host, 'host', warnings, sourceLineNumber, options.maxStringLength); + : normalizeString(parsed.host, 'host', warningCollector, sourceLineNumber, options.maxStringLength); const sessionType = parsed.sessionType === undefined ? undefined - : normalizeString(parsed.sessionType, 'sessionType', warnings, sourceLineNumber, options.maxStringLength); + : normalizeString(parsed.sessionType, 'sessionType', warningCollector, sourceLineNumber, options.maxStringLength); const object = parsed.object === undefined ? undefined - : normalizeString(parsed.object, 'object', warnings, sourceLineNumber, options.maxStringLength); + : normalizeString(parsed.object, 'object', warningCollector, sourceLineNumber, options.maxStringLength); const parameters = parseParameters( parsed.parameters, - warnings, + warningCollector, sourceLineNumber, options.maxStringLength, options.maxParameterCount, @@ -294,7 +363,7 @@ function parseLineRecord( const unknownEntries = Object.entries(parsed).filter(([key]) => !KNOWN_FIELDS.has(key)); if (unknownEntries.length > options.maxUnknownFieldCount) { - warnings.push( + warningCollector.add( createWarning('entry-limit-exceeded', 'unknown top-level field count exceeded max limit', { sourceLineNumber }), ); } @@ -323,7 +392,6 @@ export function parseSessionRecordingLog( text: string, options?: ParseSessionRecordingLogOptions, ): SessionRecordingLogParseResult { - const warnings: SessionRecordingLogWarning[] = []; const entries: ParsedSessionRecordingLogEntry[] = []; const maxLineLengthBytes = normalizeNonNegativeLimit(options?.maxLineLengthBytes, DEFAULT_MAX_LINE_LENGTH_BYTES); const maxStringLength = normalizeNonNegativeLimit(options?.maxStringLength, DEFAULT_MAX_STRING_LENGTH); @@ -339,12 +407,14 @@ export function parseSessionRecordingLog( options?.maxUnknownFieldCount, DEFAULT_MAX_UNKNOWN_FIELD_COUNT, ); + const maxWarnings = normalizeNonNegativeLimit(options?.maxWarnings, DEFAULT_MAX_WARNINGS); + const warningCollector = createWarningCollector(maxWarnings); const warnOnInvalidTimestamp = options?.warnOnInvalidTimestamp ?? true; let scannedNonEmptyLines = 0; const hasTrailingNewline = text.endsWith('\n'); if (!hasTrailingNewline && text.length > 0) { - warnings.push( + warningCollector.add( createWarning('unterminated-final-line', 'final line does not end with newline', { sourceLineNumber: getFinalLineNumber(text), }), @@ -358,17 +428,17 @@ export function parseSessionRecordingLog( scannedNonEmptyLines += 1; if (scannedNonEmptyLines > maxScannedLines) { - warnings.push(createWarning('entry-limit-exceeded', 'scanned line limit exceeded', { sourceLineNumber })); + warningCollector.add(createWarning('entry-limit-exceeded', 'scanned line limit exceeded', { sourceLineNumber })); break; } if (entries.length >= maxParsedEntries) { - warnings.push(createWarning('entry-limit-exceeded', 'parsed entry limit exceeded', { sourceLineNumber })); + warningCollector.add(createWarning('entry-limit-exceeded', 'parsed entry limit exceeded', { sourceLineNumber })); break; } - if (textEncoder.encode(line).length > maxLineLengthBytes) { - warnings.push(createWarning('entry-limit-exceeded', 'line exceeds max byte size', { sourceLineNumber })); + if (exceedsMaxLineLengthBytes(line, maxLineLengthBytes)) { + warningCollector.add(createWarning('entry-limit-exceeded', 'line exceeds max byte size', { sourceLineNumber })); continue; } @@ -376,21 +446,21 @@ export function parseSessionRecordingLog( try { parsed = JSON.parse(line); } catch { - warnings.push(createWarning('malformed-line', 'line is not valid JSON', { sourceLineNumber })); + warningCollector.add(createWarning('malformed-line', 'line is not valid JSON', { sourceLineNumber })); continue; } if (!isPlainObject(parsed)) { - warnings.push(createWarning('malformed-line', 'line JSON must be an object', { sourceLineNumber })); + warningCollector.add(createWarning('malformed-line', 'line JSON must be an object', { sourceLineNumber })); continue; } if (exceedsMaxObjectDepth(parsed, maxObjectDepth)) { - warnings.push(createWarning('invalid-field', 'line object exceeds max depth', { sourceLineNumber })); + warningCollector.add(createWarning('invalid-field', 'line object exceeds max depth', { sourceLineNumber })); continue; } - const record = parseLineRecord(parsed, warnings, sourceLineNumber, { + const record = parseLineRecord(parsed, warningCollector, sourceLineNumber, { maxStringLength, maxParameterCount, maxUnknownFieldCount, @@ -415,7 +485,7 @@ export function parseSessionRecordingLog( const seq = parsedEntry.entry.seq; if (seenSequences.has(seq)) { - warnings.push( + warningCollector.add( createWarning('duplicate-sequence', 'sequence number is duplicated', { sourceLineNumber: parsedEntry.sourceLineNumber, seq, @@ -426,7 +496,7 @@ export function parseSessionRecordingLog( } if (seq < previousSeq) { - warnings.push( + warningCollector.add( createWarning('sequence-order-mismatch', 'file order does not match sequence order', { sourceLineNumber: parsedEntry.sourceLineNumber, seq, @@ -453,7 +523,7 @@ export function parseSessionRecordingLog( const start = sortedSeq[index - 1] + 1; const end = sortedSeq[index]; for (let seq = start; seq < end; seq += 1) { - warnings.push(createWarning('missing-sequence', 'sequence number is missing', { seq })); + warningCollector.add(createWarning('missing-sequence', 'sequence number is missing', { seq })); emittedMissingWarnings += 1; if (emittedMissingWarnings >= maxMissingSequenceWarnings) { break; @@ -467,7 +537,7 @@ export function parseSessionRecordingLog( } if (missingTotal > emittedMissingWarnings) { - warnings.push( + warningCollector.add( createWarning( 'entry-limit-exceeded', `missing sequence warnings truncated: ${missingTotal - emittedMissingWarnings} additional missing sequences`, @@ -480,15 +550,15 @@ export function parseSessionRecordingLog( const hasSessionEnd = entries.some((parsedEntry) => parsedEntry.entry.event === 'session.end'); if (entries.length > 0 && !hasSessionStart) { - warnings.push(createWarning('missing-session-start', 'session.start is missing')); + warningCollector.add(createWarning('missing-session-start', 'session.start is missing')); } if (entries.length > 0 && !hasSessionEnd) { - warnings.push(createWarning('missing-session-end', 'session.end is missing')); + warningCollector.add(createWarning('missing-session-end', 'session.end is missing')); } return { entries, - warnings, + warnings: warningCollector.warnings, completionState: entries.length === 0 || hasSessionEnd ? 'complete' : 'ended-unexpectedly', }; } diff --git a/webapp/packages/session-recording-log/src/search.ts b/webapp/packages/session-recording-log/src/search.ts index fbd7d386a..c765de5f7 100644 --- a/webapp/packages/session-recording-log/src/search.ts +++ b/webapp/packages/session-recording-log/src/search.ts @@ -29,6 +29,7 @@ function buildWarningAssociationIndex(warnings: SearchSessionRecordingLogOptions for (const warning of warnings ?? []) { if (warning.sourceLineNumber !== undefined) { lineNumbers.add(warning.sourceLineNumber); + continue; } if (warning.seq !== undefined) { sequences.add(warning.seq); @@ -70,7 +71,7 @@ export function searchSessionRecordingLogEntries( const hits: SessionRecordingLogSearchHit[] = []; for (const parsedEntry of entries) { - if (eventTypes && eventTypes.length > 0 && !eventTypes.includes(parsedEntry.entry.event as never)) { + if (eventTypes && eventTypes.length > 0 && !eventTypes.includes(parsedEntry.entry.event)) { continue; } From b6e3534b304b48738397d01406f2360b71e22d4b Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 15:58:38 -0400 Subject: [PATCH 09/13] fix(webapp): harden slog parser completion and warning bounds Address latest review feedback for @devolutions/session-recording-log:\n\n- treat non-empty logs with zero valid entries as ended unexpectedly\n- detect parameter-key collisions after truncation and keep first value\n- restore conservative default aggregate warning cap to 10,000\n- add regression tests for completion-state and truncation-collision behavior\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../session-recording-log/src/parser.test.ts | 49 +++++++++++++++++++ .../session-recording-log/src/parser.ts | 15 +++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/webapp/packages/session-recording-log/src/parser.test.ts b/webapp/packages/session-recording-log/src/parser.test.ts index 3260d280c..bbe5bf72d 100644 --- a/webapp/packages/session-recording-log/src/parser.test.ts +++ b/webapp/packages/session-recording-log/src/parser.test.ts @@ -175,6 +175,27 @@ describe('parseSessionRecordingLog', () => { ).toBe(true); }); + it('caps warning volume at the default aggregate limit', () => { + const invalidParameters = Array.from({ length: 200 }, (_, index) => `"p${index}":${index}`).join(','); + const lines = Array.from( + { length: 60 }, + (_, index) => + `{"timestamp":"2026-07-16T10:00:${(index % 60).toString().padStart(2, '0')}.000Z","seq":${index},"event":"session.action","description":"Action","parameters":{${invalidParameters}}}`, + ); + const text = `${lines.join('\n')}\n`; + + const result = parseSessionRecordingLog(text); + + expect(result.warnings.length).toBeLessThanOrEqual(10001); + expect( + result.warnings.some( + (warning) => + warning.code === 'entry-limit-exceeded' && + warning.message === 'warning limit exceeded; additional warnings were truncated', + ), + ).toBe(true); + }); + it('handles deeply nested records without crashing parse flow', () => { const depth = 1000; let nested = '1'; @@ -213,6 +234,13 @@ describe('parseSessionRecordingLog', () => { expect(result.warnings.some((warning) => warning.code === 'missing-session-end')).toBe(false); }); + it('treats non-empty logs with no valid records as ended unexpectedly', () => { + const result = parseSessionRecordingLog('{not valid json}\n'); + + expect(result.entries).toHaveLength(0); + expect(result.completionState).toBe('ended-unexpectedly'); + }); + it('emits unknown-event-type warning for unrecognized events', () => { const text = [ '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start"}', @@ -280,4 +308,25 @@ describe('parseSessionRecordingLog', () => { expect(startEntry?.entry.parameters?.__proto__).toBe('kept'); expect((startEntry?.entry.unknownFields?.__proto__ as { safe?: string } | undefined)?.safe).toBe('value'); }); + + it('warns and preserves the first value on parameter key collisions after truncation', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start","parameters":{"abcd1":"one","abcd2":"two"}}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text, { maxStringLength: 4 }); + const firstEntry = result.entries[0]; + + expect(firstEntry).toBeDefined(); + expect(firstEntry?.entry.parameters).toEqual({ abcd: 'one' }); + expect( + result.warnings.some( + (warning) => + warning.code === 'invalid-field' && + warning.message === 'parameter abcd collided after truncation and was discarded', + ), + ).toBe(true); + }); }); diff --git a/webapp/packages/session-recording-log/src/parser.ts b/webapp/packages/session-recording-log/src/parser.ts index 55295cd43..82301d28c 100644 --- a/webapp/packages/session-recording-log/src/parser.ts +++ b/webapp/packages/session-recording-log/src/parser.ts @@ -30,7 +30,7 @@ const DEFAULT_MAX_PARSED_ENTRIES = 10_000; const DEFAULT_MAX_SCANNED_LINES = 20_000; const DEFAULT_MAX_MISSING_SEQUENCE_WARNINGS = 1_000; const DEFAULT_MAX_UNKNOWN_FIELD_COUNT = 100; -const DEFAULT_MAX_WARNINGS = 50_000; +const DEFAULT_MAX_WARNINGS = 10_000; function createWarning( code: SessionRecordingLogWarningCode, @@ -264,6 +264,15 @@ function parseParameters( continue; } + if (Object.hasOwn(output, key)) { + warningCollector.add( + createWarning('invalid-field', `parameter ${key} collided after truncation and was discarded`, { + sourceLineNumber, + }), + ); + continue; + } + if (rawValue.length > maxStringLength) { warningCollector.add( createWarning('string-truncated', `parameter ${key} exceeded max string length and was truncated`, { @@ -410,6 +419,7 @@ export function parseSessionRecordingLog( const maxWarnings = normalizeNonNegativeLimit(options?.maxWarnings, DEFAULT_MAX_WARNINGS); const warningCollector = createWarningCollector(maxWarnings); const warnOnInvalidTimestamp = options?.warnOnInvalidTimestamp ?? true; + let encounteredNonEmptyLine = false; let scannedNonEmptyLines = 0; const hasTrailingNewline = text.endsWith('\n'); @@ -426,6 +436,7 @@ export function parseSessionRecordingLog( continue; } + encounteredNonEmptyLine = true; scannedNonEmptyLines += 1; if (scannedNonEmptyLines > maxScannedLines) { warningCollector.add(createWarning('entry-limit-exceeded', 'scanned line limit exceeded', { sourceLineNumber })); @@ -559,6 +570,6 @@ export function parseSessionRecordingLog( return { entries, warnings: warningCollector.warnings, - completionState: entries.length === 0 || hasSessionEnd ? 'complete' : 'ended-unexpectedly', + completionState: !encounteredNonEmptyLine || hasSessionEnd ? 'complete' : 'ended-unexpectedly', }; } From 7bcaa0c861786bc298a0e330e17af88a8280a5a5 Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 16:09:38 -0400 Subject: [PATCH 10/13] ci(webapp): run slog package tests and align parser docs Address latest review findings:\n\n- execute @devolutions/session-recording-log tests in webapp CI job\n- document maxWarnings option and default in package README\n- fix lifecycle classification note to match implemented completion-state behavior\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++++ webapp/packages/session-recording-log/README.md | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed07d1f10..1bad9a55c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -490,6 +490,10 @@ jobs: run: pnpm check working-directory: webapp + - name: Run session-recording-log tests + run: pnpm --filter @devolutions/session-recording-log test + working-directory: webapp + - name: Upload gateway-ui artifact uses: actions/upload-artifact@v7 with: diff --git a/webapp/packages/session-recording-log/README.md b/webapp/packages/session-recording-log/README.md index b9b7a79f0..e32b4a1c6 100644 --- a/webapp/packages/session-recording-log/README.md +++ b/webapp/packages/session-recording-log/README.md @@ -112,6 +112,7 @@ Schema-limit options: - `maxScannedLines` - `maxMissingSequenceWarnings` - `maxUnknownFieldCount` +- `maxWarnings` Ratified v1 defaults (review decision): @@ -123,6 +124,7 @@ Ratified v1 defaults (review decision): - `maxScannedLines`: `20000` - `maxMissingSequenceWarnings`: `1000` - `maxUnknownFieldCount`: `100` +- `maxWarnings`: `10000` - search default limit: `100` - search max limit: `1000` @@ -168,7 +170,7 @@ pnpm --filter @devolutions/session-recording-log build - Added bounded missing-sequence emission to prevent unbounded gap loops on sparse large `seq` ranges. - Replaced recursive depth traversal with iterative depth checks to prevent stack-overflow failure on deeply nested JSON. - Added malformed-heavy input bounding with `maxScannedLines`, and finite default `maxParsedEntries`. -- Fixed empty-log lifecycle classification (`completionState: 'complete'` when no valid entries). +- Fixed lifecycle classification so only empty/whitespace-only input is `complete`; non-empty input without `session.end` remains `ended-unexpectedly`. - Added top-level unknown field cap (`maxUnknownFieldCount`) with `entry-limit-exceeded` warning when truncated. - Split unknown future events into explicit `unknown-event-type` warning code. - Clarified alias intent: `SessionRecordingLogRecord` is retained for AD historical naming compatibility. From 23b0234d0fa03b25c5172757a75112652d0a5e55 Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 16:23:11 -0400 Subject: [PATCH 11/13] fix(webapp): tighten slog parser scan and sequence diagnostics Address latest review findings:\n\n- enforce maxScannedLines on all physical lines (including blank lines)\n- avoid pre-scanning full files for unterminated-final-line source metadata\n- detect missing sequence gaps from start-of-stream baseline\n- add @types/node dev dependency for isolated package type-check reliability\n- add regression tests for blank-line scan cap and leading missing-sequence detection\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../session-recording-log/package.json | 1 + .../session-recording-log/src/parser.test.ts | 22 ++++ .../session-recording-log/src/parser.ts | 40 +++---- webapp/pnpm-lock.yaml | 105 +++--------------- 4 files changed, 53 insertions(+), 115 deletions(-) diff --git a/webapp/packages/session-recording-log/package.json b/webapp/packages/session-recording-log/package.json index d8b44d444..75cc1fbfb 100644 --- a/webapp/packages/session-recording-log/package.json +++ b/webapp/packages/session-recording-log/package.json @@ -18,6 +18,7 @@ "test": "vitest run" }, "devDependencies": { + "@types/node": "^20.19.0", "typescript": "~5.6.2", "vite": "^6.2.1", "vite-plugin-dts": "^4.3.0", diff --git a/webapp/packages/session-recording-log/src/parser.test.ts b/webapp/packages/session-recording-log/src/parser.test.ts index bbe5bf72d..94aaf0064 100644 --- a/webapp/packages/session-recording-log/src/parser.test.ts +++ b/webapp/packages/session-recording-log/src/parser.test.ts @@ -226,6 +226,14 @@ describe('parseSessionRecordingLog', () => { expect(result.warnings.some((warning) => warning.code === 'entry-limit-exceeded')).toBe(true); }); + it('counts blank lines toward scanned line limits', () => { + const text = Array.from({ length: 20 }, () => '').join('\n'); + + const result = parseSessionRecordingLog(text, { maxScannedLines: 5 }); + + expect(result.warnings.some((warning) => warning.code === 'entry-limit-exceeded')).toBe(true); + }); + it('treats an empty file as complete and emits no missing-session warnings', () => { const result = parseSessionRecordingLog(''); @@ -294,6 +302,20 @@ describe('parseSessionRecordingLog', () => { ).toBe(true); }); + it('reports missing sequences before the first observed sequence', () => { + const text = '{"timestamp":"2026-07-16T10:00:00.000Z","seq":5,"event":"session.end","description":"End"}\n'; + + const result = parseSessionRecordingLog(text, { maxMissingSequenceWarnings: 3 }); + + expect(result.warnings.filter((warning) => warning.code === 'missing-sequence')).toHaveLength(3); + expect( + result.warnings.some( + (warning) => + warning.code === 'entry-limit-exceeded' && warning.message.includes('additional missing sequences'), + ), + ).toBe(true); + }); + it('preserves __proto__ keys in parameters and unknown fields as data', () => { const text = [ '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start","parameters":{"__proto__":"kept"},"__proto__":{"safe":"value"}}', diff --git a/webapp/packages/session-recording-log/src/parser.ts b/webapp/packages/session-recording-log/src/parser.ts index 82301d28c..a386da1fe 100644 --- a/webapp/packages/session-recording-log/src/parser.ts +++ b/webapp/packages/session-recording-log/src/parser.ts @@ -119,17 +119,6 @@ function createLineIterator(text: string): Iterable<{ line: string; sourceLineNu }; } -function getFinalLineNumber(text: string): number { - let lineNumber = 1; - for (const character of text) { - if (character === '\n') { - lineNumber += 1; - } - } - - return lineNumber; -} - function normalizeNonNegativeLimit(value: number | undefined, fallback: number): number { if (value === undefined || !Number.isFinite(value) || value < 0) { return fallback; @@ -420,28 +409,21 @@ export function parseSessionRecordingLog( const warningCollector = createWarningCollector(maxWarnings); const warnOnInvalidTimestamp = options?.warnOnInvalidTimestamp ?? true; let encounteredNonEmptyLine = false; - let scannedNonEmptyLines = 0; + let scannedLines = 0; const hasTrailingNewline = text.endsWith('\n'); - if (!hasTrailingNewline && text.length > 0) { - warningCollector.add( - createWarning('unterminated-final-line', 'final line does not end with newline', { - sourceLineNumber: getFinalLineNumber(text), - }), - ); - } - for (const { line, sourceLineNumber } of createLineIterator(text)) { + scannedLines += 1; + if (scannedLines > maxScannedLines) { + warningCollector.add(createWarning('entry-limit-exceeded', 'scanned line limit exceeded', { sourceLineNumber })); + break; + } + if (line.trim().length === 0) { continue; } encounteredNonEmptyLine = true; - scannedNonEmptyLines += 1; - if (scannedNonEmptyLines > maxScannedLines) { - warningCollector.add(createWarning('entry-limit-exceeded', 'scanned line limit exceeded', { sourceLineNumber })); - break; - } if (entries.length >= maxParsedEntries) { warningCollector.add(createWarning('entry-limit-exceeded', 'parsed entry limit exceeded', { sourceLineNumber })); @@ -489,6 +471,10 @@ export function parseSessionRecordingLog( }); } + if (!hasTrailingNewline && text.length > 0) { + warningCollector.add(createWarning('unterminated-final-line', 'final line does not end with newline')); + } + const seenSequences = new Set(); let previousSeq = -1; @@ -517,8 +503,8 @@ export function parseSessionRecordingLog( previousSeq = seq; } - if (seenSequences.size > 1) { - const sortedSeq = Array.from(seenSequences).sort((left, right) => left - right); + if (seenSequences.size > 0) { + const sortedSeq = [-1, ...Array.from(seenSequences).sort((left, right) => left - right)]; let missingTotal = 0; for (let index = 1; index < sortedSeq.length; index += 1) { diff --git a/webapp/pnpm-lock.yaml b/webapp/pnpm-lock.yaml index ec737ebb7..295ff1c75 100644 --- a/webapp/pnpm-lock.yaml +++ b/webapp/pnpm-lock.yaml @@ -247,21 +247,24 @@ importers: packages/session-recording-log: devDependencies: + '@types/node': + specifier: ^20.19.0 + version: 20.19.27 typescript: specifier: ~5.6.2 version: 5.6.3 vite: specifier: ^6.2.1 - version: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + version: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) vite-plugin-dts: specifier: ^4.3.0 - version: 4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.6.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) vite-plugin-static-copy: specifier: ^2.3.0 - version: 2.3.2(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) + version: 2.3.2(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) vitest: specifier: ^3.1.1 - version: 3.2.7(@types/node@22.19.3)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + version: 3.2.7(@types/node@20.19.27)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) packages/shadow-player: devDependencies: @@ -10246,15 +10249,6 @@ snapshots: magic-string: 0.30.21 optionalDependencies: vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) - optional: true - - '@vitest/mocker@3.2.7(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1))': - dependencies: - '@vitest/spy': 3.2.7 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) '@vitest/pretty-format@3.2.7': dependencies: @@ -14333,28 +14327,6 @@ snapshots: - terser - tsx - yaml - optional: true - - vite-node@3.2.4(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml vite-plugin-bundle-css@0.1.1(rollup@4.59.0)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: @@ -14363,18 +14335,18 @@ snapshots: rollup: 4.59.0 vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) - vite-plugin-dts@4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.7.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-dts@4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.6.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: '@microsoft/api-extractor': 7.55.2(@types/node@20.19.27) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) '@volar/typescript': 2.4.27 - '@vue/language-core': 2.2.0(typescript@5.7.3) + '@vue/language-core': 2.2.0(typescript@5.6.3) compare-versions: 6.1.1 debug: 4.4.3 kolorist: 1.8.0 local-pkg: 1.1.2 magic-string: 0.30.21 - typescript: 5.7.3 + typescript: 5.6.3 optionalDependencies: vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: @@ -14382,26 +14354,26 @@ snapshots: - rollup - supports-color - vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-dts@4.5.4(@types/node@20.19.27)(rollup@4.59.0)(typescript@5.7.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: - '@microsoft/api-extractor': 7.55.2(@types/node@22.19.3) + '@microsoft/api-extractor': 7.55.2(@types/node@20.19.27) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) '@volar/typescript': 2.4.27 - '@vue/language-core': 2.2.0(typescript@5.6.3) + '@vue/language-core': 2.2.0(typescript@5.7.3) compare-versions: 6.1.1 debug: 4.4.3 kolorist: 1.8.0 local-pkg: 1.1.2 magic-string: 0.30.21 - typescript: 5.6.3 + typescript: 5.7.3 optionalDependencies: - vite: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): + vite-plugin-dts@4.5.4(@types/node@22.19.3)(rollup@4.59.0)(typescript@5.6.3)(vite@5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)): dependencies: '@microsoft/api-extractor': 7.55.2(@types/node@22.19.3) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) @@ -14414,7 +14386,7 @@ snapshots: magic-string: 0.30.21 typescript: 5.6.3 optionalDependencies: - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) + vite: 5.4.21(@types/node@22.19.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - rollup @@ -14552,49 +14524,6 @@ snapshots: - terser - tsx - yaml - optional: true - - vitest@3.2.7(@types/node@22.19.3)(jiti@2.6.1)(jsdom@20.0.3)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1): - dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.7 - '@vitest/mocker': 3.2.7(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1)) - '@vitest/pretty-format': 3.2.7 - '@vitest/runner': 3.2.7 - '@vitest/snapshot': 3.2.7 - '@vitest/spy': 3.2.7 - '@vitest/utils': 3.2.7 - chai: 5.3.3 - debug: 4.4.3 - expect-type: 1.4.0 - magic-string: 0.30.21 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) - vite-node: 3.2.4(@types/node@22.19.3)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.43.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 22.19.3 - jsdom: 20.0.3 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml vscode-uri@3.1.0: {} From f32b3688f371623e1947cc0ecb90226bf1fc7fbb Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 16:41:02 -0400 Subject: [PATCH 12/13] fix(webapp): harden slog parser truncation lifecycle semantics Address latest review feedback and proactive pre-push review:\n\n- mark non-empty truncated inputs as ended-unexpectedly even when scan limit is consumed by blank prefixes\n- classify lifecycle events from original event tokens and reject overlong event fields\n- preserve scan-limit safeguards while avoiding lifecycle misclassification via event truncation\n- extend regression coverage for scan-limit blank-prefix and overlong event semantics\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../session-recording-log/src/parser.test.ts | 37 +++++++++- .../session-recording-log/src/parser.ts | 70 +++++++++++++------ 2 files changed, 82 insertions(+), 25 deletions(-) diff --git a/webapp/packages/session-recording-log/src/parser.test.ts b/webapp/packages/session-recording-log/src/parser.test.ts index 94aaf0064..86c8cc453 100644 --- a/webapp/packages/session-recording-log/src/parser.test.ts +++ b/webapp/packages/session-recording-log/src/parser.test.ts @@ -234,6 +234,24 @@ describe('parseSessionRecordingLog', () => { expect(result.warnings.some((warning) => warning.code === 'entry-limit-exceeded')).toBe(true); }); + it('marks truncated non-empty input as ended unexpectedly when scan limit is zero', () => { + const text = '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start"}\n'; + + const result = parseSessionRecordingLog(text, { maxScannedLines: 0 }); + + expect(result.completionState).toBe('ended-unexpectedly'); + expect(result.warnings.some((warning) => warning.code === 'entry-limit-exceeded')).toBe(true); + }); + + it('marks truncated input as ended unexpectedly when blank prefix consumes scan limit', () => { + const text = '\n{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start"}\n'; + + const result = parseSessionRecordingLog(text, { maxScannedLines: 0 }); + + expect(result.completionState).toBe('ended-unexpectedly'); + expect(result.warnings.some((warning) => warning.code === 'entry-limit-exceeded')).toBe(true); + }); + it('treats an empty file as complete and emits no missing-session warnings', () => { const result = parseSessionRecordingLog(''); @@ -316,6 +334,21 @@ describe('parseSessionRecordingLog', () => { ).toBe(true); }); + it('rejects overlong events without deriving completion from truncated text', () => { + const text = + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.end.extra","description":"End-like"}\n'; + + const result = parseSessionRecordingLog(text, { maxStringLength: 11 }); + + expect(result.entries).toHaveLength(0); + expect(result.completionState).toBe('ended-unexpectedly'); + expect( + result.warnings.some( + (warning) => warning.code === 'invalid-field' && warning.message === 'event exceeded max string length', + ), + ).toBe(true); + }); + it('preserves __proto__ keys in parameters and unknown fields as data', () => { const text = [ '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start","parameters":{"__proto__":"kept"},"__proto__":{"safe":"value"}}', @@ -333,8 +366,8 @@ describe('parseSessionRecordingLog', () => { it('warns and preserves the first value on parameter key collisions after truncation', () => { const text = [ - '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start","parameters":{"abcd1":"one","abcd2":"two"}}', - '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"session.end","description":"End"}', + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"evt1","description":"Start","parameters":{"abcd1":"one","abcd2":"two"}}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"evt2","description":"End"}', '', ].join('\n'); diff --git a/webapp/packages/session-recording-log/src/parser.ts b/webapp/packages/session-recording-log/src/parser.ts index a386da1fe..dfa5ff0a8 100644 --- a/webapp/packages/session-recording-log/src/parser.ts +++ b/webapp/packages/session-recording-log/src/parser.ts @@ -288,7 +288,7 @@ function parseLineRecord( 'maxStringLength' | 'maxParameterCount' | 'warnOnInvalidTimestamp' | 'maxUnknownFieldCount' > >, -): SessionRecordingLogEntry | null { +): { entry: SessionRecordingLogEntry; originalEvent: string } | null { const timestamp = normalizeString( parsed.timestamp, 'timestamp', @@ -303,10 +303,20 @@ function parseLineRecord( sourceLineNumber, options.maxStringLength, ); - const event = normalizeString(parsed.event, 'event', warningCollector, sourceLineNumber, options.maxStringLength); const seq = parsed.seq; + const rawEvent = parsed.event; - if (timestamp === null || description === null || event === null) { + if (typeof rawEvent !== 'string') { + warningCollector.add(createWarning('invalid-field', 'event must be a string', { sourceLineNumber })); + return null; + } + + if (rawEvent.length > options.maxStringLength) { + warningCollector.add(createWarning('invalid-field', 'event exceeded max string length', { sourceLineNumber })); + return null; + } + + if (timestamp === null || description === null) { return null; } @@ -325,7 +335,7 @@ function parseLineRecord( ); } - if (!KNOWN_EVENTS.has(event as SessionRecordingLogKnownEvent)) { + if (!KNOWN_EVENTS.has(rawEvent as SessionRecordingLogKnownEvent)) { warningCollector.add( createWarning('unknown-event-type', 'event is not one of the known lifecycle events', { sourceLineNumber, seq }), ); @@ -372,17 +382,20 @@ function parseLineRecord( } return { - timestamp, - seq, - event, - description, - ...(actor === null || actor === undefined ? {} : { actor }), - ...(locale === null || locale === undefined ? {} : { locale }), - ...(host === null || host === undefined ? {} : { host }), - ...(sessionType === null || sessionType === undefined ? {} : { sessionType }), - ...(object === null || object === undefined ? {} : { object }), - ...(parameters ? { parameters } : {}), - ...(Object.keys(unknownFields).length === 0 ? {} : { unknownFields }), + entry: { + timestamp, + seq, + event: rawEvent, + description, + ...(actor === null || actor === undefined ? {} : { actor }), + ...(locale === null || locale === undefined ? {} : { locale }), + ...(host === null || host === undefined ? {} : { host }), + ...(sessionType === null || sessionType === undefined ? {} : { sessionType }), + ...(object === null || object === undefined ? {} : { object }), + ...(parameters ? { parameters } : {}), + ...(Object.keys(unknownFields).length === 0 ? {} : { unknownFields }), + }, + originalEvent: rawEvent, }; } @@ -409,22 +422,29 @@ export function parseSessionRecordingLog( const warningCollector = createWarningCollector(maxWarnings); const warnOnInvalidTimestamp = options?.warnOnInvalidTimestamp ?? true; let encounteredNonEmptyLine = false; + let truncatedByScannedLineLimit = false; + let hasSessionStart = false; + let hasSessionEnd = false; let scannedLines = 0; const hasTrailingNewline = text.endsWith('\n'); for (const { line, sourceLineNumber } of createLineIterator(text)) { + const isNonEmptyLine = line.trim().length > 0; + if (isNonEmptyLine) { + encounteredNonEmptyLine = true; + } + scannedLines += 1; if (scannedLines > maxScannedLines) { + truncatedByScannedLineLimit = true; warningCollector.add(createWarning('entry-limit-exceeded', 'scanned line limit exceeded', { sourceLineNumber })); break; } - if (line.trim().length === 0) { + if (!isNonEmptyLine) { continue; } - encounteredNonEmptyLine = true; - if (entries.length >= maxParsedEntries) { warningCollector.add(createWarning('entry-limit-exceeded', 'parsed entry limit exceeded', { sourceLineNumber })); break; @@ -463,8 +483,14 @@ export function parseSessionRecordingLog( continue; } + if (record.originalEvent === 'session.start') { + hasSessionStart = true; + } else if (record.originalEvent === 'session.end') { + hasSessionEnd = true; + } + entries.push({ - entry: record, + entry: record.entry, sourceLineNumber, sourceIndex: entries.length, sourceText: line, @@ -543,9 +569,6 @@ export function parseSessionRecordingLog( } } - const hasSessionStart = entries.some((parsedEntry) => parsedEntry.entry.event === 'session.start'); - const hasSessionEnd = entries.some((parsedEntry) => parsedEntry.entry.event === 'session.end'); - if (entries.length > 0 && !hasSessionStart) { warningCollector.add(createWarning('missing-session-start', 'session.start is missing')); } @@ -556,6 +579,7 @@ export function parseSessionRecordingLog( return { entries, warnings: warningCollector.warnings, - completionState: !encounteredNonEmptyLine || hasSessionEnd ? 'complete' : 'ended-unexpectedly', + completionState: + hasSessionEnd || (!encounteredNonEmptyLine && !truncatedByScannedLineLimit) ? 'complete' : 'ended-unexpectedly', }; } From d345879958c700da4c171d5f050d5d4f37614ede Mon Sep 17 00:00:00 2001 From: Krista House Date: Fri, 17 Jul 2026 16:58:00 -0400 Subject: [PATCH 13/13] fix(webapp): cap retained slog source-text memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address final PR review and proactive follow-up:\n\n- add aggregate retained source-text byte budget (maxRetainedSourceTextBytes) with bounded default\n- emit ntry-limit-exceeded warning when source-text retention budget is exceeded\n- ensure lifecycle completeness flags derive only from accepted entries (no dropped-end false complete)\n- restore threshold early-exit for UTF-8 line-size limit checks\n- add parser regression tests for source-text budget behavior and dropped-end completeness semantics\n- document new parser option/default in package README\n\nIssue: DGW-405\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../packages/session-recording-log/README.md | 2 ++ .../session-recording-log/src/model.ts | 1 + .../session-recording-log/src/parser.test.ts | 33 +++++++++++++++++++ .../session-recording-log/src/parser.ts | 26 +++++++++++---- 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/webapp/packages/session-recording-log/README.md b/webapp/packages/session-recording-log/README.md index e32b4a1c6..d59ee473f 100644 --- a/webapp/packages/session-recording-log/README.md +++ b/webapp/packages/session-recording-log/README.md @@ -105,6 +105,7 @@ Architecture Decision Record 2 (ADR-2) canonical contract: Schema-limit options: - `maxLineLengthBytes` +- `maxRetainedSourceTextBytes` - `maxStringLength` - `maxParameterCount` - `maxObjectDepth` @@ -117,6 +118,7 @@ Schema-limit options: Ratified v1 defaults (review decision): - `maxLineLengthBytes`: `262144` (256 KiB) +- `maxRetainedSourceTextBytes`: `8388608` (8 MiB) - `maxStringLength`: `4096` - `maxParameterCount`: `200` - `maxObjectDepth`: `8` diff --git a/webapp/packages/session-recording-log/src/model.ts b/webapp/packages/session-recording-log/src/model.ts index d8cfe1807..2aec3d85d 100644 --- a/webapp/packages/session-recording-log/src/model.ts +++ b/webapp/packages/session-recording-log/src/model.ts @@ -52,6 +52,7 @@ export type SessionRecordingLogCompletionState = SessionRecordingLogParseResult[ export interface ParseSessionRecordingLogOptions { warnOnInvalidTimestamp?: boolean; maxLineLengthBytes?: number; + maxRetainedSourceTextBytes?: number; maxStringLength?: number; maxParameterCount?: number; maxObjectDepth?: number; diff --git a/webapp/packages/session-recording-log/src/parser.test.ts b/webapp/packages/session-recording-log/src/parser.test.ts index 86c8cc453..a650c4a8d 100644 --- a/webapp/packages/session-recording-log/src/parser.test.ts +++ b/webapp/packages/session-recording-log/src/parser.test.ts @@ -175,6 +175,39 @@ describe('parseSessionRecordingLog', () => { ).toBe(true); }); + it('caps retained source text bytes across parsed entries', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start"}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"session.action","description":"Action"}', + '{"timestamp":"2026-07-16T10:00:02.000Z","seq":2,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text, { maxRetainedSourceTextBytes: 120 }); + + expect(result.entries).toHaveLength(1); + expect( + result.warnings.some( + (warning) => + warning.code === 'entry-limit-exceeded' && warning.message === 'retained source text byte budget exceeded', + ), + ).toBe(true); + }); + + it('does not mark session complete when source-text budget drops session.end entry', () => { + const text = [ + '{"timestamp":"2026-07-16T10:00:00.000Z","seq":0,"event":"session.start","description":"Start"}', + '{"timestamp":"2026-07-16T10:00:01.000Z","seq":1,"event":"session.end","description":"End"}', + '', + ].join('\n'); + + const result = parseSessionRecordingLog(text, { maxRetainedSourceTextBytes: 100 }); + + expect(result.entries).toHaveLength(1); + expect(result.completionState).toBe('ended-unexpectedly'); + expect(result.warnings.some((warning) => warning.code === 'missing-session-end')).toBe(true); + }); + it('caps warning volume at the default aggregate limit', () => { const invalidParameters = Array.from({ length: 200 }, (_, index) => `"p${index}":${index}`).join(','); const lines = Array.from( diff --git a/webapp/packages/session-recording-log/src/parser.ts b/webapp/packages/session-recording-log/src/parser.ts index dfa5ff0a8..ac66ec88f 100644 --- a/webapp/packages/session-recording-log/src/parser.ts +++ b/webapp/packages/session-recording-log/src/parser.ts @@ -23,6 +23,7 @@ const KNOWN_FIELDS = new Set([ ]); const DEFAULT_MAX_LINE_LENGTH_BYTES = 256 * 1024; +const DEFAULT_MAX_RETAINED_SOURCE_TEXT_BYTES = 8 * 1024 * 1024; const DEFAULT_MAX_STRING_LENGTH = 4096; const DEFAULT_MAX_PARAMETER_COUNT = 200; const DEFAULT_MAX_OBJECT_DEPTH = 8; @@ -127,7 +128,7 @@ function normalizeNonNegativeLimit(value: number | undefined, fallback: number): return Math.trunc(value); } -function exceedsMaxLineLengthBytes(value: string, maxLineLengthBytes: number): boolean { +function getUtf8ByteLength(value: string, maxBytes?: number): number { let byteLength = 0; for (let index = 0; index < value.length; index += 1) { @@ -148,13 +149,12 @@ function exceedsMaxLineLengthBytes(value: string, maxLineLengthBytes: number): b } else { byteLength += 3; } - - if (byteLength > maxLineLengthBytes) { - return true; + if (maxBytes !== undefined && byteLength > maxBytes) { + return byteLength; } } - return false; + return byteLength; } function exceedsMaxObjectDepth(value: unknown, maxObjectDepth: number): boolean { @@ -405,6 +405,10 @@ export function parseSessionRecordingLog( ): SessionRecordingLogParseResult { const entries: ParsedSessionRecordingLogEntry[] = []; const maxLineLengthBytes = normalizeNonNegativeLimit(options?.maxLineLengthBytes, DEFAULT_MAX_LINE_LENGTH_BYTES); + const maxRetainedSourceTextBytes = normalizeNonNegativeLimit( + options?.maxRetainedSourceTextBytes, + DEFAULT_MAX_RETAINED_SOURCE_TEXT_BYTES, + ); const maxStringLength = normalizeNonNegativeLimit(options?.maxStringLength, DEFAULT_MAX_STRING_LENGTH); const maxParameterCount = normalizeNonNegativeLimit(options?.maxParameterCount, DEFAULT_MAX_PARAMETER_COUNT); const maxObjectDepth = normalizeNonNegativeLimit(options?.maxObjectDepth, DEFAULT_MAX_OBJECT_DEPTH); @@ -425,6 +429,7 @@ export function parseSessionRecordingLog( let truncatedByScannedLineLimit = false; let hasSessionStart = false; let hasSessionEnd = false; + let retainedSourceTextBytes = 0; let scannedLines = 0; const hasTrailingNewline = text.endsWith('\n'); @@ -450,7 +455,8 @@ export function parseSessionRecordingLog( break; } - if (exceedsMaxLineLengthBytes(line, maxLineLengthBytes)) { + const lineByteLength = getUtf8ByteLength(line, maxLineLengthBytes); + if (lineByteLength > maxLineLengthBytes) { warningCollector.add(createWarning('entry-limit-exceeded', 'line exceeds max byte size', { sourceLineNumber })); continue; } @@ -483,6 +489,14 @@ export function parseSessionRecordingLog( continue; } + if (retainedSourceTextBytes + lineByteLength > maxRetainedSourceTextBytes) { + warningCollector.add( + createWarning('entry-limit-exceeded', 'retained source text byte budget exceeded', { sourceLineNumber }), + ); + break; + } + retainedSourceTextBytes += lineByteLength; + if (record.originalEvent === 'session.start') { hasSessionStart = true; } else if (record.originalEvent === 'session.end') {