From 1bff64537f26474b3de27e8d5bb2aeb8a02b0193 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 24 Oct 2025 08:52:21 +0800 Subject: [PATCH 01/30] Introduce related field effects and states --- editor/blockAttributes.js | 39 ++++++++++++++++++++++++++++++++ editor/decoration.js | 44 +++++++++++++++++++++++++++++------- editor/index.css | 4 ++++ editor/index.js | 39 ++++++++++++++++++++++++++++++-- runtime/index.js | 47 +++++++++++++++++++++++++++++---------- 5 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 editor/blockAttributes.js diff --git a/editor/blockAttributes.js b/editor/blockAttributes.js new file mode 100644 index 0000000..4b67fd5 --- /dev/null +++ b/editor/blockAttributes.js @@ -0,0 +1,39 @@ +import {StateField, StateEffect} from "@codemirror/state"; + +/** + * Effect to set block attributes + * @type {StateEffect<{from: number, to: number, attributes: Object}[]>} + */ +export const setBlockAttributesEffect = StateEffect.define(); + +/** + * StateField that stores block attributes keyed by line ranges + * Structure: Array of {from, to, attributes} + * @type {StateField<{from: number, to: number, attributes: Object}[]>} + */ +export const blockAttributesField = StateField.define({ + create() { + return []; + }, + update(blocks, tr) { + // If document changed, map existing blocks to new positions + if (tr.docChanged) { + blocks = blocks.map(block => ({ + from: tr.changes.mapPos(block.from), + to: tr.changes.mapPos(block.to), + attributes: block.attributes, + })); + } + + // Check for setBlockAttributesEffect + for (const effect of tr.effects) { + if (effect.is(setBlockAttributesEffect)) { + blocks = effect.value; + } + } + + return blocks; + }, +}); + +export const blockAttributes = blockAttributesField.extension; diff --git a/editor/decoration.js b/editor/decoration.js index 6d8d038..5b2272d 100644 --- a/editor/decoration.js +++ b/editor/decoration.js @@ -1,21 +1,46 @@ import {Decoration, ViewPlugin, ViewUpdate, EditorView} from "@codemirror/view"; import {outputLinesField} from "./outputLines"; -import {RangeSetBuilder} from "@codemirror/state"; +import {blockAttributesField} from "./blockAttributes"; +import {RangeSet, RangeSetBuilder} from "@codemirror/state"; const highlight = Decoration.line({attributes: {class: "cm-output-line"}}); const errorHighlight = Decoration.line({attributes: {class: "cm-output-line cm-error-line"}}); +const compactLineDecoration = Decoration.line({attributes: {class: "cm-output-line cm-compact-line"}}); // const linePrefix = Decoration.mark({attributes: {class: "cm-output-line-prefix"}}); // const lineContent = Decoration.mark({attributes: {class: "cm-output-line-content"}}); -function createWidgets(lines) { - const builder = new RangeSetBuilder(); +function createWidgets(lines, blockAttributes, state) { + // Build the range set for output lines. + const builder1 = new RangeSetBuilder(); + // Add output line decorations for (const {from, type} of lines) { - if (type === "output") builder.add(from, from, highlight); - else if (type === "error") builder.add(from, from, errorHighlight); + if (type === "output") builder1.add(from, from, highlight); + else if (type === "error") builder1.add(from, from, errorHighlight); // builder.add(from, from + 3, linePrefix); // builder.add(from + 4, to, lineContent); } - return builder.finish(); + const set1 = builder1.finish(); + + // Build the range set for block attributes. + const builder2 = new RangeSetBuilder(); + // Add block attribute decorations + for (const {from, to, attributes} of blockAttributes) { + // Apply decorations to each line in the block range + const startLine = state.doc.lineAt(from); + const endLine = state.doc.lineAt(to); + + for (let lineNum = startLine.number; lineNum <= endLine.number; lineNum++) { + const line = state.doc.line(lineNum); + if (attributes.compact === true) { + builder2.add(line.from, line.from, compactLineDecoration); + } + } + } + const set2 = builder2.finish(); + + // Range sets are required to be sorted. Fortunately, they provide a method + // to merge multiple range sets into a single sorted range set. + return RangeSet.join([set1, set2]); } export const outputDecoration = ViewPlugin.fromClass( @@ -28,14 +53,17 @@ export const outputDecoration = ViewPlugin.fromClass( /** @param {EditorView} view */ constructor(view) { - this.#decorations = createWidgets(view.state.field(outputLinesField)); + const outputLines = view.state.field(outputLinesField); + const blockAttributes = view.state.field(blockAttributesField); + this.#decorations = createWidgets(outputLines, blockAttributes, view.state); } /** @param {ViewUpdate} update */ update(update) { const newOutputLines = update.state.field(outputLinesField); + const newBlockAttributes = update.state.field(blockAttributesField); // A possible optimization would be to only update the changed lines. - this.#decorations = createWidgets(newOutputLines); + this.#decorations = createWidgets(newOutputLines, newBlockAttributes, update.state); } }, {decorations: (v) => v.decorations}, diff --git a/editor/index.css b/editor/index.css index 29cdb12..110964f 100644 --- a/editor/index.css +++ b/editor/index.css @@ -143,6 +143,10 @@ /*background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='%237d7d7d' fill-opacity='0.4' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E");*/ } + .cm-output-line.cm-compact-line { + line-height: 1; + } + /* We can't see background color, which is conflict with selection background color. * So we use svg pattern to simulate the background color. */ diff --git a/editor/index.js b/editor/index.js index 2b1d2bd..2e9ab1c 100644 --- a/editor/index.js +++ b/editor/index.js @@ -11,6 +11,7 @@ import * as eslint from "eslint-linter-browserify"; import {createRuntime} from "../runtime/index.js"; import {outputDecoration} from "./decoration.js"; import {outputLines} from "./outputLines.js"; +import {blockAttributes, setBlockAttributesEffect} from "./blockAttributes.js"; // import {outputProtection} from "./protection.js"; import {dispatch as d3Dispatch} from "d3-dispatch"; import {controls} from "./controls/index.js"; @@ -68,6 +69,7 @@ export function createEditor(container, options) { ]), javascriptLanguage.data.of({autocomplete: rechoCompletion}), outputLines, + blockAttributes, outputDecoration, controls(runtimeRef), // Disable this for now, because it prevents copying/pasting the code. @@ -89,9 +91,42 @@ export function createEditor(container, options) { window.addEventListener("keyup", onKeyUp); } - function dispatch(changes) { + function dispatch({changes, blockAttributes: blockAttrs}) { + // Map block attributes through the changes to get correct final positions + const effects = []; + + if (blockAttrs && blockAttrs.length > 0) { + // We need to map the block positions through the changes + // Create a simplified change set mapper + const sortedChanges = [...changes].sort((a, b) => a.from - b.from); + + const mapPos = (pos) => { + let mapped = pos; + for (const change of sortedChanges) { + if (change.from <= pos) { + const inserted = change.insert ? change.insert.length : 0; + const deleted = change.to ? change.to - change.from : 0; + mapped += inserted - deleted; + } + } + return mapped; + }; + + const mappedBlockAttrs = blockAttrs.map(({from, to, attributes}) => ({ + from: mapPos(from), + to: mapPos(to), + attributes, + })); + + effects.push(setBlockAttributesEffect.of(mappedBlockAttrs)); + } + // Mark this transaction as from runtime so that it will not be filtered out. - view.dispatch({changes, annotations: [Transaction.remote.of("runtime")]}); + view.dispatch({ + changes, + effects, + annotations: [Transaction.remote.of("runtime")], + }); } function onChange(update) { diff --git a/runtime/index.js b/runtime/index.js index 86a63c5..637306e 100644 --- a/runtime/index.js +++ b/runtime/index.js @@ -125,7 +125,16 @@ export function createRuntime(initialCode) { } } - dispatch(changes); + // Collect block attributes - we need to map positions through the changes + const blockAttributes = nodes + .filter((node) => Object.keys(node.state.attributes).length > 0) + .map((node) => ({ + from: node.start, + to: node.end, + attributes: node.state.attributes, + })); + + dispatch(changes, blockAttributes); }, 0); function setCode(newCode) { @@ -136,8 +145,8 @@ export function createRuntime(initialCode) { isRunning = value; } - function dispatch(changes) { - dispatcher.call("changes", null, changes); + function dispatch(changes, blockAttributes = []) { + dispatcher.call("changes", null, {changes, blockAttributes}); } function onChanges(callback) { @@ -294,7 +303,14 @@ export function createRuntime(initialCode) { for (const node of enter) { const vid = uid(); const {inputs, body, outputs, error = null} = node.transpiled; - const state = {values: [], variables: [], error: null, syntaxError: error, doc: false}; + const state = { + values: [], + variables: [], + error: null, + syntaxError: error, + doc: false, + attributes: Object.create(null), + }; node.state = state; const v = main.variable(observer(state), {shadow: {}}); if (inputs.includes("echo")) { @@ -304,14 +320,21 @@ export function createRuntime(initialCode) { inputs.filter((i) => i !== "echo" && i !== "clear"), () => { const version = v._version; // Capture version on input change. - return (value, ...args) => { - if (version < docVersion) throw new Error("stale echo"); - else if (state.variables[0] !== v) throw new Error("stale echo"); - else if (version > docVersion) clear(state); - docVersion = version; - echo(state, value, ...args); - return args.length ? [value, ...args] : value; - }; + return Object.assign( + function (value, ...args) { + if (version < docVersion) throw new Error("stale echo"); + else if (state.variables[0] !== v) throw new Error("stale echo"); + else if (version > docVersion) clear(state); + docVersion = version; + echo(state, value, ...args); + return args.length ? [value, ...args] : value; + }, + { + set: function (key, value) { + state.attributes[key] = value; + }, + }, + ); }, ); v._shadow.set("echo", vd); From 721b59f57449c1c9abb66d972e09e3eb307331db Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:21:35 +0800 Subject: [PATCH 02/30] Initialize block metadata using runtime --- editor/blockAttributes.js | 39 ---- editor/blockMetadata.ts | 54 ++++++ editor/decoration.js | 46 +++-- editor/index.css | 12 ++ editor/index.js | 37 +--- lib/IntervalTree.ts | 369 +++++++++++++++++++++++++++++++++++ runtime/index.js | 94 ++++++--- test/IntervalTree.spec.js | 395 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 929 insertions(+), 117 deletions(-) delete mode 100644 editor/blockAttributes.js create mode 100644 editor/blockMetadata.ts create mode 100644 lib/IntervalTree.ts create mode 100644 test/IntervalTree.spec.js diff --git a/editor/blockAttributes.js b/editor/blockAttributes.js deleted file mode 100644 index 4b67fd5..0000000 --- a/editor/blockAttributes.js +++ /dev/null @@ -1,39 +0,0 @@ -import {StateField, StateEffect} from "@codemirror/state"; - -/** - * Effect to set block attributes - * @type {StateEffect<{from: number, to: number, attributes: Object}[]>} - */ -export const setBlockAttributesEffect = StateEffect.define(); - -/** - * StateField that stores block attributes keyed by line ranges - * Structure: Array of {from, to, attributes} - * @type {StateField<{from: number, to: number, attributes: Object}[]>} - */ -export const blockAttributesField = StateField.define({ - create() { - return []; - }, - update(blocks, tr) { - // If document changed, map existing blocks to new positions - if (tr.docChanged) { - blocks = blocks.map(block => ({ - from: tr.changes.mapPos(block.from), - to: tr.changes.mapPos(block.to), - attributes: block.attributes, - })); - } - - // Check for setBlockAttributesEffect - for (const effect of tr.effects) { - if (effect.is(setBlockAttributesEffect)) { - blocks = effect.value; - } - } - - return blocks; - }, -}); - -export const blockAttributes = blockAttributesField.extension; diff --git a/editor/blockMetadata.ts b/editor/blockMetadata.ts new file mode 100644 index 0000000..2fd826d --- /dev/null +++ b/editor/blockMetadata.ts @@ -0,0 +1,54 @@ +import {StateField, StateEffect} from "@codemirror/state"; + +export const blockMetadataEffect = StateEffect.define(); + +type BlockMetadata = { + output: {from: number; to: number} | null; + source: {from: number; to: number}; + attributes: Record; +}; + +export const blockMetadataField = StateField.define({ + create() { + return []; + }, + update(blocks, tr) { + // Find if the block attributes effect is present. + let blocksFromEffect: BlockMetadata[] | null = null; + for (const effect of tr.effects) { + if (effect.is(blockMetadataEffect)) { + blocksFromEffect = effect.value; + break; + } + } + + if (blocksFromEffect === null) { + // If the block attributes effect is not present, then this transaction + // is made by the user, we need to update the block attributes accroding + // to the latest syntax tree. TODO + return blocks; + } else { + // Otherwise, we need to update the block attributes according to the + // metadata sent from the runtime. Most importantly, we need to translate + // the position of each block after the changes has been made. + for (const block of blocksFromEffect) { + if (block.output === null) { + const from = tr.changes.mapPos(block.source.from, -1); + const to = tr.changes.mapPos(block.source.from, 1); + block.output = {from, to: to - 1}; + } else { + const from = tr.changes.mapPos(block.output.from, -1); + const to = tr.changes.mapPos(block.output.to, 1); + block.output = {from, to: to - 1}; + } + block.source = { + from: tr.changes.mapPos(block.source.from, 1), + to: tr.changes.mapPos(block.source.to, 1), + }; + } + return blocksFromEffect; + } + }, +}); + +export const blockMetadata = blockMetadataField.extension; diff --git a/editor/decoration.js b/editor/decoration.js index 5b2272d..9e4913a 100644 --- a/editor/decoration.js +++ b/editor/decoration.js @@ -1,15 +1,18 @@ import {Decoration, ViewPlugin, ViewUpdate, EditorView} from "@codemirror/view"; import {outputLinesField} from "./outputLines"; -import {blockAttributesField} from "./blockAttributes"; +import {blockMetadataField} from "./blockMetadata"; import {RangeSet, RangeSetBuilder} from "@codemirror/state"; const highlight = Decoration.line({attributes: {class: "cm-output-line"}}); const errorHighlight = Decoration.line({attributes: {class: "cm-output-line cm-error-line"}}); const compactLineDecoration = Decoration.line({attributes: {class: "cm-output-line cm-compact-line"}}); +const debugGreenDecoration = Decoration.mark({attributes: {class: "cm-debug-mark green"}}); +const debugRedDecoration = Decoration.mark({attributes: {class: "cm-debug-mark red"}}); +const debugBlueDecoration = Decoration.mark({attributes: {class: "cm-debug-mark blue"}}); // const linePrefix = Decoration.mark({attributes: {class: "cm-output-line-prefix"}}); // const lineContent = Decoration.mark({attributes: {class: "cm-output-line-content"}}); -function createWidgets(lines, blockAttributes, state) { +function createWidgets(lines, blockMetadata, state) { // Build the range set for output lines. const builder1 = new RangeSetBuilder(); // Add output line decorations @@ -24,23 +27,40 @@ function createWidgets(lines, blockAttributes, state) { // Build the range set for block attributes. const builder2 = new RangeSetBuilder(); // Add block attribute decorations - for (const {from, to, attributes} of blockAttributes) { + for (const {output, attributes} of blockMetadata) { // Apply decorations to each line in the block range - const startLine = state.doc.lineAt(from); - const endLine = state.doc.lineAt(to); + const startLine = state.doc.lineAt(output.from); + const endLine = state.doc.lineAt(output.to); + console.log("from:", output.from); + console.log("to:", output.to); + console.log("startLine:", startLine); + console.log("endLine:", endLine); - for (let lineNum = startLine.number; lineNum <= endLine.number; lineNum++) { - const line = state.doc.line(lineNum); - if (attributes.compact === true) { + if (attributes.compact === true) { + for (let lineNum = startLine.number; lineNum <= endLine.number; lineNum++) { + const line = state.doc.line(lineNum); builder2.add(line.from, line.from, compactLineDecoration); } } } const set2 = builder2.finish(); + const builder3 = new RangeSetBuilder(); + for (const {output} of blockMetadata) { + if (output === null) continue; + builder3.add(output.from, output.to, debugRedDecoration); + } + const set3 = builder3.finish(); + + const builder4 = new RangeSetBuilder(); + for (const {source} of blockMetadata) { + builder4.add(source.from, source.to, debugGreenDecoration); + } + const set4 = builder4.finish(); + // Range sets are required to be sorted. Fortunately, they provide a method // to merge multiple range sets into a single sorted range set. - return RangeSet.join([set1, set2]); + return RangeSet.join([set1, set2, set3, set4]); } export const outputDecoration = ViewPlugin.fromClass( @@ -54,16 +74,16 @@ export const outputDecoration = ViewPlugin.fromClass( /** @param {EditorView} view */ constructor(view) { const outputLines = view.state.field(outputLinesField); - const blockAttributes = view.state.field(blockAttributesField); - this.#decorations = createWidgets(outputLines, blockAttributes, view.state); + const blockMetadata = view.state.field(blockMetadataField); + this.#decorations = createWidgets(outputLines, blockMetadata, view.state); } /** @param {ViewUpdate} update */ update(update) { const newOutputLines = update.state.field(outputLinesField); - const newBlockAttributes = update.state.field(blockAttributesField); + const blockMetadata = update.state.field(blockMetadataField); // A possible optimization would be to only update the changed lines. - this.#decorations = createWidgets(newOutputLines, newBlockAttributes, update.state); + this.#decorations = createWidgets(newOutputLines, blockMetadata, update.state); } }, {decorations: (v) => v.decorations}, diff --git a/editor/index.css b/editor/index.css index 110964f..45579fb 100644 --- a/editor/index.css +++ b/editor/index.css @@ -147,6 +147,18 @@ line-height: 1; } + .cm-debug-mark { + &.red { + background-color: #e4002440; + } + &.green { + background-color: #009a5753; + } + &.blue { + background-color: #0088f653; + } + } + /* We can't see background color, which is conflict with selection background color. * So we use svg pattern to simulate the background color. */ diff --git a/editor/index.js b/editor/index.js index 2e9ab1c..704b3fe 100644 --- a/editor/index.js +++ b/editor/index.js @@ -11,7 +11,7 @@ import * as eslint from "eslint-linter-browserify"; import {createRuntime} from "../runtime/index.js"; import {outputDecoration} from "./decoration.js"; import {outputLines} from "./outputLines.js"; -import {blockAttributes, setBlockAttributesEffect} from "./blockAttributes.js"; +import {blockMetadata, blockMetadataEffect} from "./blockMetadata.ts"; // import {outputProtection} from "./protection.js"; import {dispatch as d3Dispatch} from "d3-dispatch"; import {controls} from "./controls/index.js"; @@ -69,7 +69,7 @@ export function createEditor(container, options) { ]), javascriptLanguage.data.of({autocomplete: rechoCompletion}), outputLines, - blockAttributes, + blockMetadata, outputDecoration, controls(runtimeRef), // Disable this for now, because it prevents copying/pasting the code. @@ -91,41 +91,12 @@ export function createEditor(container, options) { window.addEventListener("keyup", onKeyUp); } - function dispatch({changes, blockAttributes: blockAttrs}) { - // Map block attributes through the changes to get correct final positions - const effects = []; - - if (blockAttrs && blockAttrs.length > 0) { - // We need to map the block positions through the changes - // Create a simplified change set mapper - const sortedChanges = [...changes].sort((a, b) => a.from - b.from); - - const mapPos = (pos) => { - let mapped = pos; - for (const change of sortedChanges) { - if (change.from <= pos) { - const inserted = change.insert ? change.insert.length : 0; - const deleted = change.to ? change.to - change.from : 0; - mapped += inserted - deleted; - } - } - return mapped; - }; - - const mappedBlockAttrs = blockAttrs.map(({from, to, attributes}) => ({ - from: mapPos(from), - to: mapPos(to), - attributes, - })); - - effects.push(setBlockAttributesEffect.of(mappedBlockAttrs)); - } - + function dispatch({changes, effects}) { // Mark this transaction as from runtime so that it will not be filtered out. view.dispatch({ changes, effects, - annotations: [Transaction.remote.of("runtime")], + annotations: [Transaction.remote.of(true)], }); } diff --git a/lib/IntervalTree.ts b/lib/IntervalTree.ts new file mode 100644 index 0000000..82f199e --- /dev/null +++ b/lib/IntervalTree.ts @@ -0,0 +1,369 @@ +/** + * Represents an interval with a low and high endpoint. + */ +export interface Interval { + low: number; + high: number; +} + +/** + * Represents an interval with associated data. + */ +export type IntervalEntry = { + interval: Interval; + data: T; +}; + +/** + * Represents a node in the interval tree. + */ +class IntervalTreeNode { + interval: Interval; + data: T; + max: number; // Maximum high value in subtree + left: IntervalTreeNode | null = null; + right: IntervalTreeNode | null = null; + height: number = 1; + + constructor(interval: Interval, data: T) { + this.interval = interval; + this.data = data; + this.max = interval.high; + } +} + +/** + * A balanced interval tree implementation using AVL tree balancing. + * Supports efficient insertion, deletion, and overlap queries. + */ +export class IntervalTree { + private root: IntervalTreeNode | null = null; + private size: number = 0; + + /** + * Creates an IntervalTree from an array using a mapping function. + * @param items - The array of items to map + * @param mapper - Function that maps each item to an interval and data value + * @returns A new IntervalTree containing all mapped intervals + */ + static from(items: S[], mapper: (item: S, index: number) => IntervalEntry | null): IntervalTree { + const tree = new IntervalTree(); + for (let i = 0, n = items.length; i < n; i++) { + const item = items[i]; + const mapped = mapper(item, i); + if (mapped) tree.insert(mapped.interval, mapped.data); + } + return tree; + } + + /** + * Returns the number of intervals in the tree. + */ + get length(): number { + return this.size; + } + + /** + * Returns true if the tree is empty. + */ + isEmpty(): boolean { + return this.root === null; + } + + /** + * Inserts an interval with associated data into the tree. + */ + insert(interval: Interval, data: T): void { + if (interval.low > interval.high) { + throw new Error("Invalid interval: low must be less than or equal to high"); + } + this.root = this.insertNode(this.root, interval, data); + this.size++; + } + + private insertNode(node: IntervalTreeNode | null, interval: Interval, data: T): IntervalTreeNode { + // Standard BST insertion + if (node === null) { + return new IntervalTreeNode(interval, data); + } + + if (interval.low < node.interval.low) { + node.left = this.insertNode(node.left, interval, data); + } else { + node.right = this.insertNode(node.right, interval, data); + } + + // Update height and max + node.height = 1 + Math.max(this.getHeight(node.left), this.getHeight(node.right)); + node.max = Math.max(node.interval.high, Math.max(this.getMax(node.left), this.getMax(node.right))); + + // Balance the tree + return this.balance(node); + } + + /** + * Deletes an interval from the tree. + * Returns true if the interval was found and deleted, false otherwise. + */ + delete(interval: Interval): boolean { + const initialSize = this.size; + this.root = this.deleteNode(this.root, interval); + return this.size < initialSize; + } + + private deleteNode(node: IntervalTreeNode | null, interval: Interval): IntervalTreeNode | null { + if (node === null) { + return null; + } + + if (interval.low < node.interval.low) { + node.left = this.deleteNode(node.left, interval); + } else if (interval.low > node.interval.low) { + node.right = this.deleteNode(node.right, interval); + } else if (interval.high === node.interval.high) { + // Found the node to delete + this.size--; + + // Node with only one child or no child + if (node.left === null) { + return node.right; + } else if (node.right === null) { + return node.left; + } + + // Node with two children: get inorder successor + const successor = this.findMin(node.right); + node.interval = successor.interval; + node.data = successor.data; + node.right = this.deleteNode(node.right, successor.interval); + } else { + // Continue searching + node.right = this.deleteNode(node.right, interval); + } + + // Update height and max + node.height = 1 + Math.max(this.getHeight(node.left), this.getHeight(node.right)); + node.max = Math.max(node.interval.high, Math.max(this.getMax(node.left), this.getMax(node.right))); + + // Balance the tree + return this.balance(node); + } + + private findMin(node: IntervalTreeNode): IntervalTreeNode { + while (node.left !== null) { + node = node.left; + } + return node; + } + + /** + * Searches for all intervals that overlap with the given interval. + */ + search(interval: Interval): Array> { + const results: Array> = []; + this.searchNode(this.root, interval, results); + return results; + } + + private searchNode(node: IntervalTreeNode | null, interval: Interval, results: Array>): void { + if (node === null) { + return; + } + + // Check if intervals overlap + if (this.doOverlap(node.interval, interval)) { + results.push({interval: node.interval, data: node.data}); + } + + // If left child exists and its max >= interval.low, search left + if (node.left !== null && node.left.max >= interval.low) { + this.searchNode(node.left, interval, results); + } + + // Search right subtree if it can contain overlapping intervals + if (node.right !== null && node.interval.low <= interval.high) { + this.searchNode(node.right, interval, results); + } + } + + /** + * Finds any one interval that overlaps with the given interval. + * Returns null if no overlapping interval is found. + */ + findAny(interval: Interval): IntervalEntry | null { + return this.findAnyNode(this.root, interval); + } + + private findAnyNode(node: IntervalTreeNode | null, interval: Interval): IntervalEntry | null { + if (node === null) { + return null; + } + + // Check if current node overlaps + if (this.doOverlap(node.interval, interval)) { + return {interval: node.interval, data: node.data}; + } + + // If left child can contain overlapping interval, search left first + if (node.left !== null && node.left.max >= interval.low) { + return this.findAnyNode(node.left, interval); + } + + // Otherwise search right + return this.findAnyNode(node.right, interval); + } + + /** + * Finds the first interval that contains the given point. + * Returns null if no interval contains the point. + */ + contains(point: number): IntervalEntry | null { + return this.containsNode(this.root, point); + } + + private containsNode(node: IntervalTreeNode | null, point: number): IntervalEntry | null { + if (node === null) { + return null; + } + + // Check if current node contains the point + if (node.interval.low <= point && point <= node.interval.high) { + return {interval: node.interval, data: node.data}; + } + + // If left child exists and its max >= point, search left first + if (node.left !== null && node.left.max >= point) { + return this.containsNode(node.left, point); + } + + // Otherwise search right + return this.containsNode(node.right, point); + } + + /** + * Returns all intervals in the tree. + */ + toArray(): Array> { + const results: Array> = []; + this.inorderTraversal(this.root, results); + return results; + } + + private inorderTraversal(node: IntervalTreeNode | null, results: Array>): void { + if (node === null) { + return; + } + this.inorderTraversal(node.left, results); + results.push({interval: node.interval, data: node.data}); + this.inorderTraversal(node.right, results); + } + + /** + * Clears all intervals from the tree. + */ + clear(): void { + this.root = null; + this.size = 0; + } + + // AVL tree balancing methods + + private getHeight(node: IntervalTreeNode | null): number { + return node === null ? 0 : node.height; + } + + private getMax(node: IntervalTreeNode | null): number { + return node === null ? -Infinity : node.max; + } + + private getBalanceFactor(node: IntervalTreeNode): number { + return this.getHeight(node.left) - this.getHeight(node.right); + } + + private balance(node: IntervalTreeNode): IntervalTreeNode { + const balanceFactor = this.getBalanceFactor(node); + + // Left heavy + if (balanceFactor > 1) { + if (this.getBalanceFactor(node.left!) < 0) { + // Left-Right case + node.left = this.rotateLeft(node.left!); + } + // Left-Left case + return this.rotateRight(node); + } + + // Right heavy + if (balanceFactor < -1) { + if (this.getBalanceFactor(node.right!) > 0) { + // Right-Left case + node.right = this.rotateRight(node.right!); + } + // Right-Right case + return this.rotateLeft(node); + } + + return node; + } + + private rotateLeft(node: IntervalTreeNode): IntervalTreeNode { + const newRoot = node.right!; + node.right = newRoot.left; + newRoot.left = node; + + // Update heights + node.height = 1 + Math.max(this.getHeight(node.left), this.getHeight(node.right)); + newRoot.height = 1 + Math.max(this.getHeight(newRoot.left), this.getHeight(newRoot.right)); + + // Update max values + node.max = Math.max(node.interval.high, Math.max(this.getMax(node.left), this.getMax(node.right))); + newRoot.max = Math.max(newRoot.interval.high, Math.max(this.getMax(newRoot.left), this.getMax(newRoot.right))); + + return newRoot; + } + + private rotateRight(node: IntervalTreeNode): IntervalTreeNode { + const newRoot = node.left!; + node.left = newRoot.right; + newRoot.right = node; + + // Update heights + node.height = 1 + Math.max(this.getHeight(node.left), this.getHeight(node.right)); + newRoot.height = 1 + Math.max(this.getHeight(newRoot.left), this.getHeight(newRoot.right)); + + // Update max values + node.max = Math.max(node.interval.high, Math.max(this.getMax(node.left), this.getMax(node.right))); + newRoot.max = Math.max(newRoot.interval.high, Math.max(this.getMax(newRoot.left), this.getMax(newRoot.right))); + + return newRoot; + } + + private doOverlap(a: Interval, b: Interval): boolean { + return a.low <= b.high && b.low <= a.high; + } + + /** + * Returns true if the tree is balanced (for testing purposes). + */ + isBalanced(): boolean { + return this.checkBalance(this.root) !== -1; + } + + private checkBalance(node: IntervalTreeNode | null): number { + if (node === null) { + return 0; + } + + const leftHeight = this.checkBalance(node.left); + if (leftHeight === -1) return -1; + + const rightHeight = this.checkBalance(node.right); + if (rightHeight === -1) return -1; + + if (Math.abs(leftHeight - rightHeight) > 1) { + return -1; + } + + return Math.max(leftHeight, rightHeight) + 1; + } +} diff --git a/runtime/index.js b/runtime/index.js index 637306e..723038d 100644 --- a/runtime/index.js +++ b/runtime/index.js @@ -6,6 +6,8 @@ import {dispatch as d3Dispatch} from "d3-dispatch"; import * as stdlib from "./stdlib.js"; import {OUTPUT_MARK, ERROR_MARK} from "./constant.js"; import {Inspector} from "./inspect.js"; +import {blockMetadataEffect} from "../editor/blockMetadata.ts"; +import {IntervalTree} from "../lib/IntervalTree.ts"; const OUTPUT_PREFIX = `//${OUTPUT_MARK}`; @@ -95,9 +97,13 @@ export function createRuntime(initialCode) { const refresh = debounce((code) => { const changes = removeChanges(code); + const removedIntervals = IntervalTree.from(changes, ({from, to}, index) => + from === to ? null : {interval: {low: from, high: to - 1}, data: index}, + ); // Insert new outputs const nodes = Array.from(nodesByKey.values()).flat(Infinity); + const blocks = []; for (const node of nodes) { const start = node.start; const {values} = node.state; @@ -121,20 +127,28 @@ export function createRuntime(initialCode) { } const prefix = error ? ERROR_PREFIX : OUTPUT_PREFIX; const prefixed = addPrefix(output, prefix); - changes.push({from: start, insert: prefixed + "\n"}); + // Search for existing changes and update the inserted text if found. + const entry = removedIntervals.contains(start - 1); + let outputRange = null; + if (entry === null) { + changes.push({from: start, insert: prefixed + "\n"}); + } else { + const change = changes[entry.data]; + change.insert = prefixed + "\n"; + outputRange = {from: change.from, to: change.to}; + } + blocks.push({ + source: {from: node.start, to: node.end}, + output: outputRange, + attributes: node.state.attributes, + }); } } - // Collect block attributes - we need to map positions through the changes - const blockAttributes = nodes - .filter((node) => Object.keys(node.state.attributes).length > 0) - .map((node) => ({ - from: node.start, - to: node.end, - attributes: node.state.attributes, - })); + // Attach block positions and attributes as effects to the transaction. + const effects = [blockMetadataEffect.of(blocks)]; - dispatch(changes, blockAttributes); + dispatch(changes, effects); }, 0); function setCode(newCode) { @@ -145,8 +159,8 @@ export function createRuntime(initialCode) { isRunning = value; } - function dispatch(changes, blockAttributes = []) { - dispatcher.call("changes", null, {changes, blockAttributes}); + function dispatch(changes, effects = []) { + dispatcher.call("changes", null, {changes, effects}); } function onChanges(callback) { @@ -205,30 +219,45 @@ export function createRuntime(initialCode) { } } + /** + * Get the changes that remove the output lines from the code. + * @param {string} code The code to remove changes from. + * @returns {{from: number, to: number, insert: ""}[]} An array of changes. + */ function removeChanges(code) { - const changes = []; - - const oldOutputs = code - .split("\n") - .map((l, i) => [l, i]) - .filter(([l]) => l.startsWith(OUTPUT_PREFIX) || l.startsWith(ERROR_PREFIX)) - .map(([_, i]) => i); - - const lineOf = (i) => { - const lines = code.split("\n"); - const line = lines[i]; - const from = lines.slice(0, i).join("\n").length; - const to = from + line.length; - return {from, to}; - }; + function matchAt(index) { + return code.startsWith(OUTPUT_PREFIX, index) || code.startsWith(ERROR_PREFIX, index); + } - for (const i of oldOutputs) { - const line = lineOf(i); - const from = line.from; - const to = line.to + 1 > code.length ? line.to : line.to + 1; - changes.push({from, to, insert: ""}); + /** Line number ranges (left-closed and right-open) of lines that contain output or error. */ + const lineNumbers = matchAt(0) ? [{begin: 0, end: 1}] : []; + /** + * The index of the first character of each line. + * If the code ends with a newline, the last index is the length of the code. + */ + const lineStartIndices = [0]; + let nextNewlineIndex = code.indexOf("\n", 0); + while (0 <= nextNewlineIndex && nextNewlineIndex < code.length) { + lineStartIndices.push(nextNewlineIndex + 1); + if (matchAt(nextNewlineIndex + 1)) { + const lineNumber = lineStartIndices.length - 1; + if (lineNumbers.length > 0 && lineNumber === lineNumbers[lineNumbers.length - 1].end) { + // Extend the last line number range. + lineNumbers[lineNumbers.length - 1].end += 1; + } else { + // Append a new line number range. + lineNumbers.push({begin: lineNumber, end: lineNumber + 1}); + } + } + nextNewlineIndex = code.indexOf("\n", nextNewlineIndex + 1); } + const changes = lineNumbers.map(({begin, end}) => ({ + from: lineStartIndices[begin], + to: lineStartIndices[end], + insert: "", + })); + return changes; } @@ -332,6 +361,7 @@ export function createRuntime(initialCode) { { set: function (key, value) { state.attributes[key] = value; + return this; }, }, ); diff --git a/test/IntervalTree.spec.js b/test/IntervalTree.spec.js new file mode 100644 index 0000000..876c0a4 --- /dev/null +++ b/test/IntervalTree.spec.js @@ -0,0 +1,395 @@ +import {it, expect, describe, beforeEach} from "vitest"; +import {IntervalTree} from "../lib/IntervalTree.ts"; + +describe("IntervalTree", () => { + let tree; + + beforeEach(() => { + tree = new IntervalTree(); + }); + + describe("static from method", () => { + it("should create tree from array of objects with mapper", () => { + const events = [ + {name: "Event A", start: 10, end: 20}, + {name: "Event B", start: 15, end: 25}, + {name: "Event C", start: 30, end: 40}, + ]; + + const tree = IntervalTree.from(events, (event) => ({ + interval: {low: event.start, high: event.end}, + data: event.name, + })); + + expect(tree.length).toBe(3); + expect(tree.isBalanced()).toBe(true); + + const results = tree.search({low: 18, high: 22}); + expect(results.length).toBe(2); + expect(results.map((r) => r.data).sort()).toEqual(["Event A", "Event B"]); + }); + + it("should create tree from array of numbers", () => { + const numbers = [1, 2, 3, 4, 5]; + + const tree = IntervalTree.from(numbers, (num) => ({ + interval: {low: num, high: num + 10}, + data: num * 2, + })); + + expect(tree.length).toBe(5); + // Intervals: [1,11], [2,12], [3,13], [4,14], [5,15] + // Searching [12, 13] overlaps with [2,12], [3,13], [4,14], [5,15] + const results = tree.search({low: 12, high: 13}); + expect(results.map((r) => r.data).sort((a, b) => a - b)).toEqual([4, 6, 8, 10]); + }); + + it("should create empty tree from empty array", () => { + const tree = IntervalTree.from([], (item) => ({ + interval: {low: 0, high: 0}, + data: item, + })); + + expect(tree.isEmpty()).toBe(true); + expect(tree.length).toBe(0); + }); + + it("should handle complex data types", () => { + const tasks = [ + {id: 1, timeRange: [0, 100], priority: "high"}, + {id: 2, timeRange: [50, 150], priority: "medium"}, + {id: 3, timeRange: [120, 200], priority: "low"}, + ]; + + const tree = IntervalTree.from(tasks, (task) => ({ + interval: {low: task.timeRange[0], high: task.timeRange[1]}, + data: {id: task.id, priority: task.priority}, + })); + + expect(tree.length).toBe(3); + + // Intervals: [0,100], [50,150], [120,200] + // Searching [75, 125] overlaps with all three + const results = tree.search({low: 75, high: 125}); + expect(results.length).toBe(3); + expect(results.find((r) => r.data.id === 1)).toBeDefined(); + expect(results.find((r) => r.data.id === 2)).toBeDefined(); + expect(results.find((r) => r.data.id === 3)).toBeDefined(); + }); + }); + + describe("basic operations", () => { + it("should create an empty tree", () => { + expect(tree.isEmpty()).toBe(true); + expect(tree.length).toBe(0); + }); + + it("should insert intervals", () => { + tree.insert({low: 15, high: 20}, "data1"); + expect(tree.isEmpty()).toBe(false); + expect(tree.length).toBe(1); + + tree.insert({low: 10, high: 30}, "data2"); + expect(tree.length).toBe(2); + }); + + it("should reject invalid intervals", () => { + expect(() => tree.insert({low: 20, high: 10}, "data")).toThrow( + "Invalid interval: low must be less than or equal to high", + ); + }); + + it("should allow single-point intervals", () => { + tree.insert({low: 5, high: 5}, "point"); + expect(tree.length).toBe(1); + }); + + it("should delete intervals", () => { + tree.insert({low: 15, high: 20}, "data1"); + tree.insert({low: 10, high: 30}, "data2"); + tree.insert({low: 17, high: 19}, "data3"); + + const deleted = tree.delete({low: 10, high: 30}); + expect(deleted).toBe(true); + expect(tree.length).toBe(2); + + const notDeleted = tree.delete({low: 100, high: 200}); + expect(notDeleted).toBe(false); + expect(tree.length).toBe(2); + }); + + it("should clear the tree", () => { + tree.insert({low: 15, high: 20}, "data1"); + tree.insert({low: 10, high: 30}, "data2"); + tree.clear(); + expect(tree.isEmpty()).toBe(true); + expect(tree.length).toBe(0); + }); + }); + + describe("contains method", () => { + beforeEach(() => { + tree.insert({low: 15, high: 20}, "interval1"); + tree.insert({low: 10, high: 30}, "interval2"); + tree.insert({low: 17, high: 19}, "interval3"); + tree.insert({low: 5, high: 20}, "interval4"); + tree.insert({low: 12, high: 15}, "interval5"); + tree.insert({low: 30, high: 40}, "interval6"); + }); + + it("should find an interval containing a point", () => { + const result = tree.contains(18); + expect(result).not.toBeNull(); + expect(result.interval.low).toBeLessThanOrEqual(18); + expect(result.interval.high).toBeGreaterThanOrEqual(18); + }); + + it("should return null when no interval contains the point", () => { + const result = tree.contains(45); + expect(result).toBeNull(); + }); + + it("should find interval at lower boundary", () => { + const result = tree.contains(15); + expect(result).not.toBeNull(); + expect(result.interval.low).toBeLessThanOrEqual(15); + expect(result.interval.high).toBeGreaterThanOrEqual(15); + }); + + it("should find interval at upper boundary", () => { + const result = tree.contains(40); + expect(result).not.toBeNull(); + expect(result.data).toBe("interval6"); + }); + + it("should find single-point interval", () => { + tree.clear(); + tree.insert({low: 25, high: 25}, "point"); + const result = tree.contains(25); + expect(result).not.toBeNull(); + expect(result.data).toBe("point"); + }); + + it("should return null for point just outside intervals", () => { + tree.clear(); + tree.insert({low: 10, high: 20}, "range"); + expect(tree.contains(9)).toBeNull(); + expect(tree.contains(21)).toBeNull(); + }); + + it("should work with negative numbers", () => { + tree.clear(); + tree.insert({low: -100, high: -50}, "negative"); + tree.insert({low: -10, high: 10}, "crosses-zero"); + + const result1 = tree.contains(-75); + expect(result1).not.toBeNull(); + expect(result1.data).toBe("negative"); + + const result2 = tree.contains(0); + expect(result2).not.toBeNull(); + expect(result2.data).toBe("crosses-zero"); + }); + + it("should handle overlapping intervals efficiently", () => { + tree.clear(); + // Insert multiple overlapping intervals + tree.insert({low: 0, high: 100}, "large"); + tree.insert({low: 40, high: 60}, "medium"); + tree.insert({low: 48, high: 52}, "small"); + + // Should find at least one + const result = tree.contains(50); + expect(result).not.toBeNull(); + expect(result.interval.low).toBeLessThanOrEqual(50); + expect(result.interval.high).toBeGreaterThanOrEqual(50); + }); + }); + + describe("overlap detection", () => { + beforeEach(() => { + tree.insert({low: 15, high: 20}, "interval1"); + tree.insert({low: 10, high: 30}, "interval2"); + tree.insert({low: 17, high: 19}, "interval3"); + tree.insert({low: 5, high: 20}, "interval4"); + tree.insert({low: 12, high: 15}, "interval5"); + tree.insert({low: 30, high: 40}, "interval6"); + }); + + it("should find all overlapping intervals", () => { + const results = tree.search({low: 14, high: 16}); + expect(results.length).toBe(4); + + const dataValues = results.map((r) => r.data).sort(); + expect(dataValues).toEqual(["interval1", "interval2", "interval4", "interval5"]); + }); + + it("should find single overlapping interval", () => { + const result = tree.findAny({low: 35, high: 37}); + expect(result).not.toBeNull(); + expect(result.data).toBe("interval6"); + }); + + it("should return empty array when no overlaps exist", () => { + const results = tree.search({low: 41, high: 50}); + expect(results.length).toBe(0); + }); + + it("should return null when no single overlap exists", () => { + const result = tree.findAny({low: 41, high: 50}); + expect(result).toBeNull(); + }); + + it("should detect overlap with single-point intervals", () => { + tree.clear(); + tree.insert({low: 10, high: 10}, "point"); + tree.insert({low: 5, high: 15}, "range"); + + const results = tree.search({low: 10, high: 10}); + expect(results.length).toBe(2); + }); + + it("should detect edge overlaps", () => { + tree.clear(); + tree.insert({low: 10, high: 20}, "edge1"); + + // Overlaps at left edge + const leftEdge = tree.search({low: 5, high: 10}); + expect(leftEdge.length).toBe(1); + + // Overlaps at right edge + const rightEdge = tree.search({low: 20, high: 25}); + expect(rightEdge.length).toBe(1); + + // No overlap just outside + const noOverlap = tree.search({low: 21, high: 25}); + expect(noOverlap.length).toBe(0); + }); + }); + + describe("tree structure", () => { + it("should maintain balance after insertions", () => { + // Insert intervals in sorted order (worst case for unbalanced tree) + for (let i = 0; i < 100; i++) { + tree.insert({low: i, high: i + 5}, `data${i}`); + } + expect(tree.isBalanced()).toBe(true); + expect(tree.length).toBe(100); + }); + + it("should maintain balance after deletions", () => { + const intervals = []; + for (let i = 0; i < 50; i++) { + const interval = {low: i, high: i + 5}; + intervals.push(interval); + tree.insert(interval, `data${i}`); + } + + // Delete every other interval + for (let i = 0; i < 50; i += 2) { + tree.delete(intervals[i]); + } + + expect(tree.isBalanced()).toBe(true); + expect(tree.length).toBe(25); + }); + }); + + describe("toArray", () => { + it("should return all intervals in sorted order", () => { + tree.insert({low: 15, high: 20}, "data1"); + tree.insert({low: 10, high: 30}, "data2"); + tree.insert({low: 17, high: 19}, "data3"); + tree.insert({low: 5, high: 8}, "data4"); + + const array = tree.toArray(); + expect(array.length).toBe(4); + + // Check that intervals are sorted by low value + for (let i = 0; i < array.length - 1; i++) { + expect(array[i].interval.low).toBeLessThanOrEqual(array[i + 1].interval.low); + } + }); + + it("should return empty array for empty tree", () => { + const array = tree.toArray(); + expect(array).toEqual([]); + }); + }); + + describe("complex scenarios", () => { + it("should handle many overlapping intervals", () => { + // Create many overlapping intervals centered around [50, 60] + for (let i = 0; i < 20; i++) { + tree.insert({low: 45 + i, high: 55 + i}, `overlap${i}`); + } + + const results = tree.search({low: 50, high: 60}); + expect(results.length).toBeGreaterThan(10); + }); + + it("should handle intervals with large ranges", () => { + tree.insert({low: 0, high: 1000000}, "large"); + tree.insert({low: 500, high: 600}, "small"); + + const results = tree.search({low: 550, high: 560}); + expect(results.length).toBe(2); + }); + + it("should handle negative intervals", () => { + tree.insert({low: -100, high: -50}, "negative1"); + tree.insert({low: -75, high: -25}, "negative2"); + tree.insert({low: -10, high: 10}, "crosses-zero"); + + const results = tree.search({low: -60, high: -40}); + expect(results.length).toBe(2); + + const crossResults = tree.search({low: -5, high: 5}); + expect(crossResults.length).toBe(1); + expect(crossResults[0].data).toBe("crosses-zero"); + }); + + it("should work with different data types", () => { + const tree2 = new IntervalTree(); + + tree2.insert({low: 1, high: 5}, {name: "Alice", age: 30}); + tree2.insert({low: 3, high: 7}, {name: "Bob", age: 25}); + + const results = tree2.search({low: 4, high: 6}); + expect(results.length).toBe(2); + expect(results.find((r) => r.data.name === "Alice")).toBeDefined(); + expect(results.find((r) => r.data.name === "Bob")).toBeDefined(); + }); + }); + + describe("stress test", () => { + it("should handle large number of random intervals efficiently", () => { + const intervals = []; + + // Insert 1000 random intervals + for (let i = 0; i < 1000; i++) { + const low = Math.floor(Math.random() * 10000); + const high = low + Math.floor(Math.random() * 100) + 1; + const interval = {low, high}; + intervals.push(interval); + tree.insert(interval, `data${i}`); + } + + expect(tree.length).toBe(1000); + expect(tree.isBalanced()).toBe(true); + + // Perform random searches + for (let i = 0; i < 100; i++) { + const searchLow = Math.floor(Math.random() * 10000); + const searchHigh = searchLow + Math.floor(Math.random() * 100) + 1; + const results = tree.search({low: searchLow, high: searchHigh}); + + // Verify all results actually overlap + for (const result of results) { + const overlaps = result.interval.low <= searchHigh && searchLow <= result.interval.high; + expect(overlaps).toBe(true); + } + } + }); + }); +}); From 89fda22d11e52ad3373fb2c89bb7ae20b73198a1 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Wed, 29 Oct 2025 02:13:48 +0800 Subject: [PATCH 03/30] Implement automatic block updates when the user changes --- editor/blockIndicator.ts | 82 +++++++++++ editor/blockMetadata.ts | 293 ++++++++++++++++++++++++++++++++++++++- editor/decoration.js | 9 +- editor/index.css | 39 ++++++ editor/index.js | 11 +- runtime/index.js | 28 ++-- 6 files changed, 440 insertions(+), 22 deletions(-) create mode 100644 editor/blockIndicator.ts diff --git a/editor/blockIndicator.ts b/editor/blockIndicator.ts new file mode 100644 index 0000000..7609ab3 --- /dev/null +++ b/editor/blockIndicator.ts @@ -0,0 +1,82 @@ +import {GutterMarker, gutter} from "@codemirror/view"; +import {BlockMetadata, blockMetadataField} from "./blockMetadata"; + +export class BlockIndicator extends GutterMarker { + constructor(private className: string) { + super(); + } + toDOM() { + const div = document.createElement("div"); + div.className = this.className; + return div; + } +} + +const indicatorMarkers = { + output: { + head: new BlockIndicator("cm-block-indicator output head"), + tail: new BlockIndicator("cm-block-indicator output tail"), + sole: new BlockIndicator("cm-block-indicator output head tail"), + body: new BlockIndicator("cm-block-indicator output"), + }, + source: { + head: new BlockIndicator("cm-block-indicator source head"), + tail: new BlockIndicator("cm-block-indicator source tail"), + sole: new BlockIndicator("cm-block-indicator source head tail"), + body: new BlockIndicator("cm-block-indicator source"), + }, + error: { + head: new BlockIndicator("cm-block-indicator error head"), + tail: new BlockIndicator("cm-block-indicator error tail"), + sole: new BlockIndicator("cm-block-indicator error head tail"), + body: new BlockIndicator("cm-block-indicator error"), + }, +}; + +export const blockIndicator = gutter({ + class: "cm-blockIndicators", + lineMarker(view, line) { + const blocks = view.state.field(blockMetadataField, false); + if (blocks === undefined) return null; + const index = findEnclosingBlock(blocks, line.from); + if (index === null) return null; + const currentLine = view.state.doc.lineAt(line.from).number; + const sourceFirstLine = view.state.doc.lineAt(blocks[index].source.from).number; + const group = blocks[index].error + ? indicatorMarkers.error + : currentLine < sourceFirstLine + ? indicatorMarkers.output + : indicatorMarkers.source; + const blockFirstLine = view.state.doc.lineAt(blocks[index].from).number; + const blockLastLine = view.state.doc.lineAt(blocks[index].to).number; + if (blockFirstLine === currentLine) { + return blockLastLine === currentLine ? group.sole : group.head; + } else if (blockLastLine === currentLine) { + return group.tail; + } else { + return group.body; + } + }, + initialSpacer() { + return indicatorMarkers.source.body; + }, +}); + +function findEnclosingBlock(blocks: BlockMetadata[], pos: number): number | null { + let left = 0; + let right = blocks.length - 1; + + while (left <= right) { + const middle = (left + right) >>> 1; + const pivot = blocks[middle]; + if (pos < pivot.from) { + right = middle - 1; + } else if (pos > pivot.to) { + left = middle + 1; + } else { + return middle; + } + } + + return null; +} diff --git a/editor/blockMetadata.ts b/editor/blockMetadata.ts index 2fd826d..5b2e380 100644 --- a/editor/blockMetadata.ts +++ b/editor/blockMetadata.ts @@ -1,11 +1,287 @@ -import {StateField, StateEffect} from "@codemirror/state"; +import {StateField, StateEffect, Transaction} from "@codemirror/state"; +import {syntaxTree} from "@codemirror/language"; +import {OUTPUT_MARK, ERROR_MARK} from "../runtime/constant.js"; +import {EditorState} from "@codemirror/state"; + +const OUTPUT_MARK_CODE_POINT = OUTPUT_MARK.codePointAt(0); +const ERROR_MARK_CODE_POINT = ERROR_MARK.codePointAt(0); + +type Range = {from: number; to: number}; + +export function BlockMetadata(output: Range | null, source: Range, attributes: Record = {}) { + return { + output, + source, + get from() { + return this.output?.from ?? this.source.from; + }, + get to() { + return this.source.to; + }, + attributes, + error: false, + }; +} + +/** + * Detect blocks in a given range by traversing the syntax tree. + * Similar to how runtime/index.js uses acorn to parse blocks, but adapted for CodeMirror. + */ +function rebuiltBlocksWithinRange(state: EditorState, from: number, to: number): BlockMetadata[] { + const blocks: BlockMetadata[] = []; + + // Collect all top-level statements and their preceding output/error comment lines + const statementRanges: Range[] = []; + const outputRanges = new Map(); // Map statement position to output range + + syntaxTree(state).iterate({ + from, + to, + enter: (node) => { + // Detect top-level statements (direct children of Script) + if (node.node.parent?.name === "Script") { + // Check if this is a statement (not a comment) + const isStatement = + node.name.includes("Statement") || + node.name.includes("Declaration") || + node.name === "ExportDeclaration" || + node.name === "ImportDeclaration" || + node.name === "Block"; + + if (isStatement) { + statementRanges.push({from: node.from, to: node.to}); + } + } + + // Detect output/error comment lines (top-level line comments) + if (node.name === "LineComment" && node.node.parent?.name === "Script") { + const line = state.doc.lineAt(node.from); + // Check if the line comment covers the entire line + if (line.from === node.from && line.to === node.to) { + const codePoint = line.text.codePointAt(2); + if (codePoint === OUTPUT_MARK_CODE_POINT || codePoint === ERROR_MARK_CODE_POINT) { + // Find consecutive output/error lines + let outputStart = line.from; + let outputEnd = line.to; + + // Look backwards for more output/error lines + let currentLineNum = line.number - 1; + while (currentLineNum >= 1) { + const prevLine = state.doc.line(currentLineNum); + const prevCodePoint = prevLine.text.codePointAt(2); + if ( + prevLine.text.startsWith("//") && + (prevCodePoint === OUTPUT_MARK_CODE_POINT || prevCodePoint === ERROR_MARK_CODE_POINT) + ) { + outputStart = prevLine.from; + currentLineNum--; + } else { + break; + } + } + + // Look forwards for more output/error lines + currentLineNum = line.number + 1; + const totalLines = state.doc.lines; + while (currentLineNum <= totalLines) { + const nextLine = state.doc.line(currentLineNum); + const nextCodePoint = nextLine.text.codePointAt(2); + if ( + nextLine.text.startsWith("//") && + (nextCodePoint === OUTPUT_MARK_CODE_POINT || nextCodePoint === ERROR_MARK_CODE_POINT) + ) { + outputEnd = nextLine.to; + currentLineNum++; + } else { + break; + } + } + + // Find the next statement after these output lines + // The output belongs to the statement immediately following it + let nextStatementLine = currentLineNum; + if (nextStatementLine <= totalLines) { + const nextStmtLine = state.doc.line(nextStatementLine); + // Store this output range to be associated with the next statement + outputRanges.set(nextStmtLine.from, {from: outputStart, to: outputEnd}); + } + } + } + } + }, + }); + + // Build block metadata from statements + for (const range of statementRanges) { + blocks.push(BlockMetadata(outputRanges.get(range.from) ?? null, range)); + } + + return blocks; +} + +function updateBlocks(blocks: BlockMetadata[], tr: Transaction): void { + console.group("updateBlocks"); + + console.log("Original blocks:", structuredClone(blocks)); + + /** + * Find the block that contains the given position. If such block does not + * exist, return the nearest block that is right before or after the given + * position. When no blocks are before and after the given position, return + * `null`. + * @param pos the position we want to find + * @param side `-1` - lower bound, `1` - upper bound + */ + function findNearestBlock(pos: number, side: -1 | 1): number | null { + let left = 0; + let right = blocks.length; + + console.log(`pos = ${pos}, side = ${side}`); + + loop: while (left < right) { + console.log(`Search range [${left}, ${right})`); + const middle = (left + right) >>> 1; + const pivot = blocks[middle]; + + const from = pivot.output?.from ?? pivot.source.from; + const to = pivot.source.to; + console.log(`Pivot: ${middle} (from = ${from}, to = ${to})`); + + done: { + if (pos < from) { + console.log("Should go left"); + // Go left and search range [left, middle + 1) + if (middle === left) { + // We cannot move left anymore. + break done; + } else { + right = middle; + continue loop; + } + } else if (to <= pos) { + console.log("Should go right"); + // Go right and search range [middle, right) + if (middle === left) { + // We cannot move right anymore. + break done; + } else { + left = middle; + continue loop; + } + } else { + return middle; + } + } + if (side < 0) { + return left; + } else { + return left === 0 ? null : left - 1; + } + } + + return null; + } + + // Collect all ranges that need to be rescanned + type ChangedRange = {oldFrom: number; oldTo: number; newFrom: number; newTo: number}; + const changedRanges: ChangedRange[] = []; + tr.changes.iterChangedRanges((oldFrom, oldTo, newFrom, newTo) => { + changedRanges.push({oldFrom, oldTo, newFrom, newTo}); + }); + + if (changedRanges.length === 0) { + console.log("No changes detected"); + console.groupEnd(); + return; + } + + const affectedBlocks = new Set(); + + // Process changed ranges one by one, because ranges are disjoint. + for (const {oldFrom, oldTo, newFrom, newTo} of changedRanges) { + console.groupCollapsed(`Range ${oldFrom}-${oldTo} -> ${newFrom}-${newTo}`); + + // Step 1: Find the blocks that are affected by the change. + + const leftmost = findNearestBlock(oldFrom, -1); + const rightmost = findNearestBlock(oldTo, 1); + + console.log(`Affected block range: ${leftmost}-${rightmost}`); + + if (leftmost === null || rightmost === null || leftmost > rightmost) { + // No blocks are affected by this change, so we can skip it. + console.groupEnd(); + continue; + } + + // Step 2: Rebuild affected blocks. + + if (leftmost === rightmost) { + // The change affects only one block. We special case this scenario since + // we can possibly reuse the existing block attributes if the changed + // content is still a single block. + const block = blocks[leftmost]; + + const newBlockFrom = tr.changes.mapPos(block.from, -1); + const newBlockTo = tr.changes.mapPos(block.to, 1); + + console.log(`Only one block is affected. Rebuilting from ${block.from} to ${block.to}...`); + const rebuiltBlocks = rebuiltBlocksWithinRange(tr.state, newBlockFrom, newBlockTo); + + console.log(`Rebuilt blocks:`, rebuiltBlocks); + + if (rebuiltBlocks.length === 1) { + const newBlock = rebuiltBlocks[0]; + newBlock.attributes = block.attributes; + affectedBlocks.add(newBlock); + blocks[leftmost] = newBlock; + } else { + blocks.splice(leftmost, 1, ...rebuiltBlocks); + } + affectedBlocks.add(block); + } else { + // Multiple blocks are affected. + const rebuiltBlocks = rebuiltBlocksWithinRange(tr.state, newFrom, newTo); + rebuiltBlocks.forEach((block) => affectedBlocks.add(block)); + blocks.splice(leftmost, rightmost - leftmost + 1, ...rebuiltBlocks); + } + + console.groupEnd(); + } + + // Step 3: Map the unaffected blocks to new positions; + for (let i = 0, n = blocks.length; i < n; i++) { + const block = blocks[i]; + // Skip affected blocks as they have been updated. + if (affectedBlocks.has(block)) continue; + + // Map unaffected blocks to new positions + block.output = block.output + ? { + from: tr.changes.mapPos(block.output.from, -1), + to: tr.changes.mapPos(block.output.to, 1), + } + : null; + block.source = { + from: tr.changes.mapPos(block.source.from, -1), + to: tr.changes.mapPos(block.source.to, 1), + }; + } + + console.log("Updated blocks:", blocks); + + console.groupEnd(); +} export const blockMetadataEffect = StateEffect.define(); -type BlockMetadata = { - output: {from: number; to: number} | null; - source: {from: number; to: number}; +export type BlockMetadata = { + output: Range | null; + source: Range; + readonly from: number; + readonly to: number; attributes: Record; + error: boolean; }; export const blockMetadataField = StateField.define({ @@ -25,12 +301,14 @@ export const blockMetadataField = StateField.define({ if (blocksFromEffect === null) { // If the block attributes effect is not present, then this transaction // is made by the user, we need to update the block attributes accroding - // to the latest syntax tree. TODO + // to the latest syntax tree. + updateBlocks(blocks, tr); return blocks; } else { // Otherwise, we need to update the block attributes according to the // metadata sent from the runtime. Most importantly, we need to translate // the position of each block after the changes has been made. + console.group("Updating blocks from the effect"); for (const block of blocksFromEffect) { if (block.output === null) { const from = tr.changes.mapPos(block.source.from, -1); @@ -45,10 +323,13 @@ export const blockMetadataField = StateField.define({ from: tr.changes.mapPos(block.source.from, 1), to: tr.changes.mapPos(block.source.to, 1), }; + console.log(`output: ${block.output?.from} - ${block.output?.to}`); + console.log(`source: ${block.source.from} - ${block.source.to}`); } + console.groupEnd(); return blocksFromEffect; } }, }); -export const blockMetadata = blockMetadataField.extension; +export const blockMetadataExtension = blockMetadataField.extension; diff --git a/editor/decoration.js b/editor/decoration.js index 9e4913a..6c7c0df 100644 --- a/editor/decoration.js +++ b/editor/decoration.js @@ -25,17 +25,15 @@ function createWidgets(lines, blockMetadata, state) { const set1 = builder1.finish(); // Build the range set for block attributes. + console.groupCollapsed("Decorations for block attributes"); const builder2 = new RangeSetBuilder(); // Add block attribute decorations for (const {output, attributes} of blockMetadata) { + if (output === null) continue; // Apply decorations to each line in the block range const startLine = state.doc.lineAt(output.from); const endLine = state.doc.lineAt(output.to); - console.log("from:", output.from); - console.log("to:", output.to); - console.log("startLine:", startLine); - console.log("endLine:", endLine); - + console.log(`Make lines from ${startLine.number} to ${endLine.number} compact`); if (attributes.compact === true) { for (let lineNum = startLine.number; lineNum <= endLine.number; lineNum++) { const line = state.doc.line(lineNum); @@ -44,6 +42,7 @@ function createWidgets(lines, blockMetadata, state) { } } const set2 = builder2.finish(); + console.groupEnd(); const builder3 = new RangeSetBuilder(); for (const {output} of blockMetadata) { diff --git a/editor/index.css b/editor/index.css index 45579fb..a69a03d 100644 --- a/editor/index.css +++ b/editor/index.css @@ -159,6 +159,45 @@ } } + .cm-blockIndicators { + padding-left: 0.25rem; + + --gap-between-blocks: 2px; + } + + .cm-gutterElement .cm-block-indicator { + width: 0.25rem; + height: 100%; + + &.output { + background: #72aa006e; + } + + &.source { + background: #1f180021; + } + + &.error { + background: #df2600d1; + } + + &.head { + height: calc(100% - var(--gap-between-blocks)); + /*border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem;*/ + + &:not(.tail) { + transform: translate(0, var(--gap-between-blocks)); + } + } + + &.tail { + height: calc(100% - var(--gap-between-blocks)); + /*border-bottom-left-radius: 0.25rem; + border-bottom-right-radius: 0.25rem;*/ + } + } + /* We can't see background color, which is conflict with selection background color. * So we use svg pattern to simulate the background color. */ diff --git a/editor/index.js b/editor/index.js index 704b3fe..d2ab2a3 100644 --- a/editor/index.js +++ b/editor/index.js @@ -11,13 +11,15 @@ import * as eslint from "eslint-linter-browserify"; import {createRuntime} from "../runtime/index.js"; import {outputDecoration} from "./decoration.js"; import {outputLines} from "./outputLines.js"; -import {blockMetadata, blockMetadataEffect} from "./blockMetadata.ts"; +import {blockMetadataExtension, blockMetadataEffect} from "./blockMetadata.ts"; // import {outputProtection} from "./protection.js"; import {dispatch as d3Dispatch} from "d3-dispatch"; import {controls} from "./controls/index.js"; import {rechoCompletion} from "./completion.js"; import {docStringTag} from "./docStringTag.js"; import {commentLink} from "./commentLink.js"; +import {blockIndicator} from "./blockIndicator.ts"; +import {lineNumbers} from "@codemirror/view"; // @see https://github.com/UziTech/eslint-linter-browserify/blob/master/example/script.js // @see https://codemirror.net/examples/lint/ @@ -38,10 +40,13 @@ export function createEditor(container, options) { const dispatcher = d3Dispatch("userInput"); const runtimeRef = {current: null}; + const myBasicSetup = Array.from(basicSetup); + myBasicSetup.splice(2, 0, blockIndicator); + const state = EditorState.create({ doc: code, extensions: [ - basicSetup, + myBasicSetup, javascript(), githubLightInit({ styles: [ @@ -69,7 +74,7 @@ export function createEditor(container, options) { ]), javascriptLanguage.data.of({autocomplete: rechoCompletion}), outputLines, - blockMetadata, + blockMetadataExtension, outputDecoration, controls(runtimeRef), // Disable this for now, because it prevents copying/pasting the code. diff --git a/runtime/index.js b/runtime/index.js index 723038d..d8f21b7 100644 --- a/runtime/index.js +++ b/runtime/index.js @@ -6,7 +6,7 @@ import {dispatch as d3Dispatch} from "d3-dispatch"; import * as stdlib from "./stdlib.js"; import {OUTPUT_MARK, ERROR_MARK} from "./constant.js"; import {Inspector} from "./inspect.js"; -import {blockMetadataEffect} from "../editor/blockMetadata.ts"; +import {BlockMetadata, blockMetadataEffect} from "../editor/blockMetadata.ts"; import {IntervalTree} from "../lib/IntervalTree.ts"; const OUTPUT_PREFIX = `//${OUTPUT_MARK}`; @@ -107,9 +107,10 @@ export function createRuntime(initialCode) { for (const node of nodes) { const start = node.start; const {values} = node.state; + let outputRange = null; + let error = false; if (values.length) { let output = ""; - let error = false; for (let i = 0; i < values.length; i++) { const line = values[i]; const n = line.length; @@ -129,7 +130,6 @@ export function createRuntime(initialCode) { const prefixed = addPrefix(output, prefix); // Search for existing changes and update the inserted text if found. const entry = removedIntervals.contains(start - 1); - let outputRange = null; if (entry === null) { changes.push({from: start, insert: prefixed + "\n"}); } else { @@ -137,14 +137,17 @@ export function createRuntime(initialCode) { change.insert = prefixed + "\n"; outputRange = {from: change.from, to: change.to}; } - blocks.push({ - source: {from: node.start, to: node.end}, - output: outputRange, - attributes: node.state.attributes, - }); } + // Add this block to the block metadata array. + const block = BlockMetadata(outputRange, {from: node.start, to: node.end}, node.state.attributes); + block.error = error; + blocks.push(block); } + blocks.sort((a, b) => a.from - b.from); + + console.log(blocks); + // Attach block positions and attributes as effects to the transaction. const effects = [blockMetadataEffect.of(blocks)]; @@ -195,6 +198,9 @@ export function createRuntime(initialCode) { function split(code) { try { + // The `parse` call here is actually unnecessary. Parsing the entire code + // is quite expensive. If we can perform the splitting operation through + // the editor's syntax tree, we can save the parsing here. return parse(code, {ecmaVersion: "latest", sourceType: "module"}).body; } catch (error) { console.error(error); @@ -282,6 +288,12 @@ export function createRuntime(initialCode) { const nodes = split(code); if (!nodes) return; + console.group("rerun"); + for (const node of nodes) { + console.log(`Node ${node.type} (${node.start}-${node.end})`); + } + console.groupEnd(); + for (const node of nodes) { const cell = code.slice(node.start, node.end); const transpiled = transpile(cell); From 7cc57c23729e3020456396318ffec7f7f92b30e2 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:00:11 +0800 Subject: [PATCH 04/30] Fix failed tests due to changes in `onChanges` --- test/snapshpts.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/snapshpts.spec.js b/test/snapshpts.spec.js index 1157c51..dd605d4 100644 --- a/test/snapshpts.spec.js +++ b/test/snapshpts.spec.js @@ -12,7 +12,7 @@ describe("snapshots", () => { console.error = () => {}; try { const runtime = createRuntime(state.doc.toString()); - runtime.onChanges((changes) => (state = state.update({changes}).state)); + runtime.onChanges((specs) => (state = state.update(specs).state)); runtime.run(); await new Promise((resolve) => setTimeout(resolve, 100)); } catch (error) {} From ddb5cd5cd9adae48d82dd875ed5619333d0df3ef Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Thu, 30 Oct 2025 01:18:23 +0800 Subject: [PATCH 05/30] Add the transaction viewer to the test page --- editor/index.js | 3 +- test/index.css | 40 +++ test/index.html | 2 +- test/main.js | 30 ++- test/transactionViewer.css | 305 +++++++++++++++++++++++ test/transactionViewer.js | 496 +++++++++++++++++++++++++++++++++++++ 6 files changed, 871 insertions(+), 5 deletions(-) create mode 100644 test/transactionViewer.css create mode 100644 test/transactionViewer.js diff --git a/editor/index.js b/editor/index.js index d2ab2a3..cc998ac 100644 --- a/editor/index.js +++ b/editor/index.js @@ -36,7 +36,7 @@ const eslintConfig = { }; export function createEditor(container, options) { - const {code, onError} = options; + const {code, onError, extensions = []} = options; const dispatcher = d3Dispatch("userInput"); const runtimeRef = {current: null}; @@ -82,6 +82,7 @@ export function createEditor(container, options) { docStringTag, commentLink, linter(esLint(new eslint.Linter(), eslintConfig)), + ...extensions, ], }); diff --git a/test/index.css b/test/index.css index 99016db..1cf70b0 100644 --- a/test/index.css +++ b/test/index.css @@ -1 +1,41 @@ @import "../editor/index.css"; + +body { + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Oxygen, + Ubuntu, + Cantarell, + "Open Sans", + "Helvetica Neue", + sans-serif; + font-size: 16px; + + margin: 0; + padding: 0.5rem; + box-sizing: border-box; + display: flex; + flex-direction: row; + gap: 0.5rem; + height: 100vh; + width: 100vw; + + main { + flex: 1; + background: #55550003; + border: 1px solid #19130029; + padding: 0.75rem; + } + + aside { + width: 25rem; + padding: 0.75rem; + background: #25250007; + border: 1px solid #19130029; + overflow-y: auto; + } +} diff --git a/test/index.html b/test/index.html index 87fca19..3261143 100644 --- a/test/index.html +++ b/test/index.html @@ -5,9 +5,9 @@ Recho (Test) + -
diff --git a/test/main.js b/test/main.js index f0b60cf..98932dd 100644 --- a/test/main.js +++ b/test/main.js @@ -1,5 +1,15 @@ import * as jsTests from "./js/index.js"; import {createEditor} from "../editor/index.js"; +import {createTransactionViewer} from "./transactionViewer.js"; + +// The main and side panels +const mainPanel = document.createElement("main"); +mainPanel.id = "main-panel"; +document.body.append(mainPanel); + +const sidePanels = document.createElement("aside"); +sidePanels.id = "side-panels"; +document.body.append(sidePanels); // Select const select = createSelect(() => { @@ -9,26 +19,40 @@ const select = createSelect(() => { }); const options = Object.keys(jsTests).map(createOption); select.append(...options); -document.body.append(select); +mainPanel.append(select); const container = document.createElement("div"); container.id = "container"; -document.body.append(container); +mainPanel.append(container); // Init app name. const initialValue = new URL(location).searchParams.get("name"); if (jsTests[initialValue]) select.value = initialValue; let preEditor = null; +let transactionViewer = null; render(); async function render() { container.innerHTML = ""; if (preEditor) preEditor.destroy(); + if (transactionViewer) transactionViewer.destroy(); + + // Clear and reset side panels + sidePanels.innerHTML = ""; + + // Create transaction viewer + transactionViewer = createTransactionViewer(sidePanels); + const editorContainer = document.createElement("div"); const code = jsTests[select.value]; - const editor = createEditor(editorContainer, {code}); + const editor = createEditor(editorContainer, { + code, + extensions: [transactionViewer.plugin], + }); editor.run(); + preEditor = editor; + const runButton = document.createElement("button"); runButton.textContent = "Run"; runButton.onclick = () => editor.run(); diff --git a/test/transactionViewer.css b/test/transactionViewer.css new file mode 100644 index 0000000..23a9443 --- /dev/null +++ b/test/transactionViewer.css @@ -0,0 +1,305 @@ +/* Transaction Viewer Styles */ +.transaction-viewer { + font-size: 14px; + + display: flex; + flex-direction: column; + height: 100%; + + h3 { + margin: 0 0 0.75rem 0; + font-size: 1.1em; + font-weight: 600; + } +} + +.transaction-controls { + display: flex; + gap: 0.5rem; + align-items: center; + margin-bottom: 0.75rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid #19130029; + + button { + padding: 0.25rem 0.5rem; + font-size: 12px; + border: 1px solid #19130029; + background: white; + border-radius: 3px; + cursor: pointer; + + &:hover { + background: #f6f6f6; + } + } + + label { + display: flex; + align-items: center; + gap: 0.25rem; + font-size: 12px; + cursor: pointer; + } +} + +.transaction-list-scrollable { + flex: 1; + min-height: 0; + overflow-y: auto; +} + +.transaction-list { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.no-transactions { + color: #666; + font-style: italic; + text-align: center; + padding: 2rem 0; +} + +.transaction-item { + border: 1px solid #d0d7de; + border-radius: 6px; + background: white; + padding: 0; + transition: all 0.2s; + + &[open] { + border-color: #8b949e; + } + + &.transaction-group { + border-color: #9a6dd7; + background: #faf8ff; + + &[open] { + border-color: #7c3aed; + } + + summary { + background: #f3e8ff; + + &:hover { + background: #e9d5ff; + } + } + } + + summary { + padding: 0.5rem; + cursor: pointer; + font-weight: 500; + font-family: ui-monospace, monospace; + font-size: 12px; + user-select: none; + background: #f6f8fa; + border-radius: 5px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; + + &:hover { + background: #eaeef2; + } + } + + .tr-summary-left { + flex: 1; + min-width: 0; + } + + .tr-summary-right { + flex-shrink: 0; + color: #6e7781; + font-size: 11px; + } + + &.user-transaction summary { + background: #dff6dd; + + &:hover { + background: #c8f0c5; + } + } + + &.remote-transaction summary { + background: #fff8c5; + + &:hover { + background: #fff3b0; + } + } + + &.doc-changed summary { + font-weight: 600; + } +} + +.tr-details { + padding: 0.75rem; + font-size: 12px; + line-height: 1.5; +} + +.tr-field { + margin-bottom: 0.5rem; + + strong { + color: #1f2328; + } +} + +.tr-change { + margin-left: 1rem; + margin-bottom: 0.5rem; + padding: 0.5rem; + background: #f6f8fa; + border-radius: 4px; + font-size: 11px; + + code { + background: #ffffff; + padding: 0.125rem 0.25rem; + border-radius: 3px; + font-family: ui-monospace, monospace; + border: 1px solid #d0d7de; + } +} + +.deleted { + color: #cf222e; + margin-top: 0.25rem; +} + +.inserted { + color: #1a7f37; + margin-top: 0.25rem; +} + +.tr-annotation { + margin-left: 1rem; + padding: 0.25rem 0.5rem; + background: #dff6ff; + border-left: 3px solid #0969da; + font-family: ui-monospace, monospace; + font-size: 11px; + margin-bottom: 0.25rem; + border-radius: 3px; +} + +.tr-effect { + margin-left: 1rem; + padding: 0.25rem 0.5rem; + background: #fbefff; + border-left: 3px solid #8250df; + font-family: ui-monospace, monospace; + font-size: 11px; + margin-bottom: 0.25rem; + border-radius: 3px; +} + +.tr-selection { + margin-left: 1rem; + padding: 0.25rem 0.5rem; + background: #fff8c5; + border-left: 3px solid #bf8700; + font-family: ui-monospace, monospace; + font-size: 11px; + margin-bottom: 0.25rem; + border-radius: 3px; +} + +.tr-property { + margin-left: 1rem; + padding: 0.25rem 0.5rem; + background: #f6f8fa; + border-left: 3px solid #6e7781; + font-family: ui-monospace, monospace; + font-size: 11px; + margin-bottom: 0.25rem; + border-radius: 3px; +} + +.tr-effect-block-metadata { + background: #f3e8ff; + border-left-color: #a855f7; + padding: 0.5rem; +} + +.tr-effect-title { + font-weight: 600; + margin-bottom: 0.5rem; + color: #7c3aed; +} + +.tr-block-metadata { + margin-left: 0.5rem; + margin-top: 0.5rem; + padding: 0.5rem; + background: white; + border: 1px solid #e9d5ff; + border-radius: 3px; +} + +.tr-block-header { + font-weight: 600; + margin-bottom: 0.25rem; + color: #6b21a8; +} + +.tr-block-detail { + font-size: 11px; + margin-left: 0.5rem; + margin-bottom: 0.125rem; + color: #4b5563; +} + +.tr-block-error { + color: #dc2626; + font-weight: 600; +} + +.tr-grouped-list { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.tr-grouped-item { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.375rem 0.5rem; + background: white; + border: 1px solid #e9d5ff; + border-radius: 4px; + font-size: 11px; + font-family: ui-monospace, monospace; +} + +.tr-grouped-index { + font-weight: 600; + color: #7c3aed; + min-width: 2.5rem; +} + +.tr-grouped-selection { + flex: 1; + color: #4b5563; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.tr-grouped-time { + color: #9ca3af; + font-size: 10px; + flex-shrink: 0; +} diff --git a/test/transactionViewer.js b/test/transactionViewer.js new file mode 100644 index 0000000..b7447e7 --- /dev/null +++ b/test/transactionViewer.js @@ -0,0 +1,496 @@ +import {ViewPlugin} from "@codemirror/view"; +import {Transaction} from "@codemirror/state"; +import {blockMetadataEffect} from "../editor/blockMetadata.ts"; + +// Maximum number of transactions to keep in history +const MAX_HISTORY = 100; + +/** + * Creates a transaction tracker plugin and UI viewer + */ +export function createTransactionViewer(container) { + const transactions = []; + const listeners = new Set(); + let nextIndex = 0; // Continuous index counter + + // Notify all listeners when transactions update + function notifyListeners() { + listeners.forEach((fn) => fn(transactions)); + } + + // Create the ViewPlugin to track transactions + const plugin = ViewPlugin.fromClass( + class { + constructor(view) { + // Capture initial state as transaction 0 + const initialTr = { + index: nextIndex++, + docChanged: false, + changes: [], + annotations: {}, + effects: [], + selection: view.state.selection.ranges.map((r) => ({ + from: r.from, + to: r.to, + anchor: r.anchor, + head: r.head, + })), + timestamp: Date.now(), + }; + transactions.push(initialTr); + notifyListeners(); + } + + update(update) { + // Process each transaction in the update + update.transactions.forEach((tr, idx) => { + const transactionData = extractTransactionData(tr, nextIndex++); + + // Add to history + transactions.push(transactionData); + + // Keep only the last MAX_HISTORY transactions + if (transactions.length > MAX_HISTORY) { + transactions.shift(); + } + }); + + if (update.transactions.length > 0) { + notifyListeners(); + } + } + }, + ); + + // Extract data from a transaction + function extractTransactionData(tr, index) { + const data = { + index, + docChanged: tr.docChanged, + changes: [], + annotations: {}, + effects: [], + selection: tr.state.selection.ranges.map((r) => ({ + from: r.from, + to: r.to, + anchor: r.anchor, + head: r.head, + })), + scrollIntoView: tr.scrollIntoView, + filter: tr.filter, + sequential: tr.sequential, + timestamp: Date.now(), + }; + + // Extract changes with line/column information + // Use startState for from/to positions (before the change) + tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { + const fromLine = tr.startState.doc.lineAt(fromA); + const toLine = tr.startState.doc.lineAt(toA); + + data.changes.push({ + from: fromA, + to: toA, + fromLine: fromLine.number, + fromCol: fromA - fromLine.from, + toLine: toLine.number, + toCol: toA - toLine.from, + insert: inserted.toString(), + }); + }); + + // Extract annotations + const userEvent = tr.annotation(Transaction.userEvent); + if (userEvent !== undefined) { + data.annotations.userEvent = userEvent; + } + + const remote = tr.annotation(Transaction.remote); + if (remote !== undefined) { + data.annotations.remote = remote; + } + + // Check for other common annotations + const addToHistory = tr.annotation(Transaction.addToHistory); + if (addToHistory !== undefined) { + data.annotations.addToHistory = addToHistory; + } + + // Extract effects + for (const effect of tr.effects) { + const effectData = { + value: effect.value, + type: "StateEffect", + }; + + // Check if this is a blockMetadataEffect + if (effect.is(blockMetadataEffect)) { + effectData.type = "blockMetadataEffect"; + effectData.blockMetadata = effect.value; + } + + data.effects.push(effectData); + } + + return data; + } + + // Create the UI + const viewerElement = document.createElement("div"); + viewerElement.className = "transaction-viewer"; + viewerElement.innerHTML = ` +

Transactions

+
+ + +
+
+
+
+ `; + + const listElement = viewerElement.querySelector(".transaction-list"); + const clearButton = viewerElement.querySelector("#clear-transactions"); + const autoScrollCheckbox = viewerElement.querySelector("#auto-scroll"); + + clearButton.onclick = () => { + transactions.length = 0; + nextIndex = 0; // Reset the index counter + renderTransactions(); + }; + + function renderTransactions() { + listElement.innerHTML = ""; + + if (transactions.length === 0) { + listElement.innerHTML = '
No transactions yet
'; + return; + } + + // Group transactions - group consecutive selection transactions + const groups = []; + let currentGroup = null; + + // Process in reverse order (newest first) + for (let i = transactions.length - 1; i >= 0; i--) { + const tr = transactions[i]; + const isSelection = tr.annotations.userEvent === "select" || tr.annotations.userEvent === "select.pointer"; + + if (isSelection) { + if (currentGroup && currentGroup.type === "selection") { + // Add to existing selection group + currentGroup.transactions.push(tr); + } else { + // Start a new selection group + currentGroup = { + type: "selection", + transactions: [tr], + }; + groups.push(currentGroup); + } + } else { + // Non-selection transaction - add as individual + groups.push({ + type: "individual", + transaction: tr, + }); + currentGroup = null; + } + } + + // Render groups + for (const group of groups) { + if (group.type === "individual") { + const item = createTransactionItem(group.transaction); + listElement.appendChild(item); + } else { + const groupItem = createSelectionGroupItem(group.transactions); + listElement.appendChild(groupItem); + } + } + + // Auto-scroll to top (latest transaction) + if (autoScrollCheckbox.checked) { + listElement.scrollTop = 0; + } + } + + /** + * + * @param {import("@codemirror/state").TransactionSpec} tr + * @returns + */ + function createSelectionGroupItem(transactions) { + const item = document.createElement("details"); + item.className = "transaction-item transaction-group"; + + const count = transactions.length; + const firstTr = transactions[0]; + const lastTr = transactions[transactions.length - 1]; + + // Format timestamps + const firstTime = new Date(firstTr.timestamp); + const lastTime = new Date(lastTr.timestamp); + const firstTimeStr = + firstTime.toLocaleTimeString("en-US", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }) + + "." + + firstTime.getMilliseconds().toString().padStart(3, "0"); + const lastTimeStr = + lastTime.toLocaleTimeString("en-US", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }) + + "." + + lastTime.getMilliseconds().toString().padStart(3, "0"); + + const summaryLeft = `#${lastTr.index}-${firstTr.index} [select] (${count} transactions)`; + const summaryRight = `${lastTimeStr} - ${firstTimeStr}`; + + // Create list of individual transactions + const transactionsList = transactions + .map((tr) => { + const time = new Date(tr.timestamp); + const timeStr = + time.toLocaleTimeString("en-US", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }) + + "." + + time.getMilliseconds().toString().padStart(3, "0"); + + const selectionInfo = tr.selection + .map((range, idx) => { + const isCursor = range.from === range.to; + return isCursor ? `cursor at ${range.from}` : `${range.from}-${range.to}`; + }) + .join(", "); + + return ` +
+ #${tr.index} + ${selectionInfo} + ${timeStr} +
+ `; + }) + .join(""); + + item.innerHTML = ` + + ${summaryLeft} + ${summaryRight} + +
+
+ ${transactionsList} +
+
+ `; + + return item; + } + + function createTransactionItem(tr) { + const item = document.createElement("details"); + item.className = "transaction-item"; + + // Add classes based on transaction type + if (tr.annotations.userEvent) { + item.classList.add("user-transaction"); + } + if (tr.annotations.remote) { + item.classList.add("remote-transaction"); + } + if (tr.docChanged) { + item.classList.add("doc-changed"); + } + + // Format timestamp as HH:MM:SS.mmm + const time = new Date(tr.timestamp); + const timeStr = + time.toLocaleTimeString("en-US", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }) + + "." + + time.getMilliseconds().toString().padStart(3, "0"); + + let summaryLeft = `#${tr.index}`; + if (tr.annotations.userEvent) { + summaryLeft += ` [${tr.annotations.userEvent}]`; + } else if (tr.annotations.remote) { + summaryLeft += ` [remote: ${JSON.stringify(tr.annotations.remote)}]`; + } + if (tr.docChanged) { + summaryLeft += ` 📝`; + } + + const details = []; + + // Document changed + details.push(`
Doc Changed: ${tr.docChanged}
`); + + // Changes + if (tr.changes.length > 0) { + details.push(`
Changes:
`); + tr.changes.forEach((change, idx) => { + const deleted = change.to - change.from; + const inserted = change.insert.length; + const samePos = change.from === change.to; + const sameLine = change.fromLine === change.toLine; + + let posInfo = `pos ${change.from}-${change.to}`; + if (sameLine) { + posInfo += ` (L${change.fromLine}:${change.fromCol}-${change.toCol})`; + } else { + posInfo += ` (L${change.fromLine}:${change.fromCol} to L${change.toLine}:${change.toCol})`; + } + + details.push(` +
+
Change ${idx + 1}: ${posInfo}
+ ${deleted > 0 ? `
Deleted ${deleted} chars
` : ""} + ${inserted > 0 ? `
Inserted: ${escapeHtml(change.insert)}
` : ""} +
+ `); + }); + } else { + details.push(`
Changes: none
`); + } + + // Annotations + if (Object.keys(tr.annotations).length > 0) { + details.push(`
Annotations:
`); + for (const [key, value] of Object.entries(tr.annotations)) { + details.push(`
${key}: ${JSON.stringify(value)}
`); + } + } else { + details.push(`
Annotations: none
`); + } + + // Effects + if (Array.isArray(tr.effects) && tr.effects.length > 0) { + details.push(`
Effects: ${tr.effects.length}
`); + tr.effects.forEach((effect, idx) => { + if (effect.type === "blockMetadataEffect" && effect.blockMetadata) { + // Special handling for blockMetadataEffect + details.push(` + + `); + } else { + // Generic effect display + details.push(` +
+ Effect ${idx + 1} (${effect.type}): ${JSON.stringify(effect.value).substring(0, 100)} +
+ `); + } + }); + } else { + details.push(`
Effects: none
`); + } + + // Selection + details.push(`
Selection:
`); + tr.selection.forEach((range, idx) => { + const isCursor = range.from === range.to; + details.push(` +
+ Range ${idx + 1}: ${isCursor ? `cursor at ${range.from}` : `${range.from}-${range.to}`} + ${!isCursor ? `(anchor: ${range.anchor}, head: ${range.head})` : ""} +
+ `); + }); + + // Additional transaction properties + const additionalProps = []; + if (tr.scrollIntoView !== undefined) { + additionalProps.push(`scrollIntoView: ${tr.scrollIntoView}`); + } + if (tr.filter !== undefined) { + additionalProps.push(`filter: ${tr.filter}`); + } + if (tr.sequential !== undefined) { + additionalProps.push(`sequential: ${tr.sequential}`); + } + + if (additionalProps.length > 0) { + details.push(`
Other Properties:
`); + additionalProps.forEach((prop) => { + details.push(`
${prop}
`); + }); + } + + item.innerHTML = ` + + ${summaryLeft} + ${timeStr} + +
+ ${details.join("")} +
+ `; + + return item; + } + + function escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } + + // Listen for transaction updates + listeners.add(renderTransactions); + + // Initial render + renderTransactions(); + + // Append to container + container.appendChild(viewerElement); + + return { + plugin, + destroy: () => { + listeners.clear(); + viewerElement.remove(); + }, + }; +} From f3a2202b2db69264ff345858feb8629924306995 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:12:14 +0800 Subject: [PATCH 06/30] Implement the playground using React --- package.json | 2 + pnpm-lock.yaml | 380 ++++++++++++++++++++- test/components/App.tsx | 50 +++ test/components/Editor.tsx | 129 +++++++ test/components/TestSelector.tsx | 30 ++ test/components/TransactionViewer.tsx | 471 ++++++++++++++++++++++++++ test/index.html | 5 +- test/main.tsx | 18 + test/store.ts | 61 ++++ test/styles.css | 41 +++ vite.config.js | 2 + 11 files changed, 1182 insertions(+), 7 deletions(-) create mode 100644 test/components/App.tsx create mode 100644 test/components/Editor.tsx create mode 100644 test/components/TestSelector.tsx create mode 100644 test/components/TransactionViewer.tsx create mode 100644 test/main.tsx create mode 100644 test/store.ts create mode 100644 test/styles.css diff --git a/package.json b/package.json index d590b79..29ecad5 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.1.14", + "@vitejs/plugin-react": "^4.3.1", "clsx": "^2.1.1", "eslint": "^9.37.0", "eslint-config-prettier": "^10.1.8", @@ -77,6 +78,7 @@ "d3-require": "^1.3.0", "eslint-linter-browserify": "^9.37.0", "friendly-words": "^1.3.1", + "jotai": "^2.10.3", "next": "^15.5.6", "nstr": "^0.1.3", "object-inspect": "^1.13.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d53fa13..2dbdf11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,9 +80,12 @@ importers: friendly-words: specifier: ^1.3.1 version: 1.3.1 + jotai: + specifier: ^2.10.3 + version: 2.15.1(@babel/core@7.28.5)(@babel/template@7.27.2)(react@19.2.0) next: specifier: ^15.5.6 - version: 15.5.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 15.5.6(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) nstr: specifier: ^0.1.3 version: 0.1.3 @@ -108,6 +111,9 @@ importers: '@tailwindcss/postcss': specifier: ^4.1.14 version: 4.1.14 + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.7.0(vite@7.1.10(jiti@2.6.1)(lightningcss@1.30.1)) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -160,10 +166,93 @@ packages: '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.4': resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + '@codemirror/autocomplete@6.19.0': resolution: {integrity: sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==} @@ -818,6 +907,9 @@ packages: '@observablehq/runtime@6.0.0': resolution: {integrity: sha512-t3UXP69O0JK20HY/neF4/DDDSDorwo92As806Y1pNTgTmj1NtoPyVpesYzfH31gTFOFrXC2cArV+wLpebMk9eA==} + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + '@rollup/rollup-android-arm-eabi@4.52.4': resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} cpu: [arm] @@ -1048,6 +1140,18 @@ packages: '@tailwindcss/postcss@4.1.14': resolution: {integrity: sha512-BdMjIxy7HUNThK87C7BC8I1rE8BVUsfNQSI5siQ4JK3iIa3w0XyVvVL9SXLWO//CtYTcp1v7zci0fYwJOjB+Zg==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -1091,6 +1195,12 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -1246,6 +1356,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + baseline-browser-mapping@2.8.28: + resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==} + hasBin: true + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1267,6 +1381,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1301,6 +1420,9 @@ packages: caniuse-lite@1.0.30001751: resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + cbor@8.1.0: resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} engines: {node: '>=12.19'} @@ -1416,6 +1538,9 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + convert-to-spaces@2.0.1: resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1565,6 +1690,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.251: + resolution: {integrity: sha512-lmyEOp4G0XT3qrYswNB4np1kx90k6QCXpnSHYv2xEsUuEu8JCobpDVYO6vMseirQyyCC6GCIGGxd5szMBa0tRA==} + emittery@1.2.0: resolution: {integrity: sha512-KxdRyyFcS85pH3dnU8Y5yFUm2YJdaHwcBZWrfG8o89ZY9a13/f9itbN+YG3ELbBo9Pg5zvIozstmuV8bX13q6g==} engines: {node: '>=14.16'} @@ -1833,6 +1961,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2128,6 +2260,24 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jotai@2.15.1: + resolution: {integrity: sha512-yHT1HAZ3ba2Q8wgaUQ+xfBzEtcS8ie687I8XVCBinfg4bNniyqLIN+utPXWKQE93LMF5fPbQSVRZqgpcN5yd6Q==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@babel/core': '>=7.0.0' + '@babel/template': '>=7.0.0' + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@babel/core': + optional: true + '@babel/template': + optional: true + '@types/react': + optional: true + react: + optional: true + js-string-escape@1.0.1: resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} engines: {node: '>= 0.8'} @@ -2155,6 +2305,11 @@ packages: canvas: optional: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2164,6 +2319,11 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -2273,6 +2433,9 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.542.0: resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==} peerDependencies: @@ -2418,6 +2581,9 @@ packages: sass: optional: true + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + nofilter@3.1.0: resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} engines: {node: '>=12.19'} @@ -2648,6 +2814,10 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + react-tooltip@5.30.0: resolution: {integrity: sha512-Yn8PfbgQ/wmqnL7oBpz1QiDaLKrzZMdSUUdk7nVeGTwzbxCAJiJzR4VSYW+eIO42F1INt57sPUmpgKv0KwJKtg==} peerDependencies: @@ -3072,6 +3242,12 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3254,6 +3430,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -3289,8 +3468,120 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/runtime@7.28.4': {} + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@codemirror/autocomplete@6.19.0': dependencies: '@codemirror/language': 6.11.3 @@ -4016,6 +4307,8 @@ snapshots: '@observablehq/runtime@6.0.0': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} + '@rollup/rollup-android-arm-eabi@4.52.4': optional: true @@ -4200,6 +4493,27 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.14 + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.5 + '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 @@ -4245,6 +4559,18 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@vitejs/plugin-react@4.7.0(vite@7.1.10(jiti@2.6.1)(lightningcss@1.30.1))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 7.1.10(jiti@2.6.1)(lightningcss@1.30.1) + transitivePeerDependencies: + - supports-color + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -4462,6 +4788,8 @@ snapshots: balanced-match@1.0.2: {} + baseline-browser-mapping@2.8.28: {} + binary-extensions@2.3.0: {} blueimp-md5@2.19.0: {} @@ -4494,6 +4822,14 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.0: + dependencies: + baseline-browser-mapping: 2.8.28 + caniuse-lite: 1.0.30001754 + electron-to-chromium: 1.5.251 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) + buffer-from@1.1.2: {} bytes@3.1.2: {} @@ -4523,6 +4859,8 @@ snapshots: caniuse-lite@1.0.30001751: {} + caniuse-lite@1.0.30001754: {} + cbor@8.1.0: dependencies: nofilter: 3.1.0 @@ -4659,6 +4997,8 @@ snapshots: content-type@1.0.5: {} + convert-source-map@2.0.0: {} + convert-to-spaces@2.0.1: {} cookie-signature@1.0.6: {} @@ -4801,6 +5141,8 @@ snapshots: ee-first@1.1.1: {} + electron-to-chromium@1.5.251: {} + emittery@1.2.0: {} emoji-regex@8.0.0: {} @@ -5215,6 +5557,8 @@ snapshots: generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -5523,6 +5867,12 @@ snapshots: jiti@2.6.1: {} + jotai@2.15.1(@babel/core@7.28.5)(@babel/template@7.27.2)(react@19.2.0): + optionalDependencies: + '@babel/core': 7.28.5 + '@babel/template': 7.27.2 + react: 19.2.0 + js-string-escape@1.0.1: {} js-tokens@4.0.0: {} @@ -5565,12 +5915,16 @@ snapshots: - supports-color - utf-8-validate + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -5660,6 +6014,10 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lucide-react@0.542.0(react@19.2.0): dependencies: react: 19.2.0 @@ -5775,7 +6133,7 @@ snapshots: negotiator@0.6.3: {} - next@15.5.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + next@15.5.6(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@next/env': 15.5.6 '@swc/helpers': 0.5.15 @@ -5783,7 +6141,7 @@ snapshots: postcss: 8.4.31 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - styled-jsx: 5.1.6(react@19.2.0) + styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.0) optionalDependencies: '@next/swc-darwin-arm64': 15.5.6 '@next/swc-darwin-x64': 15.5.6 @@ -5798,6 +6156,8 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-releases@2.0.27: {} + nofilter@3.1.0: {} normalize-path@3.0.0: {} @@ -6014,6 +6374,8 @@ snapshots: react-is@16.13.1: {} + react-refresh@0.17.0: {} + react-tooltip@5.30.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@floating-ui/dom': 1.7.4 @@ -6393,10 +6755,12 @@ snapshots: style-mod@4.1.3: {} - styled-jsx@5.1.6(react@19.2.0): + styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0): dependencies: client-only: 0.0.1 react: 19.2.0 + optionalDependencies: + '@babel/core': 7.28.5 supertap@3.0.1: dependencies: @@ -6552,6 +6916,12 @@ snapshots: unpipe@1.0.0: {} + update-browserslist-db@1.1.4(browserslist@4.28.0): + dependencies: + browserslist: 4.28.0 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -6740,6 +7110,8 @@ snapshots: y18n@5.0.8: {} + yallist@3.1.1: {} + yallist@5.0.0: {} yargs-parser@21.1.1: {} diff --git a/test/components/App.tsx b/test/components/App.tsx new file mode 100644 index 0000000..874eb05 --- /dev/null +++ b/test/components/App.tsx @@ -0,0 +1,50 @@ +import {useEffect, useState} from "react"; +import {useAtom} from "jotai"; +import {selectedTestAtom} from "../store"; +import {TestSelector} from "./TestSelector"; +import {Editor} from "./Editor"; +import {TransactionViewer} from "./TransactionViewer"; +import * as testSamples from "../js/index.js"; +import type {ViewPlugin} from "@codemirror/view"; + +export function App() { + const [selectedTest, setSelectedTest] = useAtom(selectedTestAtom); + const [transactionViewerPlugin, setTransactionViewerPlugin] = useState | undefined>(); + + // Initialize from URL on mount + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const testFromUrl = params.get("test"); + if (testFromUrl && testFromUrl in testSamples) { + setSelectedTest(testFromUrl); + } + }, [setSelectedTest]); + + // Get the current test code + const currentCode = (testSamples as Record)[selectedTest] || testSamples.helloWorld; + + return ( +
+ {/* Header */} +
+
+

Recho Playground

+ +
+
+ + {/* Main content */} +
+ {/* Editor panel */} +
+ +
+ + {/* Transaction viewer sidebar */} + +
+
+ ); +} diff --git a/test/components/Editor.tsx b/test/components/Editor.tsx new file mode 100644 index 0000000..0b261c6 --- /dev/null +++ b/test/components/Editor.tsx @@ -0,0 +1,129 @@ +import { useEffect, useRef, useState } from "react"; +import { Play, Square, RefreshCcw } from "lucide-react"; +import { createEditor } from "../../editor/index.js"; +import { cn } from "../../app/cn.js"; +import type { ViewPlugin } from "@codemirror/view"; + +interface EditorProps { + code: string; + transactionViewerPlugin?: ViewPlugin; + onError?: (error: Error) => void; +} + +function debounce(fn: (...args: any[]) => void, delay = 0) { + let timeout: ReturnType; + return (...args: any[]) => { + clearTimeout(timeout); + timeout = setTimeout(() => fn(...args), delay); + }; +} + +const onDefaultError = debounce(() => { + setTimeout(() => { + alert("Something unexpected happened. Please check the console for details."); + }, 100); +}, 0); + +export function Editor({ code, transactionViewerPlugin, onError = onDefaultError }: EditorProps) { + const containerRef = useRef(null); + const editorRef = useRef(null); + const [needRerun, setNeedRerun] = useState(false); + + useEffect(() => { + if (containerRef.current) { + containerRef.current.innerHTML = ""; + + const extensions = transactionViewerPlugin ? [transactionViewerPlugin] : []; + + editorRef.current = createEditor(containerRef.current, { + code, + extensions, + onError, + }); + + // Auto-run on mount + editorRef.current.run(); + } + + return () => { + if (editorRef.current) { + editorRef.current.destroy(); + } + }; + }, [code, transactionViewerPlugin, onError]); + + useEffect(() => { + const onInput = () => { + setNeedRerun(true); + }; + + if (editorRef.current) { + editorRef.current.on("userInput", onInput); + } + }, [code]); + + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.metaKey && e.key === "s") { + e.preventDefault(); + onRun(); + } + }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, []); + + function onRun() { + setNeedRerun(false); + editorRef.current?.run(); + } + + function onStop() { + setNeedRerun(false); + editorRef.current?.stop(); + } + + function onRerun() { + setNeedRerun(false); + editorRef.current?.stop(); + editorRef.current?.run(); + } + + function metaKey() { + return typeof navigator !== "undefined" && navigator.userAgent.includes("Mac") ? "⌘" : "Ctrl"; + } + + return ( +
+
+
Editor
+
+ + + +
+
+
+
{code}
+
+
+ ); +} diff --git a/test/components/TestSelector.tsx b/test/components/TestSelector.tsx new file mode 100644 index 0000000..3dcd275 --- /dev/null +++ b/test/components/TestSelector.tsx @@ -0,0 +1,30 @@ +import { useAtom } from "jotai"; +import { selectedTestAtom, getTestSampleName } from "../store"; +import * as testSamples from "../js/index.js"; + +export function TestSelector() { + const [selectedTest, setSelectedTest] = useAtom(selectedTestAtom); + + const handleChange = (e: React.ChangeEvent) => { + const newTest = e.target.value; + setSelectedTest(newTest); + // Update URL + const url = new URL(window.location.href); + url.searchParams.set("test", newTest); + window.history.pushState({}, "", url); + }; + + return ( + + ); +} diff --git a/test/components/TransactionViewer.tsx b/test/components/TransactionViewer.tsx new file mode 100644 index 0000000..7df4b36 --- /dev/null +++ b/test/components/TransactionViewer.tsx @@ -0,0 +1,471 @@ +import { useState, useEffect, useRef } from "react"; +import { ViewPlugin } from "@codemirror/view"; +import { Transaction as CMTransaction } from "@codemirror/state"; +import { blockMetadataEffect } from "../../editor/blockMetadata.ts"; +import { cn } from "../../app/cn.js"; + +// Maximum number of transactions to keep in history +const MAX_HISTORY = 100; + +interface TransactionRange { + from: number; + to: number; + anchor: number; + head: number; +} + +interface TransactionChange { + from: number; + to: number; + fromLine: number; + fromCol: number; + toLine: number; + toCol: number; + insert: string; +} + +interface TransactionEffect { + value: any; + type: string; + blockMetadata?: any[]; +} + +interface TransactionData { + index: number; + docChanged: boolean; + changes: TransactionChange[]; + annotations: Record; + effects: TransactionEffect[]; + selection: TransactionRange[]; + scrollIntoView?: boolean; + filter?: boolean; + sequential?: boolean; + timestamp: number; +} + +interface TransactionGroup { + type: "individual" | "selection"; + transaction?: TransactionData; + transactions?: TransactionData[]; +} + +interface TransactionViewerProps { + onPluginCreate: (plugin: ViewPlugin) => void; +} + +export function TransactionViewer({ onPluginCreate }: TransactionViewerProps) { + const [transactions, setTransactions] = useState([]); + const [autoScroll, setAutoScroll] = useState(true); + const listRef = useRef(null); + const nextIndexRef = useRef(0); + + useEffect(() => { + const transactionsList: TransactionData[] = []; + const listeners = new Set<(transactions: TransactionData[]) => void>(); + + function notifyListeners() { + listeners.forEach((fn) => fn([...transactionsList])); + } + + function extractTransactionData(tr: CMTransaction, index: number): TransactionData { + const data: TransactionData = { + index, + docChanged: tr.docChanged, + changes: [], + annotations: {}, + effects: [], + selection: tr.state.selection.ranges.map((r) => ({ + from: r.from, + to: r.to, + anchor: r.anchor, + head: r.head, + })), + scrollIntoView: tr.scrollIntoView, + filter: tr.filter, + sequential: tr.sequential, + timestamp: Date.now(), + }; + + // Extract changes + tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { + const fromLine = tr.startState.doc.lineAt(fromA); + const toLine = tr.startState.doc.lineAt(toA); + + data.changes.push({ + from: fromA, + to: toA, + fromLine: fromLine.number, + fromCol: fromA - fromLine.from, + toLine: toLine.number, + toCol: toA - toLine.from, + insert: inserted.toString(), + }); + }); + + // Extract annotations + const userEvent = tr.annotation(CMTransaction.userEvent); + if (userEvent !== undefined) { + data.annotations.userEvent = userEvent; + } + + const remote = tr.annotation(CMTransaction.remote); + if (remote !== undefined) { + data.annotations.remote = remote; + } + + const addToHistory = tr.annotation(CMTransaction.addToHistory); + if (addToHistory !== undefined) { + data.annotations.addToHistory = addToHistory; + } + + // Extract effects + for (const effect of tr.effects) { + const effectData: TransactionEffect = { + value: effect.value, + type: "StateEffect", + }; + + if (effect.is(blockMetadataEffect)) { + effectData.type = "blockMetadataEffect"; + effectData.blockMetadata = effect.value; + } + + data.effects.push(effectData); + } + + return data; + } + + const plugin = ViewPlugin.fromClass( + class { + constructor(view: any) { + const initialTr: TransactionData = { + index: nextIndexRef.current++, + docChanged: false, + changes: [], + annotations: {}, + effects: [], + selection: view.state.selection.ranges.map((r: any) => ({ + from: r.from, + to: r.to, + anchor: r.anchor, + head: r.head, + })), + timestamp: Date.now(), + }; + transactionsList.push(initialTr); + notifyListeners(); + } + + update(update: any) { + update.transactions.forEach((tr: CMTransaction) => { + const transactionData = extractTransactionData(tr, nextIndexRef.current++); + transactionsList.push(transactionData); + + if (transactionsList.length > MAX_HISTORY) { + transactionsList.shift(); + } + }); + + if (update.transactions.length > 0) { + notifyListeners(); + } + } + } + ); + + listeners.add((newTransactions) => { + setTransactions(newTransactions); + }); + + onPluginCreate(plugin); + + return () => { + listeners.clear(); + }; + }, [onPluginCreate]); + + useEffect(() => { + if (autoScroll && listRef.current) { + listRef.current.scrollTop = 0; + } + }, [transactions, autoScroll]); + + const handleClear = () => { + setTransactions([]); + nextIndexRef.current = 0; + }; + + const groupTransactions = (): TransactionGroup[] => { + const groups: TransactionGroup[] = []; + let currentGroup: TransactionGroup | null = null; + + for (let i = transactions.length - 1; i >= 0; i--) { + const tr = transactions[i]; + const isSelection = + tr.annotations.userEvent === "select" || tr.annotations.userEvent === "select.pointer"; + + if (isSelection) { + if (currentGroup && currentGroup.type === "selection") { + currentGroup.transactions!.push(tr); + } else { + currentGroup = { + type: "selection", + transactions: [tr], + }; + groups.push(currentGroup); + } + } else { + groups.push({ + type: "individual", + transaction: tr, + }); + currentGroup = null; + } + } + + return groups; + }; + + return ( +
+
+

Transactions

+
+ + +
+
+
+ {transactions.length === 0 ? ( +
No transactions yet
+ ) : ( + groupTransactions().map((group, idx) => + group.type === "individual" ? ( + + ) : ( + + ) + ) + )} +
+
+ ); +} + +function TransactionItem({ transaction: tr }: { transaction: TransactionData }) { + const [isOpen, setIsOpen] = useState(false); + + const formatTime = (timestamp: number) => { + const time = new Date(timestamp); + return ( + time.toLocaleTimeString("en-US", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }) + + "." + + time.getMilliseconds().toString().padStart(3, "0") + ); + }; + + let summaryLeft = `#${tr.index}`; + if (tr.annotations.userEvent) { + summaryLeft += ` [${tr.annotations.userEvent}]`; + } else if (tr.annotations.remote) { + summaryLeft += ` [remote: ${JSON.stringify(tr.annotations.remote)}]`; + } + if (tr.docChanged) { + summaryLeft += ` 📝`; + } + + return ( +
setIsOpen((e.target as HTMLDetailsElement).open)} + className={cn( + "mb-2 border border-gray-200 rounded text-xs", + tr.annotations.userEvent && "bg-blue-50", + tr.docChanged && "border-l-4 border-l-blue-500" + )} + > + + {summaryLeft} + {formatTime(tr.timestamp)} + +
+
+ Doc Changed: {tr.docChanged.toString()} +
+ + {tr.changes.length > 0 ? ( +
+ Changes: + {tr.changes.map((change, idx) => { + const deleted = change.to - change.from; + const inserted = change.insert.length; + const sameLine = change.fromLine === change.toLine; + + let posInfo = `pos ${change.from}-${change.to}`; + if (sameLine) { + posInfo += ` (L${change.fromLine}:${change.fromCol}-${change.toCol})`; + } else { + posInfo += ` (L${change.fromLine}:${change.fromCol} to L${change.toLine}:${change.toCol})`; + } + + return ( +
+
Change {idx + 1}: {posInfo}
+ {deleted > 0 &&
Deleted {deleted} chars
} + {inserted > 0 && ( +
+ Inserted: {change.insert} +
+ )} +
+ ); + })} +
+ ) : ( +
+ Changes: none +
+ )} + + {Object.keys(tr.annotations).length > 0 ? ( +
+ Annotations: + {Object.entries(tr.annotations).map(([key, value]) => ( +
+ {key}: {JSON.stringify(value)} +
+ ))} +
+ ) : ( +
+ Annotations: none +
+ )} + + {tr.effects.length > 0 ? ( +
+ Effects: {tr.effects.length} + {tr.effects.map((effect, idx) => ( +
+ {effect.type === "blockMetadataEffect" && effect.blockMetadata ? ( +
+
Effect {idx + 1}: blockMetadataEffect ({effect.blockMetadata.length} blocks)
+ {effect.blockMetadata.map((block: any, blockIdx: number) => ( +
+
Block {blockIdx + 1}:
+
+ {block.output !== null + ? `Output: ${block.output.from}-${block.output.to}` + : "Output: null"} +
+
Source: {block.source.from}-{block.source.to}
+ {Object.keys(block.attributes).length > 0 && ( +
Attributes: {JSON.stringify(block.attributes)}
+ )} + {block.error &&
Error: true
} +
+ ))} +
+ ) : ( +
Effect {idx + 1} ({effect.type}): {JSON.stringify(effect.value).substring(0, 100)}
+ )} +
+ ))} +
+ ) : ( +
+ Effects: none +
+ )} + +
+ Selection: + {tr.selection.map((range, idx) => { + const isCursor = range.from === range.to; + return ( +
+ Range {idx + 1}: {isCursor ? `cursor at ${range.from}` : `${range.from}-${range.to}`} + {!isCursor && ` (anchor: ${range.anchor}, head: ${range.head})`} +
+ ); + })} +
+
+
+ ); +} + +function SelectionGroupItem({ transactions }: { transactions: TransactionData[] }) { + const [isOpen, setIsOpen] = useState(false); + + const formatTime = (timestamp: number) => { + const time = new Date(timestamp); + return ( + time.toLocaleTimeString("en-US", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }) + + "." + + time.getMilliseconds().toString().padStart(3, "0") + ); + }; + + const count = transactions.length; + const firstTr = transactions[0]; + const lastTr = transactions[transactions.length - 1]; + + const summaryLeft = `#${lastTr.index}-${firstTr.index} [select] (${count} transactions)`; + const summaryRight = `${formatTime(lastTr.timestamp)} - ${formatTime(firstTr.timestamp)}`; + + return ( +
setIsOpen((e.target as HTMLDetailsElement).open)} + className="mb-2 border border-gray-300 rounded text-xs bg-gray-50" + > + + {summaryLeft} + {summaryRight} + +
+ {transactions.map((tr) => { + const selectionInfo = tr.selection + .map((range) => { + const isCursor = range.from === range.to; + return isCursor ? `cursor at ${range.from}` : `${range.from}-${range.to}`; + }) + .join(", "); + + return ( +
+ #{tr.index} + {selectionInfo} + {formatTime(tr.timestamp)} +
+ ); + })} +
+
+ ); +} diff --git a/test/index.html b/test/index.html index 3261143..3de6502 100644 --- a/test/index.html +++ b/test/index.html @@ -4,10 +4,9 @@ Recho (Test) - - - +
+ diff --git a/test/main.tsx b/test/main.tsx new file mode 100644 index 0000000..bb9e3e9 --- /dev/null +++ b/test/main.tsx @@ -0,0 +1,18 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { Provider as JotaiProvider } from "jotai"; +import { App } from "./components/App"; +import "./styles.css"; + +const root = document.getElementById("root"); +if (!root) { + throw new Error("Root element not found"); +} + +createRoot(root).render( + + + + + +); diff --git a/test/store.ts b/test/store.ts new file mode 100644 index 0000000..70c6836 --- /dev/null +++ b/test/store.ts @@ -0,0 +1,61 @@ +import { atom } from "jotai"; + +// Types +export interface Transaction { + time: number; + docChanged: boolean; + selection: { from: number; to: number } | null; + effects: string[]; + annotations: string[]; +} + +export interface TestSample { + name: string; + code: string; +} + +// Atoms +export const selectedTestAtom = atom("helloWorld"); +export const transactionHistoryAtom = atom([]); +export const autoScrollAtom = atom(true); + +// Factory functions for state operations +export function createTransaction(data: { + time: number; + docChanged: boolean; + selection: { from: number; to: number } | null; + effects: string[]; + annotations: string[]; +}): Transaction { + return { + time: data.time, + docChanged: data.docChanged, + selection: data.selection, + effects: data.effects, + annotations: data.annotations, + }; +} + +export function addTransaction( + history: Transaction[], + transaction: Transaction +): Transaction[] { + const newHistory = [...history, transaction]; + // Keep max 100 transactions + if (newHistory.length > 100) { + return newHistory.slice(-100); + } + return newHistory; +} + +export function clearTransactionHistory(): Transaction[] { + return []; +} + +export function getTestSampleName(key: string): string { + // Convert camelCase to Title Case with spaces + return key + .replace(/([A-Z])/g, " $1") + .replace(/^./, (str) => str.toUpperCase()) + .trim(); +} diff --git a/test/styles.css b/test/styles.css new file mode 100644 index 0000000..37de1dd --- /dev/null +++ b/test/styles.css @@ -0,0 +1,41 @@ +@import "tailwindcss"; +@import "../editor/index.css"; +@import "@fontsource-variable/inter"; +@import "@fontsource-variable/spline-sans-mono"; + +@theme { + --default-font-family: "Inter Variable"; + --font-mono: "Spline Sans Mono Variable"; +} + +button { + cursor: pointer; +} + +.cm-gutters { + background-color: white !important; + border-right: none !important; + padding-left: 8px !important; +} + +.cm-editor.cm-focused { + outline: none !important; +} + +/* Details/Summary styling for transaction viewer */ +details summary { + list-style: none; +} + +details summary::-webkit-details-marker { + display: none; +} + +details summary::marker { + display: none; +} + +details[open] > summary { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} diff --git a/vite.config.js b/vite.config.js index cb4a843..1abada3 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,9 @@ import {defineConfig} from "vite"; +import react from "@vitejs/plugin-react"; export default defineConfig({ root: "test", + plugins: [react()], test: { environment: "jsdom", }, From bd2e1fde5ad6e34ec45d90f723f270a576a795fb Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 16 Nov 2025 14:50:02 +0800 Subject: [PATCH 07/30] Configure TypeScript in the repo --- package.json | 1 + pnpm-lock.yaml | 3 +++ tsconfig.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 tsconfig.json diff --git a/package.json b/package.json index 29ecad5..ffcbb59 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "react-tooltip": "^5.30.0", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.14", + "typescript": "^5.9.3", "vite": "^7.1.10", "vitest": "^3.2.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2dbdf11..6290f6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,6 +150,9 @@ importers: tailwindcss: specifier: ^4.1.14 version: 4.1.14 + typescript: + specifier: ^5.9.3 + version: 5.9.3 vite: specifier: ^7.1.10 version: 7.1.10(jiti@2.6.1)(lightningcss@1.30.1) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cd321c4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,45 @@ +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "types": [], + "moduleResolution": "nodenext", + // For nodejs: + "lib": ["esnext", "DOM"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true + } +} From 1bd634c87ece1298c220541c8716b392a27f529d Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:43:22 +0800 Subject: [PATCH 08/30] WIP --- next-env.d.ts | 6 +++++ package.json | 2 ++ pnpm-lock.yaml | 71 ++++++++++++++++++++++++++++++++++++-------------- tsconfig.json | 35 ++++++++++++++++++------- 4 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 next-env.d.ts diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..830fb59 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/package.json b/package.json index ffcbb59..aceb790 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.1.14", + "@types/node": "24.10.1", + "@types/react": "19.2.6", "@vitejs/plugin-react": "^4.3.1", "clsx": "^2.1.1", "eslint": "^9.37.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6290f6e..a2c6ab1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: version: 1.2.2 '@observablehq/notebook-kit': specifier: ^1.4.1 - version: 1.4.1(@types/markdown-it@14.1.2)(jiti@2.6.1)(lightningcss@1.30.1) + version: 1.4.1(@types/markdown-it@14.1.2)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1) '@observablehq/runtime': specifier: ^6.0.0 version: 6.0.0 @@ -82,7 +82,7 @@ importers: version: 1.3.1 jotai: specifier: ^2.10.3 - version: 2.15.1(@babel/core@7.28.5)(@babel/template@7.27.2)(react@19.2.0) + version: 2.15.1(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.6)(react@19.2.0) next: specifier: ^15.5.6 version: 15.5.6(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -111,9 +111,15 @@ importers: '@tailwindcss/postcss': specifier: ^4.1.14 version: 4.1.14 + '@types/node': + specifier: 24.10.1 + version: 24.10.1 + '@types/react': + specifier: 19.2.6 + version: 19.2.6 '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.7.0(vite@7.1.10(jiti@2.6.1)(lightningcss@1.30.1)) + version: 4.7.0(vite@7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1)) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -155,10 +161,10 @@ importers: version: 5.9.3 vite: specifier: ^7.1.10 - version: 7.1.10(jiti@2.6.1)(lightningcss@1.30.1) + version: 7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1) vitest: specifier: ^3.2.4 - version: 3.2.4(@edge-runtime/vm@3.2.0)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.1) + version: 3.2.4(@edge-runtime/vm@3.2.0)(@types/node@24.10.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.1) packages: @@ -1182,6 +1188,12 @@ packages: '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + + '@types/react@19.2.6': + resolution: {integrity: sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -1573,6 +1585,9 @@ packages: resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + currently-unhandled@0.4.1: resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} engines: {node: '>=0.10.0'} @@ -3222,6 +3237,9 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@7.16.0: resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} engines: {node: '>=20.18.1'} @@ -4262,7 +4280,7 @@ snapshots: dependencies: isoformat: 0.2.1 - '@observablehq/notebook-kit@1.4.1(@types/markdown-it@14.1.2)(jiti@2.6.1)(lightningcss@1.30.1)': + '@observablehq/notebook-kit@1.4.1(@types/markdown-it@14.1.2)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1)': dependencies: '@fontsource/inter': 5.2.8 '@fontsource/source-serif-4': 5.2.9 @@ -4284,7 +4302,7 @@ snapshots: markdown-it: 14.1.0 markdown-it-anchor: 9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.1.0) typescript: 5.9.3 - vite: 7.1.10(jiti@2.6.1)(lightningcss@1.30.1) + vite: 7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1) transitivePeerDependencies: - '@types/markdown-it' - '@types/node' @@ -4544,6 +4562,14 @@ snapshots: '@types/mdurl@2.0.0': {} + '@types/node@24.10.1': + dependencies: + undici-types: 7.16.0 + + '@types/react@19.2.6': + dependencies: + csstype: 3.2.3 + '@types/unist@3.0.3': {} '@uiw/codemirror-theme-github@4.25.2(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.6)': @@ -4562,7 +4588,7 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@4.7.0(vite@7.1.10(jiti@2.6.1)(lightningcss@1.30.1))': + '@vitejs/plugin-react@4.7.0(vite@7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -4570,7 +4596,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.10(jiti@2.6.1)(lightningcss@1.30.1) + vite: 7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1) transitivePeerDependencies: - supports-color @@ -4582,13 +4608,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.10(jiti@2.6.1)(lightningcss@1.30.1))': + '@vitest/mocker@3.2.4(vite@7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.1.10(jiti@2.6.1)(lightningcss@1.30.1) + vite: 7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -5031,6 +5057,8 @@ snapshots: '@asamuzakjp/css-color': 3.2.0 rrweb-cssom: 0.8.0 + csstype@3.2.3: {} + currently-unhandled@0.4.1: dependencies: array-find-index: 1.0.2 @@ -5870,10 +5898,11 @@ snapshots: jiti@2.6.1: {} - jotai@2.15.1(@babel/core@7.28.5)(@babel/template@7.27.2)(react@19.2.0): + jotai@2.15.1(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.6)(react@19.2.0): optionalDependencies: '@babel/core': 7.28.5 '@babel/template': 7.27.2 + '@types/react': 19.2.6 react: 19.2.0 js-string-escape@1.0.1: {} @@ -6892,6 +6921,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + undici-types@7.16.0: {} + undici@7.16.0: {} unist-util-is@6.0.1: @@ -6945,13 +6976,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(jiti@2.6.1)(lightningcss@1.30.1): + vite-node@3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.10(jiti@2.6.1)(lightningcss@1.30.1) + vite: 7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1) transitivePeerDependencies: - '@types/node' - jiti @@ -6966,7 +6997,7 @@ snapshots: - tsx - yaml - vite@7.1.10(jiti@2.6.1)(lightningcss@1.30.1): + vite@7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) @@ -6975,15 +7006,16 @@ snapshots: rollup: 4.52.4 tinyglobby: 0.2.15 optionalDependencies: + '@types/node': 24.10.1 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.1 - vitest@3.2.4(@edge-runtime/vm@3.2.0)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.1): + vitest@3.2.4(@edge-runtime/vm@3.2.0)(@types/node@24.10.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.10(jiti@2.6.1)(lightningcss@1.30.1)) + '@vitest/mocker': 3.2.4(vite@7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -7001,11 +7033,12 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.10(jiti@2.6.1)(lightningcss@1.30.1) - vite-node: 3.2.4(jiti@2.6.1)(lightningcss@1.30.1) + vite: 7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1) + vite-node: 3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 3.2.0 + '@types/node': 24.10.1 jsdom: 26.1.0 transitivePeerDependencies: - jiti diff --git a/tsconfig.json b/tsconfig.json index cd321c4..70c1ea2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,6 @@ // File Layout // "rootDir": "./src", // "outDir": "./dist", - // Environment Settings // See also https://aka.ms/tsconfig/module "module": "nodenext", @@ -12,19 +11,19 @@ "types": [], "moduleResolution": "nodenext", // For nodejs: - "lib": ["esnext", "DOM"], + "lib": [ + "esnext", + "DOM" + ], // "types": ["node"], // and npm install -D @types/node - // Other Outputs "sourceMap": true, "declaration": true, "declarationMap": true, - // Stricter Typechecking Options "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, - // Style Options // "noImplicitReturns": true, // "noImplicitOverride": true, @@ -32,14 +31,32 @@ // "noUnusedParameters": true, // "noFallthroughCasesInSwitch": true, // "noPropertyAccessFromIndexSignature": true, - // Recommended Options "strict": true, - "jsx": "react-jsx", + "jsx": "preserve", "verbatimModuleSyntax": true, "isolatedModules": true, "noUncheckedSideEffectImports": true, "moduleDetection": "force", - "skipLibCheck": true - } + "skipLibCheck": true, + "allowJs": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + ".next/types/**/*.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] } From d23ee70ebd9ae84d0600a619dfb715129614d60e Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:03:55 +0800 Subject: [PATCH 09/30] Add block section to the sidebar --- test/components/App.tsx | 18 ++- test/components/BlockViewer.tsx | 211 ++++++++++++++++++++++++++ test/components/Editor.tsx | 25 ++- test/components/ResizableSplit.tsx | 70 +++++++++ test/components/TransactionViewer.tsx | 52 ++++--- 5 files changed, 338 insertions(+), 38 deletions(-) create mode 100644 test/components/BlockViewer.tsx create mode 100644 test/components/ResizableSplit.tsx diff --git a/test/components/App.tsx b/test/components/App.tsx index 874eb05..9d9c2e6 100644 --- a/test/components/App.tsx +++ b/test/components/App.tsx @@ -4,12 +4,15 @@ import {selectedTestAtom} from "../store"; import {TestSelector} from "./TestSelector"; import {Editor} from "./Editor"; import {TransactionViewer} from "./TransactionViewer"; +import {BlockViewer} from "./BlockViewer"; +import {ResizableSplit} from "./ResizableSplit"; import * as testSamples from "../js/index.js"; import type {ViewPlugin} from "@codemirror/view"; export function App() { const [selectedTest, setSelectedTest] = useAtom(selectedTestAtom); const [transactionViewerPlugin, setTransactionViewerPlugin] = useState | undefined>(); + const [blockViewerPlugin, setBlockViewerPlugin] = useState | undefined>(); // Initialize from URL on mount useEffect(() => { @@ -37,12 +40,21 @@ export function App() {
{/* Editor panel */}
- +
- {/* Transaction viewer sidebar */} + {/* Sidebar with blocks and transactions */}
diff --git a/test/components/BlockViewer.tsx b/test/components/BlockViewer.tsx new file mode 100644 index 0000000..b1c7d5a --- /dev/null +++ b/test/components/BlockViewer.tsx @@ -0,0 +1,211 @@ +import {useState, useEffect, useRef} from "react"; +import {ViewPlugin, EditorView} from "@codemirror/view"; +import {EditorSelection} from "@codemirror/state"; +import {blockMetadataField} from "../../editor/blockMetadata.ts"; +import {Locate} from "lucide-react"; + +interface BlockData { + index: number; + sourceFrom: number; + sourceTo: number; + outputFrom: number | null; + outputTo: number | null; + hasError: boolean; + attributes: Record; +} + +interface BlockViewerProps { + onPluginCreate: (plugin: ViewPlugin) => void; +} + +export function BlockViewer({onPluginCreate}: BlockViewerProps) { + const [blocks, setBlocks] = useState([]); + const [autoScroll, setAutoScroll] = useState(true); + const listRef = useRef(null); + const viewRef = useRef(null); + + useEffect(() => { + let currentBlocks: BlockData[] = []; + const listeners = new Set<(blocks: BlockData[]) => void>(); + + function notifyListeners() { + listeners.forEach((fn) => fn([...currentBlocks])); + } + + function extractBlockData(view: any): BlockData[] { + const blockMetadata = view.state.field(blockMetadataField, false); + if (!blockMetadata) return []; + + return blockMetadata.map((block: any, index: number) => ({ + index, + sourceFrom: block.source.from, + sourceTo: block.source.to, + outputFrom: block.output?.from ?? null, + outputTo: block.output?.to ?? null, + hasError: block.error || false, + attributes: block.attributes || {}, + })); + } + + const plugin = ViewPlugin.fromClass( + class { + constructor(view: any) { + viewRef.current = view; + currentBlocks = extractBlockData(view); + notifyListeners(); + } + + update(update: any) { + viewRef.current = update.view; + if ( + update.docChanged || + update.transactions.some((tr: any) => tr.effects.some((e: any) => e.is(blockMetadataField.init))) + ) { + currentBlocks = extractBlockData(update.view); + notifyListeners(); + } + } + }, + ); + + listeners.add((newBlocks) => { + setBlocks(newBlocks); + }); + + onPluginCreate(plugin); + + return () => { + listeners.clear(); + }; + }, [onPluginCreate]); + + useEffect(() => { + if (autoScroll && listRef.current) { + listRef.current.scrollTop = 0; + } + }, [blocks, autoScroll]); + + const handleLocateBlock = (block: BlockData) => { + if (!viewRef.current) return; + + const view = viewRef.current; + // The block starts at output.from (if output exists) or source.from + // The block ends at source.to + const from = block.outputFrom ?? block.sourceFrom; + const to = block.sourceTo; + + // Scroll the block into view and select the range + view.dispatch({ + effects: EditorView.scrollIntoView(from, {y: "center"}), + selection: EditorSelection.range(from, to), + }); + + view.focus(); + }; + + return ( +
+
+

Blocks ({blocks.length})

+
+ +
+
+
+ {blocks.length === 0 ? ( +
No blocks yet
+ ) : ( + blocks.map((block) => ) + )} +
+
+ ); +} + +function BlockItem({block, onLocate}: {block: BlockData; onLocate: (block: BlockData) => void}) { + const [isOpen, setIsOpen] = useState(false); + + const hasOutput = block.outputFrom !== null && block.outputTo !== null; + const hasAttributes = Object.keys(block.attributes).length > 0; + + return ( +
setIsOpen((e.target as HTMLDetailsElement).open)} + className={`mb-2 border rounded text-xs ${ + block.hasError + ? "border-red-300 bg-red-50" + : hasOutput + ? "border-green-300 bg-green-50" + : "border-gray-200 bg-white" + }`} + > + + + Block {block.index + 1} + {block.hasError && " ✗"} + {hasOutput && !block.hasError && " ➜"} + +
+ + + {block.sourceFrom}-{block.sourceTo} + +
+
+
+
+ Source Range: {block.sourceFrom}-{block.sourceTo} + ({block.sourceTo - block.sourceFrom} chars) +
+ + {hasOutput ? ( +
+ Output Range: {block.outputFrom}-{block.outputTo} + ({block.outputTo! - block.outputFrom!} chars) +
+ ) : ( +
+ Output Range: none +
+ )} + +
+ Total Range: {hasOutput ? block.outputFrom : block.sourceFrom}-{block.sourceTo} + + ({block.sourceTo - (hasOutput ? block.outputFrom! : block.sourceFrom)} chars) + +
+ + {block.hasError &&
✗ Has Error
} + + {hasAttributes && ( +
+ Attributes: +
+ {JSON.stringify(block.attributes, null, 2)} +
+
+ )} +
+
+ ); +} diff --git a/test/components/Editor.tsx b/test/components/Editor.tsx index 0b261c6..69d3ab6 100644 --- a/test/components/Editor.tsx +++ b/test/components/Editor.tsx @@ -1,12 +1,13 @@ -import { useEffect, useRef, useState } from "react"; -import { Play, Square, RefreshCcw } from "lucide-react"; -import { createEditor } from "../../editor/index.js"; -import { cn } from "../../app/cn.js"; -import type { ViewPlugin } from "@codemirror/view"; +import {useEffect, useRef, useState} from "react"; +import {Play, Square, RefreshCcw} from "lucide-react"; +import {createEditor} from "../../editor/index.js"; +import {cn} from "../../app/cn.js"; +import type {ViewPlugin} from "@codemirror/view"; interface EditorProps { code: string; transactionViewerPlugin?: ViewPlugin; + blockViewerPlugin?: ViewPlugin; onError?: (error: Error) => void; } @@ -24,7 +25,7 @@ const onDefaultError = debounce(() => { }, 100); }, 0); -export function Editor({ code, transactionViewerPlugin, onError = onDefaultError }: EditorProps) { +export function Editor({code, transactionViewerPlugin, blockViewerPlugin, onError = onDefaultError}: EditorProps) { const containerRef = useRef(null); const editorRef = useRef(null); const [needRerun, setNeedRerun] = useState(false); @@ -33,7 +34,9 @@ export function Editor({ code, transactionViewerPlugin, onError = onDefaultError if (containerRef.current) { containerRef.current.innerHTML = ""; - const extensions = transactionViewerPlugin ? [transactionViewerPlugin] : []; + const extensions = []; + if (transactionViewerPlugin) extensions.push(transactionViewerPlugin); + if (blockViewerPlugin) extensions.push(blockViewerPlugin); editorRef.current = createEditor(containerRef.current, { code, @@ -50,7 +53,7 @@ export function Editor({ code, transactionViewerPlugin, onError = onDefaultError editorRef.current.destroy(); } }; - }, [code, transactionViewerPlugin, onError]); + }, [code, transactionViewerPlugin, blockViewerPlugin, onError]); useEffect(() => { const onInput = () => { @@ -112,11 +115,7 @@ export function Editor({ code, transactionViewerPlugin, onError = onDefaultError > - diff --git a/test/components/ResizableSplit.tsx b/test/components/ResizableSplit.tsx new file mode 100644 index 0000000..2127635 --- /dev/null +++ b/test/components/ResizableSplit.tsx @@ -0,0 +1,70 @@ +import { useState, useRef, useEffect, ReactNode } from "react"; + +interface ResizableSplitProps { + top: ReactNode; + bottom: ReactNode; + defaultRatio?: number; // 0 to 1, representing top panel's height ratio +} + +export function ResizableSplit({ top, bottom, defaultRatio = 0.5 }: ResizableSplitProps) { + const [ratio, setRatio] = useState(defaultRatio); + const [isDragging, setIsDragging] = useState(false); + const containerRef = useRef(null); + + useEffect(() => { + if (!isDragging) return; + + const handleMouseMove = (e: MouseEvent) => { + if (!containerRef.current) return; + + const container = containerRef.current; + const rect = container.getBoundingClientRect(); + const y = e.clientY - rect.top; + const newRatio = Math.max(0.1, Math.min(0.9, y / rect.height)); + setRatio(newRatio); + }; + + const handleMouseUp = () => { + setIsDragging(false); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, [isDragging]); + + const handleMouseDown = (e: React.MouseEvent) => { + e.preventDefault(); + setIsDragging(true); + }; + + return ( +
+ {/* Top panel */} +
+ {top} +
+ + {/* Resize handle */} +
+
+
+
+
+ + {/* Bottom panel */} +
+ {bottom} +
+
+ ); +} diff --git a/test/components/TransactionViewer.tsx b/test/components/TransactionViewer.tsx index 7df4b36..9e47e2f 100644 --- a/test/components/TransactionViewer.tsx +++ b/test/components/TransactionViewer.tsx @@ -1,8 +1,8 @@ -import { useState, useEffect, useRef } from "react"; -import { ViewPlugin } from "@codemirror/view"; -import { Transaction as CMTransaction } from "@codemirror/state"; -import { blockMetadataEffect } from "../../editor/blockMetadata.ts"; -import { cn } from "../../app/cn.js"; +import {useState, useEffect, useRef} from "react"; +import {ViewPlugin} from "@codemirror/view"; +import {Transaction as CMTransaction} from "@codemirror/state"; +import {blockMetadataEffect} from "../../editor/blockMetadata.ts"; +import {cn} from "../../app/cn.js"; // Maximum number of transactions to keep in history const MAX_HISTORY = 100; @@ -53,7 +53,7 @@ interface TransactionViewerProps { onPluginCreate: (plugin: ViewPlugin) => void; } -export function TransactionViewer({ onPluginCreate }: TransactionViewerProps) { +export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { const [transactions, setTransactions] = useState([]); const [autoScroll, setAutoScroll] = useState(true); const listRef = useRef(null); @@ -171,7 +171,7 @@ export function TransactionViewer({ onPluginCreate }: TransactionViewerProps) { notifyListeners(); } } - } + }, ); listeners.add((newTransactions) => { @@ -202,8 +202,7 @@ export function TransactionViewer({ onPluginCreate }: TransactionViewerProps) { for (let i = transactions.length - 1; i >= 0; i--) { const tr = transactions[i]; - const isSelection = - tr.annotations.userEvent === "select" || tr.annotations.userEvent === "select.pointer"; + const isSelection = tr.annotations.userEvent === "select" || tr.annotations.userEvent === "select.pointer"; if (isSelection) { if (currentGroup && currentGroup.type === "selection") { @@ -228,7 +227,7 @@ export function TransactionViewer({ onPluginCreate }: TransactionViewerProps) { }; return ( -
+

Transactions

@@ -258,7 +257,7 @@ export function TransactionViewer({ onPluginCreate }: TransactionViewerProps) { ) : ( - ) + ), ) )}
@@ -266,7 +265,7 @@ export function TransactionViewer({ onPluginCreate }: TransactionViewerProps) { ); } -function TransactionItem({ transaction: tr }: { transaction: TransactionData }) { +function TransactionItem({transaction: tr}: {transaction: TransactionData}) { const [isOpen, setIsOpen] = useState(false); const formatTime = (timestamp: number) => { @@ -300,7 +299,7 @@ function TransactionItem({ transaction: tr }: { transaction: TransactionData }) className={cn( "mb-2 border border-gray-200 rounded text-xs", tr.annotations.userEvent && "bg-blue-50", - tr.docChanged && "border-l-4 border-l-blue-500" + tr.docChanged && "border-l-4 border-l-blue-500", )} > @@ -329,7 +328,9 @@ function TransactionItem({ transaction: tr }: { transaction: TransactionData }) return (
-
Change {idx + 1}: {posInfo}
+
+ Change {idx + 1}: {posInfo} +
{deleted > 0 &&
Deleted {deleted} chars
} {inserted > 0 && (
@@ -368,16 +369,18 @@ function TransactionItem({ transaction: tr }: { transaction: TransactionData })
{effect.type === "blockMetadataEffect" && effect.blockMetadata ? (
-
Effect {idx + 1}: blockMetadataEffect ({effect.blockMetadata.length} blocks)
+
+ Effect {idx + 1}: blockMetadataEffect ({effect.blockMetadata.length} blocks) +
{effect.blockMetadata.map((block: any, blockIdx: number) => (
Block {blockIdx + 1}:
- {block.output !== null - ? `Output: ${block.output.from}-${block.output.to}` - : "Output: null"} + {block.output !== null ? `Output: ${block.output.from}-${block.output.to}` : "Output: null"} +
+
+ Source: {block.source.from}-{block.source.to}
-
Source: {block.source.from}-{block.source.to}
{Object.keys(block.attributes).length > 0 && (
Attributes: {JSON.stringify(block.attributes)}
)} @@ -386,7 +389,9 @@ function TransactionItem({ transaction: tr }: { transaction: TransactionData }) ))}
) : ( -
Effect {idx + 1} ({effect.type}): {JSON.stringify(effect.value).substring(0, 100)}
+
+ Effect {idx + 1} ({effect.type}): {JSON.stringify(effect.value).substring(0, 100)} +
)}
))} @@ -414,7 +419,7 @@ function TransactionItem({ transaction: tr }: { transaction: TransactionData }) ); } -function SelectionGroupItem({ transactions }: { transactions: TransactionData[] }) { +function SelectionGroupItem({transactions}: {transactions: TransactionData[]}) { const [isOpen, setIsOpen] = useState(false); const formatTime = (timestamp: number) => { @@ -458,7 +463,10 @@ function SelectionGroupItem({ transactions }: { transactions: TransactionData[] .join(", "); return ( -
+
#{tr.index} {selectionInfo} {formatTime(tr.timestamp)} From 6f25d058ea4dad3f34bbfcd7af5818662f34fe93 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:12:46 +0800 Subject: [PATCH 10/30] Fix not displaying debug decorations --- editor/decoration.js | 57 ++++++++++++++++++++++++++++++++++---------- editor/index.css | 2 +- editor/index.js | 3 ++- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/editor/decoration.js b/editor/decoration.js index 6c7c0df..3f8793b 100644 --- a/editor/decoration.js +++ b/editor/decoration.js @@ -44,22 +44,30 @@ function createWidgets(lines, blockMetadata, state) { const set2 = builder2.finish(); console.groupEnd(); - const builder3 = new RangeSetBuilder(); - for (const {output} of blockMetadata) { - if (output === null) continue; - builder3.add(output.from, output.to, debugRedDecoration); - } - const set3 = builder3.finish(); + // Range sets are required to be sorted. Fortunately, they provide a method + // to merge multiple range sets into a single sorted range set. + return RangeSet.join([set1, set2]); +} + +function createDebugMarks(blockMetadata, state) { + // Build mark decorations separately from line decorations to avoid conflicts + const builder = new RangeSetBuilder(); + + for (const {output, source} of blockMetadata) { + // Add red marks for output ranges + if (output !== null && output.from < output.to) { + console.log(`Adding red decoration for output: ${output.from}-${output.to}`); + builder.add(output.from, output.to, debugRedDecoration); + } - const builder4 = new RangeSetBuilder(); - for (const {source} of blockMetadata) { - builder4.add(source.from, source.to, debugGreenDecoration); + // Add green marks for source ranges + if (source.from < source.to) { + console.log(`Adding green decoration for source: ${source.from}-${source.to}`); + builder.add(source.from, source.to, debugGreenDecoration); + } } - const set4 = builder4.finish(); - // Range sets are required to be sorted. Fortunately, they provide a method - // to merge multiple range sets into a single sorted range set. - return RangeSet.join([set1, set2, set3, set4]); + return builder.finish(); } export const outputDecoration = ViewPlugin.fromClass( @@ -87,3 +95,26 @@ export const outputDecoration = ViewPlugin.fromClass( }, {decorations: (v) => v.decorations}, ); + +export const debugDecoration = ViewPlugin.fromClass( + class { + #decorations; + + get decorations() { + return this.#decorations; + } + + /** @param {EditorView} view */ + constructor(view) { + const blockMetadata = view.state.field(blockMetadataField); + this.#decorations = createDebugMarks(blockMetadata, view.state); + } + + /** @param {ViewUpdate} update */ + update(update) { + const blockMetadata = update.state.field(blockMetadataField); + this.#decorations = createDebugMarks(blockMetadata, update.state); + } + }, + {decorations: (v) => v.decorations}, +); diff --git a/editor/index.css b/editor/index.css index a69a03d..97699c1 100644 --- a/editor/index.css +++ b/editor/index.css @@ -149,7 +149,7 @@ .cm-debug-mark { &.red { - background-color: #e4002440; + background-color: #fbbf2440; } &.green { background-color: #009a5753; diff --git a/editor/index.js b/editor/index.js index cc998ac..5268d18 100644 --- a/editor/index.js +++ b/editor/index.js @@ -9,7 +9,7 @@ import {indentWithTab} from "@codemirror/commands"; import {browser} from "globals"; import * as eslint from "eslint-linter-browserify"; import {createRuntime} from "../runtime/index.js"; -import {outputDecoration} from "./decoration.js"; +import {outputDecoration, debugDecoration} from "./decoration.js"; import {outputLines} from "./outputLines.js"; import {blockMetadataExtension, blockMetadataEffect} from "./blockMetadata.ts"; // import {outputProtection} from "./protection.js"; @@ -76,6 +76,7 @@ export function createEditor(container, options) { outputLines, blockMetadataExtension, outputDecoration, + debugDecoration, controls(runtimeRef), // Disable this for now, because it prevents copying/pasting the code. // outputProtection(), From aa66f2c5b1929ab12a1f14957bbefb4f3052d80c Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 9 Dec 2025 05:26:19 +0800 Subject: [PATCH 11/30] Add an example for sorting algorithms --- app/examples/sorting-algorithms.recho.js | 274 +++++++++++++++++++++++ runtime/controls/button.js | 6 +- 2 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 app/examples/sorting-algorithms.recho.js diff --git a/app/examples/sorting-algorithms.recho.js b/app/examples/sorting-algorithms.recho.js new file mode 100644 index 0000000..7f9eebe --- /dev/null +++ b/app/examples/sorting-algorithms.recho.js @@ -0,0 +1,274 @@ +/** + * @title Sorting Algorithms + * @author Luyu Cheng + * @created 2025-12-09 + * @pull_request 86 + * @github chengluyu + * @label Algorithm + */ + +/** + * ============================================================================ + * = Sorting Algorithms = + * ============================================================================ + */ + +//➜ ▁▂▃ +//➜ ▂▄▅▅▇▇███ +//➜ ▁█████████ +//➜ ▃▅▆▇▇██████████ +//➜ ▁▃▇████████████████ +//➜ ▆▇▇███████████████████ +//➜ ▁▄███████████████████████ +//➜ ▂▂▅▆█████████████████████████ +//➜ ▁▅█████████████████████████████ +//➜ ▃▆▇▇███████████████████████████████ +//➜ ▁▅▆▆▇███████████████████████████████████ +//➜ ▁▇████████████████████████████████████████ +//➜ ▄▇▇██████████████████████████████████████████ +//➜ +//➜ ────────────────────────────────────────────── +{ echo.set("compact", true); echo(renderNumbers(numbers.data, numbers.highlight)); } + +const maximum = recho.number(100); +const length = recho.number(46); +const swapsPerSecond = recho.number(43); + +recho.button("Insertion Sort", () => { + const numbers = randomArray(maximum, length); + setNumbers({data: numbers, highlight: null}); + play(insertionSortSwaps(numbers)); +}); + +recho.button("Selection Sort", () => { + const numbers = randomArray(maximum, length); + setNumbers({data: numbers, highlight: null}); + play(selectionSortSwaps(numbers)); +}); + +recho.button("Merge Sort", () => { + const numbers = randomArray(maximum, length); + setNumbers({data: numbers, highlight: null}); + play(mergeSortSwaps(numbers)); +}); + +recho.button("Quick Sort", () => { + const numbers = randomArray(maximum, length); + setNumbers({data: numbers, highlight: null}); + play(quickSortSwaps(numbers)); +}); + +function insertionSortSwaps(arr) { + const swaps = []; + const copy = [...arr]; // work with a copy + for (let i = 1; i < copy.length; i++) { + let j = i; + // Move element at position i to its correct position + while (j > 0 && copy[j] < copy[j - 1]) { + swaps.push([j - 1, j]); + // Swap elements + [copy[j - 1], copy[j]] = [copy[j], copy[j - 1]]; + j--; + } + } + return swaps; +} + +function selectionSortSwaps(arr) { + const swaps = []; + const copy = [...arr]; + + for (let i = 0; i < copy.length - 1; i++) { + let minIndex = i; + // Find minimum element in remaining unsorted portion + for (let j = i + 1; j < copy.length; j++) { + swaps.push([j]) + if (copy[j] < copy[minIndex]) { + minIndex = j; + } + } + // Swap if minimum is not at current position + if (minIndex !== i) { + swaps.push([i, minIndex]); + [copy[i], copy[minIndex]] = [copy[minIndex], copy[i]]; + } + } + + return swaps; +} + +function mergeSortSwaps(arr) { + const swaps = []; + const copy = [...arr]; + + function merge(left, mid, right) { + const leftArr = copy.slice(left, mid + 1); + const rightArr = copy.slice(mid + 1, right + 1); + + let i = 0, j = 0, k = left; + + while (i < leftArr.length && j < rightArr.length) { + if (leftArr[i] <= rightArr[j]) { + copy[k] = leftArr[i]; + i++; + } else { + // Element from right half needs to move before elements from left half + // This represents multiple swaps in the original array + copy[k] = rightArr[j]; + // Record swaps: right element moves past remaining left elements + const sourcePos = mid + 1 + j; + for (let pos = sourcePos; pos > k; pos--) { + swaps.push([pos - 1, pos]); + } + j++; + } + k++; + } + + // Copy remaining elements (no swaps needed as they're already in place) + while (i < leftArr.length) { + copy[k] = leftArr[i]; + i++; + k++; + } + while (j < rightArr.length) { + copy[k] = rightArr[j]; + j++; + k++; + } + } + + function mergeSort(left, right) { + if (left < right) { + const mid = Math.floor((left + right) / 2); + mergeSort(left, mid); + mergeSort(mid + 1, right); + merge(left, mid, right); + } + } + + mergeSort(0, copy.length - 1); + return swaps; +} + +function quickSortSwaps(arr) { + const swaps = []; + const copy = [...arr]; + + function medianOfThree(left, right) { + const mid = Math.floor((left + right) / 2); + const a = copy[left], b = copy[mid], c = copy[right]; + + // Find median and return its index + if ((a <= b && b <= c) || (c <= b && b <= a)) return mid; + if ((b <= a && a <= c) || (c <= a && a <= b)) return left; + return right; + } + + function partition(left, right) { + // Choose pivot using median-of-three + const pivotIndex = medianOfThree(left, right); + + // Move pivot to end + if (pivotIndex !== right) { + swaps.push([pivotIndex, right]); + [copy[pivotIndex], copy[right]] = [copy[right], copy[pivotIndex]]; + } + + const pivot = copy[right]; + let i = left - 1; + + for (let j = left; j < right; j++) { + if (copy[j] <= pivot) { + i++; + if (i !== j) { + swaps.push([i, j]); + [copy[i], copy[j]] = [copy[j], copy[i]]; + } + } + } + + // Move pivot to its final position + i++; + if (i !== right) { + swaps.push([i, right]); + [copy[i], copy[right]] = [copy[right], copy[i]]; + } + + return i; + } + + function quickSort(left, right) { + if (left < right) { + const pivotIndex = partition(left, right); + quickSort(left, pivotIndex - 1); + quickSort(pivotIndex + 1, right); + } + } + + quickSort(0, copy.length - 1); + return swaps; +} + +function play(swaps) { + if (playing !== null) { + clearInterval(playing); + } + let current = 0; + const id = setInterval(takeStep, Math.floor(1000 / swapsPerSecond)); + setPlaying(id); + function takeStep() { + if (current >= swaps.length) { + clearInterval(playing); + setPlaying(null); + return; + } + const swap = swaps[current]; + if (swap.length === 2) { + const [left, right] = swap; + setNumbers(({ data }) => { + const cloned = structuredClone(data); + const temp = cloned[left]; + cloned[left] = cloned[right]; + cloned[right] = temp; + return { data: cloned, highlight: null }; + }); + } else if (swap.length === 1) { + const [index] = swap; + setNumbers(({ data }) => { + return { data, highlight: index}; + }); + } + current++; + } +} + +function randomArray(maximum, length) { + const buffer = []; + const gen = d3.randomInt(maximum); + for (let i = 0; i < length; i++) { + buffer.push(gen()); + } + return buffer; +} + +function renderNumbers(numbers, highlight) { + const min = d3.min(numbers), max = d3.max(numbers); + const segmentCount = (max >>> 3) + ((max & 7) === 0 ? 0 : 1); + const buffer = d3.transpose(numbers.map((n, i) => { + const head = (n & 7) === 0 ? "" : blocks[n & 7]; + const body = fullBlock.repeat(n >>> 3); + const padding = " ".repeat(segmentCount - (head.length + body.length)); + const ending = i === highlight ? "╻┸" : " ─"; + return padding + head + body + ending; + })); + return buffer.map(xs => xs.join("")).join("\n"); +} + +const [playing, setPlaying] = recho.state(null); +const [numbers, setNumbers] = recho.state({ data: [], highlight: null }) + +const blocks = Array.from(" ▁▂▃▄▅▆▇"); +const fullBlock = "█"; + +const d3 = recho.require("d3"); diff --git a/runtime/controls/button.js b/runtime/controls/button.js index 6ae595b..e901262 100644 --- a/runtime/controls/button.js +++ b/runtime/controls/button.js @@ -25,9 +25,9 @@ export class ButtonRegistry { */ register(id, callback) { // Check if this ID was already registered in the current execution - if (this.currentExecutionIds.has(id)) { - return false; // Duplicate in current execution - } + // if (this.currentExecutionIds.has(id)) { + // return false; // Duplicate in current execution + // } this.callbackMap.set(id, callback); this.currentExecutionIds.add(id); From c27cad2ba5442ca27cee081ce753629b2583eeb0 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:01:21 +0800 Subject: [PATCH 12/30] Improve tracking blocks --- editor/blockMetadata.ts | 323 +++++++++++++++++++++----------- editor/decoration.js | 10 +- editor/deduplication.ts | 159 ++++++++++++++++ runtime/index.js | 14 +- test/components/BlockViewer.tsx | 7 +- 5 files changed, 391 insertions(+), 122 deletions(-) create mode 100644 editor/deduplication.ts diff --git a/editor/blockMetadata.ts b/editor/blockMetadata.ts index 5b2e380..e884e19 100644 --- a/editor/blockMetadata.ts +++ b/editor/blockMetadata.ts @@ -2,6 +2,7 @@ import {StateField, StateEffect, Transaction} from "@codemirror/state"; import {syntaxTree} from "@codemirror/language"; import {OUTPUT_MARK, ERROR_MARK} from "../runtime/constant.js"; import {EditorState} from "@codemirror/state"; +import {mergeOverlappingBlocks} from "./deduplication.js"; const OUTPUT_MARK_CODE_POINT = OUTPUT_MARK.codePointAt(0); const ERROR_MARK_CODE_POINT = ERROR_MARK.codePointAt(0); @@ -27,7 +28,7 @@ export function BlockMetadata(output: Range | null, source: Range, attributes: R * Detect blocks in a given range by traversing the syntax tree. * Similar to how runtime/index.js uses acorn to parse blocks, but adapted for CodeMirror. */ -function rebuiltBlocksWithinRange(state: EditorState, from: number, to: number): BlockMetadata[] { +function detectBlockWithinRange(state: EditorState, from: number, to: number): BlockMetadata[] { const blocks: BlockMetadata[] = []; // Collect all top-level statements and their preceding output/error comment lines @@ -41,69 +42,91 @@ function rebuiltBlocksWithinRange(state: EditorState, from: number, to: number): // Detect top-level statements (direct children of Script) if (node.node.parent?.name === "Script") { // Check if this is a statement (not a comment) - const isStatement = + if ( node.name.includes("Statement") || node.name.includes("Declaration") || node.name === "ExportDeclaration" || node.name === "ImportDeclaration" || - node.name === "Block"; - - if (isStatement) { + node.name === "Block" + ) { + // Note: The range here is right-exclusive. + console.log(`Found "${node.name}" spans from ${node.from} to ${node.to}`); statementRanges.push({from: node.from, to: node.to}); - } - } - // Detect output/error comment lines (top-level line comments) - if (node.name === "LineComment" && node.node.parent?.name === "Script") { - const line = state.doc.lineAt(node.from); - // Check if the line comment covers the entire line - if (line.from === node.from && line.to === node.to) { - const codePoint = line.text.codePointAt(2); - if (codePoint === OUTPUT_MARK_CODE_POINT || codePoint === ERROR_MARK_CODE_POINT) { - // Find consecutive output/error lines - let outputStart = line.from; - let outputEnd = line.to; - - // Look backwards for more output/error lines - let currentLineNum = line.number - 1; - while (currentLineNum >= 1) { - const prevLine = state.doc.line(currentLineNum); - const prevCodePoint = prevLine.text.codePointAt(2); - if ( - prevLine.text.startsWith("//") && - (prevCodePoint === OUTPUT_MARK_CODE_POINT || prevCodePoint === ERROR_MARK_CODE_POINT) - ) { - outputStart = prevLine.from; - currentLineNum--; - } else { - break; + let outputRange: Range | null = null; + let currentNode = node.node.prevSibling; + + console.log(`Previous node: ${currentNode}`); + while (currentNode?.name === "LineComment") { + const line = state.doc.lineAt(currentNode.from); + if (line.from === currentNode.from && line.to === currentNode.to) { + const codePoint = line.text.codePointAt(2); + if (codePoint === OUTPUT_MARK_CODE_POINT || codePoint === ERROR_MARK_CODE_POINT) { + outputRange = + outputRange === null ? {from: line.from, to: line.to} : {from: line.from, to: outputRange.to}; } } + currentNode = currentNode.prevSibling; + } + console.log(`Output Range:`, outputRange); + if (outputRange !== null) { + outputRanges.set(node.from, outputRange); + } + } + // Detect output/error comment lines (top-level line comments) + else if (node.name === "LineComment") { + // Get the line containing the comment. + const line = state.doc.lineAt(node.from); + + // Check if the line comment covers the entire line + if (line.from === node.from && line.to === node.to) { + const codePoint = line.text.codePointAt(2); + if (codePoint === OUTPUT_MARK_CODE_POINT || codePoint === ERROR_MARK_CODE_POINT) { + // Find consecutive output/error lines + let outputStart = line.from; + let outputEnd = line.to; + + // Look backwards for more output/error lines + let currentLineNum = line.number - 1; + while (currentLineNum >= 1) { + const prevLine = state.doc.line(currentLineNum); + const prevCodePoint = prevLine.text.codePointAt(2); + if ( + prevLine.text.startsWith("//") && + (prevCodePoint === OUTPUT_MARK_CODE_POINT || prevCodePoint === ERROR_MARK_CODE_POINT) + ) { + outputStart = prevLine.from; + currentLineNum--; + } else { + break; + } + } - // Look forwards for more output/error lines - currentLineNum = line.number + 1; - const totalLines = state.doc.lines; - while (currentLineNum <= totalLines) { - const nextLine = state.doc.line(currentLineNum); - const nextCodePoint = nextLine.text.codePointAt(2); - if ( - nextLine.text.startsWith("//") && - (nextCodePoint === OUTPUT_MARK_CODE_POINT || nextCodePoint === ERROR_MARK_CODE_POINT) - ) { - outputEnd = nextLine.to; - currentLineNum++; - } else { - break; + // Look forwards for more output/error lines + currentLineNum = line.number + 1; + const totalLines = state.doc.lines; + while (currentLineNum <= totalLines) { + const nextLine = state.doc.line(currentLineNum); + const nextCodePoint = nextLine.text.codePointAt(2); + if ( + nextLine.text.startsWith("//") && + (nextCodePoint === OUTPUT_MARK_CODE_POINT || nextCodePoint === ERROR_MARK_CODE_POINT) + ) { + outputEnd = nextLine.to; + currentLineNum++; + } else { + break; + } } - } - // Find the next statement after these output lines - // The output belongs to the statement immediately following it - let nextStatementLine = currentLineNum; - if (nextStatementLine <= totalLines) { - const nextStmtLine = state.doc.line(nextStatementLine); - // Store this output range to be associated with the next statement - outputRanges.set(nextStmtLine.from, {from: outputStart, to: outputEnd}); + // Find the next statement after these output lines + // The output belongs to the statement immediately following it + let nextStatementLine = currentLineNum; + if (nextStatementLine <= totalLines) { + const nextStmtLine = state.doc.line(nextStatementLine); + // Store this output range to be associated with the next statement + outputRanges.set(nextStmtLine.from, {from: outputStart, to: outputEnd}); + } } } } @@ -119,10 +142,22 @@ function rebuiltBlocksWithinRange(state: EditorState, from: number, to: number): return blocks; } +/** + * Update block metadata according to the given transaction. + * + * @param blocks the current blocks + * @param tr the editor transaction + */ function updateBlocks(blocks: BlockMetadata[], tr: Transaction): void { - console.group("updateBlocks"); - - console.log("Original blocks:", structuredClone(blocks)); + // If the transaction does not change the document, then we return early. + if (!tr.docChanged) return; + + const userEvent = tr.annotation(Transaction.userEvent); + if (userEvent) { + console.group(`updateBlocks (${userEvent})`); + } else { + console.groupCollapsed(`updateBlocks`); + } /** * Find the block that contains the given position. If such block does not @@ -136,50 +171,61 @@ function updateBlocks(blocks: BlockMetadata[], tr: Transaction): void { let left = 0; let right = blocks.length; - console.log(`pos = ${pos}, side = ${side}`); - - loop: while (left < right) { - console.log(`Search range [${left}, ${right})`); - const middle = (left + right) >>> 1; - const pivot = blocks[middle]; - - const from = pivot.output?.from ?? pivot.source.from; - const to = pivot.source.to; - console.log(`Pivot: ${middle} (from = ${from}, to = ${to})`); - - done: { - if (pos < from) { - console.log("Should go left"); - // Go left and search range [left, middle + 1) - if (middle === left) { - // We cannot move left anymore. - break done; - } else { - right = middle; - continue loop; - } - } else if (to <= pos) { - console.log("Should go right"); - // Go right and search range [middle, right) - if (middle === left) { - // We cannot move right anymore. - break done; + console.groupCollapsed(`findNearestBlock(${pos}, ${side})`); + + try { + loop: while (left < right) { + console.log(`Search range [${left}, ${right})`); + const middle = (left + right) >>> 1; + const pivot = blocks[middle]!; + + const from = pivot.output?.from ?? pivot.source.from; + const to = pivot.source.to; + console.log(`Pivot: ${middle} (from = ${from}, to = ${to})`); + + done: { + if (pos < from) { + console.log(`Should go left as ${pos} < ${from}`); + // Go left and search range [left, middle + 1) + if (middle === left) { + // We cannot move left anymore. + break done; + } else { + right = middle; + continue loop; + } + } else if (to < pos) { + console.log(`Should go right as ${to} < ${pos}`); + // Go right and search range [middle, right) + if (middle === left) { + // We cannot move right anymore. + break done; + } else { + left = middle; + continue loop; + } } else { - left = middle; - continue loop; + return middle; } + } + + // After the binary search, the `left` represents the index of the first + // block that is before the given position. + console.log(`Final range: [${left}, ${right})`); + console.log(`Final pivot: ${middle} (from = ${from}, to = ${to})`); + if (side < 0) { + // If we are looking for the block before `pos`, then just return it. + return left; } else { - return middle; + // Otherwise, return the block after `pos`, if it exists. + return left + 1 < blocks.length ? left + 1 : null; } } - if (side < 0) { - return left; - } else { - return left === 0 ? null : left - 1; - } - } - return null; + return null; + } finally { + console.groupEnd(); + } } // Collect all ranges that need to be rescanned @@ -208,42 +254,92 @@ function updateBlocks(blocks: BlockMetadata[], tr: Transaction): void { console.log(`Affected block range: ${leftmost}-${rightmost}`); - if (leftmost === null || rightmost === null || leftmost > rightmost) { - // No blocks are affected by this change, so we can skip it. + if (leftmost === null || rightmost === null) { + // No blocks are affected by this change. But we might have to introduce + // new blocks within the new range. + const newBlocks = detectBlockWithinRange(tr.state, newFrom, newTo); + newBlocks.forEach((block) => affectedBlocks.add(block)); + + // However, in some cases, the newly introduced blocks might overlap with + // existing blocks. Here I illustrate with an example. + // - First, we type `let x`, and it will add a block for `let x`. + // - Then, we continue typing ` `. The space is not a part of `let x` + // because `let x` is a complete block. + // - Next, we type `=`. The input triggers `detectBlockWithinRange`, which + // recognizes `let x =` as a new block. + // In this example, if we just add `let x =` to the `blocks` array, there + // would be two overlapping blocks `let x` and `let x =`. + + if (rightmost === null) { + blocks.push(...newBlocks); + } else { + blocks.unshift(...newBlocks); + } + console.groupEnd(); continue; } + // This should never happen. + if (leftmost > rightmost) { + throw new RangeError(`Invalid block range: ${leftmost}-${rightmost}`); + } + // Step 2: Rebuild affected blocks. if (leftmost === rightmost) { - // The change affects only one block. We special case this scenario since - // we can possibly reuse the existing block attributes if the changed - // content is still a single block. - const block = blocks[leftmost]; + // The change affects only one block and the `pos` must be in the block. + // This means that the user is editing the block. + // + // We special case this scenario since we can possibly reuse the existing + // block attributes if the changed content is still a single block. + const block = blocks[leftmost]!; const newBlockFrom = tr.changes.mapPos(block.from, -1); const newBlockTo = tr.changes.mapPos(block.to, 1); console.log(`Only one block is affected. Rebuilting from ${block.from} to ${block.to}...`); - const rebuiltBlocks = rebuiltBlocksWithinRange(tr.state, newBlockFrom, newBlockTo); + const rebuiltBlocks = detectBlockWithinRange(tr.state, newBlockFrom, newBlockTo); - console.log(`Rebuilt blocks:`, rebuiltBlocks); + console.log(`Rebuilt blocks:`, structuredClone(rebuiltBlocks)); + // If only one block is rebuilt, we can reuse the existing attributes. if (rebuiltBlocks.length === 1) { - const newBlock = rebuiltBlocks[0]; + const newBlock = rebuiltBlocks[0]!; newBlock.attributes = block.attributes; affectedBlocks.add(newBlock); blocks[leftmost] = newBlock; - } else { + } + // Otherwise, we have to insert the rebuilt blocks. + else { blocks.splice(leftmost, 1, ...rebuiltBlocks); } - affectedBlocks.add(block); + rebuiltBlocks.forEach((block) => affectedBlocks.add(block)); } else { - // Multiple blocks are affected. - const rebuiltBlocks = rebuiltBlocksWithinRange(tr.state, newFrom, newTo); + console.log(`Multiple blocks from ${leftmost} to ${rightmost} are affected`); + + // Otherwise, multiple blocks are affected. + const rebuiltBlocks = detectBlockWithinRange(tr.state, newFrom, newTo); + + const leftmostBlock = blocks[leftmost]!; + const rightmostBlock = blocks[rightmost]!; + + console.log(`touchesLeftmost: ${oldFrom < leftmostBlock.to}`); + console.log(`touchesRightmost: ${rightmostBlock.from < oldTo}`); + rebuiltBlocks.forEach((block) => affectedBlocks.add(block)); - blocks.splice(leftmost, rightmost - leftmost + 1, ...rebuiltBlocks); + + // If the range touches the rightmost block, then we need to delete one more block. + const deleteCount = rightmost - leftmost + (rightmostBlock.from <= oldTo ? 1 : 0); + console.log(`deleteCount: ${deleteCount}`); + + // If the range does not touch the leftmost block, we need to delete one + // less block and adjust the `start` parameter. + if (oldFrom <= leftmostBlock.to) { + blocks.splice(leftmost, deleteCount, ...rebuiltBlocks); + } else { + blocks.splice(leftmost + 1, deleteCount - 1, ...rebuiltBlocks); + } } console.groupEnd(); @@ -251,10 +347,12 @@ function updateBlocks(blocks: BlockMetadata[], tr: Transaction): void { // Step 3: Map the unaffected blocks to new positions; for (let i = 0, n = blocks.length; i < n; i++) { - const block = blocks[i]; + const block = blocks[i]!; // Skip affected blocks as they have been updated. if (affectedBlocks.has(block)) continue; + console.log(`Processing unaffected block at ${i}`); + // Map unaffected blocks to new positions block.output = block.output ? { @@ -268,7 +366,12 @@ function updateBlocks(blocks: BlockMetadata[], tr: Transaction): void { }; } - console.log("Updated blocks:", blocks); + console.log("Updated blocks:", structuredClone(blocks)); + + // Lastly, we will merge blocks. We have elaborated on why there might exist + // overlapping blocks. Here, we employs a segment tree. + + mergeOverlappingBlocks(blocks); console.groupEnd(); } diff --git a/editor/decoration.js b/editor/decoration.js index 3f8793b..585150f 100644 --- a/editor/decoration.js +++ b/editor/decoration.js @@ -25,7 +25,7 @@ function createWidgets(lines, blockMetadata, state) { const set1 = builder1.finish(); // Build the range set for block attributes. - console.groupCollapsed("Decorations for block attributes"); + // console.groupCollapsed("Decorations for block attributes"); const builder2 = new RangeSetBuilder(); // Add block attribute decorations for (const {output, attributes} of blockMetadata) { @@ -33,7 +33,7 @@ function createWidgets(lines, blockMetadata, state) { // Apply decorations to each line in the block range const startLine = state.doc.lineAt(output.from); const endLine = state.doc.lineAt(output.to); - console.log(`Make lines from ${startLine.number} to ${endLine.number} compact`); + // console.log(`Make lines from ${startLine.number} to ${endLine.number} compact`); if (attributes.compact === true) { for (let lineNum = startLine.number; lineNum <= endLine.number; lineNum++) { const line = state.doc.line(lineNum); @@ -42,7 +42,7 @@ function createWidgets(lines, blockMetadata, state) { } } const set2 = builder2.finish(); - console.groupEnd(); + // console.groupEnd(); // Range sets are required to be sorted. Fortunately, they provide a method // to merge multiple range sets into a single sorted range set. @@ -56,13 +56,13 @@ function createDebugMarks(blockMetadata, state) { for (const {output, source} of blockMetadata) { // Add red marks for output ranges if (output !== null && output.from < output.to) { - console.log(`Adding red decoration for output: ${output.from}-${output.to}`); + // console.log(`Adding red decoration for output: ${output.from}-${output.to}`); builder.add(output.from, output.to, debugRedDecoration); } // Add green marks for source ranges if (source.from < source.to) { - console.log(`Adding green decoration for source: ${source.from}-${source.to}`); + // console.log(`Adding green decoration for source: ${source.from}-${source.to}`); builder.add(source.from, source.to, debugGreenDecoration); } } diff --git a/editor/deduplication.ts b/editor/deduplication.ts new file mode 100644 index 0000000..c458bd1 --- /dev/null +++ b/editor/deduplication.ts @@ -0,0 +1,159 @@ +import {BlockMetadata} from "./blockMetadata.js"; + +export interface Interval { + from: number; + to: number; +} + +type Block = BlockMetadata; + +interface IntervalWithIndex extends Interval { + index: number; +} + +interface MergeGroup { + indices: number[]; + from: number; + to: number; +} + +/** + * Detects overlapping blocks and returns groups of indices that should be merged. + * + * @param blocks Array of blocks to check for overlaps + * @returns Array of merge groups, where each group contains indices of overlapping blocks + */ +export function detectOverlappingBlocks(blocks: Block[]): MergeGroup[] { + if (blocks.length === 0) return []; + + // Create a sorted array of block intervals with their indices + const intervals: IntervalWithIndex[] = blocks.map((block, index) => ({ + from: block.from, + to: block.to, + index, + })); + + // Sort by start position, then by end position + intervals.sort((a, b) => { + if (a.from !== b.from) return a.from - b.from; + return a.to - b.to; + }); + + // Track which blocks overlap + const overlappingIndices = new Set(); + + // Simple sweep line algorithm to detect overlaps + for (let i = 0; i < intervals.length - 1; i++) { + const current = intervals[i]!; + const next = intervals[i + 1]!; + + // If next block starts before or at the end of current block, they overlap + if (next.from < current.to) { + overlappingIndices.add(current.index); + overlappingIndices.add(next.index); + } + } + + if (overlappingIndices.size === 0) { + return []; + } + + // Group consecutive overlapping blocks + const sortedOverlapping = Array.from(overlappingIndices).sort((a, b) => a - b); + const mergeGroups: MergeGroup[] = []; + let currentGroup: number[] = []; + + for (const idx of sortedOverlapping) { + if (currentGroup.length === 0) { + currentGroup.push(idx); + } else { + const lastIdx = currentGroup[currentGroup.length - 1]!; + const lastBlock = blocks[lastIdx]!; + const currentBlock = blocks[idx]!; + + // Check if blocks overlap + if (currentBlock.from <= lastBlock.to) { + currentGroup.push(idx); + } else { + // Finalize current group + const groupFrom = Math.min(...currentGroup.map((i) => blocks[i]!.from)); + const groupTo = Math.max(...currentGroup.map((i) => blocks[i]!.to)); + mergeGroups.push({indices: currentGroup, from: groupFrom, to: groupTo}); + currentGroup = [idx]; + } + } + } + + // Don't forget the last group + if (currentGroup.length > 0) { + const groupFrom = Math.min(...currentGroup.map((i) => blocks[i]!.from)); + const groupTo = Math.max(...currentGroup.map((i) => blocks[i]!.to)); + mergeGroups.push({indices: currentGroup, from: groupFrom, to: groupTo}); + } + + return mergeGroups; +} + +/** + * Merges overlapping blocks in place. + * + * @param blocks Array of blocks to merge (will be modified in place) + */ +export function mergeOverlappingBlocks(blocks: Block[]): void { + const mergeGroups = detectOverlappingBlocks(blocks); + + console.log("Merge groups:", mergeGroups); + mergeGroups.forEach((group) => { + group.indices.forEach((index) => { + const block = blocks[index]!; + console.log(block); + }); + }); + + if (mergeGroups.length === 0) return; + + console.log(`Found ${mergeGroups.length} groups of overlapping blocks to merge`); + + // Process groups in reverse order to avoid index shifting issues when removing + for (const group of mergeGroups.reverse()) { + if (group.indices.length < 2) continue; + + console.log(`Merging blocks at indices ${group.indices.join(", ")} into range ${group.from}-${group.to}`); + + // Find the block with the most complete information (prefer blocks with output) + let primaryIdx = group.indices[0]!; + let primaryBlock = blocks[primaryIdx]!; + + for (const idx of group.indices) { + const block = blocks[idx]!; + if (block.output && !primaryBlock.output) { + primaryIdx = idx; + primaryBlock = block; + } + } + + // Update the primary block's range to encompass all merged blocks + if (primaryBlock.output) { + const outputFrom = Math.min( + primaryBlock.output.from, + ...group.indices.map((idx) => blocks[idx]!.output?.from ?? Infinity), + ); + const outputTo = Math.max( + primaryBlock.output.to, + ...group.indices.map((idx) => blocks[idx]!.output?.to ?? -Infinity), + ); + primaryBlock.output = {from: outputFrom, to: outputTo}; + } + + primaryBlock.source = {from: group.from, to: group.to}; + + // Keep the primary block at the first index, remove all others + const firstIdx = group.indices[0]!; + blocks[firstIdx] = primaryBlock; + + // Remove other blocks (iterate backwards to avoid index shifting) + for (let i = group.indices.length - 1; i > 0; i--) { + blocks.splice(group.indices[i]!, 1); + } + } +} diff --git a/runtime/index.js b/runtime/index.js index a1affdc..f7f8a6b 100644 --- a/runtime/index.js +++ b/runtime/index.js @@ -144,14 +144,20 @@ export function createRuntime(initialCode) { for (const node of nodes) { const start = node.start; const {values} = node.state; - if (!values.length) continue; + const sourceRange = {from: node.start, to: node.end}; + + if (!values.length) { + // Create a block even if there are no values. + blocks.push(BlockMetadata(null, sourceRange, node.state.attributes)); + continue; + } // Group values by key. Each group is a row if using table, otherwise a column. const groupValues = groups(values, (v) => v.options?.key); // We need to remove the trailing newline for table. const format = withTable(groupValues) ? (...V) => table(...V).trimEnd() : columns; - + // The range of line numbers of output lines. let outputRange = null; @@ -213,9 +219,7 @@ export function createRuntime(initialCode) { blocks.sort((a, b) => a.from - b.from); } - - console.log("Dear blocks", blocks); - + // Attach block positions and attributes as effects to the transaction. const effects = [blockMetadataEffect.of(blocks)]; diff --git a/test/components/BlockViewer.tsx b/test/components/BlockViewer.tsx index b1c7d5a..e10fb52 100644 --- a/test/components/BlockViewer.tsx +++ b/test/components/BlockViewer.tsx @@ -166,8 +166,11 @@ function BlockItem({block, onLocate}: {block: BlockData; onLocate: (block: Block > - - {block.sourceFrom}-{block.sourceTo} + + [ + {block.sourceFrom}, + {block.sourceTo} + )
From 342dbc3412db2fbe0f71f92c810e0cbe78707b94 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sat, 13 Dec 2025 22:43:38 +0800 Subject: [PATCH 13/30] Reimplement the block shifting computation --- editor/blockIndicator.ts | 3 +- editor/blockMetadata.ts | 422 +++++---------------------- editor/blocks/BlockMetadata.ts | 37 +++ editor/{ => blocks}/deduplication.ts | 2 +- editor/index.js | 2 +- eslint.config.mjs | 2 +- lib/blocks.ts | 194 ++++++++++++ lib/blocks/detect.ts | 130 +++++++++ lib/containers/heap.ts | 144 +++++++++ package.json | 1 + pnpm-lock.yaml | 3 + runtime/index.js | 7 +- test/blocks.spec.ts | 294 +++++++++++++++++++ test/blocks/affected.spec.ts | 403 +++++++++++++++++++++++++ test/blocks/detect.spec.ts | 38 +++ test/containers/heap.spec.ts | 286 ++++++++++++++++++ tsconfig.json | 17 +- 17 files changed, 1621 insertions(+), 364 deletions(-) create mode 100644 editor/blocks/BlockMetadata.ts rename editor/{ => blocks}/deduplication.ts (98%) create mode 100644 lib/blocks.ts create mode 100644 lib/blocks/detect.ts create mode 100644 lib/containers/heap.ts create mode 100644 test/blocks.spec.ts create mode 100644 test/blocks/affected.spec.ts create mode 100644 test/blocks/detect.spec.ts create mode 100644 test/containers/heap.spec.ts diff --git a/editor/blockIndicator.ts b/editor/blockIndicator.ts index 7609ab3..2425a5f 100644 --- a/editor/blockIndicator.ts +++ b/editor/blockIndicator.ts @@ -1,5 +1,6 @@ import {GutterMarker, gutter} from "@codemirror/view"; -import {BlockMetadata, blockMetadataField} from "./blockMetadata"; +import {BlockMetadata} from "./blocks/BlockMetadata.js"; +import {blockMetadataField} from "./blockMetadata.ts"; export class BlockIndicator extends GutterMarker { constructor(private className: string) { diff --git a/editor/blockMetadata.ts b/editor/blockMetadata.ts index e884e19..89fd5a6 100644 --- a/editor/blockMetadata.ts +++ b/editor/blockMetadata.ts @@ -1,156 +1,20 @@ import {StateField, StateEffect, Transaction} from "@codemirror/state"; +import {mergeOverlappingBlocks} from "./blocks/deduplication.ts"; +import {blockRangeLength, findAffectedBlockRange, getOnlyOneBlock} from "../lib/blocks.ts"; +import {detectBlocksWithinRange} from "../lib/blocks/detect.ts"; import {syntaxTree} from "@codemirror/language"; -import {OUTPUT_MARK, ERROR_MARK} from "../runtime/constant.js"; -import {EditorState} from "@codemirror/state"; -import {mergeOverlappingBlocks} from "./deduplication.js"; - -const OUTPUT_MARK_CODE_POINT = OUTPUT_MARK.codePointAt(0); -const ERROR_MARK_CODE_POINT = ERROR_MARK.codePointAt(0); - -type Range = {from: number; to: number}; - -export function BlockMetadata(output: Range | null, source: Range, attributes: Record = {}) { - return { - output, - source, - get from() { - return this.output?.from ?? this.source.from; - }, - get to() { - return this.source.to; - }, - attributes, - error: false, - }; -} - -/** - * Detect blocks in a given range by traversing the syntax tree. - * Similar to how runtime/index.js uses acorn to parse blocks, but adapted for CodeMirror. - */ -function detectBlockWithinRange(state: EditorState, from: number, to: number): BlockMetadata[] { - const blocks: BlockMetadata[] = []; - - // Collect all top-level statements and their preceding output/error comment lines - const statementRanges: Range[] = []; - const outputRanges = new Map(); // Map statement position to output range - - syntaxTree(state).iterate({ - from, - to, - enter: (node) => { - // Detect top-level statements (direct children of Script) - if (node.node.parent?.name === "Script") { - // Check if this is a statement (not a comment) - if ( - node.name.includes("Statement") || - node.name.includes("Declaration") || - node.name === "ExportDeclaration" || - node.name === "ImportDeclaration" || - node.name === "Block" - ) { - // Note: The range here is right-exclusive. - console.log(`Found "${node.name}" spans from ${node.from} to ${node.to}`); - statementRanges.push({from: node.from, to: node.to}); - - let outputRange: Range | null = null; - let currentNode = node.node.prevSibling; - - console.log(`Previous node: ${currentNode}`); - while (currentNode?.name === "LineComment") { - const line = state.doc.lineAt(currentNode.from); - if (line.from === currentNode.from && line.to === currentNode.to) { - const codePoint = line.text.codePointAt(2); - if (codePoint === OUTPUT_MARK_CODE_POINT || codePoint === ERROR_MARK_CODE_POINT) { - outputRange = - outputRange === null ? {from: line.from, to: line.to} : {from: line.from, to: outputRange.to}; - } - } - currentNode = currentNode.prevSibling; - } - console.log(`Output Range:`, outputRange); - if (outputRange !== null) { - outputRanges.set(node.from, outputRange); - } - } - // Detect output/error comment lines (top-level line comments) - else if (node.name === "LineComment") { - // Get the line containing the comment. - const line = state.doc.lineAt(node.from); - - // Check if the line comment covers the entire line - if (line.from === node.from && line.to === node.to) { - const codePoint = line.text.codePointAt(2); - if (codePoint === OUTPUT_MARK_CODE_POINT || codePoint === ERROR_MARK_CODE_POINT) { - // Find consecutive output/error lines - let outputStart = line.from; - let outputEnd = line.to; - - // Look backwards for more output/error lines - let currentLineNum = line.number - 1; - while (currentLineNum >= 1) { - const prevLine = state.doc.line(currentLineNum); - const prevCodePoint = prevLine.text.codePointAt(2); - if ( - prevLine.text.startsWith("//") && - (prevCodePoint === OUTPUT_MARK_CODE_POINT || prevCodePoint === ERROR_MARK_CODE_POINT) - ) { - outputStart = prevLine.from; - currentLineNum--; - } else { - break; - } - } - - // Look forwards for more output/error lines - currentLineNum = line.number + 1; - const totalLines = state.doc.lines; - while (currentLineNum <= totalLines) { - const nextLine = state.doc.line(currentLineNum); - const nextCodePoint = nextLine.text.codePointAt(2); - if ( - nextLine.text.startsWith("//") && - (nextCodePoint === OUTPUT_MARK_CODE_POINT || nextCodePoint === ERROR_MARK_CODE_POINT) - ) { - outputEnd = nextLine.to; - currentLineNum++; - } else { - break; - } - } - - // Find the next statement after these output lines - // The output belongs to the statement immediately following it - let nextStatementLine = currentLineNum; - if (nextStatementLine <= totalLines) { - const nextStmtLine = state.doc.line(nextStatementLine); - // Store this output range to be associated with the next statement - outputRanges.set(nextStmtLine.from, {from: outputStart, to: outputEnd}); - } - } - } - } - } - }, - }); - - // Build block metadata from statements - for (const range of statementRanges) { - blocks.push(BlockMetadata(outputRanges.get(range.from) ?? null, range)); - } - - return blocks; -} +import {MaxHeap} from "../lib/containers/heap.ts"; +import {type Range, BlockMetadata} from "./blocks/BlockMetadata.ts"; /** * Update block metadata according to the given transaction. * - * @param blocks the current blocks + * @param oldBlocks the current blocks * @param tr the editor transaction */ -function updateBlocks(blocks: BlockMetadata[], tr: Transaction): void { +function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadata[] { // If the transaction does not change the document, then we return early. - if (!tr.docChanged) return; + if (!tr.docChanged) return oldBlocks; const userEvent = tr.annotation(Transaction.userEvent); if (userEvent) { @@ -159,75 +23,6 @@ function updateBlocks(blocks: BlockMetadata[], tr: Transaction): void { console.groupCollapsed(`updateBlocks`); } - /** - * Find the block that contains the given position. If such block does not - * exist, return the nearest block that is right before or after the given - * position. When no blocks are before and after the given position, return - * `null`. - * @param pos the position we want to find - * @param side `-1` - lower bound, `1` - upper bound - */ - function findNearestBlock(pos: number, side: -1 | 1): number | null { - let left = 0; - let right = blocks.length; - - console.groupCollapsed(`findNearestBlock(${pos}, ${side})`); - - try { - loop: while (left < right) { - console.log(`Search range [${left}, ${right})`); - const middle = (left + right) >>> 1; - const pivot = blocks[middle]!; - - const from = pivot.output?.from ?? pivot.source.from; - const to = pivot.source.to; - console.log(`Pivot: ${middle} (from = ${from}, to = ${to})`); - - done: { - if (pos < from) { - console.log(`Should go left as ${pos} < ${from}`); - // Go left and search range [left, middle + 1) - if (middle === left) { - // We cannot move left anymore. - break done; - } else { - right = middle; - continue loop; - } - } else if (to < pos) { - console.log(`Should go right as ${to} < ${pos}`); - // Go right and search range [middle, right) - if (middle === left) { - // We cannot move right anymore. - break done; - } else { - left = middle; - continue loop; - } - } else { - return middle; - } - } - - // After the binary search, the `left` represents the index of the first - // block that is before the given position. - console.log(`Final range: [${left}, ${right})`); - console.log(`Final pivot: ${middle} (from = ${from}, to = ${to})`); - if (side < 0) { - // If we are looking for the block before `pos`, then just return it. - return left; - } else { - // Otherwise, return the block after `pos`, if it exists. - return left + 1 < blocks.length ? left + 1 : null; - } - } - - return null; - } finally { - console.groupEnd(); - } - } - // Collect all ranges that need to be rescanned type ChangedRange = {oldFrom: number; oldTo: number; newFrom: number; newTo: number}; const changedRanges: ChangedRange[] = []; @@ -238,155 +33,113 @@ function updateBlocks(blocks: BlockMetadata[], tr: Transaction): void { if (changedRanges.length === 0) { console.log("No changes detected"); console.groupEnd(); - return; + return oldBlocks; } + /** + * Keep track of all blocks that are affected by the change. They will not be + * added to the array of new blocks. + */ const affectedBlocks = new Set(); + const newlyCreatedBlocks = new MaxHeap( + (block) => block.from, + (a, b) => b - a, + ); + // Process changed ranges one by one, because ranges are disjoint. for (const {oldFrom, oldTo, newFrom, newTo} of changedRanges) { - console.groupCollapsed(`Range ${oldFrom}-${oldTo} -> ${newFrom}-${newTo}`); + if (oldFrom === oldTo) { + if (newFrom === oldFrom) { + console.groupCollapsed(`Insert ${newTo - newFrom} characters at ${oldFrom}`); + } else { + console.groupCollapsed(`Insert ${newTo - newFrom} characters: ${oldFrom} -> ${newFrom}-${newTo}`); + } + } else { + console.groupCollapsed(`Update: ${oldFrom}-${oldTo} -> ${newFrom}-${newTo}`); + } // Step 1: Find the blocks that are affected by the change. - const leftmost = findNearestBlock(oldFrom, -1); - const rightmost = findNearestBlock(oldTo, 1); - - console.log(`Affected block range: ${leftmost}-${rightmost}`); - - if (leftmost === null || rightmost === null) { - // No blocks are affected by this change. But we might have to introduce - // new blocks within the new range. - const newBlocks = detectBlockWithinRange(tr.state, newFrom, newTo); - newBlocks.forEach((block) => affectedBlocks.add(block)); - - // However, in some cases, the newly introduced blocks might overlap with - // existing blocks. Here I illustrate with an example. - // - First, we type `let x`, and it will add a block for `let x`. - // - Then, we continue typing ` `. The space is not a part of `let x` - // because `let x` is a complete block. - // - Next, we type `=`. The input triggers `detectBlockWithinRange`, which - // recognizes `let x =` as a new block. - // In this example, if we just add `let x =` to the `blocks` array, there - // would be two overlapping blocks `let x` and `let x =`. - - if (rightmost === null) { - blocks.push(...newBlocks); - } else { - blocks.unshift(...newBlocks); - } + const affectedBlockRange = findAffectedBlockRange(oldBlocks, oldFrom, oldTo); - console.groupEnd(); - continue; - } + console.log(`Affected block range: ${affectedBlockRange[0]}-${affectedBlockRange[1]}`); - // This should never happen. - if (leftmost > rightmost) { - throw new RangeError(`Invalid block range: ${leftmost}-${rightmost}`); + // Add the affected blocks to the set. + for (let i = affectedBlockRange[0] ?? 0, n = affectedBlockRange[1] ?? oldBlocks.length; i < n; i++) { + affectedBlocks.add(oldBlocks[i]!); } - // Step 2: Rebuild affected blocks. + // Check a corner case where the affected block range is empty but there are blocks. + if (blockRangeLength(oldBlocks.length, affectedBlockRange) === 0 && oldBlocks.length > 0) { + reportError("This should never happen"); + } - if (leftmost === rightmost) { - // The change affects only one block and the `pos` must be in the block. - // This means that the user is editing the block. - // - // We special case this scenario since we can possibly reuse the existing - // block attributes if the changed content is still a single block. - const block = blocks[leftmost]!; + // Now, we are going to compute the range which should be re-parsed. + const reparseFrom = affectedBlockRange[0] === null ? 0 : oldBlocks[affectedBlockRange[0]]!.from; + const reparseTo = affectedBlockRange[1] === null ? tr.state.doc.length : oldBlocks[affectedBlockRange[1] - 1]!.to; + const newBlocks = detectBlocksWithinRange(syntaxTree(tr.state), tr.state.doc, reparseFrom, reparseTo); - const newBlockFrom = tr.changes.mapPos(block.from, -1); - const newBlockTo = tr.changes.mapPos(block.to, 1); + // If only one block is affected and only one new block is created, we can + // simply inherit the attributes from the old block. + const soleBlockIndex = getOnlyOneBlock(oldBlocks.length, affectedBlockRange); + if (typeof soleBlockIndex === "number" && newBlocks.length === 1) { + newBlocks[0]!.attributes = oldBlocks[soleBlockIndex]!.attributes; + } - console.log(`Only one block is affected. Rebuilting from ${block.from} to ${block.to}...`); - const rebuiltBlocks = detectBlockWithinRange(tr.state, newBlockFrom, newBlockTo); + // Add new blocks to the heap, which sorts blocks by their `from` position. + for (let i = 0, n = newBlocks.length; i < n; i++) { + newlyCreatedBlocks.insert(newBlocks[i]!); + } - console.log(`Rebuilt blocks:`, structuredClone(rebuiltBlocks)); + console.groupEnd(); + } - // If only one block is rebuilt, we can reuse the existing attributes. - if (rebuiltBlocks.length === 1) { - const newBlock = rebuiltBlocks[0]!; - newBlock.attributes = block.attributes; - affectedBlocks.add(newBlock); - blocks[leftmost] = newBlock; - } - // Otherwise, we have to insert the rebuilt blocks. - else { - blocks.splice(leftmost, 1, ...rebuiltBlocks); - } - rebuiltBlocks.forEach((block) => affectedBlocks.add(block)); - } else { - console.log(`Multiple blocks from ${leftmost} to ${rightmost} are affected`); + // Step 3: Combine the array of old blocks and the heap of new blocks. - // Otherwise, multiple blocks are affected. - const rebuiltBlocks = detectBlockWithinRange(tr.state, newFrom, newTo); + const newBlocks: BlockMetadata[] = []; - const leftmostBlock = blocks[leftmost]!; - const rightmostBlock = blocks[rightmost]!; + for (let i = 0, n = oldBlocks.length; i < n; i++) { + const oldBlock = oldBlocks[i]!; - console.log(`touchesLeftmost: ${oldFrom < leftmostBlock.to}`); - console.log(`touchesRightmost: ${rightmostBlock.from < oldTo}`); + // Skip affected blocks, as they have been updated. + if (affectedBlocks.has(oldBlock)) continue; - rebuiltBlocks.forEach((block) => affectedBlocks.add(block)); + const newBlock = oldBlock.map(tr); - // If the range touches the rightmost block, then we need to delete one more block. - const deleteCount = rightmost - leftmost + (rightmostBlock.from <= oldTo ? 1 : 0); - console.log(`deleteCount: ${deleteCount}`); + // Peek the heap. Check the positional relationship between its foremost + // block and the current old block. - // If the range does not touch the leftmost block, we need to delete one - // less block and adjust the `start` parameter. - if (oldFrom <= leftmostBlock.to) { - blocks.splice(leftmost, deleteCount, ...rebuiltBlocks); - } else { - blocks.splice(leftmost + 1, deleteCount - 1, ...rebuiltBlocks); - } + while (newlyCreatedBlocks.nonEmpty() && newlyCreatedBlocks.peek.from < newBlock.from) { + newBlocks.push(newlyCreatedBlocks.peek); + newlyCreatedBlocks.extractMax(); } - console.groupEnd(); + // At this point, the heap is either empty or the foremost block is after + // the current old block's `from` position. + + newBlocks.push(newBlock); } - // Step 3: Map the unaffected blocks to new positions; - for (let i = 0, n = blocks.length; i < n; i++) { - const block = blocks[i]!; - // Skip affected blocks as they have been updated. - if (affectedBlocks.has(block)) continue; - - console.log(`Processing unaffected block at ${i}`); - - // Map unaffected blocks to new positions - block.output = block.output - ? { - from: tr.changes.mapPos(block.output.from, -1), - to: tr.changes.mapPos(block.output.to, 1), - } - : null; - block.source = { - from: tr.changes.mapPos(block.source.from, -1), - to: tr.changes.mapPos(block.source.to, 1), - }; + // In the end, push any remaining blocks from the heap. + while (newlyCreatedBlocks.nonEmpty()) { + newBlocks.push(newlyCreatedBlocks.peek); + newlyCreatedBlocks.extractMax(); } - console.log("Updated blocks:", structuredClone(blocks)); + console.log("New blocks:", newBlocks); // Lastly, we will merge blocks. We have elaborated on why there might exist // overlapping blocks. Here, we employs a segment tree. - mergeOverlappingBlocks(blocks); + // mergeOverlappingBlocks(oldBlocks); console.groupEnd(); + return newBlocks; } export const blockMetadataEffect = StateEffect.define(); -export type BlockMetadata = { - output: Range | null; - source: Range; - readonly from: number; - readonly to: number; - attributes: Record; - error: boolean; -}; - export const blockMetadataField = StateField.define({ create() { return []; @@ -405,32 +158,13 @@ export const blockMetadataField = StateField.define({ // If the block attributes effect is not present, then this transaction // is made by the user, we need to update the block attributes accroding // to the latest syntax tree. - updateBlocks(blocks, tr); - return blocks; + return updateBlocks(blocks, tr); } else { // Otherwise, we need to update the block attributes according to the // metadata sent from the runtime. Most importantly, we need to translate // the position of each block after the changes has been made. - console.group("Updating blocks from the effect"); - for (const block of blocksFromEffect) { - if (block.output === null) { - const from = tr.changes.mapPos(block.source.from, -1); - const to = tr.changes.mapPos(block.source.from, 1); - block.output = {from, to: to - 1}; - } else { - const from = tr.changes.mapPos(block.output.from, -1); - const to = tr.changes.mapPos(block.output.to, 1); - block.output = {from, to: to - 1}; - } - block.source = { - from: tr.changes.mapPos(block.source.from, 1), - to: tr.changes.mapPos(block.source.to, 1), - }; - console.log(`output: ${block.output?.from} - ${block.output?.to}`); - console.log(`source: ${block.source.from} - ${block.source.to}`); - } - console.groupEnd(); - return blocksFromEffect; + // TODO: Does this really happen??? + return blocksFromEffect.map((block) => block.map(tr)); } }, }); diff --git a/editor/blocks/BlockMetadata.ts b/editor/blocks/BlockMetadata.ts new file mode 100644 index 0000000..1f7b12a --- /dev/null +++ b/editor/blocks/BlockMetadata.ts @@ -0,0 +1,37 @@ +import type {Transaction} from "@codemirror/state"; + +export type Range = {from: number; to: number}; + +export class BlockMetadata { + public constructor( + public readonly output: Range | null, + public readonly source: Range, + public attributes: Record = {}, + public error: boolean = false, + ) {} + + get from() { + return this.output?.from ?? this.source.from; + } + + get to() { + return this.source.to; + } + + public map(tr: Transaction): BlockMetadata { + if (!tr.docChanged) return this; + const output = this.output + ? { + from: tr.changes.mapPos(this.output.from, 1), + to: tr.changes.mapPos(this.output.to, -1), + } + : null; + const source = { + from: tr.changes.mapPos(this.source.from, 1), + to: tr.changes.mapPos(this.source.to, -1), + }; + const attributes = this.attributes; + const error = this.error; + return new BlockMetadata(output, source, attributes, error); + } +} diff --git a/editor/deduplication.ts b/editor/blocks/deduplication.ts similarity index 98% rename from editor/deduplication.ts rename to editor/blocks/deduplication.ts index c458bd1..5b59a1b 100644 --- a/editor/deduplication.ts +++ b/editor/blocks/deduplication.ts @@ -1,4 +1,4 @@ -import {BlockMetadata} from "./blockMetadata.js"; +import {BlockMetadata} from "./BlockMetadata.js"; export interface Interval { from: number; diff --git a/editor/index.js b/editor/index.js index 5deacbb..6e3bacc 100644 --- a/editor/index.js +++ b/editor/index.js @@ -11,7 +11,7 @@ import * as eslint from "eslint-linter-browserify"; import {createRuntime} from "../runtime/index.js"; import {outputDecoration, debugDecoration} from "./decoration.js"; import {outputLines} from "./outputLines.js"; -import {blockMetadataExtension, blockMetadataEffect} from "./blockMetadata.ts"; +import {blockMetadataExtension} from "./blockMetadata.ts"; // import {outputProtection} from "./protection.js"; import {dispatch as d3Dispatch} from "d3-dispatch"; import {controls} from "./controls/index.js"; diff --git a/eslint.config.mjs b/eslint.config.mjs index f812d4a..57bcb5e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -6,7 +6,7 @@ import hooksPlugin from "eslint-plugin-react-hooks"; export default defineConfig([ { - files: ["editor/**/*.js", "runtime/**/*.js", "test/**/*.js", "app/**/*.js", "app/**/*.jsx"], + files: ["editor/**/*.js", "runtime/**/*.js", "test/**/*.js", "app/**/*.js", "app/**/*.jsx", "lib/**/*.js"], plugins: { react: reactPlugin, "react-hooks": hooksPlugin, diff --git a/lib/blocks.ts b/lib/blocks.ts new file mode 100644 index 0000000..ba3eb60 --- /dev/null +++ b/lib/blocks.ts @@ -0,0 +1,194 @@ +/** + * Find the block where the given position is adjacent to. There are a few + * possible cases. + * + * - If `pos` is inside a block, return the block directly. + * - Otherwise, `pos` is not inside any block. We return the block before + * and the block after `pos`. If any one of them does not exist, return + * `null` instead. + * @param blocks The array of blocks. The right boundary `to` is exclusive. + * @param pos The position of the selection + * @returns If `pos` is inside a block, return the block index. If `pos` is + * between two blocks, return the indices of the two blocks. Note that if + * `pos` is before the first block, return `[-1, 0]`. If `pos` is after + * the last block, return `[blocks.length - 1, blocks.length]`. Lastly, if + * `blocks` is empty, return `null`. + */ +export function findAdjacentBlocks( + blocks: {from: number; to: number}[], + pos: number, +): number | [number, number] | null { + try { + // console.groupCollapsed(`findAdjacentBlocks(${pos})`); + + // We maintain a binary search range [left, right). Note that the right + // boundary is exclusive, i.e., the range is empty when `left === right`. + let left = 0; + let right = blocks.length; + let DEBUG_hasEnteredLoop = false; + + // When the range is non-empty. + loop: while (left < right) { + DEBUG_hasEnteredLoop = true; + + const middle = (left + right) >>> 1; + const pivot = blocks[middle]!; + + if (pos < pivot.from) { + // We should move to the left sub-range [left, middle). + if (left === middle) { + return [middle - 1, middle]; + } else { + right = middle; + continue loop; + } + } else if (pivot.to <= pos) { + // We should move to the right sub-range [middle, right). + if (left === middle) { + return [middle, middle + 1]; + } else { + left = middle; + } + } else { + // Good news: `pos` is inside `pivot`. + return middle; + } + } + + // We can only reach here if we haven't enter the loop. + // console.assert(!DEBUG_hasEnteredLoop, "Did not return in the loop."); + + return null; + } finally { + // console.groupEnd(); + } +} + +export type BlockRange = [from: number | null, to: number | null]; + +export function blockRangeLength(blockCount: number, [from, to]: BlockRange): number { + if (from === null) { + if (to === null) { + return blockCount; + } else { + return to; + } + } else { + if (to === null) { + return blockCount - from; + } else { + return to - from; + } + } +} + +/** + * If there is only one block in the range, return its index. Otherwise, return + * `null`. + * + * @param blockCount the number of blocks + * @param param1 the range of blocks + * @returns the range of the block + */ +export function getOnlyOneBlock(blockCount: number, [from, to]: BlockRange): number | null { + if (from === null) { + if (to === null) { + return null; + } else { + return to === 1 ? 0 : null; + } + } else { + if (to === null) { + return blockCount - from === 1 ? from : null; + } else { + return to - from === 1 ? from : null; + } + } +} + +/** + * Finds the range of affected blocks in an array of disjoint and sorted blocks. + * The ranges of blocks whose indices within the returned range should be + * overlapping with range [from, to). The indices of blocks whose ranges are + * overlapping with range [from, to) should be returned. + * + * Note that the returned range does not represent the relationship between the + * edited area and the blocks. An edit may affect nearby blocks even if those + * blocks are not directly modified. For example, adding a plus `+` between two + * function calls would merge the two blocks into a new expression. + * + * @param blocks The array of blocks. Blocks are disjoint and sorted. + * @param from The starting index of the edited range. + * @param to The ending index (exclusive) of the edited range. + * @returns The range of affected blocks. The + */ +export function findAffectedBlockRange( + blocks: {from: number; to: number}[], + from: number, + to: number, +): [from: number | null, to: number | null] { + if (from > to) { + throw new RangeError("`from` must be less than or equal to `to`"); + } + + if (from < 0) { + throw new RangeError("`from` must be greater than or equal to 0"); + } + + const fromResult = findAdjacentBlocks(blocks, from); + + // In insertion transactions, `from` and `to` are identical because they do + // not delete any characters from the document. + if (from === to) { + if (typeof fromResult === "number") { + // This means that the insertion point is in a block, thus only the block + // is affected. We return [index, index + 1). + return [fromResult, fromResult + 1]; + } else if (fromResult === null) { + // There is no block at all. We should re-parse the entire document. + return [null, null]; + } else { + // The insertion point is between blocks. We should include two blocks. + const [blockIndexBefore, blockIndexAfter] = fromResult; + return [ + // `null` means we should the caller should use the beginning of the document. + blockIndexBefore === -1 ? null : blockIndexBefore, + blockIndexAfter === blocks.length ? null : blockIndexAfter + 1, + ]; + } + } + + // Otherwise, the transaction deletes characters from the document. + // Note that we use `to - 1` because we want to locate the last character. + // Using `to` is wrong because it would locate the first character of the next block. + const toResult = findAdjacentBlocks(blocks, to - 1); + + // This means that we are deleting from non-block region. + if (fromResult === null || toResult === null) { + // If there is no block at all, then both of them should be `null`. + if (fromResult !== toResult) { + throw new RangeError("`from` and `to` should be located to nowhere at the same time", { + cause: {ranges: blocks.map((block) => ({from: block.from, to: block.to})), from, to}, + }); + } + return [null, null]; + } + + if (typeof fromResult === "number") { + if (typeof toResult === "number") { + return [fromResult, toResult === blocks.length ? null : toResult + 1]; + } else { + const blockIndexAfterTo = toResult[1]; + return [fromResult, blockIndexAfterTo === blocks.length ? null : blockIndexAfterTo + 1]; + } + } else { + const blockIndexBeforeFrom = fromResult[0]; + const rangeFrom = blockIndexBeforeFrom === -1 ? null : blockIndexBeforeFrom; + if (typeof toResult === "number") { + return [rangeFrom, toResult === blocks.length ? null : toResult + 1]; + } else { + const blockIndexAfterTo = toResult[1]; + return [rangeFrom, blockIndexAfterTo === blocks.length ? null : blockIndexAfterTo + 1]; + } + } +} diff --git a/lib/blocks/detect.ts b/lib/blocks/detect.ts new file mode 100644 index 0000000..44b06df --- /dev/null +++ b/lib/blocks/detect.ts @@ -0,0 +1,130 @@ +import {type Text} from "@codemirror/state"; +import {OUTPUT_MARK, ERROR_MARK} from "../../runtime/constant.js"; +import {syntaxTree} from "@codemirror/language"; +import {BlockMetadata, type Range} from "../../editor/blocks/BlockMetadata.ts"; + +const OUTPUT_MARK_CODE_POINT = OUTPUT_MARK.codePointAt(0); +const ERROR_MARK_CODE_POINT = ERROR_MARK.codePointAt(0); + +// Since CodeMirror does not export `SyntaxNode`, we have to get it in this way. +type Tree = ReturnType; +type SyntaxNode = Tree["topNode"]; + +function extendOutputForward(doc: Text, node: SyntaxNode): Range | null { + let outputRange: Range | null = null; + let currentNode = node.node.prevSibling; + + while (currentNode?.name === "LineComment") { + const line = doc.lineAt(currentNode.from); + if (line.from === currentNode.from && line.to === currentNode.to) { + const codePoint = line.text.codePointAt(2); + if (codePoint === OUTPUT_MARK_CODE_POINT || codePoint === ERROR_MARK_CODE_POINT) { + outputRange = outputRange === null ? {from: line.from, to: line.to} : {from: line.from, to: outputRange.to}; + } + } + currentNode = currentNode.prevSibling; + } + + return outputRange; +} + +/** + * Detect blocks in a given range by traversing the syntax tree. + * Similar to how runtime/index.js uses acorn to parse blocks, but adapted for CodeMirror. + */ +export function detectBlocksWithinRange(tree: Tree, doc: Text, from: number, to: number): BlockMetadata[] { + const blocks: BlockMetadata[] = []; + + // Collect all top-level statements and their preceding output/error comment lines + const statementRanges: Range[] = []; + const outputRanges = new Map(); // Map statement position to output range + + tree.iterate({ + from, + to, + enter: (node) => { + // Detect top-level statements (direct children of Script) + if (node.node.parent?.name === "Script") { + // Check if this is a statement (not a comment) + if ( + node.name.includes("Statement") || + node.name.includes("Declaration") || + node.name === "ExportDeclaration" || + node.name === "ImportDeclaration" || + node.name === "Block" + ) { + statementRanges.push({from: node.from, to: node.to}); + + const outputRange = extendOutputForward(doc, node.node); + if (outputRange !== null) { + outputRanges.set(node.from, outputRange); + } + } + // Detect output/error comment lines (top-level line comments) + else if (node.name === "LineComment") { + // Get the line containing the comment. + const line = doc.lineAt(node.from); + + // Check if the line comment covers the entire line + if (line.from === node.from && line.to === node.to) { + const codePoint = line.text.codePointAt(2); + if (codePoint === OUTPUT_MARK_CODE_POINT || codePoint === ERROR_MARK_CODE_POINT) { + // Find consecutive output/error lines + let outputStart = line.from; + let outputEnd = line.to; + + // Look backwards for more output/error lines + let currentLineNum = line.number - 1; + while (currentLineNum >= 1) { + const prevLine = doc.line(currentLineNum); + const prevCodePoint = prevLine.text.codePointAt(2); + if ( + prevLine.text.startsWith("//") && + (prevCodePoint === OUTPUT_MARK_CODE_POINT || prevCodePoint === ERROR_MARK_CODE_POINT) + ) { + outputStart = prevLine.from; + currentLineNum--; + } else { + break; + } + } + + // Look forwards for more output/error lines + currentLineNum = line.number + 1; + const totalLines = doc.lines; + while (currentLineNum <= totalLines) { + const nextLine = doc.line(currentLineNum); + const nextCodePoint = nextLine.text.codePointAt(2); + if ( + nextLine.text.startsWith("//") && + (nextCodePoint === OUTPUT_MARK_CODE_POINT || nextCodePoint === ERROR_MARK_CODE_POINT) + ) { + outputEnd = nextLine.to; + currentLineNum++; + } else { + break; + } + } + + // Find the next statement after these output lines + // The output belongs to the statement immediately following it + let nextStatementLine = currentLineNum; + if (nextStatementLine <= totalLines) { + const nextStmtLine = doc.line(nextStatementLine); + // Store this output range to be associated with the next statement + outputRanges.set(nextStmtLine.from, {from: outputStart, to: outputEnd}); + } + } + } + } + } + }, + }); + + // Build block metadata from statements + for (const range of statementRanges) { + blocks.push(new BlockMetadata(outputRanges.get(range.from) ?? null, range)); + } + + return blocks; +} diff --git a/lib/containers/heap.ts b/lib/containers/heap.ts new file mode 100644 index 0000000..dcc34d8 --- /dev/null +++ b/lib/containers/heap.ts @@ -0,0 +1,144 @@ +export class MaxHeap { + private heap: T[] = []; + private weights: number[] = []; + private indexMap: Map = new Map(); + private keyFn: (value: T) => number; + private compareFn: (a: number, b: number) => number; + + constructor(keyFn: (value: T) => number, compareFn: (a: number, b: number) => number = (a, b) => a - b) { + this.keyFn = keyFn; + this.compareFn = compareFn; + } + + private parent(i: number): number { + return Math.floor((i - 1) / 2); + } + + private leftChild(i: number): number { + return 2 * i + 1; + } + + private rightChild(i: number): number { + return 2 * i + 2; + } + + private swap(i: number, j: number): void { + [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]]; + [this.weights[i], this.weights[j]] = [this.weights[j], this.weights[i]]; + this.indexMap.set(this.heap[i]!, i); + this.indexMap.set(this.heap[j]!, j); + } + + private percolateUp(i: number): void { + while (i > 0) { + const p = this.parent(i); + if (this.compareFn(this.weights[i]!, this.weights[p]!) > 0) { + this.swap(i, p); + i = p; + } else { + break; + } + } + } + + private percolateDown(i: number): void { + const size = this.heap.length; + while (true) { + let largest = i; + const left = this.leftChild(i); + const right = this.rightChild(i); + + if (left < size && this.compareFn(this.weights[left], this.weights[largest]) > 0) { + largest = left; + } + if (right < size && this.compareFn(this.weights[right], this.weights[largest]) > 0) { + largest = right; + } + + if (largest !== i) { + this.swap(i, largest); + i = largest; + } else { + break; + } + } + } + + insert(value: T): void { + const weight = this.keyFn(value); + const existingIndex = this.indexMap.get(value); + + if (existingIndex !== undefined) { + const oldWeight = this.weights[existingIndex]; + if (weight !== oldWeight) { + this.weights[existingIndex] = weight; + const cmp = this.compareFn(weight, oldWeight); + if (cmp > 0) { + this.percolateUp(existingIndex); + } else if (cmp < 0) { + this.percolateDown(existingIndex); + } + } + } else { + const index = this.heap.length; + this.heap.push(value); + this.weights.push(weight); + this.indexMap.set(value, index); + this.percolateUp(index); + } + } + + extractMax(): T | undefined { + if (this.heap.length === 0) { + return undefined; + } + + const max = this.heap[0]; + this.indexMap.delete(max); + + if (this.heap.length === 1) { + this.heap.pop(); + this.weights.pop(); + return max; + } + + this.heap[0] = this.heap.pop()!; + this.weights[0] = this.weights.pop()!; + this.indexMap.set(this.heap[0], 0); + this.percolateDown(0); + + return max; + } + + get peek(): T | undefined { + return this.heap[0]; + } + + peekWeight(): number | undefined { + return this.weights[0]; + } + + has(value: T): boolean { + return this.indexMap.has(value); + } + + get size(): number { + return this.heap.length; + } + + get isEmpty(): boolean { + return this.heap.length === 0; + } + + nonEmpty(): this is NonEmptyHeap { + return this.heap.length > 0; + } + + clear(): void { + this.heap = []; + this.weights = []; + this.indexMap.clear(); + } +} + +type NonEmptyHeap = Omit, "peek"> & {readonly peek: T}; diff --git a/package.json b/package.json index 21c89b7..1cfac53 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@fontsource-variable/inter": "^5.2.8", "@fontsource-variable/spline-sans-mono": "^5.2.8", "@lezer/highlight": "^1.2.2", + "@lezer/javascript": "^1.5.4", "@observablehq/notebook-kit": "^1.4.1", "@observablehq/runtime": "^6.0.0", "@uiw/codemirror-theme-github": "^4.25.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43e7cbb..ac4f334 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: '@lezer/highlight': specifier: ^1.2.2 version: 1.2.2 + '@lezer/javascript': + specifier: ^1.5.4 + version: 1.5.4 '@observablehq/notebook-kit': specifier: ^1.4.1 version: 1.4.1(@types/markdown-it@14.1.2)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1) diff --git a/runtime/index.js b/runtime/index.js index f7f8a6b..f5c41c2 100644 --- a/runtime/index.js +++ b/runtime/index.js @@ -6,7 +6,8 @@ import {dispatch as d3Dispatch} from "d3-dispatch"; import * as stdlib from "./stdlib/index.js"; import {Inspector} from "./stdlib/inspect.js"; import {OUTPUT_MARK, ERROR_MARK} from "./constant.js"; -import {BlockMetadata, blockMetadataEffect} from "../editor/blockMetadata.ts"; +import {BlockMetadata} from "../editor/blocks/BlockMetadata.ts"; +import {blockMetadataEffect} from "../editor/blockMetadata.ts"; import {IntervalTree} from "../lib/IntervalTree.ts"; import {transpileRechoJavaScript} from "./transpile.js"; import {table, getBorderCharacters} from "table"; @@ -148,7 +149,7 @@ export function createRuntime(initialCode) { if (!values.length) { // Create a block even if there are no values. - blocks.push(BlockMetadata(null, sourceRange, node.state.attributes)); + blocks.push(new BlockMetadata(null, sourceRange, node.state.attributes)); continue; } @@ -213,7 +214,7 @@ export function createRuntime(initialCode) { } // Add this block to the block metadata array. - const block = BlockMetadata(outputRange, {from: node.start, to: node.end}, node.state.attributes); + const block = new BlockMetadata(outputRange, {from: node.start, to: node.end}, node.state.attributes); block.error = error; blocks.push(block); diff --git a/test/blocks.spec.ts b/test/blocks.spec.ts new file mode 100644 index 0000000..46c1c51 --- /dev/null +++ b/test/blocks.spec.ts @@ -0,0 +1,294 @@ +import {it, expect, describe} from "vitest"; +import {findAdjacentBlocks} from "../lib/blocks.js"; + +describe("findAdjacentBlocks", () => { + describe("with sorted non-continuous ranges", () => { + it("should handle comprehensive test with all possible positions", () => { + // Generate sorted but non-continuous ranges + // For example: [2, 5), [7, 10), [15, 18), [20, 23) + const blocks = [ + {from: 2, to: 5}, + {from: 7, to: 10}, + {from: 15, to: 18}, + {from: 20, to: 23}, + ]; + + const maxPos = blocks[blocks.length - 1]!.to; + + // Test every position from 0 to maxPos + 1 + for (let pos = 0; pos <= maxPos + 1; pos++) { + const result = findAdjacentBlocks(blocks, pos); + + // Determine expected result + let expectedResult; + + // Check if pos is inside any block + const blockIndex = blocks.findIndex((block) => pos >= block.from && pos < block.to); + + if (blockIndex !== -1) { + // pos is inside a block + expectedResult = blockIndex; + } else { + // pos is not inside any block, find adjacent blocks + let beforeBlock = null; + let afterBlock = null; + + for (let i = 0; i < blocks.length; i++) { + if (blocks[i]!.to <= pos) { + beforeBlock = i; + } + } + for (let i = blocks.length - 1; i >= 0; i--) { + if (blocks[i]!.from > pos) { + afterBlock = i; + } + } + + if (beforeBlock === null) { + if (afterBlock === null) { + expectedResult = null; + } else { + expectedResult = [afterBlock - 1, afterBlock]; + } + } else if (afterBlock === null) { + expectedResult = [beforeBlock, beforeBlock + 1]; + } else { + expectedResult = [beforeBlock, afterBlock]; + } + } + + expect(result).toEqual(expectedResult); + } + }); + + it("should handle position 0 before first block [2, 5)", () => { + const blocks = [{from: 2, to: 5}]; + expect(findAdjacentBlocks(blocks, 0)).toEqual([-1, 0]); + }); + + it("should handle position 1 before first block [2, 5)", () => { + const blocks = [{from: 2, to: 5}]; + expect(findAdjacentBlocks(blocks, 1)).toEqual([-1, 0]); + }); + + it("should handle position 2 at start of block [2, 5)", () => { + const blocks = [{from: 2, to: 5}]; + expect(findAdjacentBlocks(blocks, 2)).toBe(0); + }); + + it("should handle position 3 inside block [2, 5)", () => { + const blocks = [{from: 2, to: 5}]; + expect(findAdjacentBlocks(blocks, 3)).toBe(0); + }); + + it("should handle position 4 at end-1 of block [2, 5)", () => { + const blocks = [{from: 2, to: 5}]; + expect(findAdjacentBlocks(blocks, 4)).toBe(0); + }); + + it("should handle position 5 after block [2, 5)", () => { + const blocks = [{from: 2, to: 5}]; + expect(findAdjacentBlocks(blocks, 5)).toEqual([0, 1]); + }); + + it("should handle position 6 after block [2, 5)", () => { + const blocks = [{from: 2, to: 5}]; + expect(findAdjacentBlocks(blocks, 6)).toEqual([0, 1]); + }); + + it("should handle gap between two blocks", () => { + const blocks = [ + {from: 2, to: 5}, + {from: 7, to: 10}, + ]; + expect(findAdjacentBlocks(blocks, 5)).toEqual([0, 1]); + expect(findAdjacentBlocks(blocks, 6)).toEqual([0, 1]); + }); + + it("should handle position inside second block", () => { + const blocks = [ + {from: 2, to: 5}, + {from: 7, to: 10}, + ]; + expect(findAdjacentBlocks(blocks, 7)).toBe(1); + expect(findAdjacentBlocks(blocks, 8)).toBe(1); + expect(findAdjacentBlocks(blocks, 9)).toBe(1); + }); + + it("should handle position after all blocks", () => { + const blocks = [ + {from: 2, to: 5}, + {from: 7, to: 10}, + ]; + expect(findAdjacentBlocks(blocks, 10)).toEqual([1, 2]); + expect(findAdjacentBlocks(blocks, 100)).toEqual([1, 2]); + }); + + it("should handle multiple blocks with various positions", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 15, to: 20}, + {from: 25, to: 30}, + ]; + + // Before all blocks + expect(findAdjacentBlocks(blocks, 0)).toEqual([-1, 0]); + expect(findAdjacentBlocks(blocks, 4)).toEqual([-1, 0]); + + // Inside first block + expect(findAdjacentBlocks(blocks, 5)).toBe(0); + expect(findAdjacentBlocks(blocks, 7)).toBe(0); + expect(findAdjacentBlocks(blocks, 9)).toBe(0); + + // Between first and second block + expect(findAdjacentBlocks(blocks, 10)).toEqual([0, 1]); + expect(findAdjacentBlocks(blocks, 12)).toEqual([0, 1]); + expect(findAdjacentBlocks(blocks, 14)).toEqual([0, 1]); + + // Inside second block + expect(findAdjacentBlocks(blocks, 15)).toBe(1); + expect(findAdjacentBlocks(blocks, 17)).toBe(1); + expect(findAdjacentBlocks(blocks, 19)).toBe(1); + + // Between second and third block + expect(findAdjacentBlocks(blocks, 20)).toEqual([1, 2]); + expect(findAdjacentBlocks(blocks, 22)).toEqual([1, 2]); + expect(findAdjacentBlocks(blocks, 24)).toEqual([1, 2]); + + // Inside third block + expect(findAdjacentBlocks(blocks, 25)).toBe(2); + expect(findAdjacentBlocks(blocks, 27)).toBe(2); + expect(findAdjacentBlocks(blocks, 29)).toBe(2); + + // After all blocks + expect(findAdjacentBlocks(blocks, 30)).toEqual([2, 3]); + expect(findAdjacentBlocks(blocks, 50)).toEqual([2, 3]); + }); + }); + + describe("edge cases", () => { + it("should handle empty blocks array", () => { + const blocks: [] = []; + expect(findAdjacentBlocks(blocks, 0)).toEqual(null); + expect(findAdjacentBlocks(blocks, 5)).toEqual(null); + expect(findAdjacentBlocks(blocks, 100)).toEqual(null); + }); + + it("should handle single block", () => { + const blocks = [{from: 10, to: 20}]; + + expect(findAdjacentBlocks(blocks, 0)).toEqual([-1, 0]); + expect(findAdjacentBlocks(blocks, 9)).toEqual([-1, 0]); + expect(findAdjacentBlocks(blocks, 10)).toBe(0); + expect(findAdjacentBlocks(blocks, 15)).toBe(0); + expect(findAdjacentBlocks(blocks, 19)).toBe(0); + expect(findAdjacentBlocks(blocks, 20)).toEqual([0, 1]); + expect(findAdjacentBlocks(blocks, 25)).toEqual([0, 1]); + }); + + it("should handle adjacent blocks (no gap)", () => { + const blocks = [ + {from: 0, to: 5}, + {from: 5, to: 10}, + ]; + + expect(findAdjacentBlocks(blocks, 4)).toBe(0); + expect(findAdjacentBlocks(blocks, 5)).toBe(1); + expect(findAdjacentBlocks(blocks, 6)).toBe(1); + }); + + it("should handle blocks starting at 0", () => { + const blocks = [ + {from: 0, to: 3}, + {from: 5, to: 8}, + ]; + + expect(findAdjacentBlocks(blocks, 0)).toBe(0); + expect(findAdjacentBlocks(blocks, 1)).toBe(0); + expect(findAdjacentBlocks(blocks, 2)).toBe(0); + expect(findAdjacentBlocks(blocks, 3)).toEqual([0, 1]); + expect(findAdjacentBlocks(blocks, 4)).toEqual([0, 1]); + expect(findAdjacentBlocks(blocks, 5)).toBe(1); + }); + + it("should handle single-width blocks", () => { + const blocks = [ + {from: 0, to: 1}, + {from: 3, to: 4}, + {from: 6, to: 7}, + ]; + + expect(findAdjacentBlocks(blocks, 0)).toBe(0); + expect(findAdjacentBlocks(blocks, 1)).toEqual([0, 1]); + expect(findAdjacentBlocks(blocks, 2)).toEqual([0, 1]); + expect(findAdjacentBlocks(blocks, 3)).toBe(1); + expect(findAdjacentBlocks(blocks, 4)).toEqual([1, 2]); + expect(findAdjacentBlocks(blocks, 5)).toEqual([1, 2]); + expect(findAdjacentBlocks(blocks, 6)).toBe(2); + expect(findAdjacentBlocks(blocks, 7)).toEqual([2, 3]); + }); + + it("should handle large gaps between blocks", () => { + const blocks = [ + {from: 0, to: 5}, + {from: 100, to: 105}, + {from: 1000, to: 1005}, + ]; + + expect(findAdjacentBlocks(blocks, 50)).toEqual([0, 1]); + expect(findAdjacentBlocks(blocks, 99)).toEqual([0, 1]); + expect(findAdjacentBlocks(blocks, 100)).toBe(1); + expect(findAdjacentBlocks(blocks, 500)).toEqual([1, 2]); + expect(findAdjacentBlocks(blocks, 999)).toEqual([1, 2]); + expect(findAdjacentBlocks(blocks, 1000)).toBe(2); + expect(findAdjacentBlocks(blocks, 2000)).toEqual([2, 3]); + }); + }); + + function generateBlocks(blockCount: number) { + const blocks = []; + let currentPos = Math.floor(Math.random() * 5); + + for (let i = 0; i < blockCount; i++) { + const width = Math.floor(Math.random() * 5) + 1; + const gap = Math.floor(Math.random() * 5) + 1; + blocks.push({from: currentPos, to: currentPos + width}); + currentPos += width + gap; + } + + const maxPos = blocks[blocks.length - 1]!.to; + + return {blocks, maxPos}; + } + + describe("randomized comprehensive tests", () => { + it("should handle random sorted non-continuous ranges", () => { + for (let z = 0; z < 10; z++) { + const {blocks, maxPos} = generateBlocks(2 ** z); + + // Test every position from 0 to maxPos + 1 + for (let pos = 0; pos <= maxPos + 1; pos++) { + const result = findAdjacentBlocks(blocks, pos); + + // Verify the result is correct + const blockIndex = blocks.findIndex((block) => pos >= block.from && pos < block.to); + + if (blockIndex !== -1) { + expect(result).toBe(blockIndex); + } else { + expect(Array.isArray(result)).toBe(true); + const [before, after] = result as [number, number]; + + if (0 <= before && before < blocks.length) { + expect(blocks[before]!.to).toBeLessThanOrEqual(pos); + } + if (0 <= after && after < blocks.length) { + expect(blocks[after]!.from).toBeGreaterThan(pos); + } + expect(before + 1).toBe(after); + } + } + } + }); + }); +}); diff --git a/test/blocks/affected.spec.ts b/test/blocks/affected.spec.ts new file mode 100644 index 0000000..cc6d353 --- /dev/null +++ b/test/blocks/affected.spec.ts @@ -0,0 +1,403 @@ +import {it, expect, describe} from "vitest"; +import {findAffectedBlockRange} from "../../lib/blocks.js"; + +describe("findAffectedBlockRange", () => { + function generateBlocks(blockCount: number) { + const blocks = []; + let currentPos = Math.floor(Math.random() * 5); + + for (let i = 0; i < blockCount; i++) { + const width = Math.floor(Math.random() * 5) + 1; + const gap = Math.floor(Math.random() * 5) + 1; + blocks.push({from: currentPos, to: currentPos + width}); + currentPos += width + gap; + } + + const maxPos = blocks.length > 0 ? blocks[blocks.length - 1]!.to : 0; + + return {blocks, maxPos}; + } + + describe("error cases", () => { + it("should throw when from > to", () => { + const blocks = [{from: 5, to: 10}]; + expect(() => findAffectedBlockRange(blocks, 10, 5)).toThrow("`from` must be less than or equal to `to`"); + }); + + it("should throw when from < 0", () => { + const blocks = [{from: 5, to: 10}]; + expect(() => findAffectedBlockRange(blocks, -1, 5)).toThrow("`from` must be greater than or equal to 0"); + }); + }); + + describe("insertion cases (from === to)", () => { + it("should return [index, index+1) when insertion point is inside a block", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 15, to: 20}, + ]; + + // Insert at position 7 (inside first block) + expect(findAffectedBlockRange(blocks, 7, 7)).toEqual([0, 1]); + + // Insert at position 17 (inside second block) + expect(findAffectedBlockRange(blocks, 17, 17)).toEqual([1, 2]); + }); + + it("should return a range contains two blocks when insertion point is between two blocks", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 15, to: 20}, + ]; + + // Insert at position 12 (between blocks) + expect(findAffectedBlockRange(blocks, 12, 12)).toStrictEqual([0, 2]); + + // Insert at position 10 (right after first block) + expect(findAffectedBlockRange(blocks, 10, 10)).toStrictEqual([0, 2]); + + // Insert at position 10 (right before first block) + expect(findAffectedBlockRange(blocks, 14, 14)).toStrictEqual([0, 2]); + }); + + it("should return [null, 1] when insertion point is before all blocks", () => { + const blocks = [{from: 5, to: 10}]; + expect(findAffectedBlockRange(blocks, 0, 0)).toStrictEqual([null, 1]); + expect(findAffectedBlockRange(blocks, 3, 3)).toStrictEqual([null, 1]); + }); + + it("should return [N - 1, null] when insertion point is after all blocks", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 12, to: 15}, + ]; + const N = blocks.length; + expect(findAffectedBlockRange(blocks, 15, 15)).toStrictEqual([1, null]); + expect(findAffectedBlockRange(blocks, 16, 16)).toStrictEqual([1, null]); + }); + + it("should return [null, null] for empty blocks array", () => { + const blocks: {from: number; to: number}[] = []; + expect(findAffectedBlockRange(blocks, 5, 5)).toStrictEqual([null, null]); + }); + + it("should return two adjacent blocks when insertion point is between them", () => { + const blocks = [ + {from: 2, to: 4}, + {from: 6, to: 11}, + {from: 16, to: 19}, + {from: 24, to: 25}, + {from: 27, to: 30}, + ]; + expect(findAffectedBlockRange(blocks, 4, 4)).toStrictEqual([0, 2]); + }); + }); + + describe("deletion cases (from < to)", () => { + it("should handle range inside a single block", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 15, to: 20}, + {from: 25, to: 30}, + ]; + + // Delete [6, 8) inside first block. We should only re-parse the block. + expect(findAffectedBlockRange(blocks, 6, 8)).toEqual([0, 1]); + + // Delete [16, 19) inside second block. We should only re-parse the block. + expect(findAffectedBlockRange(blocks, 16, 19)).toEqual([1, 2]); + }); + + it("should handle range spanning multiple blocks", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 15, to: 20}, + {from: 25, to: 30}, + ]; + + // Delete [6, 18) spanning first two blocks + expect(findAffectedBlockRange(blocks, 6, 18)).toEqual([0, 2]); + + // Delete [16, 28) spanning second and third blocks + expect(findAffectedBlockRange(blocks, 16, 28)).toEqual([1, 3]); + + // Delete [6, 28) spanning all three blocks + expect(findAffectedBlockRange(blocks, 6, 28)).toEqual([0, 3]); + }); + + it("should handle range from gap to inside block", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 15, to: 20}, + ]; + + // Delete [12, 18) from gap into second block. The first block also needs + // to be re-parsed. + expect(findAffectedBlockRange(blocks, 12, 18)).toEqual([0, 2]); + }); + + it("should handle range from inside block to gap", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 15, to: 20}, + ]; + + // Delete [7, 12) from first block into gap. The second block also needs + // to be re-parsed because it might be affected by deletion. For example, + // deleting the `//` part of a comment. + expect(findAffectedBlockRange(blocks, 7, 12)).toEqual([0, 2]); + }); + + it("should handle range spanning gap between blocks", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 15, to: 20}, + ]; + + // Delete [10, 15) exactly the gap. The deletion of the gap might affect + // its surrounding blocks. + expect(findAffectedBlockRange(blocks, 10, 15)).toEqual([0, 2]); + }); + + it("should return an empty range when range is entirely in a gap", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 20, to: 25}, + ]; + + // Delete [12, 18) entirely in gap of 0 and 1. The deletion of the gap + // might affect its surrounding blocks. + expect(findAffectedBlockRange(blocks, 12, 18)).toStrictEqual([0, 2]); + }); + + it("should return null when range is before all blocks", () => { + const blocks = [{from: 10, to: 15}]; + + // The returned range means "from the beginning of the document to the + // `to` of the first block". + expect(findAffectedBlockRange(blocks, 0, 5)).toStrictEqual([null, 1]); + }); + + it("should return null when range is after all blocks", () => { + const blocks = [{from: 5, to: 10}]; + + // Similar to the previous test, but now the range is after all blocks. + // The returned range means "from the beginning of the last block to the + // end of the document". + expect(findAffectedBlockRange(blocks, 15, 20)).toStrictEqual([0, null]); + }); + + it("should handle range starting at block boundary", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 15, to: 20}, + ]; + + // Delete [5, 8) starting at block start. Only the first block is affected. + expect(findAffectedBlockRange(blocks, 5, 8)).toEqual([0, 1]); + + // Delete [10, 18) starting at block end. The deletion happens within a + // gap. Thus, the second block is also affected. + expect(findAffectedBlockRange(blocks, 10, 18)).toEqual([0, 2]); + }); + + it("should handle range ending at block boundary", () => { + const blocks = [ + {from: 5, to: 10}, + {from: 15, to: 20}, + ]; + + // Delete [7, 10) ending at block end + expect(findAffectedBlockRange(blocks, 7, 10)).toStrictEqual([0, 1]); + + // Delete [12, 15) ending at block start + expect(findAffectedBlockRange(blocks, 12, 15)).toStrictEqual([0, 2]); + }); + }); + + function overlaps(m: {from: number; to: number}, n: {from: number; to: number}): boolean { + return m.from <= n.to && n.from < m.to; + } + + type Location = + | {type: "contained"; index: number} + | { + type: "between"; + /** + * Note that `previous` ranges from 0 to `blocks.length - 2`. Thus, + * using `previous + 2` is safe. + */ + previous: number; + } + | {type: "afterAll"} + | {type: "beforeAll"}; + + function locateNaive(blocks: {from: number; to: number}[], pos: number): Location { + let location: Location | null = null; + for (let i = 0; i < blocks.length; i++) { + const block = blocks[i]!; + if (block.from <= pos && pos < block.to) { + location = {type: "contained", index: i}; + } + } + if (location === null) { + if (pos < blocks[0]!.from) { + location = {type: "beforeAll"}; + } else { + for (let i = 1; i < blocks.length; i++) { + const previous = blocks[i - 1]!; + const next = blocks[i]!; + if (previous.to <= pos && pos < next.from) { + location = {type: "between", previous: i - 1}; + } + } + if (location === null) { + location = {type: "afterAll"}; + } + } + } + return location; + } + + describe("comprehensive random tests", () => { + it("should verify invariants for all valid ranges", () => { + for (let trial = 0; trial < 10; trial++) { + const {blocks, maxPos} = generateBlocks(2 * trial); + + if (blocks.length === 0) continue; + + // Test all valid ranges [from, to) where from <= to + for (let from = 0; from <= maxPos + 1; from++) { + for (let to = from; to <= maxPos + 1; to++) { + let fromLocation: Location = locateNaive(blocks, from); + let expected: [number | null, number | null]; + if (from === to) { + switch (fromLocation.type) { + case "contained": + // Include the block it is contained in. + expected = [fromLocation.index, fromLocation.index + 1]; + break; + case "between": + // Include the adjacent blocks. + expected = [fromLocation.previous, fromLocation.previous + 2]; + break; + case "beforeAll": + // Extend to the beginning of the document (represented by + // `null`) and include the first element. + expected = [null, 1]; + break; + case "afterAll": + // Extend to the end of the document (represented by `null`) + // and include the last element. + expected = [blocks.length - 1, null]; + break; + } + } else { + let toLocation: Location = locateNaive(blocks, to - 1); + let fromIndex: number | null; + switch (fromLocation.type) { + case "contained": + // Just include the block it is contained in. + fromIndex = fromLocation.index; + break; + case "between": + // To include the previous block. + fromIndex = fromLocation.previous; + break; + case "afterAll": + // To include the last element. + fromIndex = blocks.length - 1; + break; + case "beforeAll": + // Extend to the beginning of the document (represented by `null`). + fromIndex = null; + break; + } + let toIndex: number | null; + switch (toLocation.type) { + case "contained": + // To include the block it is contained in. `+1` because the + // `to` is exclusive. + toIndex = toLocation.index + 1; + break; + case "between": + // To include the block it is contained in. `+2` because the + // `to` is exclusive and it is the previous block's index. + toIndex = toLocation.previous + 2; + break; + case "afterAll": + // Extend to the end of the document (represented by `null`). + toIndex = null; + break; + case "beforeAll": + // Include the first block. `1` because the `to` is exclusive. + toIndex = 1; + break; + } + expected = [fromIndex, toIndex]; + } + + const result = findAffectedBlockRange(blocks, from, to); + + expect( + result, + `Expected affected block range to be [${expected[0]}, ${expected[1]}): (blocks = ${JSON.stringify(blocks)}, from = ${from}, to = ${to})`, + ).toStrictEqual(expected); + } + } + } + }); + + it("should handle edge cases with adjacent blocks", () => { + const blocks = [ + {from: 0, to: 5}, + {from: 5, to: 10}, + {from: 10, to: 15}, + ]; + + // Range exactly covering one block + expect(findAffectedBlockRange(blocks, 0, 5)).toEqual([0, 1]); + expect(findAffectedBlockRange(blocks, 5, 10)).toEqual([1, 2]); + + // Range covering multiple adjacent blocks + expect(findAffectedBlockRange(blocks, 0, 10)).toEqual([0, 2]); + expect(findAffectedBlockRange(blocks, 5, 15)).toEqual([1, 3]); + expect(findAffectedBlockRange(blocks, 0, 15)).toEqual([0, 3]); + + // Single point at boundary + expect(findAffectedBlockRange(blocks, 5, 5)).toEqual([1, 2]); + expect(findAffectedBlockRange(blocks, 10, 10)).toEqual([2, 3]); + }); + + it("should handle single block edge cases", () => { + const blocks = [{from: 10, to: 20}]; + + // Range inside block + expect(findAffectedBlockRange(blocks, 12, 15)).toEqual([0, 1]); + + // Range covering entire block + expect(findAffectedBlockRange(blocks, 10, 20)).toEqual([0, 1]); + + // Range overlapping start + expect(findAffectedBlockRange(blocks, 5, 15)).toEqual([null, 1]); + + // Range overlapping end + expect(findAffectedBlockRange(blocks, 15, 25)).toEqual([0, null]); + + // Range covering block and beyond + expect(findAffectedBlockRange(blocks, 5, 25)).toEqual([null, null]); + + // Range before block + expect(findAffectedBlockRange(blocks, 0, 5)).toStrictEqual([null, 1]); + + // Range after block + expect(findAffectedBlockRange(blocks, 25, 30)).toStrictEqual([0, null]); + + // Range touching start boundary + expect(findAffectedBlockRange(blocks, 5, 10)).toStrictEqual([null, 1]); + + // Range touching end boundary + expect(findAffectedBlockRange(blocks, 20, 25)).toStrictEqual([0, null]); + }); + }); +}); diff --git a/test/blocks/detect.spec.ts b/test/blocks/detect.spec.ts new file mode 100644 index 0000000..8051c66 --- /dev/null +++ b/test/blocks/detect.spec.ts @@ -0,0 +1,38 @@ +import {parser} from "@lezer/javascript"; +import {detectBlocksWithinRange} from "../../lib/blocks/detect.js"; +import {it, expect, describe} from "vitest"; +import {Text} from "@codemirror/state"; + +describe("block detection simple test", () => { + it("test parse", () => { + const code = "const x = 0;"; + const tree = parser.parse(code); + expect(tree).toBeDefined(); + }); + + function viewEachLineAsBlock(lines: string[]) { + const blocks = []; + for (let i = 0, from = 0; i < lines.length; i++) { + const line = lines[i]!; + blocks.push({from, to: from + line.length}); + from += line.length + 1; + } + return blocks; + } + + it("should detect two blocks in a full parse", () => { + const lines = [`const code = "const x = 0;";`, `const y = 1;`]; + const doc = Text.of(lines); + const tree = parser.parse(doc.toString()); + const blocks = detectBlocksWithinRange(tree, doc, 0, doc.length); + expect(blocks).toMatchObject(viewEachLineAsBlock(lines)); + }); + + it("should detect the first block in a partial parse", () => { + const lines = [`const code = "const x = 0;";`, `const y = 1;`]; + const doc = Text.of(lines); + const tree = parser.parse(doc.toString()); + const blocks = detectBlocksWithinRange(tree, doc, 0, 5); + expect(blocks).toMatchObject(viewEachLineAsBlock(lines).slice(0, 1)); + }); +}); diff --git a/test/containers/heap.spec.ts b/test/containers/heap.spec.ts new file mode 100644 index 0000000..404223d --- /dev/null +++ b/test/containers/heap.spec.ts @@ -0,0 +1,286 @@ +import {it, expect, describe} from "vitest"; +import {MaxHeap} from "../../lib/containers/heap.js"; + +describe("MaxHeap", () => { + describe("basic operations", () => { + it("should insert and extract values in max order", () => { + const heap = new MaxHeap((x) => x); + + heap.insert(5); + heap.insert(3); + heap.insert(8); + heap.insert(1); + heap.insert(10); + + expect(heap.size).toBe(5); + expect(heap.extractMax()).toBe(10); + expect(heap.extractMax()).toBe(8); + expect(heap.extractMax()).toBe(5); + expect(heap.extractMax()).toBe(3); + expect(heap.extractMax()).toBe(1); + expect(heap.isEmpty).toBe(true); + }); + + it("should peek without removing", () => { + const heap = new MaxHeap((x) => x); + + heap.insert(5); + heap.insert(10); + heap.insert(3); + + expect(heap.peek).toBe(10); + expect(heap.peekWeight()).toBe(10); + expect(heap.size).toBe(3); + + expect(heap.extractMax()).toBe(10); + expect(heap.peek).toBe(5); + expect(heap.size).toBe(2); + }); + + it("should handle empty heap", () => { + const heap = new MaxHeap((x) => x); + + expect(heap.isEmpty).toBe(true); + expect(heap.size).toBe(0); + expect(heap.peek).toBeUndefined(); + expect(heap.peekWeight()).toBeUndefined(); + expect(heap.extractMax()).toBeUndefined(); + }); + + it("should check if value exists", () => { + const heap = new MaxHeap((x) => x); + + heap.insert(5); + heap.insert(10); + + expect(heap.has(5)).toBe(true); + expect(heap.has(10)).toBe(true); + expect(heap.has(3)).toBe(false); + + heap.extractMax(); + expect(heap.has(10)).toBe(false); + expect(heap.has(5)).toBe(true); + }); + + it("should clear the heap", () => { + const heap = new MaxHeap((x) => x); + + heap.insert(5); + heap.insert(10); + heap.insert(3); + + expect(heap.size).toBe(3); + + heap.clear(); + + expect(heap.isEmpty).toBe(true); + expect(heap.size).toBe(0); + expect(heap.has(5)).toBe(false); + }); + }); + + describe("update existing values", () => { + it("should update value with increased weight and percolate up", () => { + const weights = new Map([ + ["a", 5], + ["b", 10], + ["c", 3], + ]); + + const heap = new MaxHeap((x) => weights.get(x)!); + + heap.insert("a"); + heap.insert("b"); + heap.insert("c"); + + expect(heap.peek).toBe("b"); + + // Update "a" to have weight 15 + weights.set("a", 15); + heap.insert("a"); + + expect(heap.peek).toBe("a"); + expect(heap.peekWeight()).toBe(15); + }); + + it("should update value with decreased weight and percolate down", () => { + const weights = new Map([ + ["a", 5], + ["b", 10], + ["c", 3], + ]); + + const heap = new MaxHeap((x) => weights.get(x)!); + + heap.insert("a"); + heap.insert("b"); + heap.insert("c"); + + expect(heap.peek).toBe("b"); + + // Update "b" to have weight 1 + weights.set("b", 1); + heap.insert("b"); + + expect(heap.peek).toBe("a"); + expect(heap.peekWeight()).toBe(5); + + heap.extractMax(); + expect(heap.peek).toBe("c"); + expect(heap.peekWeight()).toBe(3); + }); + + it("should not change heap when weight stays the same", () => { + const weights = new Map([ + ["a", 5], + ["b", 10], + ]); + + const heap = new MaxHeap((x) => weights.get(x)!); + + heap.insert("a"); + heap.insert("b"); + + expect(heap.size).toBe(2); + expect(heap.peek).toBe("b"); + + // Re-insert "b" with same weight + heap.insert("b"); + + expect(heap.size).toBe(2); + expect(heap.peek).toBe("b"); + }); + + it("should handle multiple updates on same value", () => { + const weights = new Map([["a", 5]]); + + const heap = new MaxHeap((x) => weights.get(x)!); + + heap.insert("a"); + expect(heap.peekWeight()).toBe(5); + + weights.set("a", 10); + heap.insert("a"); + expect(heap.peekWeight()).toBe(10); + + weights.set("a", 3); + heap.insert("a"); + expect(heap.peekWeight()).toBe(3); + + weights.set("a", 7); + heap.insert("a"); + expect(heap.peekWeight()).toBe(7); + + expect(heap.size).toBe(1); + }); + }); + + describe("custom comparator", () => { + it("should use custom comparator for min heap behavior", () => { + // Reverse comparator for min heap + const heap = new MaxHeap( + (x) => x, + (a, b) => b - a, + ); + + heap.insert(5); + heap.insert(3); + heap.insert(8); + heap.insert(1); + + expect(heap.extractMax()).toBe(1); + expect(heap.extractMax()).toBe(3); + expect(heap.extractMax()).toBe(5); + expect(heap.extractMax()).toBe(8); + }); + + it("should work with objects and custom key function", () => { + interface Task { + name: string; + priority: number; + } + + const heap = new MaxHeap((task) => task.priority); + + heap.insert({name: "Low", priority: 1}); + heap.insert({name: "High", priority: 10}); + heap.insert({name: "Medium", priority: 5}); + + expect(heap.extractMax()?.name).toBe("High"); + expect(heap.extractMax()?.name).toBe("Medium"); + expect(heap.extractMax()?.name).toBe("Low"); + }); + }); + + describe("edge cases", () => { + it("should handle single element", () => { + const heap = new MaxHeap((x) => x); + + heap.insert(42); + expect(heap.size).toBe(1); + expect(heap.peek).toBe(42); + expect(heap.extractMax()).toBe(42); + expect(heap.isEmpty).toBe(true); + }); + + it("should handle duplicate weights", () => { + const heap = new MaxHeap((x) => Math.floor(x / 10)); + + heap.insert(15); + heap.insert(12); + heap.insert(18); + + // All have weight 1 + expect(heap.size).toBe(3); + expect(heap.extractMax()).toBe(15); // First inserted with weight 1 + }); + + it("should handle large number of elements", () => { + const heap = new MaxHeap((x) => x); + const values = Array.from({length: 100}, (_, i) => i); + + // Insert in random order + const shuffled = [...values].sort(() => Math.random() - 0.5); + shuffled.forEach((v) => heap.insert(v)); + + expect(heap.size).toBe(100); + + // Extract all and verify descending order + const extracted = []; + while (!heap.isEmpty) { + extracted.push(heap.extractMax()!); + } + + expect(extracted).toEqual(values.reverse()); + }); + + it("should maintain heap property after multiple operations", () => { + const weights = new Map(); + const heap = new MaxHeap((x) => weights.get(x)!); + + // Insert values + weights.set("a", 5); + weights.set("b", 10); + weights.set("c", 3); + weights.set("d", 7); + + heap.insert("a"); + heap.insert("b"); + heap.insert("c"); + heap.insert("d"); + + // Update some values + weights.set("c", 15); + heap.insert("c"); + + weights.set("b", 2); + heap.insert("b"); + + // Extract and verify order + expect(heap.extractMax()).toBe("c"); // 15 + expect(heap.extractMax()).toBe("d"); // 7 + expect(heap.extractMax()).toBe("a"); // 5 + expect(heap.extractMax()).toBe("b"); // 2 + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 70c1ea2..c0fa300 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,11 +10,9 @@ "target": "esnext", "types": [], "moduleResolution": "nodenext", + "allowImportingTsExtensions": true, // For nodejs: - "lib": [ - "esnext", - "DOM" - ], + "lib": ["esnext", "DOM"], // "types": ["node"], // and npm install -D @types/node // Other Outputs @@ -50,13 +48,6 @@ } ] }, - "include": [ - "next-env.d.ts", - ".next/types/**/*.ts", - "**/*.ts", - "**/*.tsx" - ], - "exclude": [ - "node_modules" - ] + "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] } From 477713f5d2d8dace7161bc19b4b25278401fbbfe Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 14 Dec 2025 04:19:04 +0800 Subject: [PATCH 14/30] Improve the UI of Recho Playground --- editor/blocks/BlockMetadata.ts | 3 +- lib/blocks/detect.ts | 6 +- package.json | 2 + pnpm-lock.yaml | 26 +++ runtime/index.js | 6 +- test/components/App.tsx | 62 ++--- test/components/BlockItem.tsx | 87 +++++++ test/components/BlockViewer.tsx | 101 +------- test/components/Editor.tsx | 11 +- test/components/ResizableSplit.tsx | 70 ------ test/components/SelectionGroupItem.tsx | 61 +++++ test/components/TransactionItem.tsx | 166 +++++++++++++ test/components/TransactionViewer.tsx | 309 +++---------------------- test/components/UserEvent.tsx | 99 ++++++++ test/components/types.ts | 56 +++++ tsconfig.json | 3 +- 16 files changed, 589 insertions(+), 479 deletions(-) create mode 100644 test/components/BlockItem.tsx delete mode 100644 test/components/ResizableSplit.tsx create mode 100644 test/components/SelectionGroupItem.tsx create mode 100644 test/components/TransactionItem.tsx create mode 100644 test/components/UserEvent.tsx create mode 100644 test/components/types.ts diff --git a/editor/blocks/BlockMetadata.ts b/editor/blocks/BlockMetadata.ts index 1f7b12a..f6fafec 100644 --- a/editor/blocks/BlockMetadata.ts +++ b/editor/blocks/BlockMetadata.ts @@ -4,6 +4,7 @@ export type Range = {from: number; to: number}; export class BlockMetadata { public constructor( + public readonly name: string, public readonly output: Range | null, public readonly source: Range, public attributes: Record = {}, @@ -32,6 +33,6 @@ export class BlockMetadata { }; const attributes = this.attributes; const error = this.error; - return new BlockMetadata(output, source, attributes, error); + return new BlockMetadata(this.name, output, source, attributes, error); } } diff --git a/lib/blocks/detect.ts b/lib/blocks/detect.ts index 44b06df..b226fd9 100644 --- a/lib/blocks/detect.ts +++ b/lib/blocks/detect.ts @@ -36,7 +36,7 @@ export function detectBlocksWithinRange(tree: Tree, doc: Text, from: number, to: const blocks: BlockMetadata[] = []; // Collect all top-level statements and their preceding output/error comment lines - const statementRanges: Range[] = []; + const statementRanges: (Range & {name: string})[] = []; const outputRanges = new Map(); // Map statement position to output range tree.iterate({ @@ -53,7 +53,7 @@ export function detectBlocksWithinRange(tree: Tree, doc: Text, from: number, to: node.name === "ImportDeclaration" || node.name === "Block" ) { - statementRanges.push({from: node.from, to: node.to}); + statementRanges.push({from: node.from, to: node.to, name: node.name}); const outputRange = extendOutputForward(doc, node.node); if (outputRange !== null) { @@ -123,7 +123,7 @@ export function detectBlocksWithinRange(tree: Tree, doc: Text, from: number, to: // Build block metadata from statements for (const range of statementRanges) { - blocks.push(new BlockMetadata(outputRanges.get(range.from) ?? null, range)); + blocks.push(new BlockMetadata(range.name, outputRanges.get(range.from) ?? null, range)); } return blocks; diff --git a/package.json b/package.json index 1cfac53..c9d1e18 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,8 @@ "lucide-react": "^0.542.0", "postcss": "^8.5.6", "prettier": "^3.6.2", + "react-inspector": "^9.0.0", + "react-resizable-panels": "^3.0.6", "react-tooltip": "^5.30.0", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac4f334..0435a48 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -156,6 +156,12 @@ importers: prettier: specifier: ^3.6.2 version: 3.6.2 + react-inspector: + specifier: ^9.0.0 + version: 9.0.0(react@19.2.0) + react-resizable-panels: + specifier: ^3.0.6 + version: 3.0.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-tooltip: specifier: ^5.30.0 version: 5.30.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -2880,6 +2886,11 @@ packages: peerDependencies: react: ^19.2.0 + react-inspector@9.0.0: + resolution: {integrity: sha512-w/VJucSeHxlwRa2nfM2k7YhpT1r5EtlDOClSR+L7DyQP91QMdfFEDXDs9bPYN4kzP7umFtom7L0b2GGjph4Kow==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -2887,6 +2898,12 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} + react-resizable-panels@3.0.6: + resolution: {integrity: sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew==} + peerDependencies: + react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-tooltip@5.30.0: resolution: {integrity: sha512-Yn8PfbgQ/wmqnL7oBpz1QiDaLKrzZMdSUUdk7nVeGTwzbxCAJiJzR4VSYW+eIO42F1INt57sPUmpgKv0KwJKtg==} peerDependencies: @@ -6487,10 +6504,19 @@ snapshots: react: 19.2.0 scheduler: 0.27.0 + react-inspector@9.0.0(react@19.2.0): + dependencies: + react: 19.2.0 + react-is@16.13.1: {} react-refresh@0.17.0: {} + react-resizable-panels@3.0.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-tooltip@5.30.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@floating-ui/dom': 1.7.4 diff --git a/runtime/index.js b/runtime/index.js index f5c41c2..f0d26d2 100644 --- a/runtime/index.js +++ b/runtime/index.js @@ -149,7 +149,7 @@ export function createRuntime(initialCode) { if (!values.length) { // Create a block even if there are no values. - blocks.push(new BlockMetadata(null, sourceRange, node.state.attributes)); + blocks.push(new BlockMetadata(node.type, sourceRange, node.state.attributes)); continue; } @@ -214,7 +214,7 @@ export function createRuntime(initialCode) { } // Add this block to the block metadata array. - const block = new BlockMetadata(outputRange, {from: node.start, to: node.end}, node.state.attributes); + const block = new BlockMetadata(node.type, outputRange, {from: node.start, to: node.end}, node.state.attributes); block.error = error; blocks.push(block); @@ -363,7 +363,7 @@ export function createRuntime(initialCode) { const nodes = split(code); if (!nodes) return; - console.group("rerun"); + console.groupCollapsed("rerun"); for (const node of nodes) { console.log(`Node ${node.type} (${node.start}-${node.end})`); } diff --git a/test/components/App.tsx b/test/components/App.tsx index 9d9c2e6..47aac9a 100644 --- a/test/components/App.tsx +++ b/test/components/App.tsx @@ -1,13 +1,13 @@ -import {useEffect, useState} from "react"; +import type {ViewPlugin} from "@codemirror/view"; import {useAtom} from "jotai"; -import {selectedTestAtom} from "../store"; -import {TestSelector} from "./TestSelector"; -import {Editor} from "./Editor"; -import {TransactionViewer} from "./TransactionViewer"; -import {BlockViewer} from "./BlockViewer"; -import {ResizableSplit} from "./ResizableSplit"; +import {useEffect, useState} from "react"; +import {Panel, PanelGroup, PanelResizeHandle} from "react-resizable-panels"; import * as testSamples from "../js/index.js"; -import type {ViewPlugin} from "@codemirror/view"; +import {selectedTestAtom} from "../store.ts"; +import {BlockViewer} from "./BlockViewer.tsx"; +import {Editor} from "./Editor.tsx"; +import {TestSelector} from "./TestSelector.tsx"; +import {TransactionViewer} from "./TransactionViewer.tsx"; export function App() { const [selectedTest, setSelectedTest] = useAtom(selectedTestAtom); @@ -36,27 +36,31 @@ export function App() {
- {/* Main content */} -
- {/* Editor panel */} -
- -
- - {/* Sidebar with blocks and transactions */} - -
+ + +
+ +
+
+ + + + + + + + + + + + +
); } diff --git a/test/components/BlockItem.tsx b/test/components/BlockItem.tsx new file mode 100644 index 0000000..d5b6c85 --- /dev/null +++ b/test/components/BlockItem.tsx @@ -0,0 +1,87 @@ +import {CircleXIcon, Locate, SquareTerminalIcon, TerminalIcon} from "lucide-react"; +import {useState} from "react"; +import type {BlockData} from "./types.ts"; + +export function BlockItem({block, onLocate}: {block: BlockData; onLocate: (block: BlockData) => void}) { + const [isOpen, setIsOpen] = useState(false); + + const hasOutput = block.outputFrom !== null && block.outputTo !== null; + const hasAttributes = Object.keys(block.attributes).length > 0; + + return ( +
setIsOpen((e.target as HTMLDetailsElement).open)} + className={`mb-2 border rounded text-xs ${ + block.hasError + ? "border-red-300 bg-red-50" + : hasOutput + ? "border-green-300 bg-green-50" + : "border-gray-200 bg-white" + }`} + > + +
+ {block.index + 1} + {block.hasError && } + {hasOutput && !block.hasError && } +
+ {block.name} +
+ + [ + {block.sourceFrom}, + {block.sourceTo} + ) + + +
+
+
+
+ Source Range: {block.sourceFrom}-{block.sourceTo} + ({block.sourceTo - block.sourceFrom} chars) +
+ + {hasOutput ? ( +
+ Output Range: {block.outputFrom}-{block.outputTo} + ({block.outputTo! - block.outputFrom!} chars) +
+ ) : ( +
+ Output Range: none +
+ )} + +
+ Total Range: {hasOutput ? block.outputFrom : block.sourceFrom}-{block.sourceTo} + + ({block.sourceTo - (hasOutput ? block.outputFrom! : block.sourceFrom)} chars) + +
+ + {block.hasError &&
✗ Has Error
} + + {hasAttributes && ( +
+ Attributes: +
+ {JSON.stringify(block.attributes, null, 2)} +
+
+ )} +
+
+ ); +} diff --git a/test/components/BlockViewer.tsx b/test/components/BlockViewer.tsx index e10fb52..718925e 100644 --- a/test/components/BlockViewer.tsx +++ b/test/components/BlockViewer.tsx @@ -1,18 +1,9 @@ -import {useState, useEffect, useRef} from "react"; -import {ViewPlugin, EditorView} from "@codemirror/view"; import {EditorSelection} from "@codemirror/state"; +import {EditorView, ViewPlugin} from "@codemirror/view"; +import {useEffect, useRef, useState} from "react"; import {blockMetadataField} from "../../editor/blockMetadata.ts"; -import {Locate} from "lucide-react"; - -interface BlockData { - index: number; - sourceFrom: number; - sourceTo: number; - outputFrom: number | null; - outputTo: number | null; - hasError: boolean; - attributes: Record; -} +import type {BlockData} from "./types.ts"; +import {BlockItem} from "./BlockItem.tsx"; interface BlockViewerProps { onPluginCreate: (plugin: ViewPlugin) => void; @@ -37,6 +28,7 @@ export function BlockViewer({onPluginCreate}: BlockViewerProps) { if (!blockMetadata) return []; return blockMetadata.map((block: any, index: number) => ({ + name: block.name, index, sourceFrom: block.source.from, sourceTo: block.source.to, @@ -129,86 +121,3 @@ export function BlockViewer({onPluginCreate}: BlockViewerProps) {
); } - -function BlockItem({block, onLocate}: {block: BlockData; onLocate: (block: BlockData) => void}) { - const [isOpen, setIsOpen] = useState(false); - - const hasOutput = block.outputFrom !== null && block.outputTo !== null; - const hasAttributes = Object.keys(block.attributes).length > 0; - - return ( -
setIsOpen((e.target as HTMLDetailsElement).open)} - className={`mb-2 border rounded text-xs ${ - block.hasError - ? "border-red-300 bg-red-50" - : hasOutput - ? "border-green-300 bg-green-50" - : "border-gray-200 bg-white" - }`} - > - - - Block {block.index + 1} - {block.hasError && " ✗"} - {hasOutput && !block.hasError && " ➜"} - -
- - - [ - {block.sourceFrom}, - {block.sourceTo} - ) - -
-
-
-
- Source Range: {block.sourceFrom}-{block.sourceTo} - ({block.sourceTo - block.sourceFrom} chars) -
- - {hasOutput ? ( -
- Output Range: {block.outputFrom}-{block.outputTo} - ({block.outputTo! - block.outputFrom!} chars) -
- ) : ( -
- Output Range: none -
- )} - -
- Total Range: {hasOutput ? block.outputFrom : block.sourceFrom}-{block.sourceTo} - - ({block.sourceTo - (hasOutput ? block.outputFrom! : block.sourceFrom)} chars) - -
- - {block.hasError &&
✗ Has Error
} - - {hasAttributes && ( -
- Attributes: -
- {JSON.stringify(block.attributes, null, 2)} -
-
- )} -
-
- ); -} diff --git a/test/components/Editor.tsx b/test/components/Editor.tsx index 69d3ab6..c588499 100644 --- a/test/components/Editor.tsx +++ b/test/components/Editor.tsx @@ -5,6 +5,7 @@ import {cn} from "../../app/cn.js"; import type {ViewPlugin} from "@codemirror/view"; interface EditorProps { + className?: string; code: string; transactionViewerPlugin?: ViewPlugin; blockViewerPlugin?: ViewPlugin; @@ -25,7 +26,13 @@ const onDefaultError = debounce(() => { }, 100); }, 0); -export function Editor({code, transactionViewerPlugin, blockViewerPlugin, onError = onDefaultError}: EditorProps) { +export function Editor({ + className, + code, + transactionViewerPlugin, + blockViewerPlugin, + onError = onDefaultError, +}: EditorProps) { const containerRef = useRef(null); const editorRef = useRef(null); const [needRerun, setNeedRerun] = useState(false); @@ -97,7 +104,7 @@ export function Editor({code, transactionViewerPlugin, blockViewerPlugin, onErro } return ( -
+
Editor
diff --git a/test/components/ResizableSplit.tsx b/test/components/ResizableSplit.tsx deleted file mode 100644 index 2127635..0000000 --- a/test/components/ResizableSplit.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useState, useRef, useEffect, ReactNode } from "react"; - -interface ResizableSplitProps { - top: ReactNode; - bottom: ReactNode; - defaultRatio?: number; // 0 to 1, representing top panel's height ratio -} - -export function ResizableSplit({ top, bottom, defaultRatio = 0.5 }: ResizableSplitProps) { - const [ratio, setRatio] = useState(defaultRatio); - const [isDragging, setIsDragging] = useState(false); - const containerRef = useRef(null); - - useEffect(() => { - if (!isDragging) return; - - const handleMouseMove = (e: MouseEvent) => { - if (!containerRef.current) return; - - const container = containerRef.current; - const rect = container.getBoundingClientRect(); - const y = e.clientY - rect.top; - const newRatio = Math.max(0.1, Math.min(0.9, y / rect.height)); - setRatio(newRatio); - }; - - const handleMouseUp = () => { - setIsDragging(false); - }; - - document.addEventListener("mousemove", handleMouseMove); - document.addEventListener("mouseup", handleMouseUp); - - return () => { - document.removeEventListener("mousemove", handleMouseMove); - document.removeEventListener("mouseup", handleMouseUp); - }; - }, [isDragging]); - - const handleMouseDown = (e: React.MouseEvent) => { - e.preventDefault(); - setIsDragging(true); - }; - - return ( -
- {/* Top panel */} -
- {top} -
- - {/* Resize handle */} -
-
-
-
-
- - {/* Bottom panel */} -
- {bottom} -
-
- ); -} diff --git a/test/components/SelectionGroupItem.tsx b/test/components/SelectionGroupItem.tsx new file mode 100644 index 0000000..6a49caa --- /dev/null +++ b/test/components/SelectionGroupItem.tsx @@ -0,0 +1,61 @@ +import {useState} from "react"; +import type {TransactionData} from "./types.ts"; + +export function SelectionGroupItem({transactions}: {transactions: TransactionData[]}) { + const [isOpen, setIsOpen] = useState(false); + + const formatTime = (timestamp: number) => { + const time = new Date(timestamp); + return ( + time.toLocaleTimeString("en-US", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }) + + "." + + time.getMilliseconds().toString().padStart(3, "0") + ); + }; + + const count = transactions.length; + const firstTr = transactions[0]!; + const lastTr = transactions[transactions.length - 1]!; + + const summaryLeft = `#${lastTr.index}-${firstTr.index} [select] (${count} transactions)`; + const summaryRight = `${formatTime(lastTr.timestamp)} - ${formatTime(firstTr.timestamp)}`; + + return ( +
setIsOpen((e.target as HTMLDetailsElement).open)} + className="mb-2 border border-gray-300 rounded text-xs bg-gray-50" + > + + {summaryLeft} + {summaryRight} + +
+ {transactions.map((tr) => { + const selectionInfo = tr.selection + .map((range) => { + const isCursor = range.from === range.to; + return isCursor ? `cursor at ${range.from}` : `${range.from}-${range.to}`; + }) + .join(", "); + + return ( +
+ #{tr.index} + {selectionInfo} + {formatTime(tr.timestamp)} +
+ ); + })} +
+
+ ); +} diff --git a/test/components/TransactionItem.tsx b/test/components/TransactionItem.tsx new file mode 100644 index 0000000..71b5d42 --- /dev/null +++ b/test/components/TransactionItem.tsx @@ -0,0 +1,166 @@ +import {useState, type ReactNode} from "react"; +import type {TransactionData} from "./types.ts"; +import {ObjectInspector} from "react-inspector"; +import {cn} from "../../app/cn.js"; +import {DeleteIcon, KeyboardIcon} from "lucide-react"; +import {UserEvent} from "./UserEvent.tsx"; + +export function TransactionItem({transaction: tr}: {transaction: TransactionData}) { + const [isOpen, setIsOpen] = useState(false); + + const formatTime = (timestamp: number) => { + const time = new Date(timestamp); + return ( + time.toLocaleTimeString("en-US", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }) + + "." + + time.getMilliseconds().toString().padStart(3, "0") + ); + }; + + const summaryNodes: ReactNode[] = [#{tr.index}]; + + let summaryLeft = `#${tr.index.toString().padStart(3, "0")}`; + if (typeof tr.annotations.userEvent === "string") { + summaryLeft += ` [${tr.annotations.userEvent}]`; + summaryNodes.push(); + } else if (tr.annotations.remote) { + summaryLeft += ` [remote: ${JSON.stringify(tr.annotations.remote)}]`; + } + if (tr.docChanged) { + summaryLeft += ` 📝`; + } + + return ( +
setIsOpen((e.target as HTMLDetailsElement).open)} + className={cn( + "mb-2 border border-gray-200 rounded text-xs", + tr.annotations.userEvent && "bg-blue-50", + tr.docChanged && "border-l-4 border-l-blue-500", + )} + > + + {summaryNodes} + {formatTime(tr.timestamp)} + +
+
+ Doc Changed: {tr.docChanged.toString()} +
+ + {tr.changes.length > 0 ? ( +
+ Changes: + {tr.changes.map((change, idx) => { + const deleted = change.to - change.from; + const inserted = change.insert.length; + const sameLine = change.fromLine === change.toLine; + + let posInfo = `pos ${change.from}-${change.to}`; + if (sameLine) { + posInfo += ` (L${change.fromLine}:${change.fromCol}-${change.toCol})`; + } else { + posInfo += ` (L${change.fromLine}:${change.fromCol} to L${change.toLine}:${change.toCol})`; + } + + return ( +
+
+ Change {idx + 1}: {posInfo} +
+ {deleted > 0 &&
Deleted {deleted} chars
} + {inserted > 0 && ( +
+ Inserted: {change.insert} +
+ )} +
+ ); + })} +
+ ) : ( +
+ Changes: none +
+ )} + + {Object.keys(tr.annotations).length > 0 ? ( +
+ Annotations: + {Object.entries(tr.annotations).map(([key, value]) => ( +
+ {key}: {JSON.stringify(value)} +
+ ))} +
+ ) : ( +
+ Annotations: none +
+ )} + + {tr.effects.length > 0 ? ( +
+ Effects: {tr.effects.length} + {tr.effects.map((effect, idx) => ( +
+ {effect.type === "blockMetadataEffect" && effect.blockMetadata ? ( +
+
+ Effect {idx + 1}: blockMetadataEffect ({effect.blockMetadata.length} blocks) +
+ {effect.blockMetadata.map((block: any, blockIdx: number) => ( +
+
Block {blockIdx + 1}:
+
+ {block.output !== null ? `Output: ${block.output.from}-${block.output.to}` : "Output: null"} +
+
+ Source: {block.source.from}-{block.source.to} +
+ {Object.keys(block.attributes).length > 0 && ( +
Attributes: {JSON.stringify(block.attributes)}
+ )} + {block.error &&
Error: true
} +
+ ))} +
+ ) : ( +
+ + Effect {idx + 1} ({effect.type}):{" "} + + +
+ )} +
+ ))} +
+ ) : ( +
+ Effects: none +
+ )} + +
+ Selection: + {tr.selection.map((range, idx) => { + const isCursor = range.from === range.to; + return ( +
+ Range {idx + 1}: {isCursor ? `cursor at ${range.from}` : `${range.from}-${range.to}`} + {!isCursor && ` (anchor: ${range.anchor}, head: ${range.head})`} +
+ ); + })} +
+
+
+ ); +} diff --git a/test/components/TransactionViewer.tsx b/test/components/TransactionViewer.tsx index 9e47e2f..11d8bb5 100644 --- a/test/components/TransactionViewer.tsx +++ b/test/components/TransactionViewer.tsx @@ -1,61 +1,19 @@ -import {useState, useEffect, useRef} from "react"; +import {useState, useEffect, useRef, useMemo} from "react"; import {ViewPlugin} from "@codemirror/view"; import {Transaction as CMTransaction} from "@codemirror/state"; import {blockMetadataEffect} from "../../editor/blockMetadata.ts"; import {cn} from "../../app/cn.js"; +import {SelectionGroupItem} from "./SelectionGroupItem.tsx"; +import type {TransactionEffect, TransactionData, TransactionGroup, TransactionViewerProps} from "./types.ts"; +import {TransactionItem} from "./TransactionItem.tsx"; // Maximum number of transactions to keep in history const MAX_HISTORY = 100; -interface TransactionRange { - from: number; - to: number; - anchor: number; - head: number; -} - -interface TransactionChange { - from: number; - to: number; - fromLine: number; - fromCol: number; - toLine: number; - toCol: number; - insert: string; -} - -interface TransactionEffect { - value: any; - type: string; - blockMetadata?: any[]; -} - -interface TransactionData { - index: number; - docChanged: boolean; - changes: TransactionChange[]; - annotations: Record; - effects: TransactionEffect[]; - selection: TransactionRange[]; - scrollIntoView?: boolean; - filter?: boolean; - sequential?: boolean; - timestamp: number; -} - -interface TransactionGroup { - type: "individual" | "selection"; - transaction?: TransactionData; - transactions?: TransactionData[]; -} - -interface TransactionViewerProps { - onPluginCreate: (plugin: ViewPlugin) => void; -} - export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { const [transactions, setTransactions] = useState([]); const [autoScroll, setAutoScroll] = useState(true); + const [showEffects, setShowEffects] = useState(false); const listRef = useRef(null); const nextIndexRef = useRef(0); @@ -81,8 +39,6 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { head: r.head, })), scrollIntoView: tr.scrollIntoView, - filter: tr.filter, - sequential: tr.sequential, timestamp: Date.now(), }; @@ -120,14 +76,20 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { // Extract effects for (const effect of tr.effects) { - const effectData: TransactionEffect = { - value: effect.value, - type: "StateEffect", - }; + let effectData: TransactionEffect; if (effect.is(blockMetadataEffect)) { - effectData.type = "blockMetadataEffect"; - effectData.blockMetadata = effect.value; + const value = structuredClone(effect.value); + effectData = { + type: "blockMetadataEffect", + blockMetadata: value, + value: value, + }; + } else { + effectData = { + value: effect.value, + type: "StateEffect", + }; } data.effects.push(effectData); @@ -196,12 +158,16 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { nextIndexRef.current = 0; }; - const groupTransactions = (): TransactionGroup[] => { + const filteredTransactions = useMemo(() => { + return showEffects ? transactions : transactions.filter((tr) => tr.effects.length === 0); + }, [transactions, showEffects]); + + const groupedTransactions = useMemo(() => { const groups: TransactionGroup[] = []; let currentGroup: TransactionGroup | null = null; - for (let i = transactions.length - 1; i >= 0; i--) { - const tr = transactions[i]; + for (let i = filteredTransactions.length - 1; i >= 0; i--) { + const tr = filteredTransactions[i]!; const isSelection = tr.annotations.userEvent === "select" || tr.annotations.userEvent === "select.pointer"; if (isSelection) { @@ -218,13 +184,14 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { groups.push({ type: "individual", transaction: tr, + transactions: undefined, }); currentGroup = null; } } return groups; - }; + }, [filteredTransactions]); return (
@@ -246,13 +213,22 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { /> Auto-scroll +
{transactions.length === 0 ? (
No transactions yet
) : ( - groupTransactions().map((group, idx) => + groupedTransactions.map((group, idx) => group.type === "individual" ? ( ) : ( @@ -264,216 +240,3 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) {
); } - -function TransactionItem({transaction: tr}: {transaction: TransactionData}) { - const [isOpen, setIsOpen] = useState(false); - - const formatTime = (timestamp: number) => { - const time = new Date(timestamp); - return ( - time.toLocaleTimeString("en-US", { - hour12: false, - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }) + - "." + - time.getMilliseconds().toString().padStart(3, "0") - ); - }; - - let summaryLeft = `#${tr.index}`; - if (tr.annotations.userEvent) { - summaryLeft += ` [${tr.annotations.userEvent}]`; - } else if (tr.annotations.remote) { - summaryLeft += ` [remote: ${JSON.stringify(tr.annotations.remote)}]`; - } - if (tr.docChanged) { - summaryLeft += ` 📝`; - } - - return ( -
setIsOpen((e.target as HTMLDetailsElement).open)} - className={cn( - "mb-2 border border-gray-200 rounded text-xs", - tr.annotations.userEvent && "bg-blue-50", - tr.docChanged && "border-l-4 border-l-blue-500", - )} - > - - {summaryLeft} - {formatTime(tr.timestamp)} - -
-
- Doc Changed: {tr.docChanged.toString()} -
- - {tr.changes.length > 0 ? ( -
- Changes: - {tr.changes.map((change, idx) => { - const deleted = change.to - change.from; - const inserted = change.insert.length; - const sameLine = change.fromLine === change.toLine; - - let posInfo = `pos ${change.from}-${change.to}`; - if (sameLine) { - posInfo += ` (L${change.fromLine}:${change.fromCol}-${change.toCol})`; - } else { - posInfo += ` (L${change.fromLine}:${change.fromCol} to L${change.toLine}:${change.toCol})`; - } - - return ( -
-
- Change {idx + 1}: {posInfo} -
- {deleted > 0 &&
Deleted {deleted} chars
} - {inserted > 0 && ( -
- Inserted: {change.insert} -
- )} -
- ); - })} -
- ) : ( -
- Changes: none -
- )} - - {Object.keys(tr.annotations).length > 0 ? ( -
- Annotations: - {Object.entries(tr.annotations).map(([key, value]) => ( -
- {key}: {JSON.stringify(value)} -
- ))} -
- ) : ( -
- Annotations: none -
- )} - - {tr.effects.length > 0 ? ( -
- Effects: {tr.effects.length} - {tr.effects.map((effect, idx) => ( -
- {effect.type === "blockMetadataEffect" && effect.blockMetadata ? ( -
-
- Effect {idx + 1}: blockMetadataEffect ({effect.blockMetadata.length} blocks) -
- {effect.blockMetadata.map((block: any, blockIdx: number) => ( -
-
Block {blockIdx + 1}:
-
- {block.output !== null ? `Output: ${block.output.from}-${block.output.to}` : "Output: null"} -
-
- Source: {block.source.from}-{block.source.to} -
- {Object.keys(block.attributes).length > 0 && ( -
Attributes: {JSON.stringify(block.attributes)}
- )} - {block.error &&
Error: true
} -
- ))} -
- ) : ( -
- Effect {idx + 1} ({effect.type}): {JSON.stringify(effect.value).substring(0, 100)} -
- )} -
- ))} -
- ) : ( -
- Effects: none -
- )} - -
- Selection: - {tr.selection.map((range, idx) => { - const isCursor = range.from === range.to; - return ( -
- Range {idx + 1}: {isCursor ? `cursor at ${range.from}` : `${range.from}-${range.to}`} - {!isCursor && ` (anchor: ${range.anchor}, head: ${range.head})`} -
- ); - })} -
-
-
- ); -} - -function SelectionGroupItem({transactions}: {transactions: TransactionData[]}) { - const [isOpen, setIsOpen] = useState(false); - - const formatTime = (timestamp: number) => { - const time = new Date(timestamp); - return ( - time.toLocaleTimeString("en-US", { - hour12: false, - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }) + - "." + - time.getMilliseconds().toString().padStart(3, "0") - ); - }; - - const count = transactions.length; - const firstTr = transactions[0]; - const lastTr = transactions[transactions.length - 1]; - - const summaryLeft = `#${lastTr.index}-${firstTr.index} [select] (${count} transactions)`; - const summaryRight = `${formatTime(lastTr.timestamp)} - ${formatTime(firstTr.timestamp)}`; - - return ( -
setIsOpen((e.target as HTMLDetailsElement).open)} - className="mb-2 border border-gray-300 rounded text-xs bg-gray-50" - > - - {summaryLeft} - {summaryRight} - -
- {transactions.map((tr) => { - const selectionInfo = tr.selection - .map((range) => { - const isCursor = range.from === range.to; - return isCursor ? `cursor at ${range.from}` : `${range.from}-${range.to}`; - }) - .join(", "); - - return ( -
- #{tr.index} - {selectionInfo} - {formatTime(tr.timestamp)} -
- ); - })} -
-
- ); -} diff --git a/test/components/UserEvent.tsx b/test/components/UserEvent.tsx new file mode 100644 index 0000000..a2fc6b8 --- /dev/null +++ b/test/components/UserEvent.tsx @@ -0,0 +1,99 @@ +import { + ArrowDownUpIcon, + ClipboardPasteIcon, + CornerDownLeftIcon, + DeleteIcon, + KeyboardIcon, + LanguagesIcon, + ListPlusIcon, + MoveIcon, + RedoDotIcon, + ScissorsIcon, + SquareDashedMousePointerIcon, + SquareMousePointerIcon, + TextCursorInputIcon, + UndoDotIcon, + type LucideIcon, +} from "lucide-react"; +import {cn} from "../../app/cn.js"; + +export function UserEvent({userEvent}: {userEvent: string}) { + let Icon: LucideIcon; + let className: string = ""; + let text: string; + switch (userEvent) { + case "input": + Icon = CornerDownLeftIcon; + text = "Newline"; + break; + case "input.type": + Icon = KeyboardIcon; + text = "Input"; + break; + case "input.type.compose": + Icon = LanguagesIcon; + text = "Input"; + break; + case "input.paste": + Icon = ClipboardPasteIcon; + text = "Paste"; + break; + case "input.drop": + Icon = SquareMousePointerIcon; + text = "Drop"; + break; + case "input.copyline": + Icon = ListPlusIcon; + text = "Duplicate Line"; + break; + case "input.complete": + Icon = ListPlusIcon; + text = "Complete"; + break; + case "delete.selection": + Icon = TextCursorInputIcon; + text = "Delete Selection"; + break; + case "delete.forward": + Icon = DeleteIcon; + className = "rotate-180"; + text = "Delete"; + break; + case "delete.backward": + Icon = DeleteIcon; + text = "Backspace"; + break; + case "delete.cut": + Icon = ScissorsIcon; + text = "Cut"; + break; + case "move": + Icon = MoveIcon; + text = "Move (External)"; + break; + case "move.line": + Icon = ArrowDownUpIcon; + text = "Move Line"; + break; + case "move.drop": + Icon = SquareDashedMousePointerIcon; + text = "Move"; + break; + case "undo": + Icon = UndoDotIcon; + text = "Undo"; + break; + case "redo": + Icon = RedoDotIcon; + text = "Redo"; + break; + default: + return null; + } + return ( +
+ + {text} +
+ ); +} diff --git a/test/components/types.ts b/test/components/types.ts new file mode 100644 index 0000000..ea50e96 --- /dev/null +++ b/test/components/types.ts @@ -0,0 +1,56 @@ +import type {ViewPlugin} from "@codemirror/view"; + +export interface BlockData { + name: string; + index: number; + sourceFrom: number; + sourceTo: number; + outputFrom: number | null; + outputTo: number | null; + hasError: boolean; + attributes: Record; +} + +export interface TransactionRange { + from: number; + to: number; + anchor: number; + head: number; +} + +export interface TransactionChange { + from: number; + to: number; + fromLine: number; + fromCol: number; + toLine: number; + toCol: number; + insert: string; +} + +export interface TransactionEffect { + value: any; + type: string; + blockMetadata?: any[]; +} + +export interface TransactionData { + index: number; + docChanged: boolean; + changes: TransactionChange[]; + annotations: Record; + effects: TransactionEffect[]; + selection: TransactionRange[]; + scrollIntoView?: boolean; + timestamp: number; +} + +export interface TransactionGroup { + type: "individual" | "selection"; + transaction?: TransactionData; + transactions?: TransactionData[]; +} + +export interface TransactionViewerProps { + onPluginCreate: (plugin: ViewPlugin) => void; +} diff --git a/tsconfig.json b/tsconfig.json index c0fa300..c72a67b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,14 +21,13 @@ "declarationMap": true, // Stricter Typechecking Options "noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": true, + // "exactOptionalPropertyTypes": true, // Style Options // "noImplicitReturns": true, // "noImplicitOverride": true, // "noUnusedLocals": true, // "noUnusedParameters": true, // "noFallthroughCasesInSwitch": true, - // "noPropertyAccessFromIndexSignature": true, // Recommended Options "strict": true, "jsx": "preserve", From 949c269f08cf705012f14522e702e63ffc5beff9 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 14 Dec 2025 05:40:03 +0800 Subject: [PATCH 15/30] Fix duplicated blocks --- editor/blockMetadata.ts | 10 +- editor/blocks/BlockMetadata.ts | 6 +- editor/blocks/dedup.ts | 28 ++++ editor/blocks/deduplication.ts | 2 +- lib/containers/range-tree.ts | 228 +++++++++++++++++++++++++++++++++ runtime/index.js | 13 +- 6 files changed, 273 insertions(+), 14 deletions(-) create mode 100644 editor/blocks/dedup.ts create mode 100644 lib/containers/range-tree.ts diff --git a/editor/blockMetadata.ts b/editor/blockMetadata.ts index 89fd5a6..9498aee 100644 --- a/editor/blockMetadata.ts +++ b/editor/blockMetadata.ts @@ -5,6 +5,7 @@ import {detectBlocksWithinRange} from "../lib/blocks/detect.ts"; import {syntaxTree} from "@codemirror/language"; import {MaxHeap} from "../lib/containers/heap.ts"; import {type Range, BlockMetadata} from "./blocks/BlockMetadata.ts"; +import {deduplicateNaive} from "./blocks/dedup.ts"; /** * Update block metadata according to the given transaction. @@ -129,13 +130,10 @@ function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadat console.log("New blocks:", newBlocks); - // Lastly, we will merge blocks. We have elaborated on why there might exist - // overlapping blocks. Here, we employs a segment tree. - - // mergeOverlappingBlocks(oldBlocks); + const deduplicatedBlocks = deduplicateNaive(newBlocks); console.groupEnd(); - return newBlocks; + return deduplicatedBlocks; } export const blockMetadataEffect = StateEffect.define(); @@ -163,7 +161,7 @@ export const blockMetadataField = StateField.define({ // Otherwise, we need to update the block attributes according to the // metadata sent from the runtime. Most importantly, we need to translate // the position of each block after the changes has been made. - // TODO: Does this really happen??? + console.log(blocksFromEffect); return blocksFromEffect.map((block) => block.map(tr)); } }, diff --git a/editor/blocks/BlockMetadata.ts b/editor/blocks/BlockMetadata.ts index f6fafec..4e9a43c 100644 --- a/editor/blocks/BlockMetadata.ts +++ b/editor/blocks/BlockMetadata.ts @@ -23,13 +23,13 @@ export class BlockMetadata { if (!tr.docChanged) return this; const output = this.output ? { - from: tr.changes.mapPos(this.output.from, 1), - to: tr.changes.mapPos(this.output.to, -1), + from: tr.changes.mapPos(this.output.from, -1), + to: tr.changes.mapPos(this.output.to, 1), } : null; const source = { from: tr.changes.mapPos(this.source.from, 1), - to: tr.changes.mapPos(this.source.to, -1), + to: tr.changes.mapPos(this.source.to, 1), }; const attributes = this.attributes; const error = this.error; diff --git a/editor/blocks/dedup.ts b/editor/blocks/dedup.ts new file mode 100644 index 0000000..2198192 --- /dev/null +++ b/editor/blocks/dedup.ts @@ -0,0 +1,28 @@ +import type {BlockMetadata} from "./BlockMetadata.ts"; + +export function deduplicateNaive(blocks: BlockMetadata[]): BlockMetadata[] { + const newBlocks: BlockMetadata[] = []; + let last: BlockMetadata | null = null; + + for (let i = 0, n = blocks.length; i < n; i++) { + const block = blocks[i]!; + if (last === null || last.to <= block.from) { + newBlocks.push((last = block)); + } else if (last.from === block.from && last.to === block.to) { + // The block is a duplicate of the last block, so we skip it. + continue; + } else if (last.source.from === block.source.from && last.source.to === block.source.to) { + // The block is a duplicate of the last block, but doesn't have an output. + // We can also skip it. + continue; + } else { + console.error("Found a weird overlap."); + console.log("newBlocks", newBlocks.slice()); + console.log("i", i); + console.log("last", last); + console.log("block", block); + } + } + + return newBlocks; +} diff --git a/editor/blocks/deduplication.ts b/editor/blocks/deduplication.ts index 5b59a1b..3f4596e 100644 --- a/editor/blocks/deduplication.ts +++ b/editor/blocks/deduplication.ts @@ -1,4 +1,4 @@ -import {BlockMetadata} from "./BlockMetadata.js"; +import {BlockMetadata} from "./BlockMetadata.ts"; export interface Interval { from: number; diff --git a/lib/containers/range-tree.ts b/lib/containers/range-tree.ts new file mode 100644 index 0000000..3e0f632 --- /dev/null +++ b/lib/containers/range-tree.ts @@ -0,0 +1,228 @@ +/** + * Represents a half-open range [from, to) where from is inclusive and to is exclusive. + */ +export interface Range { + from: number; + to: number; +} + +/** + * Node in the AVL-based range tree. + */ +class RangeNode { + range: Range; + max: number; // Maximum to value in this subtree + height: number; + left: RangeNode | null = null; + right: RangeNode | null = null; + + constructor(range: Range) { + this.range = range; + this.max = range.to; + this.height = 1; + } +} + +/** + * A balanced range tree that supports efficient interval operations. + * Ranges are left-inclusive and right-exclusive: [from, to). + */ +export class RangeTree { + private root: RangeNode | null = null; + + /** + * Inserts a new range into the tree. + */ + insert(from: number, to: number): void { + if (from >= to) { + throw new Error("Invalid range: from must be less than to"); + } + this.root = this.insertNode(this.root, {from, to}); + } + + /** + * Checks if any range in the tree overlaps with the given range. + */ + hasOverlap(from: number, to: number): boolean { + if (from >= to) { + throw new Error("Invalid range: from must be less than to"); + } + return this.searchOverlap(this.root, {from, to}) !== null; + } + + /** + * Checks if any range in the tree contains the given position. + */ + contains(position: number): boolean { + return this.searchContains(this.root, position) !== null; + } + + /** + * Retrieves all ranges that overlap with the given range. + */ + findAllOverlapping(from: number, to: number): Range[] { + if (from >= to) { + throw new Error("Invalid range: from must be less than to"); + } + const result: Range[] = []; + this.collectOverlapping(this.root, {from, to}, result); + return result; + } + + private insertNode(node: RangeNode | null, range: Range): RangeNode { + // Standard BST insertion + if (node === null) { + return new RangeNode(range); + } + + if (range.from < node.range.from) { + node.left = this.insertNode(node.left, range); + } else { + node.right = this.insertNode(node.right, range); + } + + // Update height and max + this.updateNode(node); + + // Balance the node + return this.balance(node); + } + + private searchOverlap(node: RangeNode | null, range: Range): RangeNode | null { + if (node === null) { + return null; + } + + // Check if current node's range overlaps with the search range + if (this.overlaps(node.range, range)) { + return node; + } + + // If left child exists and its max is greater than range.from, + // there might be an overlap in the left subtree + if (node.left !== null && node.left.max > range.from) { + const leftResult = this.searchOverlap(node.left, range); + if (leftResult !== null) { + return leftResult; + } + } + + // Search in the right subtree + return this.searchOverlap(node.right, range); + } + + private searchContains(node: RangeNode | null, position: number): RangeNode | null { + if (node === null) { + return null; + } + + // Check if current node's range contains the position + if (node.range.from <= position && position < node.range.to) { + return node; + } + + // If left child exists and its max is greater than position, + // there might be a containing range in the left subtree + if (node.left !== null && node.left.max > position) { + const leftResult = this.searchContains(node.left, position); + if (leftResult !== null) { + return leftResult; + } + } + + // Search in the right subtree + return this.searchContains(node.right, position); + } + + private collectOverlapping(node: RangeNode | null, range: Range, result: Range[]): void { + if (node === null) { + return; + } + + // Check if current node's range overlaps + if (this.overlaps(node.range, range)) { + result.push({...node.range}); + } + + // If left child exists and its max is greater than range.from, + // there might be overlaps in the left subtree + if (node.left !== null && node.left.max > range.from) { + this.collectOverlapping(node.left, range, result); + } + + // Always check right subtree if the range might extend there + if (node.right !== null && node.range.from < range.to) { + this.collectOverlapping(node.right, range, result); + } + } + + private overlaps(a: Range, b: Range): boolean { + // Two half-open ranges [a.from, a.to) and [b.from, b.to) overlap if: + // a.from < b.to AND b.from < a.to + return a.from < b.to && b.from < a.to; + } + + private updateNode(node: RangeNode): void { + const leftHeight = node.left?.height ?? 0; + const rightHeight = node.right?.height ?? 0; + node.height = Math.max(leftHeight, rightHeight) + 1; + + const leftMax = node.left?.max ?? Number.NEGATIVE_INFINITY; + const rightMax = node.right?.max ?? Number.NEGATIVE_INFINITY; + node.max = Math.max(node.range.to, leftMax, rightMax); + } + + private getBalance(node: RangeNode): number { + const leftHeight = node.left?.height ?? 0; + const rightHeight = node.right?.height ?? 0; + return leftHeight - rightHeight; + } + + private balance(node: RangeNode): RangeNode { + const balance = this.getBalance(node); + + // Left heavy + if (balance > 1) { + if (node.left && this.getBalance(node.left) < 0) { + // Left-Right case + node.left = this.rotateLeft(node.left); + } + // Left-Left case + return this.rotateRight(node); + } + + // Right heavy + if (balance < -1) { + if (node.right && this.getBalance(node.right) > 0) { + // Right-Left case + node.right = this.rotateRight(node.right); + } + // Right-Right case + return this.rotateLeft(node); + } + + return node; + } + + private rotateLeft(node: RangeNode): RangeNode { + const newRoot = node.right!; + node.right = newRoot.left; + newRoot.left = node; + + this.updateNode(node); + this.updateNode(newRoot); + + return newRoot; + } + + private rotateRight(node: RangeNode): RangeNode { + const newRoot = node.left!; + node.left = newRoot.right; + newRoot.right = node; + + this.updateNode(node); + this.updateNode(newRoot); + + return newRoot; + } +} diff --git a/runtime/index.js b/runtime/index.js index f0d26d2..8555746 100644 --- a/runtime/index.js +++ b/runtime/index.js @@ -135,6 +135,8 @@ export function createRuntime(initialCode) { const refresh = debounce((code) => { const changes = removeChanges(code); + + // Construct an interval tree containing the ranges to be deleted. const removedIntervals = IntervalTree.from(changes, ({from, to}, index) => from === to ? null : {interval: {low: from, high: to - 1}, data: index}, ); @@ -149,7 +151,7 @@ export function createRuntime(initialCode) { if (!values.length) { // Create a block even if there are no values. - blocks.push(new BlockMetadata(node.type, sourceRange, node.state.attributes)); + blocks.push(new BlockMetadata(node.type, null, sourceRange, node.state.attributes)); continue; } @@ -159,9 +161,6 @@ export function createRuntime(initialCode) { // We need to remove the trailing newline for table. const format = withTable(groupValues) ? (...V) => table(...V).trimEnd() : columns; - // The range of line numbers of output lines. - let outputRange = null; - // If any value is an error, set the error flag. let error = false; @@ -203,10 +202,16 @@ export function createRuntime(initialCode) { }); const prefixed = addPrefix(formatted, error ? ERROR_PREFIX : OUTPUT_PREFIX); + // The range of line numbers of output lines. + let outputRange = null; + // Search for existing changes and update the inserted text if found. const entry = removedIntervals.contains(start - 1); + + // Entry not found. This is a new output. if (entry === null) { changes.push({from: start, insert: prefixed + "\n"}); + outputRange = {from: start, to: start}; } else { const change = changes[entry.data]; change.insert = prefixed + "\n"; From a7337b341d2ba762f5840c1dc833714cf56e1395 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 14 Dec 2025 14:58:48 +0800 Subject: [PATCH 16/30] Minor refactor --- editor/blocks/BlockMetadata.ts | 21 +++- test/components/TransactionItem.tsx | 55 +++++---- test/components/TransactionViewer.tsx | 160 +++++++++++--------------- test/components/types.ts | 3 +- 4 files changed, 114 insertions(+), 125 deletions(-) diff --git a/editor/blocks/BlockMetadata.ts b/editor/blocks/BlockMetadata.ts index 4e9a43c..497d5e1 100644 --- a/editor/blocks/BlockMetadata.ts +++ b/editor/blocks/BlockMetadata.ts @@ -3,6 +3,14 @@ import type {Transaction} from "@codemirror/state"; export type Range = {from: number; to: number}; export class BlockMetadata { + /** + * Create a new `BlockMetadata` instance. + * @param name a descriptive name of this block + * @param output the range of the output region + * @param source the range of the source region + * @param attributes any user-customized attributes of this block + * @param error whether this block has an error + */ public constructor( public readonly name: string, public readonly output: Range | null, @@ -11,16 +19,25 @@ export class BlockMetadata { public error: boolean = false, ) {} + /** + * Get the start position (inclusive) of this block. + */ get from() { return this.output?.from ?? this.source.from; } + /** + * Get the end position (exclusive) of this block. + */ get to() { return this.source.to; } public map(tr: Transaction): BlockMetadata { + // If no changes were made to the document, return the current instance. if (!tr.docChanged) return this; + + // Otherwise, map the output and source ranges. const output = this.output ? { from: tr.changes.mapPos(this.output.from, -1), @@ -31,8 +48,6 @@ export class BlockMetadata { from: tr.changes.mapPos(this.source.from, 1), to: tr.changes.mapPos(this.source.to, 1), }; - const attributes = this.attributes; - const error = this.error; - return new BlockMetadata(this.name, output, source, attributes, error); + return new BlockMetadata(this.name, output, source, this.attributes, this.error); } } diff --git a/test/components/TransactionItem.tsx b/test/components/TransactionItem.tsx index 71b5d42..04d6c83 100644 --- a/test/components/TransactionItem.tsx +++ b/test/components/TransactionItem.tsx @@ -108,37 +108,34 @@ export function TransactionItem({transaction: tr}: {transaction: TransactionData {tr.effects.length > 0 ? (
Effects: {tr.effects.length} - {tr.effects.map((effect, idx) => ( -
- {effect.type === "blockMetadataEffect" && effect.blockMetadata ? ( -
-
- Effect {idx + 1}: blockMetadataEffect ({effect.blockMetadata.length} blocks) + {tr.blockMetadata ? ( +
+
blockMetadataEffect ({tr.blockMetadata.length} blocks)
+ {tr.blockMetadata.map((block: any, blockIdx: number) => ( +
+
Block {blockIdx + 1}:
+
+ {block.output !== null ? `Output: ${block.output.from}-${block.output.to}` : "Output: null"}
- {effect.blockMetadata.map((block: any, blockIdx: number) => ( -
-
Block {blockIdx + 1}:
-
- {block.output !== null ? `Output: ${block.output.from}-${block.output.to}` : "Output: null"} -
-
- Source: {block.source.from}-{block.source.to} -
- {Object.keys(block.attributes).length > 0 && ( -
Attributes: {JSON.stringify(block.attributes)}
- )} - {block.error &&
Error: true
} -
- ))} -
- ) : ( -
- - Effect {idx + 1} ({effect.type}):{" "} - - +
+ Source: {block.source.from}-{block.source.to} +
+ {Object.keys(block.attributes).length > 0 && ( +
Attributes: {JSON.stringify(block.attributes)}
+ )} + {block.error &&
Error: true
}
- )} + ))} +
+ ) : null} + {tr.effects.map((effect, idx) => ( +
+
+ + Effect {idx + 1} ({effect.type}):{" "} + + +
))}
diff --git a/test/components/TransactionViewer.tsx b/test/components/TransactionViewer.tsx index 11d8bb5..6c819dc 100644 --- a/test/components/TransactionViewer.tsx +++ b/test/components/TransactionViewer.tsx @@ -1,6 +1,6 @@ import {useState, useEffect, useRef, useMemo} from "react"; -import {ViewPlugin} from "@codemirror/view"; -import {Transaction as CMTransaction} from "@codemirror/state"; +import {ViewPlugin, ViewUpdate} from "@codemirror/view"; +import {Transaction as Tr} from "@codemirror/state"; import {blockMetadataEffect} from "../../editor/blockMetadata.ts"; import {cn} from "../../app/cn.js"; import {SelectionGroupItem} from "./SelectionGroupItem.tsx"; @@ -10,6 +10,70 @@ import {TransactionItem} from "./TransactionItem.tsx"; // Maximum number of transactions to keep in history const MAX_HISTORY = 100; +function extractTransactionData(tr: Tr, index: number): TransactionData { + const data: TransactionData = { + index, + docChanged: tr.docChanged, + changes: [], + annotations: {}, + effects: [], + selection: tr.state.selection.ranges.map((r) => ({ + from: r.from, + to: r.to, + anchor: r.anchor, + head: r.head, + })), + scrollIntoView: tr.scrollIntoView, + timestamp: Date.now(), + blockMetadata: null, + }; + + // Extract changes + tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { + const fromLine = tr.startState.doc.lineAt(fromA); + const toLine = tr.startState.doc.lineAt(toA); + + data.changes.push({ + from: fromA, + to: toA, + fromLine: fromLine.number, + fromCol: fromA - fromLine.from, + toLine: toLine.number, + toCol: toA - toLine.from, + insert: inserted.toString(), + }); + }); + + // Extract annotations + const userEvent = tr.annotation(Tr.userEvent); + if (userEvent !== undefined) { + data.annotations.userEvent = userEvent; + } + + const remote = tr.annotation(Tr.remote); + if (remote !== undefined) { + data.annotations.remote = remote; + } + + const addToHistory = tr.annotation(Tr.addToHistory); + if (addToHistory !== undefined) { + data.annotations.addToHistory = addToHistory; + } + + for (const effect of tr.effects) { + if (effect.is(blockMetadataEffect)) { + data.blockMetadata = Array.from(effect.value); + } else { + data.effects.push({ + value: effect.value, + type: "StateEffect", + }); + } + } + + return data; +} + export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { const [transactions, setTransactions] = useState([]); const [autoScroll, setAutoScroll] = useState(true); @@ -25,102 +89,14 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { listeners.forEach((fn) => fn([...transactionsList])); } - function extractTransactionData(tr: CMTransaction, index: number): TransactionData { - const data: TransactionData = { - index, - docChanged: tr.docChanged, - changes: [], - annotations: {}, - effects: [], - selection: tr.state.selection.ranges.map((r) => ({ - from: r.from, - to: r.to, - anchor: r.anchor, - head: r.head, - })), - scrollIntoView: tr.scrollIntoView, - timestamp: Date.now(), - }; - - // Extract changes - tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { - const fromLine = tr.startState.doc.lineAt(fromA); - const toLine = tr.startState.doc.lineAt(toA); - - data.changes.push({ - from: fromA, - to: toA, - fromLine: fromLine.number, - fromCol: fromA - fromLine.from, - toLine: toLine.number, - toCol: toA - toLine.from, - insert: inserted.toString(), - }); - }); - - // Extract annotations - const userEvent = tr.annotation(CMTransaction.userEvent); - if (userEvent !== undefined) { - data.annotations.userEvent = userEvent; - } - - const remote = tr.annotation(CMTransaction.remote); - if (remote !== undefined) { - data.annotations.remote = remote; - } - - const addToHistory = tr.annotation(CMTransaction.addToHistory); - if (addToHistory !== undefined) { - data.annotations.addToHistory = addToHistory; - } - - // Extract effects - for (const effect of tr.effects) { - let effectData: TransactionEffect; - - if (effect.is(blockMetadataEffect)) { - const value = structuredClone(effect.value); - effectData = { - type: "blockMetadataEffect", - blockMetadata: value, - value: value, - }; - } else { - effectData = { - value: effect.value, - type: "StateEffect", - }; - } - - data.effects.push(effectData); - } - - return data; - } - const plugin = ViewPlugin.fromClass( class { constructor(view: any) { - const initialTr: TransactionData = { - index: nextIndexRef.current++, - docChanged: false, - changes: [], - annotations: {}, - effects: [], - selection: view.state.selection.ranges.map((r: any) => ({ - from: r.from, - to: r.to, - anchor: r.anchor, - head: r.head, - })), - timestamp: Date.now(), - }; - transactionsList.push(initialTr); notifyListeners(); } - update(update: any) { - update.transactions.forEach((tr: CMTransaction) => { + update(update: ViewUpdate) { + update.transactions.forEach((tr: Tr) => { const transactionData = extractTransactionData(tr, nextIndexRef.current++); transactionsList.push(transactionData); diff --git a/test/components/types.ts b/test/components/types.ts index ea50e96..8a49467 100644 --- a/test/components/types.ts +++ b/test/components/types.ts @@ -1,4 +1,5 @@ import type {ViewPlugin} from "@codemirror/view"; +import type {BlockMetadata} from "../../editor/blocks/BlockMetadata.ts"; export interface BlockData { name: string; @@ -31,7 +32,6 @@ export interface TransactionChange { export interface TransactionEffect { value: any; type: string; - blockMetadata?: any[]; } export interface TransactionData { @@ -43,6 +43,7 @@ export interface TransactionData { selection: TransactionRange[]; scrollIntoView?: boolean; timestamp: number; + blockMetadata: BlockMetadata[] | null; } export interface TransactionGroup { From a2f03eb63e184241b92e2efc8d0747c2c54870eb Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 14 Dec 2025 15:13:32 +0800 Subject: [PATCH 17/30] Configure ESLint for TypeScript --- editor/blockMetadata.ts | 3 +- editor/blocks/deduplication.ts | 159 -------------------- eslint.config.mjs | 14 +- lib/blocks.ts | 9 +- lib/blocks/detect.ts | 2 +- package.json | 3 + pnpm-lock.yaml | 208 ++++++++++++++++++++++++++ test/components/Editor.tsx | 4 +- test/components/TransactionItem.tsx | 16 +- test/components/TransactionViewer.tsx | 3 +- test/components/types.ts | 10 +- 11 files changed, 244 insertions(+), 187 deletions(-) delete mode 100644 editor/blocks/deduplication.ts diff --git a/editor/blockMetadata.ts b/editor/blockMetadata.ts index 9498aee..0614354 100644 --- a/editor/blockMetadata.ts +++ b/editor/blockMetadata.ts @@ -1,10 +1,9 @@ import {StateField, StateEffect, Transaction} from "@codemirror/state"; -import {mergeOverlappingBlocks} from "./blocks/deduplication.ts"; import {blockRangeLength, findAffectedBlockRange, getOnlyOneBlock} from "../lib/blocks.ts"; import {detectBlocksWithinRange} from "../lib/blocks/detect.ts"; import {syntaxTree} from "@codemirror/language"; import {MaxHeap} from "../lib/containers/heap.ts"; -import {type Range, BlockMetadata} from "./blocks/BlockMetadata.ts"; +import {BlockMetadata} from "./blocks/BlockMetadata.ts"; import {deduplicateNaive} from "./blocks/dedup.ts"; /** diff --git a/editor/blocks/deduplication.ts b/editor/blocks/deduplication.ts deleted file mode 100644 index 3f4596e..0000000 --- a/editor/blocks/deduplication.ts +++ /dev/null @@ -1,159 +0,0 @@ -import {BlockMetadata} from "./BlockMetadata.ts"; - -export interface Interval { - from: number; - to: number; -} - -type Block = BlockMetadata; - -interface IntervalWithIndex extends Interval { - index: number; -} - -interface MergeGroup { - indices: number[]; - from: number; - to: number; -} - -/** - * Detects overlapping blocks and returns groups of indices that should be merged. - * - * @param blocks Array of blocks to check for overlaps - * @returns Array of merge groups, where each group contains indices of overlapping blocks - */ -export function detectOverlappingBlocks(blocks: Block[]): MergeGroup[] { - if (blocks.length === 0) return []; - - // Create a sorted array of block intervals with their indices - const intervals: IntervalWithIndex[] = blocks.map((block, index) => ({ - from: block.from, - to: block.to, - index, - })); - - // Sort by start position, then by end position - intervals.sort((a, b) => { - if (a.from !== b.from) return a.from - b.from; - return a.to - b.to; - }); - - // Track which blocks overlap - const overlappingIndices = new Set(); - - // Simple sweep line algorithm to detect overlaps - for (let i = 0; i < intervals.length - 1; i++) { - const current = intervals[i]!; - const next = intervals[i + 1]!; - - // If next block starts before or at the end of current block, they overlap - if (next.from < current.to) { - overlappingIndices.add(current.index); - overlappingIndices.add(next.index); - } - } - - if (overlappingIndices.size === 0) { - return []; - } - - // Group consecutive overlapping blocks - const sortedOverlapping = Array.from(overlappingIndices).sort((a, b) => a - b); - const mergeGroups: MergeGroup[] = []; - let currentGroup: number[] = []; - - for (const idx of sortedOverlapping) { - if (currentGroup.length === 0) { - currentGroup.push(idx); - } else { - const lastIdx = currentGroup[currentGroup.length - 1]!; - const lastBlock = blocks[lastIdx]!; - const currentBlock = blocks[idx]!; - - // Check if blocks overlap - if (currentBlock.from <= lastBlock.to) { - currentGroup.push(idx); - } else { - // Finalize current group - const groupFrom = Math.min(...currentGroup.map((i) => blocks[i]!.from)); - const groupTo = Math.max(...currentGroup.map((i) => blocks[i]!.to)); - mergeGroups.push({indices: currentGroup, from: groupFrom, to: groupTo}); - currentGroup = [idx]; - } - } - } - - // Don't forget the last group - if (currentGroup.length > 0) { - const groupFrom = Math.min(...currentGroup.map((i) => blocks[i]!.from)); - const groupTo = Math.max(...currentGroup.map((i) => blocks[i]!.to)); - mergeGroups.push({indices: currentGroup, from: groupFrom, to: groupTo}); - } - - return mergeGroups; -} - -/** - * Merges overlapping blocks in place. - * - * @param blocks Array of blocks to merge (will be modified in place) - */ -export function mergeOverlappingBlocks(blocks: Block[]): void { - const mergeGroups = detectOverlappingBlocks(blocks); - - console.log("Merge groups:", mergeGroups); - mergeGroups.forEach((group) => { - group.indices.forEach((index) => { - const block = blocks[index]!; - console.log(block); - }); - }); - - if (mergeGroups.length === 0) return; - - console.log(`Found ${mergeGroups.length} groups of overlapping blocks to merge`); - - // Process groups in reverse order to avoid index shifting issues when removing - for (const group of mergeGroups.reverse()) { - if (group.indices.length < 2) continue; - - console.log(`Merging blocks at indices ${group.indices.join(", ")} into range ${group.from}-${group.to}`); - - // Find the block with the most complete information (prefer blocks with output) - let primaryIdx = group.indices[0]!; - let primaryBlock = blocks[primaryIdx]!; - - for (const idx of group.indices) { - const block = blocks[idx]!; - if (block.output && !primaryBlock.output) { - primaryIdx = idx; - primaryBlock = block; - } - } - - // Update the primary block's range to encompass all merged blocks - if (primaryBlock.output) { - const outputFrom = Math.min( - primaryBlock.output.from, - ...group.indices.map((idx) => blocks[idx]!.output?.from ?? Infinity), - ); - const outputTo = Math.max( - primaryBlock.output.to, - ...group.indices.map((idx) => blocks[idx]!.output?.to ?? -Infinity), - ); - primaryBlock.output = {from: outputFrom, to: outputTo}; - } - - primaryBlock.source = {from: group.from, to: group.to}; - - // Keep the primary block at the first index, remove all others - const firstIdx = group.indices[0]!; - blocks[firstIdx] = primaryBlock; - - // Remove other blocks (iterate backwards to avoid index shifting) - for (let i = group.indices.length - 1; i > 0; i--) { - blocks.splice(group.indices[i]!, 1); - } - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs index 57bcb5e..f4a2295 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,10 +3,17 @@ import {defineConfig} from "eslint/config"; import eslintConfigPrettier from "eslint-config-prettier/flat"; import reactPlugin from "eslint-plugin-react"; import hooksPlugin from "eslint-plugin-react-hooks"; +import tseslint from "typescript-eslint"; export default defineConfig([ { - files: ["editor/**/*.js", "runtime/**/*.js", "test/**/*.js", "app/**/*.js", "app/**/*.jsx", "lib/**/*.js"], + files: [ + "editor/**/*.{js,ts,tsx}", + "runtime/**/*.{js,ts}", + "test/**/*.{js,ts,tsx}", + "app/**/*.{js,jsx,ts,tsx}", + "lib/**/*.{js,ts}", + ], plugins: { react: reactPlugin, "react-hooks": hooksPlugin, @@ -34,6 +41,11 @@ export default defineConfig([ "spaced-comment": ["error", "always"], }, }, + // TypeScript-specific configuration + ...tseslint.configs.recommended.map((config) => ({ + ...config, + files: ["editor/**/*.{ts,tsx}", "runtime/**/*.ts", "test/**/*.{ts,tsx}", "app/**/*.{ts,tsx}", "lib/**/*.ts"], + })), { ignores: ["**/*.recho.js", "test/output/**/*"], }, diff --git a/lib/blocks.ts b/lib/blocks.ts index ba3eb60..f51947c 100644 --- a/lib/blocks.ts +++ b/lib/blocks.ts @@ -25,12 +25,9 @@ export function findAdjacentBlocks( // boundary is exclusive, i.e., the range is empty when `left === right`. let left = 0; let right = blocks.length; - let DEBUG_hasEnteredLoop = false; // When the range is non-empty. - loop: while (left < right) { - DEBUG_hasEnteredLoop = true; - + while (left < right) { const middle = (left + right) >>> 1; const pivot = blocks[middle]!; @@ -40,7 +37,6 @@ export function findAdjacentBlocks( return [middle - 1, middle]; } else { right = middle; - continue loop; } } else if (pivot.to <= pos) { // We should move to the right sub-range [middle, right). @@ -55,9 +51,6 @@ export function findAdjacentBlocks( } } - // We can only reach here if we haven't enter the loop. - // console.assert(!DEBUG_hasEnteredLoop, "Did not return in the loop."); - return null; } finally { // console.groupEnd(); diff --git a/lib/blocks/detect.ts b/lib/blocks/detect.ts index b226fd9..be9f6f0 100644 --- a/lib/blocks/detect.ts +++ b/lib/blocks/detect.ts @@ -108,7 +108,7 @@ export function detectBlocksWithinRange(tree: Tree, doc: Text, from: number, to: // Find the next statement after these output lines // The output belongs to the statement immediately following it - let nextStatementLine = currentLineNum; + const nextStatementLine = currentLineNum; if (nextStatementLine <= totalLines) { const nextStmtLine = doc.line(nextStatementLine); // Store this output range to be associated with the next statement diff --git a/package.json b/package.json index c9d1e18..daaeca9 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,8 @@ "@tailwindcss/postcss": "^4.1.14", "@types/node": "24.10.1", "@types/react": "19.2.6", + "@typescript-eslint/eslint-plugin": "^8.49.0", + "@typescript-eslint/parser": "^8.49.0", "@vercel/analytics": "^1.5.0", "@vitejs/plugin-react": "^4.3.1", "clsx": "^2.1.1", @@ -56,6 +58,7 @@ "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.14", "typescript": "^5.9.3", + "typescript-eslint": "^8.49.0", "vite": "^7.1.10", "vitest": "^3.2.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0435a48..213da11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,6 +123,12 @@ importers: '@types/react': specifier: 19.2.6 version: 19.2.6 + '@typescript-eslint/eslint-plugin': + specifier: ^8.49.0 + version: 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.49.0 + version: 8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@vercel/analytics': specifier: ^1.5.0 version: 1.5.0(next@15.5.6(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0) @@ -174,6 +180,9 @@ importers: typescript: specifier: ^5.9.3 version: 5.9.3 + typescript-eslint: + specifier: ^8.49.0 + version: 8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.1.10 version: 7.1.10(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.1) @@ -1212,6 +1221,65 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@typescript-eslint/eslint-plugin@8.49.0': + resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.49.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.49.0': + resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.49.0': + resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.49.0': + resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.49.0': + resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.49.0': + resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.49.0': + resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.49.0': + resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.49.0': + resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.49.0': + resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@uiw/codemirror-theme-github@4.25.2': resolution: {integrity: sha512-9g3ujmYCNU2VQCp0+XzI1NS5hSZGgXRtH+5yWli5faiPvHGYZUVke+5Pnzdn/1tkgW6NpTQ7U/JHsyQkgbnZ/w==} @@ -1440,6 +1508,9 @@ packages: brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2152,6 +2223,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -2609,6 +2684,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -3274,6 +3353,12 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -3305,6 +3390,13 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typescript-eslint@8.49.0: + resolution: {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -4652,6 +4744,97 @@ snapshots: '@types/unist@3.0.3': {} + '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 + eslint: 9.37.0(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.49.0 + debug: 4.4.3 + eslint: 9.37.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.49.0': + dependencies: + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 + + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.37.0(jiti@2.6.1) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.49.0': {} + + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + eslint: 9.37.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.49.0': + dependencies: + '@typescript-eslint/types': 8.49.0 + eslint-visitor-keys: 4.2.1 + '@uiw/codemirror-theme-github@4.25.2(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.6)': dependencies: '@uiw/codemirror-themes': 4.25.2(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.6) @@ -4941,6 +5124,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -5828,6 +6015,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -6249,6 +6438,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + minipass@7.1.2: {} minizlib@3.1.0: @@ -6989,6 +7182,10 @@ snapshots: trim-lines@3.0.1: {} + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + tslib@2.8.1: {} type-check@0.4.0: @@ -7035,6 +7232,17 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typescript-eslint@8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.37.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} uc.micro@2.1.0: {} diff --git a/test/components/Editor.tsx b/test/components/Editor.tsx index c588499..708952b 100644 --- a/test/components/Editor.tsx +++ b/test/components/Editor.tsx @@ -12,9 +12,9 @@ interface EditorProps { onError?: (error: Error) => void; } -function debounce(fn: (...args: any[]) => void, delay = 0) { +function debounce(fn: (...args: Args) => void, delay = 0) { let timeout: ReturnType; - return (...args: any[]) => { + return (...args: Args) => { clearTimeout(timeout); timeout = setTimeout(() => fn(...args), delay); }; diff --git a/test/components/TransactionItem.tsx b/test/components/TransactionItem.tsx index 04d6c83..fe00c3f 100644 --- a/test/components/TransactionItem.tsx +++ b/test/components/TransactionItem.tsx @@ -2,8 +2,8 @@ import {useState, type ReactNode} from "react"; import type {TransactionData} from "./types.ts"; import {ObjectInspector} from "react-inspector"; import {cn} from "../../app/cn.js"; -import {DeleteIcon, KeyboardIcon} from "lucide-react"; import {UserEvent} from "./UserEvent.tsx"; +import {PencilLineIcon, RefreshCcw} from "lucide-react"; export function TransactionItem({transaction: tr}: {transaction: TransactionData}) { const [isOpen, setIsOpen] = useState(false); @@ -22,17 +22,19 @@ export function TransactionItem({transaction: tr}: {transaction: TransactionData ); }; - const summaryNodes: ReactNode[] = [#{tr.index}]; + const summaryNodes: ReactNode[] = [ + + #{tr.index} + , + ]; - let summaryLeft = `#${tr.index.toString().padStart(3, "0")}`; if (typeof tr.annotations.userEvent === "string") { - summaryLeft += ` [${tr.annotations.userEvent}]`; - summaryNodes.push(); + summaryNodes.push(); } else if (tr.annotations.remote) { - summaryLeft += ` [remote: ${JSON.stringify(tr.annotations.remote)}]`; + summaryNodes.push(); } if (tr.docChanged) { - summaryLeft += ` 📝`; + summaryNodes.push(); } return ( diff --git a/test/components/TransactionViewer.tsx b/test/components/TransactionViewer.tsx index 6c819dc..bb8737b 100644 --- a/test/components/TransactionViewer.tsx +++ b/test/components/TransactionViewer.tsx @@ -2,9 +2,8 @@ import {useState, useEffect, useRef, useMemo} from "react"; import {ViewPlugin, ViewUpdate} from "@codemirror/view"; import {Transaction as Tr} from "@codemirror/state"; import {blockMetadataEffect} from "../../editor/blockMetadata.ts"; -import {cn} from "../../app/cn.js"; import {SelectionGroupItem} from "./SelectionGroupItem.tsx"; -import type {TransactionEffect, TransactionData, TransactionGroup, TransactionViewerProps} from "./types.ts"; +import type {TransactionData, TransactionGroup, TransactionViewerProps} from "./types.ts"; import {TransactionItem} from "./TransactionItem.tsx"; // Maximum number of transactions to keep in history diff --git a/test/components/types.ts b/test/components/types.ts index 8a49467..1e0a86c 100644 --- a/test/components/types.ts +++ b/test/components/types.ts @@ -1,4 +1,4 @@ -import type {ViewPlugin} from "@codemirror/view"; +import type {PluginValue, ViewPlugin} from "@codemirror/view"; import type {BlockMetadata} from "../../editor/blocks/BlockMetadata.ts"; export interface BlockData { @@ -9,7 +9,7 @@ export interface BlockData { outputFrom: number | null; outputTo: number | null; hasError: boolean; - attributes: Record; + attributes: Record; } export interface TransactionRange { @@ -30,7 +30,7 @@ export interface TransactionChange { } export interface TransactionEffect { - value: any; + value: unknown; type: string; } @@ -38,7 +38,7 @@ export interface TransactionData { index: number; docChanged: boolean; changes: TransactionChange[]; - annotations: Record; + annotations: Record; effects: TransactionEffect[]; selection: TransactionRange[]; scrollIntoView?: boolean; @@ -53,5 +53,5 @@ export interface TransactionGroup { } export interface TransactionViewerProps { - onPluginCreate: (plugin: ViewPlugin) => void; + onPluginCreate: (plugin: ViewPlugin) => void; } From 2ced0796716064cfca45650cd6ba7a69bc73f0ea Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sun, 14 Dec 2025 15:38:30 +0800 Subject: [PATCH 18/30] Fix type errors and code style --- package.json | 1 + pnpm-lock.yaml | 12 ++++++++++++ test/blocks/affected.spec.ts | 9 ++------- test/components/App.tsx | 6 +++--- test/components/BlockItem.tsx | 2 +- test/components/BlockViewer.tsx | 18 +++++++++--------- test/components/Editor.tsx | 15 +++++++++++---- test/components/TestSelector.tsx | 4 ++-- test/components/TransactionItem.tsx | 2 +- test/components/TransactionViewer.tsx | 2 +- test/main.tsx | 10 +++++----- test/store.ts | 11 ++++------- test/types/css.d.ts | 1 + tsconfig.json | 2 +- 14 files changed, 54 insertions(+), 41 deletions(-) create mode 100644 test/types/css.d.ts diff --git a/package.json b/package.json index 6b2af2a..53e25f9 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@tailwindcss/postcss": "^4.1.14", "@types/node": "24.10.1", "@types/react": "19.2.6", + "@types/react-dom": "^19.2.3", "@typescript-eslint/eslint-plugin": "^8.49.0", "@typescript-eslint/parser": "^8.49.0", "@vercel/analytics": "^1.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7a90a3..654e951 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,6 +123,9 @@ importers: '@types/react': specifier: 19.2.6 version: 19.2.6 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.6) '@typescript-eslint/eslint-plugin': specifier: ^8.49.0 version: 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) @@ -1225,6 +1228,11 @@ packages: '@types/node@24.10.1': resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + '@types/react@19.2.6': resolution: {integrity: sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==} @@ -4757,6 +4765,10 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/react-dom@19.2.3(@types/react@19.2.6)': + dependencies: + '@types/react': 19.2.6 + '@types/react@19.2.6': dependencies: csstype: 3.2.3 diff --git a/test/blocks/affected.spec.ts b/test/blocks/affected.spec.ts index cc6d353..54c464f 100644 --- a/test/blocks/affected.spec.ts +++ b/test/blocks/affected.spec.ts @@ -71,7 +71,6 @@ describe("findAffectedBlockRange", () => { {from: 5, to: 10}, {from: 12, to: 15}, ]; - const N = blocks.length; expect(findAffectedBlockRange(blocks, 15, 15)).toStrictEqual([1, null]); expect(findAffectedBlockRange(blocks, 16, 16)).toStrictEqual([1, null]); }); @@ -215,10 +214,6 @@ describe("findAffectedBlockRange", () => { }); }); - function overlaps(m: {from: number; to: number}, n: {from: number; to: number}): boolean { - return m.from <= n.to && n.from < m.to; - } - type Location = | {type: "contained"; index: number} | { @@ -269,7 +264,7 @@ describe("findAffectedBlockRange", () => { // Test all valid ranges [from, to) where from <= to for (let from = 0; from <= maxPos + 1; from++) { for (let to = from; to <= maxPos + 1; to++) { - let fromLocation: Location = locateNaive(blocks, from); + const fromLocation = locateNaive(blocks, from); let expected: [number | null, number | null]; if (from === to) { switch (fromLocation.type) { @@ -293,7 +288,7 @@ describe("findAffectedBlockRange", () => { break; } } else { - let toLocation: Location = locateNaive(blocks, to - 1); + const toLocation = locateNaive(blocks, to - 1); let fromIndex: number | null; switch (fromLocation.type) { case "contained": diff --git a/test/components/App.tsx b/test/components/App.tsx index 47aac9a..35d9955 100644 --- a/test/components/App.tsx +++ b/test/components/App.tsx @@ -1,4 +1,4 @@ -import type {ViewPlugin} from "@codemirror/view"; +import type {PluginValue, ViewPlugin} from "@codemirror/view"; import {useAtom} from "jotai"; import {useEffect, useState} from "react"; import {Panel, PanelGroup, PanelResizeHandle} from "react-resizable-panels"; @@ -11,8 +11,8 @@ import {TransactionViewer} from "./TransactionViewer.tsx"; export function App() { const [selectedTest, setSelectedTest] = useAtom(selectedTestAtom); - const [transactionViewerPlugin, setTransactionViewerPlugin] = useState | undefined>(); - const [blockViewerPlugin, setBlockViewerPlugin] = useState | undefined>(); + const [transactionViewerPlugin, setTransactionViewerPlugin] = useState | undefined>(); + const [blockViewerPlugin, setBlockViewerPlugin] = useState | undefined>(); // Initialize from URL on mount useEffect(() => { diff --git a/test/components/BlockItem.tsx b/test/components/BlockItem.tsx index d5b6c85..1c6765c 100644 --- a/test/components/BlockItem.tsx +++ b/test/components/BlockItem.tsx @@ -1,4 +1,4 @@ -import {CircleXIcon, Locate, SquareTerminalIcon, TerminalIcon} from "lucide-react"; +import {CircleXIcon, Locate, SquareTerminalIcon} from "lucide-react"; import {useState} from "react"; import type {BlockData} from "./types.ts"; diff --git a/test/components/BlockViewer.tsx b/test/components/BlockViewer.tsx index 718925e..7826cb9 100644 --- a/test/components/BlockViewer.tsx +++ b/test/components/BlockViewer.tsx @@ -1,12 +1,12 @@ -import {EditorSelection} from "@codemirror/state"; -import {EditorView, ViewPlugin} from "@codemirror/view"; +import {EditorSelection, type Transaction} from "@codemirror/state"; +import {EditorView, ViewPlugin, ViewUpdate, type PluginValue} from "@codemirror/view"; import {useEffect, useRef, useState} from "react"; -import {blockMetadataField} from "../../editor/blockMetadata.ts"; +import {blockMetadataEffect, blockMetadataField} from "../../editor/blockMetadata.ts"; import type {BlockData} from "./types.ts"; import {BlockItem} from "./BlockItem.tsx"; interface BlockViewerProps { - onPluginCreate: (plugin: ViewPlugin) => void; + onPluginCreate: (plugin: ViewPlugin) => void; } export function BlockViewer({onPluginCreate}: BlockViewerProps) { @@ -23,11 +23,11 @@ export function BlockViewer({onPluginCreate}: BlockViewerProps) { listeners.forEach((fn) => fn([...currentBlocks])); } - function extractBlockData(view: any): BlockData[] { + function extractBlockData(view: EditorView): BlockData[] { const blockMetadata = view.state.field(blockMetadataField, false); if (!blockMetadata) return []; - return blockMetadata.map((block: any, index: number) => ({ + return blockMetadata.map((block, index) => ({ name: block.name, index, sourceFrom: block.source.from, @@ -41,17 +41,17 @@ export function BlockViewer({onPluginCreate}: BlockViewerProps) { const plugin = ViewPlugin.fromClass( class { - constructor(view: any) { + constructor(view: EditorView) { viewRef.current = view; currentBlocks = extractBlockData(view); notifyListeners(); } - update(update: any) { + update(update: ViewUpdate) { viewRef.current = update.view; if ( update.docChanged || - update.transactions.some((tr: any) => tr.effects.some((e: any) => e.is(blockMetadataField.init))) + update.transactions.some((tr: Transaction) => tr.effects.some((effect) => effect.is(blockMetadataEffect))) ) { currentBlocks = extractBlockData(update.view); notifyListeners(); diff --git a/test/components/Editor.tsx b/test/components/Editor.tsx index 708952b..a3b204c 100644 --- a/test/components/Editor.tsx +++ b/test/components/Editor.tsx @@ -2,13 +2,13 @@ import {useEffect, useRef, useState} from "react"; import {Play, Square, RefreshCcw} from "lucide-react"; import {createEditor} from "../../editor/index.js"; import {cn} from "../../app/cn.js"; -import type {ViewPlugin} from "@codemirror/view"; +import type {PluginValue, ViewPlugin} from "@codemirror/view"; interface EditorProps { className?: string; code: string; - transactionViewerPlugin?: ViewPlugin; - blockViewerPlugin?: ViewPlugin; + transactionViewerPlugin?: ViewPlugin; + blockViewerPlugin?: ViewPlugin; onError?: (error: Error) => void; } @@ -26,6 +26,13 @@ const onDefaultError = debounce(() => { }, 100); }, 0); +type EditorRuntime = { + run: () => void; + stop: () => void; + on: (event: unknown, callback: () => void) => unknown; + destroy: () => void; +}; + export function Editor({ className, code, @@ -34,7 +41,7 @@ export function Editor({ onError = onDefaultError, }: EditorProps) { const containerRef = useRef(null); - const editorRef = useRef(null); + const editorRef = useRef(null); const [needRerun, setNeedRerun] = useState(false); useEffect(() => { diff --git a/test/components/TestSelector.tsx b/test/components/TestSelector.tsx index 3dcd275..11bbeb1 100644 --- a/test/components/TestSelector.tsx +++ b/test/components/TestSelector.tsx @@ -1,5 +1,5 @@ -import { useAtom } from "jotai"; -import { selectedTestAtom, getTestSampleName } from "../store"; +import {useAtom} from "jotai"; +import {selectedTestAtom, getTestSampleName} from "../store.ts"; import * as testSamples from "../js/index.js"; export function TestSelector() { diff --git a/test/components/TransactionItem.tsx b/test/components/TransactionItem.tsx index fe00c3f..9219d6b 100644 --- a/test/components/TransactionItem.tsx +++ b/test/components/TransactionItem.tsx @@ -113,7 +113,7 @@ export function TransactionItem({transaction: tr}: {transaction: TransactionData {tr.blockMetadata ? (
blockMetadataEffect ({tr.blockMetadata.length} blocks)
- {tr.blockMetadata.map((block: any, blockIdx: number) => ( + {tr.blockMetadata.map((block, blockIdx) => (
Block {blockIdx + 1}:
diff --git a/test/components/TransactionViewer.tsx b/test/components/TransactionViewer.tsx index bb8737b..a7c44a6 100644 --- a/test/components/TransactionViewer.tsx +++ b/test/components/TransactionViewer.tsx @@ -90,7 +90,7 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { const plugin = ViewPlugin.fromClass( class { - constructor(view: any) { + constructor() { notifyListeners(); } diff --git a/test/main.tsx b/test/main.tsx index bb9e3e9..31df8df 100644 --- a/test/main.tsx +++ b/test/main.tsx @@ -1,7 +1,7 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import { Provider as JotaiProvider } from "jotai"; -import { App } from "./components/App"; +import {StrictMode} from "react"; +import {createRoot} from "react-dom/client"; +import {Provider as JotaiProvider} from "jotai"; +import {App} from "./components/App.tsx"; import "./styles.css"; const root = document.getElementById("root"); @@ -14,5 +14,5 @@ createRoot(root).render( - + , ); diff --git a/test/store.ts b/test/store.ts index 70c6836..276f1a1 100644 --- a/test/store.ts +++ b/test/store.ts @@ -1,10 +1,10 @@ -import { atom } from "jotai"; +import {atom} from "jotai"; // Types export interface Transaction { time: number; docChanged: boolean; - selection: { from: number; to: number } | null; + selection: {from: number; to: number} | null; effects: string[]; annotations: string[]; } @@ -23,7 +23,7 @@ export const autoScrollAtom = atom(true); export function createTransaction(data: { time: number; docChanged: boolean; - selection: { from: number; to: number } | null; + selection: {from: number; to: number} | null; effects: string[]; annotations: string[]; }): Transaction { @@ -36,10 +36,7 @@ export function createTransaction(data: { }; } -export function addTransaction( - history: Transaction[], - transaction: Transaction -): Transaction[] { +export function addTransaction(history: Transaction[], transaction: Transaction): Transaction[] { const newHistory = [...history, transaction]; // Keep max 100 transactions if (newHistory.length > 100) { diff --git a/test/types/css.d.ts b/test/types/css.d.ts new file mode 100644 index 0000000..ef6d741 --- /dev/null +++ b/test/types/css.d.ts @@ -0,0 +1 @@ +declare module "*.css" {} diff --git a/tsconfig.json b/tsconfig.json index c72a67b..6df60f3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,7 @@ "declaration": true, "declarationMap": true, // Stricter Typechecking Options - "noUncheckedIndexedAccess": true, + // "noUncheckedIndexedAccess": true, // "exactOptionalPropertyTypes": true, // Style Options // "noImplicitReturns": true, From da66991f5ecbafe571443f829f5a106035715629 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:53:30 +0800 Subject: [PATCH 19/30] Improve the transaction viewer --- test/components/Remote.tsx | 29 +++++++++++++++++++++++++++ test/components/TransactionItem.tsx | 5 +++-- test/components/TransactionViewer.tsx | 17 +++++----------- test/components/types.ts | 14 ++++++++----- 4 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 test/components/Remote.tsx diff --git a/test/components/Remote.tsx b/test/components/Remote.tsx new file mode 100644 index 0000000..87283f9 --- /dev/null +++ b/test/components/Remote.tsx @@ -0,0 +1,29 @@ +import {BadgeQuestionMarkIcon, HashIcon, RefreshCcw, type LucideIcon} from "lucide-react"; +import {cn} from "../../app/cn.js"; + +export function Remote({remoteValue}: {remoteValue: unknown}) { + let Icon: LucideIcon; + let className: string = ""; + let text: string; + switch (remoteValue) { + case true: + Icon = RefreshCcw; + text = "Runtime"; + className = "animate-pulse"; + break; + case "control.number": + Icon = HashIcon; + text = "recho.number"; + break; + default: + Icon = BadgeQuestionMarkIcon; + text = "Unknown Source"; + break; + } + return ( +
+ + {text} +
+ ); +} diff --git a/test/components/TransactionItem.tsx b/test/components/TransactionItem.tsx index 9219d6b..1502c9b 100644 --- a/test/components/TransactionItem.tsx +++ b/test/components/TransactionItem.tsx @@ -3,7 +3,8 @@ import type {TransactionData} from "./types.ts"; import {ObjectInspector} from "react-inspector"; import {cn} from "../../app/cn.js"; import {UserEvent} from "./UserEvent.tsx"; -import {PencilLineIcon, RefreshCcw} from "lucide-react"; +import {PencilLineIcon} from "lucide-react"; +import {Remote} from "./Remote.tsx"; export function TransactionItem({transaction: tr}: {transaction: TransactionData}) { const [isOpen, setIsOpen] = useState(false); @@ -31,7 +32,7 @@ export function TransactionItem({transaction: tr}: {transaction: TransactionData if (typeof tr.annotations.userEvent === "string") { summaryNodes.push(); } else if (tr.annotations.remote) { - summaryNodes.push(); + summaryNodes.push(); } if (tr.docChanged) { summaryNodes.push(); diff --git a/test/components/TransactionViewer.tsx b/test/components/TransactionViewer.tsx index a7c44a6..2fb4a8a 100644 --- a/test/components/TransactionViewer.tsx +++ b/test/components/TransactionViewer.tsx @@ -149,18 +149,11 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { if (currentGroup && currentGroup.type === "selection") { currentGroup.transactions!.push(tr); } else { - currentGroup = { - type: "selection", - transactions: [tr], - }; + currentGroup = {type: "selection", transactions: [tr]}; groups.push(currentGroup); } } else { - groups.push({ - type: "individual", - transaction: tr, - transactions: undefined, - }); + groups.push({type: "individual", transaction: tr}); currentGroup = null; } } @@ -203,11 +196,11 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { {transactions.length === 0 ? (
No transactions yet
) : ( - groupedTransactions.map((group, idx) => + groupedTransactions.map((group) => group.type === "individual" ? ( - + ) : ( - + ), ) )} diff --git a/test/components/types.ts b/test/components/types.ts index 1e0a86c..668f602 100644 --- a/test/components/types.ts +++ b/test/components/types.ts @@ -46,11 +46,15 @@ export interface TransactionData { blockMetadata: BlockMetadata[] | null; } -export interface TransactionGroup { - type: "individual" | "selection"; - transaction?: TransactionData; - transactions?: TransactionData[]; -} +export type TransactionGroup = + | { + type: "individual"; + transaction: TransactionData; + } + | { + type: "selection"; + transactions: TransactionData[]; + }; export interface TransactionViewerProps { onPluginCreate: (plugin: ViewPlugin) => void; From 858b6debd8a49671bafd4e3c77940fd279328653 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:54:17 +0800 Subject: [PATCH 20/30] Provide types for Observable's runtime --- types/observablehq__runtime.d.ts | 123 +++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 types/observablehq__runtime.d.ts diff --git a/types/observablehq__runtime.d.ts b/types/observablehq__runtime.d.ts new file mode 100644 index 0000000..c0518a9 --- /dev/null +++ b/types/observablehq__runtime.d.ts @@ -0,0 +1,123 @@ +declare module "@observablehq/runtime" { + // Error types + export class RuntimeError extends Error { + constructor(message: string, input?: string); + input?: string; + } + + // Observer interface + export interface Observer { + pending?(): void; + fulfilled?(value: T, name?: string | null): void; + rejected?(error: Error, name?: string | null): void; + } + + // Variable options + export interface VariableOptions { + shadow?: Record; + } + + // Variable definition function type + export type DefinitionFunction = (...inputs: any[]) => T | Promise | Generator; + + // Special symbols for module builtins + export const variable_variable: unique symbol; + export const variable_invalidation: unique symbol; + export const variable_visibility: unique symbol; + + // Variable class + export class Variable { + constructor( + type: number, + module: Module, + observer?: Observer | boolean, + options?: VariableOptions + ); + + // Define a variable with optional name, inputs, and definition + define(definition: DefinitionFunction | T): this; + define(name: string, definition: DefinitionFunction | T): this; + define(inputs: string[], definition: DefinitionFunction): this; + define(name: string | null, inputs: string[], definition: DefinitionFunction): this; + + // Import a variable from another module + import(remote: string, module: Module): this; + import(remote: string, name: string, module: Module): this; + + // Delete the variable + delete(): this; + + // Internal properties (readonly from external perspective) + readonly _name: string | null; + readonly _module: Module; + readonly _promise: Promise; + readonly _value: T | undefined; + readonly _version: number; + } + + // Module class + export class Module { + constructor(runtime: Runtime, builtins?: Array<[string, any]>); + + // Define a new variable + define(): Variable; + define(definition: DefinitionFunction): Variable; + define(name: string, definition: DefinitionFunction): Variable; + define(inputs: string[], definition: DefinitionFunction): Variable; + define(name: string | null, inputs: string[], definition: DefinitionFunction): Variable; + + // Redefine an existing variable + redefine(name: string): Variable; + redefine(name: string, definition: DefinitionFunction): Variable; + redefine(name: string, inputs: string[], definition: DefinitionFunction): Variable; + + // Import a variable from another module + import(): Variable; + import(remote: string, module: Module): Variable; + import(remote: string, name: string, module: Module): Variable; + + // Create a variable with an observer + variable(observer?: Observer | boolean, options?: VariableOptions): Variable; + + // Derive a new module with injected variables + derive( + injects: Array, + injectModule: Module + ): Module; + + // Get the value of a variable by name + value(name: string): Promise; + + // Add a builtin to the module + builtin(name: string, value: any): void; + + // Internal properties + readonly _runtime: Runtime; + readonly _scope: Map; + } + + // Runtime builtins type + export type RuntimeBuiltins = Record; + + // Runtime global function type + export type RuntimeGlobal = (name: string) => any; + + // Runtime class + export class Runtime { + constructor(builtins?: RuntimeBuiltins, global?: RuntimeGlobal); + + // Create a new module + module(): Module; + module( + define: (runtime: Runtime, observer: (variable: Variable) => Observer) => void, + observer?: (variable: Variable) => Observer + ): Module; + + // Dispose the runtime + dispose(): void; + + // Internal properties + readonly _builtin: Module; + readonly _modules: Map; + } +} From e1461c0131ae0e67299e0c21033689fa676ea03f Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 15 Dec 2025 04:14:20 +0800 Subject: [PATCH 21/30] Extract output related logic --- runtime/index.js | 119 ++++------------------------------------------ runtime/output.js | 112 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 111 deletions(-) create mode 100644 runtime/output.js diff --git a/runtime/index.js b/runtime/index.js index 8555746..9a8ee6d 100644 --- a/runtime/index.js +++ b/runtime/index.js @@ -1,30 +1,21 @@ import {transpileJavaScript} from "@observablehq/notebook-kit"; import {Runtime} from "@observablehq/runtime"; import {parse} from "acorn"; -import {group, groups, max} from "d3-array"; +import {group} from "d3-array"; import {dispatch as d3Dispatch} from "d3-dispatch"; import * as stdlib from "./stdlib/index.js"; import {Inspector} from "./stdlib/inspect.js"; -import {OUTPUT_MARK, ERROR_MARK} from "./constant.js"; import {BlockMetadata} from "../editor/blocks/BlockMetadata.ts"; import {blockMetadataEffect} from "../editor/blockMetadata.ts"; import {IntervalTree} from "../lib/IntervalTree.ts"; import {transpileRechoJavaScript} from "./transpile.js"; -import {table, getBorderCharacters} from "table"; import {ButtonRegistry, makeButton} from "./controls/button.js"; - -const OUTPUT_PREFIX = `//${OUTPUT_MARK}`; - -const ERROR_PREFIX = `//${ERROR_MARK}`; +import {addPrefix, makeOutput, OUTPUT_PREFIX, ERROR_PREFIX} from "./output.js"; function uid() { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); } -function isError(value) { - return value instanceof Error; -} - function safeEval(code, inputs, __setEcho__) { const create = (code) => { // Ensure the current echo function is bound for the executing cell. @@ -52,55 +43,6 @@ function debounce(fn, delay = 0) { }; } -function padStringWidth(string) { - const lines = string.split("\n"); - const maxLength = max(lines, (line) => line.length); - return lines.map((line) => line.padEnd(maxLength)).join("\n"); -} - -function padStringHeight(string, height) { - const lines = string.split("\n"); - const diff = height - lines.length; - return lines.join("\n") + "\n".repeat(diff); -} - -function merge(...strings) { - const maxHeight = max(strings, (string) => string.split("\n").length); - const aligned = strings - .map((string) => padStringHeight(string, maxHeight)) - .map((string) => padStringWidth(string)) - .map((string) => string.split("\n")); - let output = ""; - for (let i = 0; i < maxHeight; i++) { - for (let j = 0; j < aligned.length; j++) { - const line = aligned[j][i]; - output += line; - output += j < aligned.length - 1 ? " " : ""; - } - output += i < maxHeight - 1 ? "\n" : ""; - } - return output; -} - -function addPrefix(string, prefix) { - const lines = string.split("\n"); - return lines.map((line) => `${prefix} ${line}`).join("\n"); -} - -function withTable(groups) { - return groups.length > 1 || groups[0][0] !== undefined; -} - -function columns(data, options) { - const values = data[0].slice(1); - let output = ""; - for (let i = 0; i < values.length; i++) { - output += values[i]; - output += i < values.length - 1 ? "\n" : ""; - } - return output; -} - export function createRuntime(initialCode) { let code = initialCode; let prevCode = null; @@ -155,52 +97,7 @@ export function createRuntime(initialCode) { continue; } - // Group values by key. Each group is a row if using table, otherwise a column. - const groupValues = groups(values, (v) => v.options?.key); - - // We need to remove the trailing newline for table. - const format = withTable(groupValues) ? (...V) => table(...V).trimEnd() : columns; - - // If any value is an error, set the error flag. - let error = false; - - // Create a table to store the formatted values. - const data = []; - - for (const [key, V] of groupValues) { - const values = V.map((v) => v.values); - - // Format the key as a header for table. - const row = ["{" + key + "}"]; - - for (let i = 0; i < values.length; i++) { - const line = values[i]; - const n = line.length; - - // Each cell can have multiple values. Format each value as a string. - const items = line.map((v) => { - if (isError(v)) error = true; - - // Disable string quoting for multi-value outputs to improve readability. - // Example: echo("a =", 1) produces "a = 1" instead of "a = "1"" - const options = n === 1 ? {} : {quote: false}; - const inspector = v instanceof Inspector ? v : new Inspector(v, options); - return inspector.format(); - }); - - // Merge all formatted values into a single cell. - row.push(merge(...items)); - } - - data.push(row); - } - - // Format the table into a single string and add prefix. - const formatted = format(data, { - border: getBorderCharacters("ramac"), - columnDefault: {alignment: "right"}, - }); - const prefixed = addPrefix(formatted, error ? ERROR_PREFIX : OUTPUT_PREFIX); + const {output, error} = makeOutput(values); // The range of line numbers of output lines. let outputRange = null; @@ -210,22 +107,22 @@ export function createRuntime(initialCode) { // Entry not found. This is a new output. if (entry === null) { - changes.push({from: start, insert: prefixed + "\n"}); + changes.push({from: start, insert: output + "\n"}); outputRange = {from: start, to: start}; } else { const change = changes[entry.data]; - change.insert = prefixed + "\n"; + change.insert = output + "\n"; outputRange = {from: change.from, to: change.to}; } // Add this block to the block metadata array. - const block = new BlockMetadata(node.type, outputRange, {from: node.start, to: node.end}, node.state.attributes); + const block = new BlockMetadata(node.type, outputRange, sourceRange, node.state.attributes); block.error = error; blocks.push(block); - - blocks.sort((a, b) => a.from - b.from); } + blocks.sort((a, b) => a.from - b.from); + // Attach block positions and attributes as effects to the transaction. const effects = [blockMetadataEffect.of(blocks)]; diff --git a/runtime/output.js b/runtime/output.js new file mode 100644 index 0000000..2604995 --- /dev/null +++ b/runtime/output.js @@ -0,0 +1,112 @@ +import {groups, max} from "d3-array"; +import {getBorderCharacters, table} from "table"; +import {ERROR_MARK, OUTPUT_MARK} from "./constant.js"; +import {Inspector} from "./stdlib/inspect.js"; + +export const OUTPUT_PREFIX = `//${OUTPUT_MARK}`; + +export const ERROR_PREFIX = `//${ERROR_MARK}`; + +function isError(value) { + return value instanceof Error; +} + +export function addPrefix(string, prefix) { + const lines = string.split("\n"); + return lines.map((line) => `${prefix} ${line}`).join("\n"); +} + +function withTable(groups) { + return groups.length > 1 || groups[0][0] !== undefined; +} + +function columns(data, options) { + const values = data[0].slice(1); + let output = ""; + for (let i = 0; i < values.length; i++) { + output += values[i]; + output += i < values.length - 1 ? "\n" : ""; + } + return output; +} + +function padStringWidth(string) { + const lines = string.split("\n"); + const maxLength = max(lines, (line) => line.length); + return lines.map((line) => line.padEnd(maxLength)).join("\n"); +} + +function padStringHeight(string, height) { + const lines = string.split("\n"); + const diff = height - lines.length; + return lines.join("\n") + "\n".repeat(diff); +} + +function merge(...strings) { + const maxHeight = max(strings, (string) => string.split("\n").length); + const aligned = strings + .map((string) => padStringHeight(string, maxHeight)) + .map((string) => padStringWidth(string)) + .map((string) => string.split("\n")); + let output = ""; + for (let i = 0; i < maxHeight; i++) { + for (let j = 0; j < aligned.length; j++) { + const line = aligned[j][i]; + output += line; + output += j < aligned.length - 1 ? " " : ""; + } + output += i < maxHeight - 1 ? "\n" : ""; + } + return output; +} + +export function makeOutput(values) { + // Group values by key. Each group is a row if using table, otherwise a column. + const groupValues = groups(values, (v) => v.options?.key); + + // We need to remove the trailing newline for table. + const format = withTable(groupValues) ? (...V) => table(...V).trimEnd() : columns; + + // If any value is an error, set the error flag. + let error = false; + + // Create a table to store the formatted values. + const data = []; + + for (const [key, V] of groupValues) { + const values = V.map((v) => v.values); + + // Format the key as a header for table. + const row = ["{" + key + "}"]; + + for (let i = 0; i < values.length; i++) { + const line = values[i]; + const n = line.length; + + // Each cell can have multiple values. Format each value as a string. + const items = line.map((v) => { + if (isError(v)) error = true; + + // Disable string quoting for multi-value outputs to improve readability. + // Example: echo("a =", 1) produces "a = 1" instead of "a = "1"" + const options = n === 1 ? {} : {quote: false}; + const inspector = v instanceof Inspector ? v : new Inspector(v, options); + return inspector.format(); + }); + + // Merge all formatted values into a single cell. + row.push(merge(...items)); + } + + data.push(row); + } + + // Format the table into a single string and add prefix. + const formatted = format(data, { + border: getBorderCharacters("ramac"), + columnDefault: {alignment: "right"}, + }); + const prefixed = addPrefix(formatted, error ? ERROR_PREFIX : OUTPUT_PREFIX); + + return {output: prefixed, error}; +} From 12635fc0573b084fa7759278d4529bf480e620a2 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Mon, 15 Dec 2025 04:14:33 +0800 Subject: [PATCH 22/30] Support fast switching examples in the playground --- test/components/TestSelector.tsx | 67 +++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/test/components/TestSelector.tsx b/test/components/TestSelector.tsx index 11bbeb1..45a13a9 100644 --- a/test/components/TestSelector.tsx +++ b/test/components/TestSelector.tsx @@ -1,30 +1,67 @@ import {useAtom} from "jotai"; import {selectedTestAtom, getTestSampleName} from "../store.ts"; import * as testSamples from "../js/index.js"; +import {ChevronLeftIcon, ChevronRightIcon} from "lucide-react"; +import {useEffect, useMemo} from "react"; + +const tests = Object.keys(testSamples).toSorted(); export function TestSelector() { const [selectedTest, setSelectedTest] = useAtom(selectedTestAtom); - const handleChange = (e: React.ChangeEvent) => { - const newTest = e.target.value; - setSelectedTest(newTest); + const selectedIndex = useMemo(() => tests.indexOf(selectedTest), [selectedTest]); + + useEffect(() => { // Update URL const url = new URL(window.location.href); - url.searchParams.set("test", newTest); + url.searchParams.set("test", selectedTest); window.history.pushState({}, "", url); + }, [selectedTest]); + + const handleChange = (e: React.ChangeEvent) => { + const newTest = e.target.value; + setSelectedTest(newTest); + }; + + const handlePrevious = () => { + if (selectedIndex > 0) { + setSelectedTest(tests[selectedIndex - 1]); + } + }; + + const handleNext = () => { + if (selectedIndex < tests.length - 1) { + setSelectedTest(tests[selectedIndex + 1]); + } }; return ( - +
+ + + +
); } From 9b6c871fb124ae69c26dc19b08d393b1316181fa Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:28:37 +0800 Subject: [PATCH 23/30] Optimize the playground UI --- test/components/App.tsx | 4 ++-- test/components/BlockItem.tsx | 6 +++--- test/components/TransactionItem.tsx | 2 +- test/components/TransactionViewer.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/components/App.tsx b/test/components/App.tsx index 35d9955..9013ce6 100644 --- a/test/components/App.tsx +++ b/test/components/App.tsx @@ -48,13 +48,13 @@ export function App() { />
- + - + diff --git a/test/components/BlockItem.tsx b/test/components/BlockItem.tsx index 1c6765c..802fbb8 100644 --- a/test/components/BlockItem.tsx +++ b/test/components/BlockItem.tsx @@ -20,13 +20,13 @@ export function BlockItem({block, onLocate}: {block: BlockData; onLocate: (block : "border-gray-200 bg-white" }`} > - -
+ +
{block.index + 1} {block.hasError && } {hasOutput && !block.hasError && }
- {block.name} + {block.name}
[ diff --git a/test/components/TransactionItem.tsx b/test/components/TransactionItem.tsx index 1502c9b..2a73387 100644 --- a/test/components/TransactionItem.tsx +++ b/test/components/TransactionItem.tsx @@ -48,7 +48,7 @@ export function TransactionItem({transaction: tr}: {transaction: TransactionData tr.docChanged && "border-l-4 border-l-blue-500", )} > - + {summaryNodes} {formatTime(tr.timestamp)} diff --git a/test/components/TransactionViewer.tsx b/test/components/TransactionViewer.tsx index 2fb4a8a..68eb684 100644 --- a/test/components/TransactionViewer.tsx +++ b/test/components/TransactionViewer.tsx @@ -165,7 +165,7 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) {

Transactions

-
+
- {block.name} +
+ {block.name} + {block.id} +
[ @@ -38,7 +41,7 @@ export function BlockItem({block, onLocate}: {block: BlockData; onLocate: (block onClick={(e) => { e.preventDefault(); e.stopPropagation(); - onLocate(block); + onLocate(block.outputFrom ?? block.sourceFrom, block.sourceTo); }} className="p-1 hover:bg-gray-200 rounded transition-colors" title="Locate block in editor" @@ -48,22 +51,56 @@ export function BlockItem({block, onLocate}: {block: BlockData; onLocate: (block
-
- Source Range: {block.sourceFrom}-{block.sourceTo} - ({block.sourceTo - block.sourceFrom} chars) -
- {hasOutput ? ( -
+
Output Range: {block.outputFrom}-{block.outputTo} ({block.outputTo! - block.outputFrom!} chars) +
) : ( -
+
Output Range: none +
)} +
+ Source Range: {block.sourceFrom}-{block.sourceTo} + ({block.sourceTo - block.sourceFrom} chars) + +
+ +
Total Range: {hasOutput ? block.outputFrom : block.sourceFrom}-{block.sourceTo} diff --git a/test/components/BlockViewer.tsx b/test/components/BlockViewer.tsx index 7826cb9..e8353b4 100644 --- a/test/components/BlockViewer.tsx +++ b/test/components/BlockViewer.tsx @@ -1,75 +1,16 @@ -import {EditorSelection, type Transaction} from "@codemirror/state"; -import {EditorView, ViewPlugin, ViewUpdate, type PluginValue} from "@codemirror/view"; import {useEffect, useRef, useState} from "react"; -import {blockMetadataEffect, blockMetadataField} from "../../editor/blockMetadata.ts"; -import type {BlockData} from "./types.ts"; +import type {BlockData} from "./block-data.ts"; import {BlockItem} from "./BlockItem.tsx"; +import type { List } from "immutable"; interface BlockViewerProps { - onPluginCreate: (plugin: ViewPlugin) => void; + blocks: List; + onLocateBlock: (from: number, to: number) => void; } -export function BlockViewer({onPluginCreate}: BlockViewerProps) { - const [blocks, setBlocks] = useState([]); +export function BlockViewer({blocks, onLocateBlock}: BlockViewerProps) { const [autoScroll, setAutoScroll] = useState(true); const listRef = useRef(null); - const viewRef = useRef(null); - - useEffect(() => { - let currentBlocks: BlockData[] = []; - const listeners = new Set<(blocks: BlockData[]) => void>(); - - function notifyListeners() { - listeners.forEach((fn) => fn([...currentBlocks])); - } - - function extractBlockData(view: EditorView): BlockData[] { - const blockMetadata = view.state.field(blockMetadataField, false); - if (!blockMetadata) return []; - - return blockMetadata.map((block, index) => ({ - name: block.name, - index, - sourceFrom: block.source.from, - sourceTo: block.source.to, - outputFrom: block.output?.from ?? null, - outputTo: block.output?.to ?? null, - hasError: block.error || false, - attributes: block.attributes || {}, - })); - } - - const plugin = ViewPlugin.fromClass( - class { - constructor(view: EditorView) { - viewRef.current = view; - currentBlocks = extractBlockData(view); - notifyListeners(); - } - - update(update: ViewUpdate) { - viewRef.current = update.view; - if ( - update.docChanged || - update.transactions.some((tr: Transaction) => tr.effects.some((effect) => effect.is(blockMetadataEffect))) - ) { - currentBlocks = extractBlockData(update.view); - notifyListeners(); - } - } - }, - ); - - listeners.add((newBlocks) => { - setBlocks(newBlocks); - }); - - onPluginCreate(plugin); - - return () => { - listeners.clear(); - }; - }, [onPluginCreate]); useEffect(() => { if (autoScroll && listRef.current) { @@ -77,28 +18,10 @@ export function BlockViewer({onPluginCreate}: BlockViewerProps) { } }, [blocks, autoScroll]); - const handleLocateBlock = (block: BlockData) => { - if (!viewRef.current) return; - - const view = viewRef.current; - // The block starts at output.from (if output exists) or source.from - // The block ends at source.to - const from = block.outputFrom ?? block.sourceFrom; - const to = block.sourceTo; - - // Scroll the block into view and select the range - view.dispatch({ - effects: EditorView.scrollIntoView(from, {y: "center"}), - selection: EditorSelection.range(from, to), - }); - - view.focus(); - }; - return (
-

Blocks ({blocks.length})

+

Blocks ({blocks.size})

- {blocks.length === 0 ? ( + {blocks.size === 0 ? (
No blocks yet
) : ( - blocks.map((block) => ) + blocks.toSeq().map((block) => ).toArray() )}
diff --git a/test/components/TransactionViewer.tsx b/test/components/TransactionViewer.tsx index 68eb684..8ff85e1 100644 --- a/test/components/TransactionViewer.tsx +++ b/test/components/TransactionViewer.tsx @@ -1,138 +1,26 @@ import {useState, useEffect, useRef, useMemo} from "react"; -import {ViewPlugin, ViewUpdate} from "@codemirror/view"; -import {Transaction as Tr} from "@codemirror/state"; -import {blockMetadataEffect} from "../../editor/blockMetadata.ts"; import {SelectionGroupItem} from "./SelectionGroupItem.tsx"; -import type {TransactionData, TransactionGroup, TransactionViewerProps} from "./types.ts"; +import type {TransactionData, TransactionGroup} from "./transaction-data.ts"; import {TransactionItem} from "./TransactionItem.tsx"; +import type { List } from "immutable"; -// Maximum number of transactions to keep in history -const MAX_HISTORY = 100; - -function extractTransactionData(tr: Tr, index: number): TransactionData { - const data: TransactionData = { - index, - docChanged: tr.docChanged, - changes: [], - annotations: {}, - effects: [], - selection: tr.state.selection.ranges.map((r) => ({ - from: r.from, - to: r.to, - anchor: r.anchor, - head: r.head, - })), - scrollIntoView: tr.scrollIntoView, - timestamp: Date.now(), - blockMetadata: null, - }; - - // Extract changes - tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { - const fromLine = tr.startState.doc.lineAt(fromA); - const toLine = tr.startState.doc.lineAt(toA); - - data.changes.push({ - from: fromA, - to: toA, - fromLine: fromLine.number, - fromCol: fromA - fromLine.from, - toLine: toLine.number, - toCol: toA - toLine.from, - insert: inserted.toString(), - }); - }); - - // Extract annotations - const userEvent = tr.annotation(Tr.userEvent); - if (userEvent !== undefined) { - data.annotations.userEvent = userEvent; - } - - const remote = tr.annotation(Tr.remote); - if (remote !== undefined) { - data.annotations.remote = remote; - } - - const addToHistory = tr.annotation(Tr.addToHistory); - if (addToHistory !== undefined) { - data.annotations.addToHistory = addToHistory; - } - - for (const effect of tr.effects) { - if (effect.is(blockMetadataEffect)) { - data.blockMetadata = Array.from(effect.value); - } else { - data.effects.push({ - value: effect.value, - type: "StateEffect", - }); - } - } - - return data; +export type TransactionViewerProps = { + transactions: List; + onClear: () => void; } -export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { - const [transactions, setTransactions] = useState([]); + +export function TransactionViewer({transactions, onClear}: TransactionViewerProps) { const [autoScroll, setAutoScroll] = useState(true); const [showEffects, setShowEffects] = useState(false); const listRef = useRef(null); - const nextIndexRef = useRef(0); - - useEffect(() => { - const transactionsList: TransactionData[] = []; - const listeners = new Set<(transactions: TransactionData[]) => void>(); - - function notifyListeners() { - listeners.forEach((fn) => fn([...transactionsList])); - } - - const plugin = ViewPlugin.fromClass( - class { - constructor() { - notifyListeners(); - } - - update(update: ViewUpdate) { - update.transactions.forEach((tr: Tr) => { - const transactionData = extractTransactionData(tr, nextIndexRef.current++); - transactionsList.push(transactionData); - - if (transactionsList.length > MAX_HISTORY) { - transactionsList.shift(); - } - }); - - if (update.transactions.length > 0) { - notifyListeners(); - } - } - }, - ); - - listeners.add((newTransactions) => { - setTransactions(newTransactions); - }); - - onPluginCreate(plugin); - - return () => { - listeners.clear(); - }; - }, [onPluginCreate]); useEffect(() => { if (autoScroll && listRef.current) { - listRef.current.scrollTop = 0; + listRef.current.scrollTo({top: listRef.current.scrollHeight, behavior: "smooth"}); } }, [transactions, autoScroll]); - const handleClear = () => { - setTransactions([]); - nextIndexRef.current = 0; - }; - const filteredTransactions = useMemo(() => { return showEffects ? transactions : transactions.filter((tr) => tr.effects.length === 0); }, [transactions, showEffects]); @@ -140,14 +28,11 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { const groupedTransactions = useMemo(() => { const groups: TransactionGroup[] = []; let currentGroup: TransactionGroup | null = null; - - for (let i = filteredTransactions.length - 1; i >= 0; i--) { - const tr = filteredTransactions[i]!; + filteredTransactions.forEach((tr) => { const isSelection = tr.annotations.userEvent === "select" || tr.annotations.userEvent === "select.pointer"; - if (isSelection) { if (currentGroup && currentGroup.type === "selection") { - currentGroup.transactions!.push(tr); + currentGroup.transactions.push(tr); } else { currentGroup = {type: "selection", transactions: [tr]}; groups.push(currentGroup); @@ -156,8 +41,7 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) { groups.push({type: "individual", transaction: tr}); currentGroup = null; } - } - + }) return groups; }, [filteredTransactions]); @@ -167,7 +51,7 @@ export function TransactionViewer({onPluginCreate}: TransactionViewerProps) {

Transactions

- {transactions.length === 0 ? ( + {transactions.size === 0 ? (
No transactions yet
) : ( groupedTransactions.map((group) => diff --git a/test/components/block-data.ts b/test/components/block-data.ts new file mode 100644 index 0000000..81521b1 --- /dev/null +++ b/test/components/block-data.ts @@ -0,0 +1,31 @@ +import { blockMetadataField } from "../../editor/blockMetadata.ts"; +import type { EditorView } from "@codemirror/view"; + +export interface BlockData { + id: string; + name: string; + index: number; + sourceFrom: number; + sourceTo: number; + outputFrom: number | null; + outputTo: number | null; + hasError: boolean; + attributes: Record; +} + +export function extractBlockData(view: EditorView): BlockData[] { + const blockMetadata = view.state.field(blockMetadataField, false); + if (!blockMetadata) return []; + + return blockMetadata.map((block, index) => ({ + id: block.id, + name: block.name, + index, + sourceFrom: block.source.from, + sourceTo: block.source.to, + outputFrom: block.output?.from ?? null, + outputTo: block.output?.to ?? null, + hasError: block.error || false, + attributes: block.attributes || {}, + })); +} diff --git a/test/components/transaction-data.ts b/test/components/transaction-data.ts new file mode 100644 index 0000000..4bc17f4 --- /dev/null +++ b/test/components/transaction-data.ts @@ -0,0 +1,111 @@ +import { blockMetadataEffect } from "../../editor/blockMetadata.ts"; +import type { BlockMetadata } from "../../editor/blocks/BlockMetadata.ts"; +import { Transaction as Tr } from "@codemirror/state"; + +export interface TransactionRange { + from: number; + to: number; + anchor: number; + head: number; +} + +export interface TransactionChange { + from: number; + to: number; + fromLine: number; + fromCol: number; + toLine: number; + toCol: number; + insert: string; +} + +export interface TransactionEffect { + value: unknown; + type: string; +} + +export interface TransactionData { + index: number; + docChanged: boolean; + changes: TransactionChange[]; + annotations: Record; + effects: TransactionEffect[]; + selection: TransactionRange[]; + scrollIntoView?: boolean; + timestamp: number; + blockMetadata: BlockMetadata[] | null; +} + +export type TransactionGroup = + | { + type: "individual"; + transaction: TransactionData; + } + | { + type: "selection"; + transactions: TransactionData[]; + }; + +export function extractTransactionData(tr: Tr, index: number): TransactionData { + const data: TransactionData = { + index, + docChanged: tr.docChanged, + changes: [], + annotations: {}, + effects: [], + selection: tr.state.selection.ranges.map((r) => ({ + from: r.from, + to: r.to, + anchor: r.anchor, + head: r.head, + })), + scrollIntoView: tr.scrollIntoView, + timestamp: Date.now(), + blockMetadata: null, + }; + + // Extract changes + tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { + const fromLine = tr.startState.doc.lineAt(fromA); + const toLine = tr.startState.doc.lineAt(toA); + + data.changes.push({ + from: fromA, + to: toA, + fromLine: fromLine.number, + fromCol: fromA - fromLine.from, + toLine: toLine.number, + toCol: toA - toLine.from, + insert: inserted.toString(), + }); + }); + + // Extract annotations + const userEvent = tr.annotation(Tr.userEvent); + if (userEvent !== undefined) { + data.annotations.userEvent = userEvent; + } + + const remote = tr.annotation(Tr.remote); + if (remote !== undefined) { + data.annotations.remote = remote; + } + + const addToHistory = tr.annotation(Tr.addToHistory); + if (addToHistory !== undefined) { + data.annotations.addToHistory = addToHistory; + } + + for (const effect of tr.effects) { + if (effect.is(blockMetadataEffect)) { + data.blockMetadata = Array.from(effect.value); + } else { + data.effects.push({ + value: effect.value, + type: "StateEffect", + }); + } + } + + return data; +} \ No newline at end of file diff --git a/test/components/types.ts b/test/components/types.ts deleted file mode 100644 index 668f602..0000000 --- a/test/components/types.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type {PluginValue, ViewPlugin} from "@codemirror/view"; -import type {BlockMetadata} from "../../editor/blocks/BlockMetadata.ts"; - -export interface BlockData { - name: string; - index: number; - sourceFrom: number; - sourceTo: number; - outputFrom: number | null; - outputTo: number | null; - hasError: boolean; - attributes: Record; -} - -export interface TransactionRange { - from: number; - to: number; - anchor: number; - head: number; -} - -export interface TransactionChange { - from: number; - to: number; - fromLine: number; - fromCol: number; - toLine: number; - toCol: number; - insert: string; -} - -export interface TransactionEffect { - value: unknown; - type: string; -} - -export interface TransactionData { - index: number; - docChanged: boolean; - changes: TransactionChange[]; - annotations: Record; - effects: TransactionEffect[]; - selection: TransactionRange[]; - scrollIntoView?: boolean; - timestamp: number; - blockMetadata: BlockMetadata[] | null; -} - -export type TransactionGroup = - | { - type: "individual"; - transaction: TransactionData; - } - | { - type: "selection"; - transactions: TransactionData[]; - }; - -export interface TransactionViewerProps { - onPluginCreate: (plugin: ViewPlugin) => void; -} From 66047d6d2217639ec7d67572ee330be53bc70701 Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Thu, 18 Dec 2025 17:54:17 +0800 Subject: [PATCH 27/30] Fix code style --- editor/blockIndicator.ts | 12 +- lib/blocks/detect.ts | 2 +- next.config.js | 6 +- pnpm-lock.yaml | 5822 ++++++++++++++----------- runtime/index.js | 2 +- test/components/App.tsx | 12 +- test/components/BlockItem.tsx | 41 +- test/components/BlockViewer.tsx | 7 +- test/components/TransactionViewer.tsx | 7 +- test/components/block-data.ts | 4 +- test/components/transaction-data.ts | 8 +- types/observablehq__runtime.d.ts | 14 +- webpack-empty-module.js | 1 - 13 files changed, 3378 insertions(+), 2560 deletions(-) diff --git a/editor/blockIndicator.ts b/editor/blockIndicator.ts index 0369e4e..5510b62 100644 --- a/editor/blockIndicator.ts +++ b/editor/blockIndicator.ts @@ -3,7 +3,10 @@ import {BlockMetadata} from "./blocks/BlockMetadata.js"; import {blockMetadataField} from "./blockMetadata.ts"; export class BlockIndicator extends GutterMarker { - constructor(private className: string, private blockId: string | null = null) { + constructor( + private className: string, + private blockId: string | null = null, + ) { super(); } toDOM() { @@ -14,7 +17,10 @@ export class BlockIndicator extends GutterMarker { } } -const constant = (x: T) => (): T => x; +const constant = + (x: T) => + (): T => + x; const indicatorMarkers = { output: { @@ -41,7 +47,7 @@ export const blockIndicator = gutter({ class: "cm-blockIndicators", /** * Create a gutter marker for the given line. - * + * * @param view the editor view * @param line a record used to represent information about a block-level element * in the editor view. diff --git a/lib/blocks/detect.ts b/lib/blocks/detect.ts index 47d896e..eb252f6 100644 --- a/lib/blocks/detect.ts +++ b/lib/blocks/detect.ts @@ -124,7 +124,7 @@ export function detectBlocksWithinRange(tree: Tree, doc: Text, from: number, to: // Build block metadata from statements for (const range of statementRanges) { - blocks.push(new BlockMetadata(nanoid(),range.name, outputRanges.get(range.from) ?? null, range)); + blocks.push(new BlockMetadata(nanoid(), range.name, outputRanges.get(range.from) ?? null, range)); } return blocks; diff --git a/next.config.js b/next.config.js index ad7e546..b24c418 100644 --- a/next.config.js +++ b/next.config.js @@ -20,17 +20,17 @@ const nextConfig = { // Replace node: scheme imports with empty module stubs // eslint-linter-browserify references these but they're not needed in browser const emptyModulePath = path.resolve(__dirname, "webpack-empty-module.js"); - + config.plugins.push( new webpack.NormalModuleReplacementPlugin(/^node:path$/, (resource) => { resource.request = emptyModulePath; - }) + }), ); config.plugins.push( new webpack.NormalModuleReplacementPlugin(/^node:util$/, (resource) => { resource.request = emptyModulePath; - }) + }), ); } return config; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 547b4a7..f025433 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,59 +1,58 @@ -lockfileVersion: '9.0' +lockfileVersion: "9.0" settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: - .: dependencies: - '@babel/runtime': + "@babel/runtime": specifier: ^7.28.4 version: 7.28.4 - '@codemirror/autocomplete': + "@codemirror/autocomplete": specifier: ^6.20.0 version: 6.20.0 - '@codemirror/commands': + "@codemirror/commands": specifier: ^6.10.0 version: 6.10.0 - '@codemirror/lang-javascript': + "@codemirror/lang-javascript": specifier: ^6.2.4 version: 6.2.4 - '@codemirror/language': + "@codemirror/language": specifier: ^6.11.3 version: 6.11.3 - '@codemirror/language-data': + "@codemirror/language-data": specifier: ^6.5.2 version: 6.5.2 - '@codemirror/lint': + "@codemirror/lint": specifier: ^6.9.2 version: 6.9.2 - '@codemirror/state': + "@codemirror/state": specifier: ^6.5.2 version: 6.5.2 - '@codemirror/view': + "@codemirror/view": specifier: ^6.39.4 version: 6.39.4 - '@fontsource-variable/inter': + "@fontsource-variable/inter": specifier: ^5.2.8 version: 5.2.8 - '@fontsource-variable/spline-sans-mono': + "@fontsource-variable/spline-sans-mono": specifier: ^5.2.8 version: 5.2.8 - '@lezer/highlight': + "@lezer/highlight": specifier: ^1.2.3 version: 1.2.3 - '@lezer/javascript': + "@lezer/javascript": specifier: ^1.5.4 version: 1.5.4 - '@observablehq/notebook-kit': + "@observablehq/notebook-kit": specifier: ^1.5.0 version: 1.5.0(@types/markdown-it@14.1.2)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) - '@observablehq/runtime': + "@observablehq/runtime": specifier: ^6.0.0 version: 6.0.0 - '@uiw/codemirror-theme-github': + "@uiw/codemirror-theme-github": specifier: ^4.25.3 version: 4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4) acorn: @@ -117,28 +116,28 @@ importers: specifier: ^6.9.0 version: 6.9.0 devDependencies: - '@tailwindcss/postcss': + "@tailwindcss/postcss": specifier: ^4.1.14 version: 4.1.17 - '@types/node': + "@types/node": specifier: 24.10.1 version: 24.10.1 - '@types/react': + "@types/react": specifier: 19.2.6 version: 19.2.6 - '@types/react-dom': + "@types/react-dom": specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.6) - '@typescript-eslint/eslint-plugin': + "@typescript-eslint/eslint-plugin": specifier: ^8.49.0 version: 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': + "@typescript-eslint/parser": specifier: ^8.49.0 version: 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@vercel/analytics': + "@vercel/analytics": specifier: ^1.6.1 version: 1.6.1(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) - '@vitejs/plugin-react': + "@vitejs/plugin-react": specifier: ^4.3.1 version: 4.7.0(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) clsx: @@ -200,1137 +199,1386 @@ importers: version: 3.2.4(@edge-runtime/vm@3.2.0)(@types/node@24.10.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2) packages: - - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@asamuzakjp/css-color@3.2.0': - resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} - engines: {node: '>=6.9.0'} + "@alloc/quick-lru@5.2.0": + resolution: + {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: ">=10"} + + "@asamuzakjp/css-color@3.2.0": + resolution: + {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + + "@babel/code-frame@7.27.1": + resolution: + {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: ">=6.9.0"} + + "@babel/compat-data@7.28.5": + resolution: + {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: ">=6.9.0"} + + "@babel/core@7.28.5": + resolution: + {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: ">=6.9.0"} + + "@babel/generator@7.28.5": + resolution: + {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: ">=6.9.0"} + + "@babel/helper-compilation-targets@7.27.2": + resolution: + {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: ">=6.9.0"} + + "@babel/helper-globals@7.28.0": + resolution: + {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: ">=6.9.0"} + + "@babel/helper-module-imports@7.27.1": + resolution: + {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: ">=6.9.0"} + + "@babel/helper-module-transforms@7.28.3": + resolution: + {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: ">=6.9.0"} peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} - engines: {node: '>=6.0.0'} + "@babel/core": ^7.0.0 + + "@babel/helper-plugin-utils@7.27.1": + resolution: + {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: ">=6.9.0"} + + "@babel/helper-string-parser@7.27.1": + resolution: + {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: ">=6.9.0"} + + "@babel/helper-validator-identifier@7.28.5": + resolution: + {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: ">=6.9.0"} + + "@babel/helper-validator-option@7.27.1": + resolution: + {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: ">=6.9.0"} + + "@babel/helpers@7.28.4": + resolution: + {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: ">=6.9.0"} + + "@babel/parser@7.28.5": + resolution: + {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: ">=6.0.0"} hasBin: true - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} + "@babel/plugin-transform-react-jsx-self@7.27.1": + resolution: + {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: ">=6.9.0"} peerDependencies: - '@babel/core': ^7.0.0-0 + "@babel/core": ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} + "@babel/plugin-transform-react-jsx-source@7.27.1": + resolution: + {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: ">=6.9.0"} peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} - engines: {node: '>=6.9.0'} - - '@codemirror/autocomplete@6.20.0': - resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} - - '@codemirror/commands@6.10.0': - resolution: {integrity: sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==} - - '@codemirror/lang-angular@0.1.4': - resolution: {integrity: sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==} - - '@codemirror/lang-cpp@6.0.3': - resolution: {integrity: sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==} - - '@codemirror/lang-css@6.3.1': - resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} - - '@codemirror/lang-go@6.0.1': - resolution: {integrity: sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==} - - '@codemirror/lang-html@6.4.11': - resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==} - - '@codemirror/lang-java@6.0.2': - resolution: {integrity: sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==} + "@babel/core": ^7.0.0-0 - '@codemirror/lang-javascript@6.2.4': - resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==} + "@babel/runtime@7.28.4": + resolution: + {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: ">=6.9.0"} - '@codemirror/lang-jinja@6.0.0': - resolution: {integrity: sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==} + "@babel/template@7.27.2": + resolution: + {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: ">=6.9.0"} + + "@babel/traverse@7.28.5": + resolution: + {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: ">=6.9.0"} + + "@babel/types@7.28.5": + resolution: + {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: ">=6.9.0"} - '@codemirror/lang-json@6.0.2': - resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} + "@codemirror/autocomplete@6.20.0": + resolution: + {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} - '@codemirror/lang-less@6.0.2': - resolution: {integrity: sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==} + "@codemirror/commands@6.10.0": + resolution: + {integrity: sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==} - '@codemirror/lang-liquid@6.3.0': - resolution: {integrity: sha512-fY1YsUExcieXRTsCiwX/bQ9+PbCTA/Fumv7C7mTUZHoFkibfESnaXwpr2aKH6zZVwysEunsHHkaIpM/pl3xETQ==} + "@codemirror/lang-angular@0.1.4": + resolution: + {integrity: sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==} - '@codemirror/lang-markdown@6.5.0': - resolution: {integrity: sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==} + "@codemirror/lang-cpp@6.0.3": + resolution: + {integrity: sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==} - '@codemirror/lang-php@6.0.2': - resolution: {integrity: sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==} + "@codemirror/lang-css@6.3.1": + resolution: + {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} - '@codemirror/lang-python@6.2.1': - resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==} + "@codemirror/lang-go@6.0.1": + resolution: + {integrity: sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==} + + "@codemirror/lang-html@6.4.11": + resolution: + {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==} + + "@codemirror/lang-java@6.0.2": + resolution: + {integrity: sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==} + + "@codemirror/lang-javascript@6.2.4": + resolution: + {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==} + + "@codemirror/lang-jinja@6.0.0": + resolution: + {integrity: sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==} + + "@codemirror/lang-json@6.0.2": + resolution: + {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} + + "@codemirror/lang-less@6.0.2": + resolution: + {integrity: sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==} + + "@codemirror/lang-liquid@6.3.0": + resolution: + {integrity: sha512-fY1YsUExcieXRTsCiwX/bQ9+PbCTA/Fumv7C7mTUZHoFkibfESnaXwpr2aKH6zZVwysEunsHHkaIpM/pl3xETQ==} + + "@codemirror/lang-markdown@6.5.0": + resolution: + {integrity: sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==} + + "@codemirror/lang-php@6.0.2": + resolution: + {integrity: sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==} + + "@codemirror/lang-python@6.2.1": + resolution: + {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==} + + "@codemirror/lang-rust@6.0.2": + resolution: + {integrity: sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==} + + "@codemirror/lang-sass@6.0.2": + resolution: + {integrity: sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==} + + "@codemirror/lang-sql@6.10.0": + resolution: + {integrity: sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==} + + "@codemirror/lang-vue@0.1.3": + resolution: + {integrity: sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==} + + "@codemirror/lang-wast@6.0.2": + resolution: + {integrity: sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==} + + "@codemirror/lang-xml@6.1.0": + resolution: + {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==} + + "@codemirror/lang-yaml@6.1.2": + resolution: + {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==} + + "@codemirror/language-data@6.5.2": + resolution: + {integrity: sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==} + + "@codemirror/language@6.11.3": + resolution: + {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} - '@codemirror/lang-rust@6.0.2': - resolution: {integrity: sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==} + "@codemirror/legacy-modes@6.5.2": + resolution: + {integrity: sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==} - '@codemirror/lang-sass@6.0.2': - resolution: {integrity: sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==} + "@codemirror/lint@6.9.2": + resolution: + {integrity: sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==} - '@codemirror/lang-sql@6.10.0': - resolution: {integrity: sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==} + "@codemirror/search@6.5.11": + resolution: + {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==} - '@codemirror/lang-vue@0.1.3': - resolution: {integrity: sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==} + "@codemirror/state@6.5.2": + resolution: + {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} - '@codemirror/lang-wast@6.0.2': - resolution: {integrity: sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==} + "@codemirror/view@6.39.4": + resolution: + {integrity: sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==} - '@codemirror/lang-xml@6.1.0': - resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==} - - '@codemirror/lang-yaml@6.1.2': - resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==} - - '@codemirror/language-data@6.5.2': - resolution: {integrity: sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==} - - '@codemirror/language@6.11.3': - resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} - - '@codemirror/legacy-modes@6.5.2': - resolution: {integrity: sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==} - - '@codemirror/lint@6.9.2': - resolution: {integrity: sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==} - - '@codemirror/search@6.5.11': - resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==} - - '@codemirror/state@6.5.2': - resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} - - '@codemirror/view@6.39.4': - resolution: {integrity: sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==} - - '@csstools/color-helpers@5.1.0': - resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} - engines: {node: '>=18'} - - '@csstools/css-calc@2.1.4': - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} - engines: {node: '>=18'} + "@csstools/color-helpers@5.1.0": + resolution: + {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: ">=18"} + + "@csstools/css-calc@2.1.4": + resolution: + {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: ">=18"} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 - '@csstools/css-color-parser@3.1.0': - resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} - engines: {node: '>=18'} + "@csstools/css-color-parser@3.1.0": + resolution: + {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: ">=18"} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 - '@csstools/css-parser-algorithms@3.0.5': - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} - engines: {node: '>=18'} + "@csstools/css-parser-algorithms@3.0.5": + resolution: + {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: ">=18"} peerDependencies: - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} - engines: {node: '>=18'} - - '@edge-runtime/primitives@4.1.0': - resolution: {integrity: sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==} - engines: {node: '>=16'} - - '@edge-runtime/vm@3.2.0': - resolution: {integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==} - engines: {node: '>=16'} - - '@emnapi/runtime@1.7.1': - resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} - - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} - engines: {node: '>=18'} + "@csstools/css-tokenizer": ^3.0.4 + + "@csstools/css-tokenizer@3.0.4": + resolution: + {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: ">=18"} + + "@edge-runtime/primitives@4.1.0": + resolution: + {integrity: sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==} + engines: {node: ">=16"} + + "@edge-runtime/vm@3.2.0": + resolution: + {integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==} + engines: {node: ">=16"} + + "@emnapi/runtime@1.7.1": + resolution: + {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + + "@esbuild/aix-ppc64@0.25.12": + resolution: + {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: ">=18"} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} + "@esbuild/android-arm64@0.25.12": + resolution: + {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: ">=18"} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} + "@esbuild/android-arm@0.25.12": + resolution: + {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: ">=18"} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} + "@esbuild/android-x64@0.25.12": + resolution: + {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: ">=18"} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} + "@esbuild/darwin-arm64@0.25.12": + resolution: + {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: ">=18"} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} + "@esbuild/darwin-x64@0.25.12": + resolution: + {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: ">=18"} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} + "@esbuild/freebsd-arm64@0.25.12": + resolution: + {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: ">=18"} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} + "@esbuild/freebsd-x64@0.25.12": + resolution: + {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: ">=18"} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} + "@esbuild/linux-arm64@0.25.12": + resolution: + {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: ">=18"} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} + "@esbuild/linux-arm@0.25.12": + resolution: + {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: ">=18"} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} + "@esbuild/linux-ia32@0.25.12": + resolution: + {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: ">=18"} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} + "@esbuild/linux-loong64@0.25.12": + resolution: + {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: ">=18"} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} + "@esbuild/linux-mips64el@0.25.12": + resolution: + {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: ">=18"} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} + "@esbuild/linux-ppc64@0.25.12": + resolution: + {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: ">=18"} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} + "@esbuild/linux-riscv64@0.25.12": + resolution: + {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: ">=18"} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} + "@esbuild/linux-s390x@0.25.12": + resolution: + {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: ">=18"} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} + "@esbuild/linux-x64@0.25.12": + resolution: + {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: ">=18"} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} - engines: {node: '>=18'} + "@esbuild/netbsd-arm64@0.25.12": + resolution: + {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: ">=18"} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} + "@esbuild/netbsd-x64@0.25.12": + resolution: + {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: ">=18"} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} - engines: {node: '>=18'} + "@esbuild/openbsd-arm64@0.25.12": + resolution: + {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: ">=18"} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} + "@esbuild/openbsd-x64@0.25.12": + resolution: + {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: ">=18"} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} - engines: {node: '>=18'} + "@esbuild/openharmony-arm64@0.25.12": + resolution: + {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: ">=18"} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} + "@esbuild/sunos-x64@0.25.12": + resolution: + {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: ">=18"} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} + "@esbuild/win32-arm64@0.25.12": + resolution: + {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: ">=18"} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} + "@esbuild/win32-ia32@0.25.12": + resolution: + {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: ">=18"} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} + "@esbuild/win32-x64@0.25.12": + resolution: + {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: ">=18"} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.0': - resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + "@eslint-community/eslint-utils@4.9.0": + resolution: + {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + "@eslint-community/regexpp@4.12.2": + resolution: + {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.1': - resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + "@eslint/config-array@0.21.1": + resolution: + {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + "@eslint/config-helpers@0.4.2": + resolution: + {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + "@eslint/core@0.17.0": + resolution: + {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.3': - resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + "@eslint/eslintrc@3.3.3": + resolution: + {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.1': - resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + "@eslint/js@9.39.1": + resolution: + {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + "@eslint/object-schema@2.1.7": + resolution: + {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + "@eslint/plugin-kit@0.4.1": + resolution: + {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@floating-ui/core@1.7.3': - resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} - - '@floating-ui/dom@1.7.4': - resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} - - '@floating-ui/utils@0.2.10': - resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - - '@fontsource-variable/inter@5.2.8': - resolution: {integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==} - - '@fontsource-variable/spline-sans-mono@5.2.8': - resolution: {integrity: sha512-Z1yYiEtP+8Gll5BJaR9dICK/ZTxoTWH+ZBH6rOTMWajPSOxwl7IZplkwX2J1QhMxqulhhdMrkRx6SkkLM+jLQw==} - - '@fontsource/inter@5.2.8': - resolution: {integrity: sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==} - - '@fontsource/source-serif-4@5.2.9': - resolution: {integrity: sha512-er/Pym9emsEVJNf947umJ4kXarXfsiN6CN7kTYinefKRaHLwiquiiHOZvKvxWgkV8JMCf3pV3g0NcsPFpVCH9w==} - - '@fontsource/spline-sans-mono@5.2.8': - resolution: {integrity: sha512-lxHnpGHjPGPXpv+H0SwDZxUkbYrpqSlUBfe1f/ScERQcKhLbQxDMzd7TpRPqIVWC/Ze2WLu0vcOBuL1I8BD5tQ==} - - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - - '@img/colour@1.0.0': - resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + "@floating-ui/core@1.7.3": + resolution: + {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + "@floating-ui/dom@1.7.4": + resolution: + {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + "@floating-ui/utils@0.2.10": + resolution: + {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + "@fontsource-variable/inter@5.2.8": + resolution: + {integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==} + + "@fontsource-variable/spline-sans-mono@5.2.8": + resolution: + {integrity: sha512-Z1yYiEtP+8Gll5BJaR9dICK/ZTxoTWH+ZBH6rOTMWajPSOxwl7IZplkwX2J1QhMxqulhhdMrkRx6SkkLM+jLQw==} + + "@fontsource/inter@5.2.8": + resolution: + {integrity: sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==} + + "@fontsource/source-serif-4@5.2.9": + resolution: + {integrity: sha512-er/Pym9emsEVJNf947umJ4kXarXfsiN6CN7kTYinefKRaHLwiquiiHOZvKvxWgkV8JMCf3pV3g0NcsPFpVCH9w==} + + "@fontsource/spline-sans-mono@5.2.8": + resolution: + {integrity: sha512-lxHnpGHjPGPXpv+H0SwDZxUkbYrpqSlUBfe1f/ScERQcKhLbQxDMzd7TpRPqIVWC/Ze2WLu0vcOBuL1I8BD5tQ==} + + "@humanfs/core@0.19.1": + resolution: + {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: ">=18.18.0"} + + "@humanfs/node@0.16.7": + resolution: + {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: ">=18.18.0"} + + "@humanwhocodes/module-importer@1.0.1": + resolution: + {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: ">=12.22"} + + "@humanwhocodes/retry@0.4.3": + resolution: + {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: ">=18.18"} + + "@img/colour@1.0.0": + resolution: + {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: ">=18"} + + "@img/sharp-darwin-arm64@0.34.5": + resolution: + {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + "@img/sharp-darwin-x64@0.34.5": + resolution: + {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + "@img/sharp-libvips-darwin-arm64@1.2.4": + resolution: + {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + "@img/sharp-libvips-darwin-x64@1.2.4": + resolution: + {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + "@img/sharp-libvips-linux-arm64@1.2.4": + resolution: + {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + "@img/sharp-libvips-linux-arm@1.2.4": + resolution: + {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + "@img/sharp-libvips-linux-ppc64@1.2.4": + resolution: + {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + "@img/sharp-libvips-linux-riscv64@1.2.4": + resolution: + {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + "@img/sharp-libvips-linux-s390x@1.2.4": + resolution: + {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + "@img/sharp-libvips-linux-x64@1.2.4": + resolution: + {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + "@img/sharp-libvips-linuxmusl-arm64@1.2.4": + resolution: + {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + "@img/sharp-libvips-linuxmusl-x64@1.2.4": + resolution: + {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + "@img/sharp-linux-arm64@0.34.5": + resolution: + {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + "@img/sharp-linux-arm@0.34.5": + resolution: + {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + "@img/sharp-linux-ppc64@0.34.5": + resolution: + {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + "@img/sharp-linux-riscv64@0.34.5": + resolution: + {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + "@img/sharp-linux-s390x@0.34.5": + resolution: + {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + "@img/sharp-linux-x64@0.34.5": + resolution: + {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + "@img/sharp-linuxmusl-arm64@0.34.5": + resolution: + {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + "@img/sharp-linuxmusl-x64@0.34.5": + resolution: + {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + "@img/sharp-wasm32@0.34.5": + resolution: + {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + "@img/sharp-win32-arm64@0.34.5": + resolution: + {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + "@img/sharp-win32-ia32@0.34.5": + resolution: + {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + "@img/sharp-win32-x64@0.34.5": + resolution: + {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + "@jridgewell/gen-mapping@0.3.13": + resolution: + {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + "@jridgewell/remapping@2.3.5": + resolution: + {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} + "@jridgewell/resolve-uri@3.1.2": + resolution: + {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: ">=6.0.0"} - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + "@jridgewell/sourcemap-codec@1.5.5": + resolution: + {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + "@jridgewell/trace-mapping@0.3.31": + resolution: + {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@lezer/common@1.4.0': - resolution: {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==} + "@lezer/common@1.4.0": + resolution: + {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==} - '@lezer/cpp@1.1.4': - resolution: {integrity: sha512-aYSdZyUueeTgnfXQntiGUqKNW5WujlAsIbbHzkfJDneSZoyjPg8ObmWG3bzDPVYMC/Kf4l43WJLCunPnYFfQ0g==} + "@lezer/cpp@1.1.4": + resolution: + {integrity: sha512-aYSdZyUueeTgnfXQntiGUqKNW5WujlAsIbbHzkfJDneSZoyjPg8ObmWG3bzDPVYMC/Kf4l43WJLCunPnYFfQ0g==} - '@lezer/css@1.3.0': - resolution: {integrity: sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==} + "@lezer/css@1.3.0": + resolution: + {integrity: sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==} - '@lezer/go@1.0.1': - resolution: {integrity: sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==} + "@lezer/go@1.0.1": + resolution: + {integrity: sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==} - '@lezer/highlight@1.2.3': - resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + "@lezer/highlight@1.2.3": + resolution: + {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} - '@lezer/html@1.3.12': - resolution: {integrity: sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==} + "@lezer/html@1.3.12": + resolution: + {integrity: sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==} - '@lezer/java@1.1.3': - resolution: {integrity: sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==} + "@lezer/java@1.1.3": + resolution: + {integrity: sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==} - '@lezer/javascript@1.5.4': - resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} + "@lezer/javascript@1.5.4": + resolution: + {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} - '@lezer/json@1.0.3': - resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + "@lezer/json@1.0.3": + resolution: + {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} - '@lezer/lr@1.4.5': - resolution: {integrity: sha512-/YTRKP5yPPSo1xImYQk7AZZMAgap0kegzqCSYHjAL9x1AZ0ZQW+IpcEzMKagCsbTsLnVeWkxYrCNeXG8xEPrjg==} + "@lezer/lr@1.4.5": + resolution: + {integrity: sha512-/YTRKP5yPPSo1xImYQk7AZZMAgap0kegzqCSYHjAL9x1AZ0ZQW+IpcEzMKagCsbTsLnVeWkxYrCNeXG8xEPrjg==} - '@lezer/markdown@1.6.1': - resolution: {integrity: sha512-72ah+Sml7lD8Wn7lnz9vwYmZBo9aQT+I2gjK/0epI+gjdwUbWw3MJ/ZBGEqG1UfrIauRqH37/c5mVHXeCTGXtA==} + "@lezer/markdown@1.6.1": + resolution: + {integrity: sha512-72ah+Sml7lD8Wn7lnz9vwYmZBo9aQT+I2gjK/0epI+gjdwUbWw3MJ/ZBGEqG1UfrIauRqH37/c5mVHXeCTGXtA==} - '@lezer/php@1.0.5': - resolution: {integrity: sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==} + "@lezer/php@1.0.5": + resolution: + {integrity: sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==} - '@lezer/python@1.1.18': - resolution: {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==} + "@lezer/python@1.1.18": + resolution: + {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==} - '@lezer/rust@1.0.2': - resolution: {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==} + "@lezer/rust@1.0.2": + resolution: + {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==} - '@lezer/sass@1.1.0': - resolution: {integrity: sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==} + "@lezer/sass@1.1.0": + resolution: + {integrity: sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==} - '@lezer/xml@1.0.6': - resolution: {integrity: sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==} + "@lezer/xml@1.0.6": + resolution: + {integrity: sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==} - '@lezer/yaml@1.0.3': - resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==} + "@lezer/yaml@1.0.3": + resolution: + {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==} - '@marijn/find-cluster-break@1.0.2': - resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + "@marijn/find-cluster-break@1.0.2": + resolution: + {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} - '@next/env@15.5.7': - resolution: {integrity: sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==} + "@next/env@15.5.7": + resolution: + {integrity: sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==} - '@next/swc-darwin-arm64@15.5.7': - resolution: {integrity: sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==} - engines: {node: '>= 10'} + "@next/swc-darwin-arm64@15.5.7": + resolution: + {integrity: sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==} + engines: {node: ">= 10"} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.5.7': - resolution: {integrity: sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==} - engines: {node: '>= 10'} + "@next/swc-darwin-x64@15.5.7": + resolution: + {integrity: sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==} + engines: {node: ">= 10"} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.5.7': - resolution: {integrity: sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==} - engines: {node: '>= 10'} + "@next/swc-linux-arm64-gnu@15.5.7": + resolution: + {integrity: sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==} + engines: {node: ">= 10"} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.5.7': - resolution: {integrity: sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==} - engines: {node: '>= 10'} + "@next/swc-linux-arm64-musl@15.5.7": + resolution: + {integrity: sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==} + engines: {node: ">= 10"} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.5.7': - resolution: {integrity: sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==} - engines: {node: '>= 10'} + "@next/swc-linux-x64-gnu@15.5.7": + resolution: + {integrity: sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==} + engines: {node: ">= 10"} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.5.7': - resolution: {integrity: sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==} - engines: {node: '>= 10'} + "@next/swc-linux-x64-musl@15.5.7": + resolution: + {integrity: sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==} + engines: {node: ">= 10"} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.5.7': - resolution: {integrity: sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==} - engines: {node: '>= 10'} + "@next/swc-win32-arm64-msvc@15.5.7": + resolution: + {integrity: sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==} + engines: {node: ">= 10"} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.5.7': - resolution: {integrity: sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==} - engines: {node: '>= 10'} + "@next/swc-win32-x64-msvc@15.5.7": + resolution: + {integrity: sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==} + engines: {node: ">= 10"} cpu: [x64] os: [win32] - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + "@nodelib/fs.scandir@2.1.5": + resolution: + {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: ">= 8"} - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + "@nodelib/fs.stat@2.0.5": + resolution: + {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: ">= 8"} - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + "@nodelib/fs.walk@1.2.8": + resolution: + {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: ">= 8"} - '@observablehq/inspector@5.0.1': - resolution: {integrity: sha512-euwWxwDa6KccU4G3D2JBD7GI/2McJh/z7HHEzJKbj2TDa7zhI37eTbTxiwE9rgTWBagvVBel+hAmnJRYBYOv2Q==} + "@observablehq/inspector@5.0.1": + resolution: + {integrity: sha512-euwWxwDa6KccU4G3D2JBD7GI/2McJh/z7HHEzJKbj2TDa7zhI37eTbTxiwE9rgTWBagvVBel+hAmnJRYBYOv2Q==} - '@observablehq/notebook-kit@1.5.0': - resolution: {integrity: sha512-9hNJbGWGhUMwYplHy15uDU7qldkxW3YPK3bWbTXQfV46auFHCGOV0wkU3jMylkdBGtb8YZFtwRmTZxHt2nHRxA==} + "@observablehq/notebook-kit@1.5.0": + resolution: + {integrity: sha512-9hNJbGWGhUMwYplHy15uDU7qldkxW3YPK3bWbTXQfV46auFHCGOV0wkU3jMylkdBGtb8YZFtwRmTZxHt2nHRxA==} hasBin: true peerDependencies: - '@databricks/sql': ^1.11.0 - '@duckdb/node-api': ^1.3.2-alpha.26 - '@google-cloud/bigquery': ^8.1.1 + "@databricks/sql": ^1.11.0 + "@duckdb/node-api": ^1.3.2-alpha.26 + "@google-cloud/bigquery": ^8.1.1 postgres: ^3.4.7 snowflake-sdk: ^2.1.3 peerDependenciesMeta: - '@databricks/sql': + "@databricks/sql": optional: true - '@duckdb/node-api': + "@duckdb/node-api": optional: true - '@google-cloud/bigquery': + "@google-cloud/bigquery": optional: true postgres: optional: true snowflake-sdk: optional: true - '@observablehq/parser@6.1.0': - resolution: {integrity: sha512-S9qfCrAMrL2J229FwMGbyzPskCMqvPkodkn4MJ2r012Bc4yqaNjl8HyT/dKY1zjOwsSrryFQoCiwvWxS8IeASg==} - engines: {node: '>=14.5.0'} + "@observablehq/parser@6.1.0": + resolution: + {integrity: sha512-S9qfCrAMrL2J229FwMGbyzPskCMqvPkodkn4MJ2r012Bc4yqaNjl8HyT/dKY1zjOwsSrryFQoCiwvWxS8IeASg==} + engines: {node: ">=14.5.0"} - '@observablehq/runtime@6.0.0': - resolution: {integrity: sha512-t3UXP69O0JK20HY/neF4/DDDSDorwo92As806Y1pNTgTmj1NtoPyVpesYzfH31gTFOFrXC2cArV+wLpebMk9eA==} + "@observablehq/runtime@6.0.0": + resolution: + {integrity: sha512-t3UXP69O0JK20HY/neF4/DDDSDorwo92As806Y1pNTgTmj1NtoPyVpesYzfH31gTFOFrXC2cArV+wLpebMk9eA==} - '@rolldown/pluginutils@1.0.0-beta.27': - resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + "@rolldown/pluginutils@1.0.0-beta.27": + resolution: + {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - '@rollup/rollup-android-arm-eabi@4.53.3': - resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + "@rollup/rollup-android-arm-eabi@4.53.3": + resolution: + {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.53.3': - resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + "@rollup/rollup-android-arm64@4.53.3": + resolution: + {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.53.3': - resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + "@rollup/rollup-darwin-arm64@4.53.3": + resolution: + {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.53.3': - resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + "@rollup/rollup-darwin-x64@4.53.3": + resolution: + {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.53.3': - resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + "@rollup/rollup-freebsd-arm64@4.53.3": + resolution: + {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.53.3': - resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + "@rollup/rollup-freebsd-x64@4.53.3": + resolution: + {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.53.3': - resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + "@rollup/rollup-linux-arm-gnueabihf@4.53.3": + resolution: + {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.53.3': - resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + "@rollup/rollup-linux-arm-musleabihf@4.53.3": + resolution: + {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.53.3': - resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + "@rollup/rollup-linux-arm64-gnu@4.53.3": + resolution: + {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.53.3': - resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + "@rollup/rollup-linux-arm64-musl@4.53.3": + resolution: + {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.53.3': - resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + "@rollup/rollup-linux-loong64-gnu@4.53.3": + resolution: + {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.53.3': - resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + "@rollup/rollup-linux-ppc64-gnu@4.53.3": + resolution: + {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.53.3': - resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + "@rollup/rollup-linux-riscv64-gnu@4.53.3": + resolution: + {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.53.3': - resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + "@rollup/rollup-linux-riscv64-musl@4.53.3": + resolution: + {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.53.3': - resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + "@rollup/rollup-linux-s390x-gnu@4.53.3": + resolution: + {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.53.3': - resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + "@rollup/rollup-linux-x64-gnu@4.53.3": + resolution: + {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.53.3': - resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + "@rollup/rollup-linux-x64-musl@4.53.3": + resolution: + {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.53.3': - resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + "@rollup/rollup-openharmony-arm64@4.53.3": + resolution: + {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.53.3': - resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + "@rollup/rollup-win32-arm64-msvc@4.53.3": + resolution: + {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.53.3': - resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + "@rollup/rollup-win32-ia32-msvc@4.53.3": + resolution: + {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.53.3': - resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + "@rollup/rollup-win32-x64-gnu@4.53.3": + resolution: + {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.53.3': - resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + "@rollup/rollup-win32-x64-msvc@4.53.3": + resolution: + {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} cpu: [x64] os: [win32] - '@shikijs/core@3.19.0': - resolution: {integrity: sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA==} + "@shikijs/core@3.19.0": + resolution: + {integrity: sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA==} - '@shikijs/engine-javascript@3.19.0': - resolution: {integrity: sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ==} + "@shikijs/engine-javascript@3.19.0": + resolution: + {integrity: sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ==} - '@shikijs/engine-oniguruma@3.19.0': - resolution: {integrity: sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg==} + "@shikijs/engine-oniguruma@3.19.0": + resolution: + {integrity: sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg==} - '@shikijs/langs@3.19.0': - resolution: {integrity: sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg==} + "@shikijs/langs@3.19.0": + resolution: + {integrity: sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg==} - '@shikijs/themes@3.19.0': - resolution: {integrity: sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A==} + "@shikijs/themes@3.19.0": + resolution: + {integrity: sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A==} - '@shikijs/types@3.19.0': - resolution: {integrity: sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==} + "@shikijs/types@3.19.0": + resolution: + {integrity: sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==} - '@shikijs/vscode-textmate@10.0.2': - resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + "@shikijs/vscode-textmate@10.0.2": + resolution: + {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@sindresorhus/slugify@2.2.1': - resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==} - engines: {node: '>=12'} + "@sindresorhus/slugify@2.2.1": + resolution: + {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==} + engines: {node: ">=12"} - '@sindresorhus/transliterate@1.6.0': - resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} - engines: {node: '>=12'} + "@sindresorhus/transliterate@1.6.0": + resolution: + {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} + engines: {node: ">=12"} - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + "@swc/helpers@0.5.15": + resolution: + {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@tailwindcss/node@4.1.17': - resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} + "@tailwindcss/node@4.1.17": + resolution: + {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} - '@tailwindcss/oxide-android-arm64@4.1.17': - resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-android-arm64@4.1.17": + resolution: + {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==} + engines: {node: ">= 10"} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.17': - resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-darwin-arm64@4.1.17": + resolution: + {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==} + engines: {node: ">= 10"} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.17': - resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-darwin-x64@4.1.17": + resolution: + {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==} + engines: {node: ">= 10"} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.17': - resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-freebsd-x64@4.1.17": + resolution: + {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==} + engines: {node: ">= 10"} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': - resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17": + resolution: + {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==} + engines: {node: ">= 10"} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': - resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-linux-arm64-gnu@4.1.17": + resolution: + {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==} + engines: {node: ">= 10"} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.17': - resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-linux-arm64-musl@4.1.17": + resolution: + {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} + engines: {node: ">= 10"} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.17': - resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-linux-x64-gnu@4.1.17": + resolution: + {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} + engines: {node: ">= 10"} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.17': - resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-linux-x64-musl@4.1.17": + resolution: + {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} + engines: {node: ">= 10"} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.17': - resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} - engines: {node: '>=14.0.0'} + "@tailwindcss/oxide-wasm32-wasi@4.1.17": + resolution: + {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} + engines: {node: ">=14.0.0"} cpu: [wasm32] bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' + - "@napi-rs/wasm-runtime" + - "@emnapi/core" + - "@emnapi/runtime" + - "@tybys/wasm-util" + - "@emnapi/wasi-threads" - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': - resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-win32-arm64-msvc@4.1.17": + resolution: + {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==} + engines: {node: ">= 10"} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.17': - resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-win32-x64-msvc@4.1.17": + resolution: + {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==} + engines: {node: ">= 10"} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.17': - resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==} - engines: {node: '>= 10'} + "@tailwindcss/oxide@4.1.17": + resolution: + {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==} + engines: {node: ">= 10"} - '@tailwindcss/postcss@4.1.17': - resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==} + "@tailwindcss/postcss@4.1.17": + resolution: + {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==} - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + "@types/babel__core@7.20.5": + resolution: + {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + "@types/babel__generator@7.27.0": + resolution: + {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + "@types/babel__template@7.4.4": + resolution: + {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + "@types/babel__traverse@7.28.0": + resolution: + {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + "@types/chai@5.2.3": + resolution: + {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + "@types/deep-eql@4.0.2": + resolution: + {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + "@types/estree@1.0.8": + resolution: + {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/hast@3.0.4': - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + "@types/hast@3.0.4": + resolution: + {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + "@types/json-schema@7.0.15": + resolution: + {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/linkify-it@5.0.0': - resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + "@types/linkify-it@5.0.0": + resolution: + {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} - '@types/markdown-it@14.1.2': - resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + "@types/markdown-it@14.1.2": + resolution: + {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} - '@types/mdast@4.0.4': - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + "@types/mdast@4.0.4": + resolution: + {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - '@types/mdurl@2.0.0': - resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + "@types/mdurl@2.0.0": + resolution: + {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + "@types/node@24.10.1": + resolution: + {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + "@types/react-dom@19.2.3": + resolution: + {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: - '@types/react': ^19.2.0 + "@types/react": ^19.2.0 - '@types/react@19.2.6': - resolution: {integrity: sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==} + "@types/react@19.2.6": + resolution: + {integrity: sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==} - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + "@types/unist@3.0.3": + resolution: + {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@typescript-eslint/eslint-plugin@8.49.0': - resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} + "@typescript-eslint/eslint-plugin@8.49.0": + resolution: + {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.49.0 + "@typescript-eslint/parser": ^8.49.0 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: ">=4.8.4 <6.0.0" - '@typescript-eslint/parser@8.49.0': - resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} + "@typescript-eslint/parser@8.49.0": + resolution: + {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: ">=4.8.4 <6.0.0" - '@typescript-eslint/project-service@8.49.0': - resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} + "@typescript-eslint/project-service@8.49.0": + resolution: + {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: ">=4.8.4 <6.0.0" - '@typescript-eslint/scope-manager@8.49.0': - resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} + "@typescript-eslint/scope-manager@8.49.0": + resolution: + {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.49.0': - resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} + "@typescript-eslint/tsconfig-utils@8.49.0": + resolution: + {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: ">=4.8.4 <6.0.0" - '@typescript-eslint/type-utils@8.49.0': - resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} + "@typescript-eslint/type-utils@8.49.0": + resolution: + {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: ">=4.8.4 <6.0.0" - '@typescript-eslint/types@8.49.0': - resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} + "@typescript-eslint/types@8.49.0": + resolution: + {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.49.0': - resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} + "@typescript-eslint/typescript-estree@8.49.0": + resolution: + {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: ">=4.8.4 <6.0.0" - '@typescript-eslint/utils@8.49.0': - resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} + "@typescript-eslint/utils@8.49.0": + resolution: + {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: ">=4.8.4 <6.0.0" - '@typescript-eslint/visitor-keys@8.49.0': - resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} + "@typescript-eslint/visitor-keys@8.49.0": + resolution: + {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@uiw/codemirror-theme-github@4.25.3': - resolution: {integrity: sha512-KdmcO9VicsBgsDErNrNBqwMuTbJRIpeMl9oIjmrNx2iEfIDSOMBIKlX+BkgwTAU+VmhqYY/68/kmF1K8z2FxrQ==} + "@uiw/codemirror-theme-github@4.25.3": + resolution: + {integrity: sha512-KdmcO9VicsBgsDErNrNBqwMuTbJRIpeMl9oIjmrNx2iEfIDSOMBIKlX+BkgwTAU+VmhqYY/68/kmF1K8z2FxrQ==} - '@uiw/codemirror-themes@4.25.3': - resolution: {integrity: sha512-k7/B7Vf4jU/WcdewgJWP9tMFxbjB6UpUymZ3fx/TsbGwt2JXAouw0uyqCn1RlYBfr7YQnvEs3Ju9ECkd2sKzdg==} + "@uiw/codemirror-themes@4.25.3": + resolution: + {integrity: sha512-k7/B7Vf4jU/WcdewgJWP9tMFxbjB6UpUymZ3fx/TsbGwt2JXAouw0uyqCn1RlYBfr7YQnvEs3Ju9ECkd2sKzdg==} peerDependencies: - '@codemirror/language': '>=6.0.0' - '@codemirror/state': '>=6.0.0' - '@codemirror/view': '>=6.0.0' + "@codemirror/language": ">=6.0.0" + "@codemirror/state": ">=6.0.0" + "@codemirror/view": ">=6.0.0" - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + "@ungap/structured-clone@1.3.0": + resolution: + {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vercel/analytics@1.6.1': - resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==} + "@vercel/analytics@1.6.1": + resolution: + {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==} peerDependencies: - '@remix-run/react': ^2 - '@sveltejs/kit': ^1 || ^2 - next: '>= 13' + "@remix-run/react": ^2 + "@sveltejs/kit": ^1 || ^2 + next: ">= 13" react: ^18 || ^19 || ^19.0.0-rc - svelte: '>= 4' + svelte: ">= 4" vue: ^3 vue-router: ^4 peerDependenciesMeta: - '@remix-run/react': + "@remix-run/react": optional: true - '@sveltejs/kit': + "@sveltejs/kit": optional: true next: optional: true @@ -1343,17 +1591,20 @@ packages: vue-router: optional: true - '@vitejs/plugin-react@4.7.0': - resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + "@vitejs/plugin-react@4.7.0": + resolution: + {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + "@vitest/expect@3.2.4": + resolution: + {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + "@vitest/mocker@3.2.4": + resolution: + {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 @@ -1363,693 +1614,872 @@ packages: vite: optional: true - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + "@vitest/pretty-format@3.2.4": + resolution: + {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + "@vitest/runner@3.2.4": + resolution: + {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + "@vitest/snapshot@3.2.4": + resolution: + {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + "@vitest/spy@3.2.4": + resolution: + {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + "@vitest/utils@3.2.4": + resolution: + {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: ">= 0.6"} acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + resolution: + {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} + resolution: + {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: ">=0.4.0"} acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} + resolution: + {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: ">=0.4.0"} hasBin: true agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} + resolution: + {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: ">= 14"} aggregate-error@4.0.1: - resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} + engines: {node: ">=12"} ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + resolution: + {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + resolution: + {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: ">=8"} ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: ">=12"} ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: ">=8"} ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: ">=12"} any-base@1.1.0: - resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} + resolution: + {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + resolution: + {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: ">= 8"} argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + resolution: + {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + resolution: + {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: ">= 0.4"} array-find-index@1.0.2: - resolution: {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==} + engines: {node: ">=0.10.0"} array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + resolution: + {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: ">= 0.4"} array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: ">= 0.4"} array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: ">= 0.4"} array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: ">= 0.4"} array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: ">= 0.4"} arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: ">= 0.4"} arrgv@1.0.2: - resolution: {integrity: sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==} - engines: {node: '>=8.0.0'} + resolution: + {integrity: sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==} + engines: {node: ">=8.0.0"} arrify@3.0.0: - resolution: {integrity: sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==} + engines: {node: ">=12"} assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: ">=12"} astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: ">=8"} async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: ">= 0.4"} ava@5.3.1: - resolution: {integrity: sha512-Scv9a4gMOXB6+ni4toLuhAm9KYWEjsgBglJl+kMGI5+IVDt120CCDZyB5HNU9DjmLI2t4I0GbnxGLmmRfGTJGg==} - engines: {node: '>=14.19 <15 || >=16.15 <17 || >=18'} + resolution: + {integrity: sha512-Scv9a4gMOXB6+ni4toLuhAm9KYWEjsgBglJl+kMGI5+IVDt120CCDZyB5HNU9DjmLI2t4I0GbnxGLmmRfGTJGg==} + engines: {node: ">=14.19 <15 || >=16.15 <17 || >=18"} hasBin: true peerDependencies: - '@ava/typescript': '*' + "@ava/typescript": "*" peerDependenciesMeta: - '@ava/typescript': + "@ava/typescript": optional: true available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: ">= 0.4"} balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + resolution: + {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} baseline-browser-mapping@2.8.28: - resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==} + resolution: + {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==} hasBin: true binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: ">=8"} blueimp-md5@2.19.0: - resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} + resolution: + {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} body-parser@1.20.4: - resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + resolution: + {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + engines: {node: ">= 0.8", npm: 1.2.8000 || >= 1.4.16} boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + resolution: + {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + resolution: + {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + resolution: + {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: ">=8"} browserslist@4.28.0: - resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + resolution: + {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + resolution: + {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: ">= 0.8"} cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: ">=8"} call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: ">= 0.4"} call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: ">= 0.4"} call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: ">= 0.4"} callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: ">=6"} callsites@4.2.0: - resolution: {integrity: sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==} - engines: {node: '>=12.20'} + resolution: + {integrity: sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==} + engines: {node: ">=12.20"} caniuse-lite@1.0.30001754: - resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + resolution: + {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} caniuse-lite@1.0.30001760: - resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + resolution: + {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} cbor@8.1.0: - resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} - engines: {node: '>=12.19'} + resolution: + {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} + engines: {node: ">=12.19"} ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + resolution: + {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} - engines: {node: '>=18'} + 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'} + resolution: + {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: ">=10"} chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + resolution: + {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + resolution: + {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + resolution: + {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} + resolution: + {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: ">= 16"} cheerio-select@2.1.0: - resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + resolution: + {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} cheerio@1.1.2: - resolution: {integrity: sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==} - engines: {node: '>=20.18.1'} + resolution: + {integrity: sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==} + engines: {node: ">=20.18.1"} chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + resolution: + {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: ">= 8.10.0"} chunkd@2.0.1: - resolution: {integrity: sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==} + resolution: + {integrity: sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==} ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: ">=8"} ci-parallel-vars@1.0.1: - resolution: {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==} + resolution: + {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==} classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + resolution: + {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} clean-stack@4.2.0: - resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} + engines: {node: ">=12"} clean-yaml-object@0.1.0: - resolution: {integrity: sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw==} + engines: {node: ">=0.10.0"} cli-truncate@3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + resolution: + {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + resolution: + {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: ">=12"} clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: ">=6"} code-excerpt@4.0.0: - resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} + resolution: + {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} codemirror@6.0.2: - resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} + resolution: + {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + resolution: + {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: ">=7.0.0"} color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + resolution: + {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + resolution: + {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} common-path-prefix@3.0.0: - resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} + resolution: + {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: + {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} concordance@5.0.4: - resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} - engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} + resolution: + {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} + engines: {node: ">=10.18.0 <11 || >=12.14.0 <13 || >=14"} content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: ">= 0.6"} content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: ">= 0.6"} convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + resolution: + {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} convert-to-spaces@2.0.1: - resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} + resolution: + {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} cookie-signature@1.0.7: - resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + resolution: + {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: ">= 0.6"} crelt@1.0.6: - resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + resolution: + {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + resolution: + {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: ">= 8"} css-select@5.2.2: - resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + resolution: + {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} + resolution: + {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: ">= 6"} cssstyle@4.6.0: - resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: ">=18"} csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + resolution: + {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} currently-unhandled@0.4.1: - resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} + engines: {node: ">=0.10.0"} d3-array@3.2.4: - resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: ">=12"} d3-dispatch@3.0.1: - resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: ">=12"} d3-require@1.3.0: - resolution: {integrity: sha512-XaNc2azaAwXhGjmCMtxlD+AowpMfLimVsAoTMpqrvb8CWoA4QqyV12mc4Ue6KSoDvfuS831tsumfhDYxGd4FGA==} + resolution: + {integrity: sha512-XaNc2azaAwXhGjmCMtxlD+AowpMfLimVsAoTMpqrvb8CWoA4QqyV12mc4Ue6KSoDvfuS831tsumfhDYxGd4FGA==} data-urls@5.0.0: - resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: ">=18"} data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: ">= 0.4"} data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: ">= 0.4"} data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: ">= 0.4"} date-time@3.1.0: - resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} + engines: {node: ">=6"} debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + resolution: + {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: - supports-color: '*' + supports-color: "*" peerDependenciesMeta: supports-color: optional: true debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} + resolution: + {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: ">=6.0"} peerDependencies: - supports-color: '*' + supports-color: "*" peerDependenciesMeta: supports-color: optional: true decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + resolution: + {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} + 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==} + resolution: + {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: ">= 0.4"} define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: ">= 0.4"} depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: ">= 0.8"} dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: ">=6"} destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + resolution: + {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: ">= 0.8", npm: 1.2.8000 || >= 1.4.16} detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: ">=8"} devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + resolution: + {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: ">=8"} doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: ">=0.10.0"} dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + resolution: + {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + resolution: + {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} + resolution: + {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: ">= 4"} domutils@3.2.2: - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + resolution: + {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: ">= 0.4"} eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + resolution: + {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + resolution: + {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} electron-to-chromium@1.5.251: - resolution: {integrity: sha512-lmyEOp4G0XT3qrYswNB4np1kx90k6QCXpnSHYv2xEsUuEu8JCobpDVYO6vMseirQyyCC6GCIGGxd5szMBa0tRA==} + resolution: + {integrity: sha512-lmyEOp4G0XT3qrYswNB4np1kx90k6QCXpnSHYv2xEsUuEu8JCobpDVYO6vMseirQyyCC6GCIGGxd5szMBa0tRA==} emittery@1.2.0: - resolution: {integrity: sha512-KxdRyyFcS85pH3dnU8Y5yFUm2YJdaHwcBZWrfG8o89ZY9a13/f9itbN+YG3ELbBo9Pg5zvIozstmuV8bX13q6g==} - engines: {node: '>=14.16'} + resolution: + {integrity: sha512-KxdRyyFcS85pH3dnU8Y5yFUm2YJdaHwcBZWrfG8o89ZY9a13/f9itbN+YG3ELbBo9Pg5zvIozstmuV8bX13q6g==} + engines: {node: ">=14.16"} emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + resolution: + {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + resolution: + {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: ">= 0.8"} encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: ">= 0.8"} encoding-sniffer@0.2.1: - resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} + resolution: + {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} enhanced-resolve@5.18.3: - resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} - engines: {node: '>=10.13.0'} + resolution: + {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: ">=10.13.0"} entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} + resolution: + {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: ">=0.12"} entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} + resolution: + {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: ">=0.12"} es-abstract@1.24.0: - resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: ">= 0.4"} es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: ">= 0.4"} es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: ">= 0.4"} es-iterator-helpers@1.2.1: - resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: ">= 0.4"} es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + resolution: + {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: ">= 0.4"} es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: ">= 0.4"} es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: ">= 0.4"} es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: ">= 0.4"} esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: ">=18"} hasBin: true escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: ">=6"} escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + resolution: + {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: ">=8"} escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: ">=10"} escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: ">=12"} eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + resolution: + {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true peerDependencies: - eslint: '>=7.0.0' + eslint: ">=7.0.0" eslint-linter-browserify@9.39.1: - resolution: {integrity: sha512-XdoocZkDVe9svpVe1V9q0cW3pufacQ0z7Unhu4E4AaeHHxF4AZa5+HRHYMKO30NUKEQfDv+ITibXUNi1C6QGbQ==} + resolution: + {integrity: sha512-XdoocZkDVe9svpVe1V9q0cW3pufacQ0z7Unhu4E4AaeHHxF4AZa5+HRHYMKO30NUKEQfDv+ITibXUNi1C6QGbQ==} eslint-plugin-react-hooks@5.2.0: - resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: ">=10"} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 eslint-plugin-react@7.37.5: - resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} - engines: {node: '>=4'} + resolution: + {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: ">=4"} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + resolution: + {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + resolution: + {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + resolution: + {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint@9.39.1: - resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + resolution: + {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: - jiti: '*' + jiti: "*" peerDependenciesMeta: jiti: optional: true espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + resolution: + {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} + resolution: + {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: ">=4"} hasBin: true esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} + resolution: + {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: ">=0.10"} esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + resolution: + {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: ">=4.0"} estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} + resolution: + {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: ">=4.0"} estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + resolution: + {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: ">=0.10.0"} etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: ">= 0.6"} expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} + resolution: + {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: ">=12.0.0"} express@4.22.1: - resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} - engines: {node: '>= 0.10.0'} + resolution: + {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + engines: {node: ">= 0.10.0"} fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + resolution: + {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + resolution: + {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} + resolution: + {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: ">=8.6.0"} fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + resolution: + {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + resolution: + {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + resolution: + {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + resolution: + {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} + resolution: + {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: ">=12.0.0"} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2057,419 +2487,524 @@ packages: optional: true figures@5.0.0: - resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} - engines: {node: '>=14'} + resolution: + {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} + engines: {node: ">=14"} file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} + resolution: + {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: ">=16.0.0"} fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: ">=8"} finalhandler@1.3.2: - resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: ">= 0.8"} find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: ">=10"} find-up@6.3.0: - resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} + resolution: + {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} + resolution: + {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: ">=16"} flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + resolution: + {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: ">= 0.4"} forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: ">= 0.6"} fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: ">= 0.6"} friendly-words@1.3.1: - resolution: {integrity: sha512-gLlK15jM/U/oFtYkw4At0cVS0kWst41BRPG4EnhP/L7ZGn8rnOSlLuffIvO/JIK06TYx7abRpNMTzkwpHL+kEA==} + resolution: + {integrity: sha512-gLlK15jM/U/oFtYkw4At0cVS0kWst41BRPG4EnhP/L7ZGn8rnOSlLuffIvO/JIK06TYx7abRpNMTzkwpHL+kEA==} fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + resolution: + {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + resolution: + {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: ">= 0.4"} functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + resolution: + {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: ">= 0.4"} gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} + resolution: + {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: ">=6.9.0"} get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + resolution: + {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: ">= 0.4"} get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: ">= 0.4"} get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: ">= 0.4"} glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + resolution: + {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: ">= 6"} glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + resolution: + {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: ">=10.13.0"} globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: ">=18"} globals@16.5.0: - resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: ">=18"} globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: ">= 0.4"} globby@13.2.2: - resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + resolution: + {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: ">= 0.4"} graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + resolution: + {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: ">= 0.4"} has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: ">=8"} has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + resolution: + {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: ">= 0.4"} has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: ">= 0.4"} has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: ">= 0.4"} hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: ">= 0.4"} hast-util-to-html@9.0.5: - resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + resolution: + {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + resolution: + {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} html-encoding-sniffer@4.0.0: - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: ">=18"} html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + resolution: + {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} htmlparser2@10.0.0: - resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + resolution: + {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: ">= 0.8"} http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: ">= 0.8"} http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} + resolution: + {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: ">= 14"} https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} + resolution: + {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: ">= 14"} iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: ">=0.10.0"} iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: ">=0.10.0"} ignore-by-default@2.1.0: - resolution: {integrity: sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==} - engines: {node: '>=10 <11 || >=12 <13 || >=14'} + resolution: + {integrity: sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==} + engines: {node: ">=10 <11 || >=12 <13 || >=14"} ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} + resolution: + {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: ">= 4"} ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} + resolution: + {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: ">= 4"} immutable@5.1.4: - resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} + resolution: + {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: ">=6"} imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + resolution: + {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: ">=0.8.19"} indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: ">=12"} inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + resolution: + {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: ">= 0.4"} internmap@2.0.3: - resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: ">=12"} ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} + resolution: + {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: ">= 0.10"} irregular-plurals@3.5.0: - resolution: {integrity: sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==} + engines: {node: ">=8"} is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: ">= 0.4"} is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: ">= 0.4"} is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: ">= 0.4"} is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: ">=8"} is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: ">= 0.4"} is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: ">= 0.4"} is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: ">= 0.4"} is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: ">= 0.4"} is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: ">= 0.4"} is-error@2.2.2: - resolution: {integrity: sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==} + resolution: + {integrity: sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==} is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: ">=0.10.0"} is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: ">= 0.4"} is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: ">=8"} is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: ">=12"} is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: ">= 0.4"} is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: ">=0.10.0"} is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: ">= 0.4"} is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: ">= 0.4"} is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: ">= 0.4"} is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + resolution: + {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: ">=0.12.0"} is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: ">=0.10.0"} is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + resolution: + {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + resolution: + {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: ">= 0.4"} is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: ">= 0.4"} is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: ">= 0.4"} is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: ">= 0.4"} is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: ">= 0.4"} is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: ">= 0.4"} is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: ">=12"} is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: ">= 0.4"} is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: ">= 0.4"} is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: ">= 0.4"} isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + resolution: + {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + resolution: + {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} isoformat@0.2.1: - resolution: {integrity: sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==} + resolution: + {integrity: sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==} iterator.prototype@1.1.5: - resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: ">= 0.4"} jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + resolution: + {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true jotai@2.15.1: - resolution: {integrity: sha512-yHT1HAZ3ba2Q8wgaUQ+xfBzEtcS8ie687I8XVCBinfg4bNniyqLIN+utPXWKQE93LMF5fPbQSVRZqgpcN5yd6Q==} - engines: {node: '>=12.20.0'} + resolution: + {integrity: sha512-yHT1HAZ3ba2Q8wgaUQ+xfBzEtcS8ie687I8XVCBinfg4bNniyqLIN+utPXWKQE93LMF5fPbQSVRZqgpcN5yd6Q==} + engines: {node: ">=12.20.0"} peerDependencies: - '@babel/core': '>=7.0.0' - '@babel/template': '>=7.0.0' - '@types/react': '>=17.0.0' - react: '>=17.0.0' + "@babel/core": ">=7.0.0" + "@babel/template": ">=7.0.0" + "@types/react": ">=17.0.0" + react: ">=17.0.0" peerDependenciesMeta: - '@babel/core': + "@babel/core": optional: true - '@babel/template': + "@babel/template": optional: true - '@types/react': + "@types/react": optional: true react: optional: true js-string-escape@1.0.1: - resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} + engines: {node: ">= 0.8"} js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + resolution: + {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + resolution: + {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + resolution: + {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + resolution: + {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsdom@26.1.0: - resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: ">=18"} peerDependencies: canvas: ^3.0.0 peerDependenciesMeta: @@ -2477,288 +3012,355 @@ packages: optional: true jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: ">=6"} hasBin: true json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + resolution: + {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + resolution: + {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + resolution: + {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + resolution: + {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: ">=6"} hasBin: true jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} + resolution: + {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: ">=4.0"} keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + resolution: + {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + resolution: + {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: ">= 0.8.0"} lightningcss-android-arm64@1.30.2: - resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} - engines: {node: '>= 12.0.0'} + resolution: + {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + 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'} + resolution: + {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + 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'} + resolution: + {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: ">= 12.0.0"} cpu: [x64] os: [darwin] lightningcss-freebsd-x64@1.30.2: - resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} - engines: {node: '>= 12.0.0'} + resolution: + {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + 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'} + resolution: + {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + 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'} + resolution: + {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: ">= 12.0.0"} cpu: [arm64] os: [linux] lightningcss-linux-arm64-musl@1.30.2: - resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} - engines: {node: '>= 12.0.0'} + resolution: + {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: ">= 12.0.0"} cpu: [arm64] os: [linux] lightningcss-linux-x64-gnu@1.30.2: - resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} - engines: {node: '>= 12.0.0'} + resolution: + {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: ">= 12.0.0"} cpu: [x64] os: [linux] lightningcss-linux-x64-musl@1.30.2: - resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} - engines: {node: '>= 12.0.0'} + resolution: + {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: ">= 12.0.0"} cpu: [x64] os: [linux] lightningcss-win32-arm64-msvc@1.30.2: - resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} - engines: {node: '>= 12.0.0'} + resolution: + {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + 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'} + resolution: + {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: ">= 12.0.0"} cpu: [x64] os: [win32] lightningcss@1.30.2: - resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} - engines: {node: '>= 12.0.0'} + resolution: + {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: ">= 12.0.0"} linkify-it@5.0.0: - resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + resolution: + {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} load-json-file@7.0.1: - resolution: {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==} + resolution: + {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: ">=10"} locate-path@7.2.0: - resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + resolution: + {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + resolution: + {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} lodash.samplesize@4.2.0: - resolution: {integrity: sha512-1ZhKV7/nuISuaQdxfCqrs4HHxXIYN+0Z4f7NMQn2PHkxFZJGavJQ1j/paxyJnLJmN2ZamNN6SMepneV+dCgQTA==} + resolution: + {integrity: sha512-1ZhKV7/nuISuaQdxfCqrs4HHxXIYN+0Z4f7NMQn2PHkxFZJGavJQ1j/paxyJnLJmN2ZamNN6SMepneV+dCgQTA==} lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + resolution: + {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + resolution: + {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + resolution: + {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + resolution: + {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + resolution: + {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + resolution: + {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} lucide-react@0.542.0: - resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==} + resolution: + {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + resolution: + {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} map-age-cleaner@0.1.3: - resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==} + engines: {node: ">=6"} markdown-it-anchor@9.2.0: - resolution: {integrity: sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==} + resolution: + {integrity: sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==} peerDependencies: - '@types/markdown-it': '*' - markdown-it: '*' + "@types/markdown-it": "*" + markdown-it: "*" markdown-it@14.1.0: - resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + resolution: + {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true matcher@5.0.0: - resolution: {integrity: sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==} + resolution: + {integrity: sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: ">= 0.4"} md5-hex@3.0.1: - resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} + engines: {node: ">=8"} mdast-util-to-hast@13.2.1: - resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + resolution: + {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} mdurl@2.0.0: - resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + resolution: + {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: ">= 0.6"} mem@9.0.2: - resolution: {integrity: sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==} - engines: {node: '>=12.20'} + resolution: + {integrity: sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==} + engines: {node: ">=12.20"} merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + resolution: + {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + resolution: + {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: ">= 8"} methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: ">= 0.6"} micromark-util-character@2.1.1: - resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + resolution: + {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} micromark-util-encode@2.0.1: - resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + resolution: + {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} micromark-util-sanitize-uri@2.0.1: - resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + resolution: + {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} micromark-util-symbol@2.0.1: - resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + resolution: + {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} micromark-util-types@2.0.2: - resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + resolution: + {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + resolution: + {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: ">=8.6"} mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: ">= 0.6"} mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: ">= 0.6"} mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} + resolution: + {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: ">=4"} hasBin: true mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: ">=12"} minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + resolution: + {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} + resolution: + {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: ">=16 || 14 >=14.17"} ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + resolution: + {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + resolution: + {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + resolution: + {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true nanoid@5.1.6: - resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} + resolution: + {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} engines: {node: ^18 || >=20} hasBin: true natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + resolution: + {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: ">= 0.6"} next@15.5.7: - resolution: {integrity: sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==} + resolution: + {integrity: sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.51.1 - babel-plugin-react-compiler: '*' + "@opentelemetry/api": ^1.1.0 + "@playwright/test": ^1.51.1 + babel-plugin-react-compiler: "*" react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: - '@opentelemetry/api': + "@opentelemetry/api": optional: true - '@playwright/test': + "@playwright/test": optional: true babel-plugin-react-compiler: optional: true @@ -2766,759 +3368,951 @@ packages: optional: true node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + resolution: + {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} nofilter@3.1.0: - resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} - engines: {node: '>=12.19'} + resolution: + {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} + engines: {node: ">=12.19"} normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: ">=0.10.0"} nstr@0.1.3: - resolution: {integrity: sha512-8JEagITWMjggz2Ofmgkax8tFR5xRQ2Q4AB6jj0JvmhHB5Myd9AHn1H05k6bXyMyOWXFkFBjqvpAudQMaPaFQow==} + resolution: + {integrity: sha512-8JEagITWMjggz2Ofmgkax8tFR5xRQ2Q4AB6jj0JvmhHB5Myd9AHn1H05k6bXyMyOWXFkFBjqvpAudQMaPaFQow==} nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + resolution: + {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} nwsapi@2.2.23: - resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + resolution: + {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: ">=0.10.0"} object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: ">= 0.4"} object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: ">= 0.4"} object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: ">= 0.4"} object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: ">= 0.4"} object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: ">= 0.4"} object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: ">= 0.4"} on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: ">= 0.8"} oniguruma-parser@0.12.1: - resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + resolution: + {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} oniguruma-to-es@4.3.4: - resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + resolution: + {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} + resolution: + {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: ">= 0.8.0"} own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: ">= 0.4"} p-defer@1.0.0: - resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} - engines: {node: '>=4'} + resolution: + {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} + engines: {node: ">=4"} p-event@5.0.1: - resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==} + resolution: + {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: ">=10"} p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + resolution: + {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: ">=10"} p-locate@6.0.0: - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + resolution: + {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} p-map@5.5.0: - resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} + engines: {node: ">=12"} p-timeout@5.1.0: - resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} + engines: {node: ">=12"} parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: ">=6"} parse-ms@3.0.0: - resolution: {integrity: sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==} + engines: {node: ">=12"} parse5-htmlparser2-tree-adapter@7.1.0: - resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + resolution: + {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} parse5-parser-stream@7.1.2: - resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + resolution: + {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} parse5@7.3.0: - resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + resolution: + {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: ">= 0.8"} path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: ">=8"} path-exists@5.0.0: - resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + resolution: + {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: ">=8"} path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + resolution: + {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} path-to-regexp@0.1.12: - resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + resolution: + {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: ">=8"} pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + resolution: + {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} + resolution: + {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: ">= 14.16"} picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + resolution: + {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + resolution: + {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: ">=8.6"} picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: ">=12"} pkg-conf@4.0.0: - resolution: {integrity: sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==} + resolution: + {integrity: sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} plur@5.1.0: - resolution: {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==} + resolution: + {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: ">= 0.4"} postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + resolution: + {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + resolution: + {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} + resolution: + {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: ">= 0.8.0"} prettier@3.7.4: - resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} - engines: {node: '>=14'} + resolution: + {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + engines: {node: ">=14"} hasBin: true pretty-ms@8.0.0: - resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} - engines: {node: '>=14.16'} + resolution: + {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} + engines: {node: ">=14.16"} prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + resolution: + {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} property-information@7.1.0: - resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + resolution: + {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} + resolution: + {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: ">= 0.10"} punycode.js@2.3.1: - resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: ">=6"} punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: ">=6"} qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} + resolution: + {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: ">=0.6"} queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + resolution: + {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: ">= 0.6"} raw-body@2.5.3: - resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: ">= 0.8"} react-dom@19.2.1: - resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} + resolution: + {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} peerDependencies: react: ^19.2.1 react-inspector@9.0.0: - resolution: {integrity: sha512-w/VJucSeHxlwRa2nfM2k7YhpT1r5EtlDOClSR+L7DyQP91QMdfFEDXDs9bPYN4kzP7umFtom7L0b2GGjph4Kow==} + resolution: + {integrity: sha512-w/VJucSeHxlwRa2nfM2k7YhpT1r5EtlDOClSR+L7DyQP91QMdfFEDXDs9bPYN4kzP7umFtom7L0b2GGjph4Kow==} peerDependencies: react: ^18.0.0 || ^19.0.0 react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + resolution: + {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: ">=0.10.0"} react-resizable-panels@3.0.6: - resolution: {integrity: sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew==} + resolution: + {integrity: sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew==} peerDependencies: react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-tooltip@5.30.0: - resolution: {integrity: sha512-Yn8PfbgQ/wmqnL7oBpz1QiDaLKrzZMdSUUdk7nVeGTwzbxCAJiJzR4VSYW+eIO42F1INt57sPUmpgKv0KwJKtg==} + resolution: + {integrity: sha512-Yn8PfbgQ/wmqnL7oBpz1QiDaLKrzZMdSUUdk7nVeGTwzbxCAJiJzR4VSYW+eIO42F1INt57sPUmpgKv0KwJKtg==} peerDependencies: - react: '>=16.14.0' - react-dom: '>=16.14.0' + react: ">=16.14.0" + react-dom: ">=16.14.0" react@19.2.1: - resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} + engines: {node: ">=0.10.0"} readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + resolution: + {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: ">=8.10.0"} reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: ">= 0.4"} regex-recursion@6.0.2: - resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + resolution: + {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} regex-utilities@2.3.0: - resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + resolution: + {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} regex@6.1.0: - resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + resolution: + {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: ">= 0.4"} require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: ">=0.10.0"} require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: ">=0.10.0"} resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: ">=8"} resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} + resolution: + {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: ">=4"} resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: ">=8"} resolve@2.0.0-next.5: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + resolution: + {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + resolution: + {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: ">=1.0.0", node: ">=0.10.0"} rollup@4.53.3: - resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + resolution: + {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + engines: {node: ">=18.0.0", npm: ">=8.0.0"} hasBin: true rrweb-cssom@0.8.0: - resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + resolution: + {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + resolution: + {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} - engines: {node: '>=0.4'} + resolution: + {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: ">=0.4"} safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + resolution: + {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: ">= 0.4"} safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: ">= 0.4"} safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + resolution: + {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} + resolution: + {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: ">=v12.22.7"} scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + resolution: + {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + resolution: + {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: ">=10"} hasBin: true send@0.19.0: - resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} - engines: {node: '>= 0.8.0'} + resolution: + {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: ">= 0.8.0"} send@0.19.1: - resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==} - engines: {node: '>= 0.8.0'} + resolution: + {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==} + engines: {node: ">= 0.8.0"} serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: ">=10"} serve-static@1.16.2: - resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} - engines: {node: '>= 0.8.0'} + resolution: + {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: ">= 0.8.0"} set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: ">= 0.4"} set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: ">= 0.4"} set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: ">= 0.4"} setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + resolution: + {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + resolution: + {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: ">=8"} shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: ">=8"} shiki@3.19.0: - resolution: {integrity: sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA==} + resolution: + {integrity: sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA==} short-uuid@5.2.0: - resolution: {integrity: sha512-296/Nzi4DmANh93iYBwT4NoYRJuHnKEzefrkSagQbTH/A6NTaB68hSPDjm5IlbI5dx9FXdmtqPcj6N5H+CPm6w==} - engines: {node: '>=14'} + resolution: + {integrity: sha512-296/Nzi4DmANh93iYBwT4NoYRJuHnKEzefrkSagQbTH/A6NTaB68hSPDjm5IlbI5dx9FXdmtqPcj6N5H+CPm6w==} + engines: {node: ">=14"} side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: ">= 0.4"} side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: ">= 0.4"} side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: ">= 0.4"} side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: ">= 0.4"} siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + resolution: + {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} + resolution: + {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: ">=14"} slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: ">=12"} slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: ">=10"} slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: ">=12"} source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: ">=0.10.0"} source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + resolution: + {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: ">=0.10.0"} space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + resolution: + {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + resolution: + {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: ">=10"} stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + resolution: + {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: ">= 0.8"} statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: ">= 0.8"} std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + resolution: + {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: ">= 0.4"} string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: ">=8"} string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: ">=12"} string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: ">= 0.4"} string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + resolution: + {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: ">= 0.4"} string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: ">= 0.4"} string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: ">= 0.4"} stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + resolution: + {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: ">=8"} strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: ">=12"} strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: ">=8"} strip-literal@3.1.0: - resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + resolution: + {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} style-mod@4.1.3: - resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + resolution: + {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} styled-jsx@5.1.6: - resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} - engines: {node: '>= 12.0.0'} + resolution: + {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: ">= 12.0.0"} peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + "@babel/core": "*" + babel-plugin-macros: "*" + react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" peerDependenciesMeta: - '@babel/core': + "@babel/core": optional: true babel-plugin-macros: optional: true supertap@3.0.1: - resolution: {integrity: sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==} + resolution: + {integrity: sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: ">=8"} supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: ">= 0.4"} symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + resolution: + {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} table@6.9.0: - resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} - engines: {node: '>=10.0.0'} + resolution: + {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: ">=10.0.0"} tailwind-merge@3.4.0: - resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + resolution: + {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} tailwindcss@4.1.17: - resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} + resolution: + {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: ">=6"} temp-dir@3.0.0: - resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} - engines: {node: '>=14.16'} + resolution: + {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} + engines: {node: ">=14.16"} time-zone@1.0.0: - resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} - engines: {node: '>=4'} + resolution: + {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} + engines: {node: ">=4"} tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + resolution: + {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + resolution: + {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} + resolution: + {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: ">=12.0.0"} tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + 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'} + 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'} + resolution: + {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: ">=14.0.0"} tldts-core@6.1.86: - resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + resolution: + {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} tldts@6.1.86: - resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + resolution: + {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} hasBin: true to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + resolution: + {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: ">=8.0"} toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} + resolution: + {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: ">=0.6"} tough-cookie@5.1.2: - resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} - engines: {node: '>=16'} + resolution: + {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: ">=16"} tr46@5.1.1: - resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: ">=18"} trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + resolution: + {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} - engines: {node: '>=18.12'} + resolution: + {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: ">=18.12"} peerDependencies: - typescript: '>=4.8.4' + typescript: ">=4.8.4" tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + resolution: + {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + resolution: + {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: ">= 0.8.0"} type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: ">=10"} type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} + resolution: + {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: ">= 0.6"} typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: ">= 0.4"} typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: ">= 0.4"} typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: ">= 0.4"} typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: ">= 0.4"} typescript-eslint@8.49.0: - resolution: {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==} + resolution: + {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: ">=4.8.4 <6.0.0" typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} + resolution: + {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: ">=14.17"} hasBin: true uc.micro@2.1.0: - resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + resolution: + {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: ">= 0.4"} undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + resolution: + {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} undici@7.16.0: - resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} - engines: {node: '>=20.18.1'} + resolution: + {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} + engines: {node: ">=20.18.1"} unist-util-is@6.0.1: - resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + resolution: + {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + resolution: + {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + resolution: + {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} unist-util-visit-parents@6.0.2: - resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + resolution: + {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + resolution: + {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: ">= 0.8"} update-browserslist-db@1.1.4: - resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + resolution: + {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true peerDependencies: - browserslist: '>= 4.21.0' + browserslist: ">= 4.21.0" uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + resolution: + {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} + resolution: + {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: ">= 0.4.0"} uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + resolution: + {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} + resolution: + {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: ">= 0.8"} vfile-message@4.0.3: - resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + resolution: + {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + resolution: + {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + resolution: + {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true vite@7.2.7: - resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==} + resolution: + {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" less: ^4.0.0 lightningcss: ^1.21.0 sass: ^1.70.0 sass-embedded: ^1.70.0 - stylus: '>=0.54.8' + stylus: ">=0.54.8" sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 peerDependenciesMeta: - '@types/node': + "@types/node": optional: true jiti: optional: true @@ -3542,27 +4336,28 @@ packages: optional: true vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + resolution: + {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} 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.4 - '@vitest/ui': 3.2.4 - happy-dom: '*' - jsdom: '*' + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.2.4 + "@vitest/ui": 3.2.4 + happy-dom: "*" + jsdom: "*" peerDependenciesMeta: - '@edge-runtime/vm': + "@edge-runtime/vm": optional: true - '@types/debug': + "@types/debug": optional: true - '@types/node': + "@types/node": optional: true - '@vitest/browser': + "@vitest/browser": optional: true - '@vitest/ui': + "@vitest/ui": optional: true happy-dom: optional: true @@ -3570,76 +4365,93 @@ packages: optional: true w3c-keyname@2.2.8: - resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + resolution: + {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: ">=18"} webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: ">=12"} well-known-symbols@2.0.0: - resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} - engines: {node: '>=6'} + resolution: + {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} + engines: {node: ">=6"} whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: ">=18"} whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: ">=18"} whatwg-url@14.2.0: - resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: ">=18"} which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: ">= 0.4"} which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: ">= 0.4"} which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: ">= 0.4"} which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} - engines: {node: '>= 0.4'} + resolution: + {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: ">= 0.4"} which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} + resolution: + {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: ">= 8"} hasBin: true why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + resolution: + {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: ">=8"} hasBin: true word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} + resolution: + {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: ">=0.10.0"} wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: ">=10"} write-file-atomic@5.0.1: - resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + resolution: + {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} + resolution: + {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: ">=10.0.0"} peerDependencies: bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' + utf-8-validate: ">=5.0.2" peerDependenciesMeta: bufferutil: optional: true @@ -3647,70 +4459,78 @@ packages: optional: true xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} + resolution: + {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: ">=18"} xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + resolution: + {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: ">=10"} yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + resolution: + {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: ">=12"} yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + resolution: + {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: ">=12"} yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + resolution: + {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: ">=10"} yocto-queue@1.2.2: - resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} - engines: {node: '>=12.20'} + resolution: + {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: ">=12.20"} zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + resolution: + {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} snapshots: + "@alloc/quick-lru@5.2.0": {} - '@alloc/quick-lru@5.2.0': {} - - '@asamuzakjp/css-color@3.2.0': + "@asamuzakjp/css-color@3.2.0": dependencies: - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + "@csstools/css-calc": 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + "@csstools/css-color-parser": 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + "@csstools/css-parser-algorithms": 3.0.5(@csstools/css-tokenizer@3.0.4) + "@csstools/css-tokenizer": 3.0.4 lru-cache: 10.4.3 - '@babel/code-frame@7.27.1': + "@babel/code-frame@7.27.1": dependencies: - '@babel/helper-validator-identifier': 7.28.5 + "@babel/helper-validator-identifier": 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.5': {} + "@babel/compat-data@7.28.5": {} - '@babel/core@7.28.5': + "@babel/core@7.28.5": dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 - '@jridgewell/remapping': 2.3.5 + "@babel/code-frame": 7.27.1 + "@babel/generator": 7.28.5 + "@babel/helper-compilation-targets": 7.27.2 + "@babel/helper-module-transforms": 7.28.3(@babel/core@7.28.5) + "@babel/helpers": 7.28.4 + "@babel/parser": 7.28.5 + "@babel/template": 7.27.2 + "@babel/traverse": 7.28.5 + "@babel/types": 7.28.5 + "@jridgewell/remapping": 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 gensync: 1.0.0-beta.2 @@ -3719,475 +4539,475 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.5': + "@babel/generator@7.28.5": dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + "@babel/parser": 7.28.5 + "@babel/types": 7.28.5 + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.27.2': + "@babel/helper-compilation-targets@7.27.2": dependencies: - '@babel/compat-data': 7.28.5 - '@babel/helper-validator-option': 7.27.1 + "@babel/compat-data": 7.28.5 + "@babel/helper-validator-option": 7.27.1 browserslist: 4.28.0 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-globals@7.28.0': {} + "@babel/helper-globals@7.28.0": {} - '@babel/helper-module-imports@7.27.1': + "@babel/helper-module-imports@7.27.1": dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + "@babel/traverse": 7.28.5 + "@babel/types": 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + "@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)": dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + "@babel/core": 7.28.5 + "@babel/helper-module-imports": 7.27.1 + "@babel/helper-validator-identifier": 7.28.5 + "@babel/traverse": 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.27.1': {} + "@babel/helper-plugin-utils@7.27.1": {} - '@babel/helper-string-parser@7.27.1': {} + "@babel/helper-string-parser@7.27.1": {} - '@babel/helper-validator-identifier@7.28.5': {} + "@babel/helper-validator-identifier@7.28.5": {} - '@babel/helper-validator-option@7.27.1': {} + "@babel/helper-validator-option@7.27.1": {} - '@babel/helpers@7.28.4': + "@babel/helpers@7.28.4": dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + "@babel/template": 7.27.2 + "@babel/types": 7.28.5 - '@babel/parser@7.28.5': + "@babel/parser@7.28.5": dependencies: - '@babel/types': 7.28.5 + "@babel/types": 7.28.5 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': + "@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)": dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + "@babel/core": 7.28.5 + "@babel/helper-plugin-utils": 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + "@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)": dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + "@babel/core": 7.28.5 + "@babel/helper-plugin-utils": 7.27.1 - '@babel/runtime@7.28.4': {} + "@babel/runtime@7.28.4": {} - '@babel/template@7.27.2': + "@babel/template@7.27.2": dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + "@babel/code-frame": 7.27.1 + "@babel/parser": 7.28.5 + "@babel/types": 7.28.5 - '@babel/traverse@7.28.5': + "@babel/traverse@7.28.5": dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + "@babel/code-frame": 7.27.1 + "@babel/generator": 7.28.5 + "@babel/helper-globals": 7.28.0 + "@babel/parser": 7.28.5 + "@babel/template": 7.27.2 + "@babel/types": 7.28.5 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.5': + "@babel/types@7.28.5": dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + "@babel/helper-string-parser": 7.27.1 + "@babel/helper-validator-identifier": 7.28.5 - '@codemirror/autocomplete@6.20.0': + "@codemirror/autocomplete@6.20.0": dependencies: - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 + "@lezer/common": 1.4.0 - '@codemirror/commands@6.10.0': + "@codemirror/commands@6.10.0": dependencies: - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 + "@lezer/common": 1.4.0 - '@codemirror/lang-angular@0.1.4': + "@codemirror/lang-angular@0.1.4": dependencies: - '@codemirror/lang-html': 6.4.11 - '@codemirror/lang-javascript': 6.2.4 - '@codemirror/language': 6.11.3 - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@codemirror/lang-html": 6.4.11 + "@codemirror/lang-javascript": 6.2.4 + "@codemirror/language": 6.11.3 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@codemirror/lang-cpp@6.0.3': + "@codemirror/lang-cpp@6.0.3": dependencies: - '@codemirror/language': 6.11.3 - '@lezer/cpp': 1.1.4 + "@codemirror/language": 6.11.3 + "@lezer/cpp": 1.1.4 - '@codemirror/lang-css@6.3.1': + "@codemirror/lang-css@6.3.1": dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@lezer/common': 1.4.0 - '@lezer/css': 1.3.0 + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@lezer/common": 1.4.0 + "@lezer/css": 1.3.0 - '@codemirror/lang-go@6.0.1': + "@codemirror/lang-go@6.0.1": dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@lezer/common': 1.4.0 - '@lezer/go': 1.0.1 + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@lezer/common": 1.4.0 + "@lezer/go": 1.0.1 - '@codemirror/lang-html@6.4.11': + "@codemirror/lang-html@6.4.11": dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/lang-css': 6.3.1 - '@codemirror/lang-javascript': 6.2.4 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 - '@lezer/css': 1.3.0 - '@lezer/html': 1.3.12 + "@codemirror/autocomplete": 6.20.0 + "@codemirror/lang-css": 6.3.1 + "@codemirror/lang-javascript": 6.2.4 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 + "@lezer/common": 1.4.0 + "@lezer/css": 1.3.0 + "@lezer/html": 1.3.12 - '@codemirror/lang-java@6.0.2': + "@codemirror/lang-java@6.0.2": dependencies: - '@codemirror/language': 6.11.3 - '@lezer/java': 1.1.3 + "@codemirror/language": 6.11.3 + "@lezer/java": 1.1.3 - '@codemirror/lang-javascript@6.2.4': + "@codemirror/lang-javascript@6.2.4": dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/language': 6.11.3 - '@codemirror/lint': 6.9.2 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 - '@lezer/javascript': 1.5.4 + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.11.3 + "@codemirror/lint": 6.9.2 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 + "@lezer/common": 1.4.0 + "@lezer/javascript": 1.5.4 - '@codemirror/lang-jinja@6.0.0': + "@codemirror/lang-jinja@6.0.0": dependencies: - '@codemirror/lang-html': 6.4.11 - '@codemirror/language': 6.11.3 - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@codemirror/lang-html": 6.4.11 + "@codemirror/language": 6.11.3 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@codemirror/lang-json@6.0.2': + "@codemirror/lang-json@6.0.2": dependencies: - '@codemirror/language': 6.11.3 - '@lezer/json': 1.0.3 + "@codemirror/language": 6.11.3 + "@lezer/json": 1.0.3 - '@codemirror/lang-less@6.0.2': + "@codemirror/lang-less@6.0.2": dependencies: - '@codemirror/lang-css': 6.3.1 - '@codemirror/language': 6.11.3 - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@codemirror/lang-css": 6.3.1 + "@codemirror/language": 6.11.3 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@codemirror/lang-liquid@6.3.0': + "@codemirror/lang-liquid@6.3.0": dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/lang-html': 6.4.11 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 - - '@codemirror/lang-markdown@6.5.0': - dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/lang-html': 6.4.11 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 - '@lezer/markdown': 1.6.1 - - '@codemirror/lang-php@6.0.2': - dependencies: - '@codemirror/lang-html': 6.4.11 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@lezer/common': 1.4.0 - '@lezer/php': 1.0.5 - - '@codemirror/lang-python@6.2.1': - dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@lezer/common': 1.4.0 - '@lezer/python': 1.1.18 - - '@codemirror/lang-rust@6.0.2': - dependencies: - '@codemirror/language': 6.11.3 - '@lezer/rust': 1.0.2 - - '@codemirror/lang-sass@6.0.2': - dependencies: - '@codemirror/lang-css': 6.3.1 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@lezer/common': 1.4.0 - '@lezer/sass': 1.1.0 - - '@codemirror/lang-sql@6.10.0': - dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 - - '@codemirror/lang-vue@0.1.3': - dependencies: - '@codemirror/lang-html': 6.4.11 - '@codemirror/lang-javascript': 6.2.4 - '@codemirror/language': 6.11.3 - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 - - '@codemirror/lang-wast@6.0.2': - dependencies: - '@codemirror/language': 6.11.3 - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 - - '@codemirror/lang-xml@6.1.0': - dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 - '@lezer/xml': 1.0.6 - - '@codemirror/lang-yaml@6.1.2': - dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 - '@lezer/yaml': 1.0.3 - - '@codemirror/language-data@6.5.2': - dependencies: - '@codemirror/lang-angular': 0.1.4 - '@codemirror/lang-cpp': 6.0.3 - '@codemirror/lang-css': 6.3.1 - '@codemirror/lang-go': 6.0.1 - '@codemirror/lang-html': 6.4.11 - '@codemirror/lang-java': 6.0.2 - '@codemirror/lang-javascript': 6.2.4 - '@codemirror/lang-jinja': 6.0.0 - '@codemirror/lang-json': 6.0.2 - '@codemirror/lang-less': 6.0.2 - '@codemirror/lang-liquid': 6.3.0 - '@codemirror/lang-markdown': 6.5.0 - '@codemirror/lang-php': 6.0.2 - '@codemirror/lang-python': 6.2.1 - '@codemirror/lang-rust': 6.0.2 - '@codemirror/lang-sass': 6.0.2 - '@codemirror/lang-sql': 6.10.0 - '@codemirror/lang-vue': 0.1.3 - '@codemirror/lang-wast': 6.0.2 - '@codemirror/lang-xml': 6.1.0 - '@codemirror/lang-yaml': 6.1.2 - '@codemirror/language': 6.11.3 - '@codemirror/legacy-modes': 6.5.2 - - '@codemirror/language@6.11.3': - dependencies: - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@codemirror/autocomplete": 6.20.0 + "@codemirror/lang-html": 6.4.11 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 + + "@codemirror/lang-markdown@6.5.0": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/lang-html": 6.4.11 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 + "@lezer/common": 1.4.0 + "@lezer/markdown": 1.6.1 + + "@codemirror/lang-php@6.0.2": + dependencies: + "@codemirror/lang-html": 6.4.11 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@lezer/common": 1.4.0 + "@lezer/php": 1.0.5 + + "@codemirror/lang-python@6.2.1": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@lezer/common": 1.4.0 + "@lezer/python": 1.1.18 + + "@codemirror/lang-rust@6.0.2": + dependencies: + "@codemirror/language": 6.11.3 + "@lezer/rust": 1.0.2 + + "@codemirror/lang-sass@6.0.2": + dependencies: + "@codemirror/lang-css": 6.3.1 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@lezer/common": 1.4.0 + "@lezer/sass": 1.1.0 + + "@codemirror/lang-sql@6.10.0": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 + + "@codemirror/lang-vue@0.1.3": + dependencies: + "@codemirror/lang-html": 6.4.11 + "@codemirror/lang-javascript": 6.2.4 + "@codemirror/language": 6.11.3 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 + + "@codemirror/lang-wast@6.0.2": + dependencies: + "@codemirror/language": 6.11.3 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 + + "@codemirror/lang-xml@6.1.0": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 + "@lezer/common": 1.4.0 + "@lezer/xml": 1.0.6 + + "@codemirror/lang-yaml@6.1.2": + dependencies: + "@codemirror/autocomplete": 6.20.0 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 + "@lezer/yaml": 1.0.3 + + "@codemirror/language-data@6.5.2": + dependencies: + "@codemirror/lang-angular": 0.1.4 + "@codemirror/lang-cpp": 6.0.3 + "@codemirror/lang-css": 6.3.1 + "@codemirror/lang-go": 6.0.1 + "@codemirror/lang-html": 6.4.11 + "@codemirror/lang-java": 6.0.2 + "@codemirror/lang-javascript": 6.2.4 + "@codemirror/lang-jinja": 6.0.0 + "@codemirror/lang-json": 6.0.2 + "@codemirror/lang-less": 6.0.2 + "@codemirror/lang-liquid": 6.3.0 + "@codemirror/lang-markdown": 6.5.0 + "@codemirror/lang-php": 6.0.2 + "@codemirror/lang-python": 6.2.1 + "@codemirror/lang-rust": 6.0.2 + "@codemirror/lang-sass": 6.0.2 + "@codemirror/lang-sql": 6.10.0 + "@codemirror/lang-vue": 0.1.3 + "@codemirror/lang-wast": 6.0.2 + "@codemirror/lang-xml": 6.1.0 + "@codemirror/lang-yaml": 6.1.2 + "@codemirror/language": 6.11.3 + "@codemirror/legacy-modes": 6.5.2 + + "@codemirror/language@6.11.3": + dependencies: + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 style-mod: 4.1.3 - '@codemirror/legacy-modes@6.5.2': + "@codemirror/legacy-modes@6.5.2": dependencies: - '@codemirror/language': 6.11.3 + "@codemirror/language": 6.11.3 - '@codemirror/lint@6.9.2': + "@codemirror/lint@6.9.2": dependencies: - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 crelt: 1.0.6 - '@codemirror/search@6.5.11': + "@codemirror/search@6.5.11": dependencies: - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 crelt: 1.0.6 - '@codemirror/state@6.5.2': + "@codemirror/state@6.5.2": dependencies: - '@marijn/find-cluster-break': 1.0.2 + "@marijn/find-cluster-break": 1.0.2 - '@codemirror/view@6.39.4': + "@codemirror/view@6.39.4": dependencies: - '@codemirror/state': 6.5.2 + "@codemirror/state": 6.5.2 crelt: 1.0.6 style-mod: 4.1.3 w3c-keyname: 2.2.8 - '@csstools/color-helpers@5.1.0': {} + "@csstools/color-helpers@5.1.0": {} - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + "@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)": dependencies: - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + "@csstools/css-parser-algorithms": 3.0.5(@csstools/css-tokenizer@3.0.4) + "@csstools/css-tokenizer": 3.0.4 - '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + "@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)": dependencies: - '@csstools/color-helpers': 5.1.0 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + "@csstools/color-helpers": 5.1.0 + "@csstools/css-calc": 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + "@csstools/css-parser-algorithms": 3.0.5(@csstools/css-tokenizer@3.0.4) + "@csstools/css-tokenizer": 3.0.4 - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + "@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)": dependencies: - '@csstools/css-tokenizer': 3.0.4 + "@csstools/css-tokenizer": 3.0.4 - '@csstools/css-tokenizer@3.0.4': {} + "@csstools/css-tokenizer@3.0.4": {} - '@edge-runtime/primitives@4.1.0': + "@edge-runtime/primitives@4.1.0": optional: true - '@edge-runtime/vm@3.2.0': + "@edge-runtime/vm@3.2.0": dependencies: - '@edge-runtime/primitives': 4.1.0 + "@edge-runtime/primitives": 4.1.0 optional: true - '@emnapi/runtime@1.7.1': + "@emnapi/runtime@1.7.1": dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.25.12': + "@esbuild/aix-ppc64@0.25.12": optional: true - '@esbuild/android-arm64@0.25.12': + "@esbuild/android-arm64@0.25.12": optional: true - '@esbuild/android-arm@0.25.12': + "@esbuild/android-arm@0.25.12": optional: true - '@esbuild/android-x64@0.25.12': + "@esbuild/android-x64@0.25.12": optional: true - '@esbuild/darwin-arm64@0.25.12': + "@esbuild/darwin-arm64@0.25.12": optional: true - '@esbuild/darwin-x64@0.25.12': + "@esbuild/darwin-x64@0.25.12": optional: true - '@esbuild/freebsd-arm64@0.25.12': + "@esbuild/freebsd-arm64@0.25.12": optional: true - '@esbuild/freebsd-x64@0.25.12': + "@esbuild/freebsd-x64@0.25.12": optional: true - '@esbuild/linux-arm64@0.25.12': + "@esbuild/linux-arm64@0.25.12": optional: true - '@esbuild/linux-arm@0.25.12': + "@esbuild/linux-arm@0.25.12": optional: true - '@esbuild/linux-ia32@0.25.12': + "@esbuild/linux-ia32@0.25.12": optional: true - '@esbuild/linux-loong64@0.25.12': + "@esbuild/linux-loong64@0.25.12": optional: true - '@esbuild/linux-mips64el@0.25.12': + "@esbuild/linux-mips64el@0.25.12": optional: true - '@esbuild/linux-ppc64@0.25.12': + "@esbuild/linux-ppc64@0.25.12": optional: true - '@esbuild/linux-riscv64@0.25.12': + "@esbuild/linux-riscv64@0.25.12": optional: true - '@esbuild/linux-s390x@0.25.12': + "@esbuild/linux-s390x@0.25.12": optional: true - '@esbuild/linux-x64@0.25.12': + "@esbuild/linux-x64@0.25.12": optional: true - '@esbuild/netbsd-arm64@0.25.12': + "@esbuild/netbsd-arm64@0.25.12": optional: true - '@esbuild/netbsd-x64@0.25.12': + "@esbuild/netbsd-x64@0.25.12": optional: true - '@esbuild/openbsd-arm64@0.25.12': + "@esbuild/openbsd-arm64@0.25.12": optional: true - '@esbuild/openbsd-x64@0.25.12': + "@esbuild/openbsd-x64@0.25.12": optional: true - '@esbuild/openharmony-arm64@0.25.12': + "@esbuild/openharmony-arm64@0.25.12": optional: true - '@esbuild/sunos-x64@0.25.12': + "@esbuild/sunos-x64@0.25.12": optional: true - '@esbuild/win32-arm64@0.25.12': + "@esbuild/win32-arm64@0.25.12": optional: true - '@esbuild/win32-ia32@0.25.12': + "@esbuild/win32-ia32@0.25.12": optional: true - '@esbuild/win32-x64@0.25.12': + "@esbuild/win32-x64@0.25.12": optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))': + "@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))": dependencies: eslint: 9.39.1(jiti@2.6.1) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.2': {} + "@eslint-community/regexpp@4.12.2": {} - '@eslint/config-array@0.21.1': + "@eslint/config-array@0.21.1": dependencies: - '@eslint/object-schema': 2.1.7 + "@eslint/object-schema": 2.1.7 debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.4.2': + "@eslint/config-helpers@0.4.2": dependencies: - '@eslint/core': 0.17.0 + "@eslint/core": 0.17.0 - '@eslint/core@0.17.0': + "@eslint/core@0.17.0": dependencies: - '@types/json-schema': 7.0.15 + "@types/json-schema": 7.0.15 - '@eslint/eslintrc@3.3.3': + "@eslint/eslintrc@3.3.3": dependencies: ajv: 6.12.6 debug: 4.4.3 @@ -4201,316 +5021,316 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.39.1': {} + "@eslint/js@9.39.1": {} - '@eslint/object-schema@2.1.7': {} + "@eslint/object-schema@2.1.7": {} - '@eslint/plugin-kit@0.4.1': + "@eslint/plugin-kit@0.4.1": dependencies: - '@eslint/core': 0.17.0 + "@eslint/core": 0.17.0 levn: 0.4.1 - '@floating-ui/core@1.7.3': + "@floating-ui/core@1.7.3": dependencies: - '@floating-ui/utils': 0.2.10 + "@floating-ui/utils": 0.2.10 - '@floating-ui/dom@1.7.4': + "@floating-ui/dom@1.7.4": dependencies: - '@floating-ui/core': 1.7.3 - '@floating-ui/utils': 0.2.10 + "@floating-ui/core": 1.7.3 + "@floating-ui/utils": 0.2.10 - '@floating-ui/utils@0.2.10': {} + "@floating-ui/utils@0.2.10": {} - '@fontsource-variable/inter@5.2.8': {} + "@fontsource-variable/inter@5.2.8": {} - '@fontsource-variable/spline-sans-mono@5.2.8': {} + "@fontsource-variable/spline-sans-mono@5.2.8": {} - '@fontsource/inter@5.2.8': {} + "@fontsource/inter@5.2.8": {} - '@fontsource/source-serif-4@5.2.9': {} + "@fontsource/source-serif-4@5.2.9": {} - '@fontsource/spline-sans-mono@5.2.8': {} + "@fontsource/spline-sans-mono@5.2.8": {} - '@humanfs/core@0.19.1': {} + "@humanfs/core@0.19.1": {} - '@humanfs/node@0.16.7': + "@humanfs/node@0.16.7": dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.4.3 + "@humanfs/core": 0.19.1 + "@humanwhocodes/retry": 0.4.3 - '@humanwhocodes/module-importer@1.0.1': {} + "@humanwhocodes/module-importer@1.0.1": {} - '@humanwhocodes/retry@0.4.3': {} + "@humanwhocodes/retry@0.4.3": {} - '@img/colour@1.0.0': + "@img/colour@1.0.0": optional: true - '@img/sharp-darwin-arm64@0.34.5': + "@img/sharp-darwin-arm64@0.34.5": optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 + "@img/sharp-libvips-darwin-arm64": 1.2.4 optional: true - '@img/sharp-darwin-x64@0.34.5': + "@img/sharp-darwin-x64@0.34.5": optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + "@img/sharp-libvips-darwin-x64": 1.2.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + "@img/sharp-libvips-darwin-arm64@1.2.4": optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + "@img/sharp-libvips-darwin-x64@1.2.4": optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + "@img/sharp-libvips-linux-arm64@1.2.4": optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + "@img/sharp-libvips-linux-arm@1.2.4": optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + "@img/sharp-libvips-linux-ppc64@1.2.4": optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + "@img/sharp-libvips-linux-riscv64@1.2.4": optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + "@img/sharp-libvips-linux-s390x@1.2.4": optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + "@img/sharp-libvips-linux-x64@1.2.4": optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + "@img/sharp-libvips-linuxmusl-arm64@1.2.4": optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': + "@img/sharp-libvips-linuxmusl-x64@1.2.4": optional: true - '@img/sharp-linux-arm64@0.34.5': + "@img/sharp-linux-arm64@0.34.5": optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + "@img/sharp-libvips-linux-arm64": 1.2.4 optional: true - '@img/sharp-linux-arm@0.34.5': + "@img/sharp-linux-arm@0.34.5": optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + "@img/sharp-libvips-linux-arm": 1.2.4 optional: true - '@img/sharp-linux-ppc64@0.34.5': + "@img/sharp-linux-ppc64@0.34.5": optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + "@img/sharp-libvips-linux-ppc64": 1.2.4 optional: true - '@img/sharp-linux-riscv64@0.34.5': + "@img/sharp-linux-riscv64@0.34.5": optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + "@img/sharp-libvips-linux-riscv64": 1.2.4 optional: true - '@img/sharp-linux-s390x@0.34.5': + "@img/sharp-linux-s390x@0.34.5": optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + "@img/sharp-libvips-linux-s390x": 1.2.4 optional: true - '@img/sharp-linux-x64@0.34.5': + "@img/sharp-linux-x64@0.34.5": optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + "@img/sharp-libvips-linux-x64": 1.2.4 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': + "@img/sharp-linuxmusl-arm64@0.34.5": optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 optional: true - '@img/sharp-linuxmusl-x64@0.34.5': + "@img/sharp-linuxmusl-x64@0.34.5": optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + "@img/sharp-libvips-linuxmusl-x64": 1.2.4 optional: true - '@img/sharp-wasm32@0.34.5': + "@img/sharp-wasm32@0.34.5": dependencies: - '@emnapi/runtime': 1.7.1 + "@emnapi/runtime": 1.7.1 optional: true - '@img/sharp-win32-arm64@0.34.5': + "@img/sharp-win32-arm64@0.34.5": optional: true - '@img/sharp-win32-ia32@0.34.5': + "@img/sharp-win32-ia32@0.34.5": optional: true - '@img/sharp-win32-x64@0.34.5': + "@img/sharp-win32-x64@0.34.5": optional: true - '@jridgewell/gen-mapping@0.3.13': + "@jridgewell/gen-mapping@0.3.13": dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + "@jridgewell/sourcemap-codec": 1.5.5 + "@jridgewell/trace-mapping": 0.3.31 - '@jridgewell/remapping@2.3.5': + "@jridgewell/remapping@2.3.5": dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 - '@jridgewell/resolve-uri@3.1.2': {} + "@jridgewell/resolve-uri@3.1.2": {} - '@jridgewell/sourcemap-codec@1.5.5': {} + "@jridgewell/sourcemap-codec@1.5.5": {} - '@jridgewell/trace-mapping@0.3.31': + "@jridgewell/trace-mapping@0.3.31": dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.5 - '@lezer/common@1.4.0': {} + "@lezer/common@1.4.0": {} - '@lezer/cpp@1.1.4': + "@lezer/cpp@1.1.4": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/css@1.3.0': + "@lezer/css@1.3.0": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/go@1.0.1': + "@lezer/go@1.0.1": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/highlight@1.2.3': + "@lezer/highlight@1.2.3": dependencies: - '@lezer/common': 1.4.0 + "@lezer/common": 1.4.0 - '@lezer/html@1.3.12': + "@lezer/html@1.3.12": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/java@1.1.3': + "@lezer/java@1.1.3": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/javascript@1.5.4': + "@lezer/javascript@1.5.4": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/json@1.0.3': + "@lezer/json@1.0.3": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/lr@1.4.5': + "@lezer/lr@1.4.5": dependencies: - '@lezer/common': 1.4.0 + "@lezer/common": 1.4.0 - '@lezer/markdown@1.6.1': + "@lezer/markdown@1.6.1": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 - '@lezer/php@1.0.5': + "@lezer/php@1.0.5": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/python@1.1.18': + "@lezer/python@1.1.18": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/rust@1.0.2': + "@lezer/rust@1.0.2": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/sass@1.1.0': + "@lezer/sass@1.1.0": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/xml@1.0.6': + "@lezer/xml@1.0.6": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@lezer/yaml@1.0.3': + "@lezer/yaml@1.0.3": dependencies: - '@lezer/common': 1.4.0 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + "@lezer/common": 1.4.0 + "@lezer/highlight": 1.2.3 + "@lezer/lr": 1.4.5 - '@marijn/find-cluster-break@1.0.2': {} + "@marijn/find-cluster-break@1.0.2": {} - '@next/env@15.5.7': {} + "@next/env@15.5.7": {} - '@next/swc-darwin-arm64@15.5.7': + "@next/swc-darwin-arm64@15.5.7": optional: true - '@next/swc-darwin-x64@15.5.7': + "@next/swc-darwin-x64@15.5.7": optional: true - '@next/swc-linux-arm64-gnu@15.5.7': + "@next/swc-linux-arm64-gnu@15.5.7": optional: true - '@next/swc-linux-arm64-musl@15.5.7': + "@next/swc-linux-arm64-musl@15.5.7": optional: true - '@next/swc-linux-x64-gnu@15.5.7': + "@next/swc-linux-x64-gnu@15.5.7": optional: true - '@next/swc-linux-x64-musl@15.5.7': + "@next/swc-linux-x64-musl@15.5.7": optional: true - '@next/swc-win32-arm64-msvc@15.5.7': + "@next/swc-win32-arm64-msvc@15.5.7": optional: true - '@next/swc-win32-x64-msvc@15.5.7': + "@next/swc-win32-x64-msvc@15.5.7": optional: true - '@nodelib/fs.scandir@2.1.5': + "@nodelib/fs.scandir@2.1.5": dependencies: - '@nodelib/fs.stat': 2.0.5 + "@nodelib/fs.stat": 2.0.5 run-parallel: 1.2.0 - '@nodelib/fs.stat@2.0.5': {} + "@nodelib/fs.stat@2.0.5": {} - '@nodelib/fs.walk@1.2.8': + "@nodelib/fs.walk@1.2.8": dependencies: - '@nodelib/fs.scandir': 2.1.5 + "@nodelib/fs.scandir": 2.1.5 fastq: 1.19.1 - '@observablehq/inspector@5.0.1': + "@observablehq/inspector@5.0.1": dependencies: isoformat: 0.2.1 - '@observablehq/notebook-kit@1.5.0(@types/markdown-it@14.1.2)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)': - dependencies: - '@fontsource/inter': 5.2.8 - '@fontsource/source-serif-4': 5.2.9 - '@fontsource/spline-sans-mono': 5.2.8 - '@lezer/common': 1.4.0 - '@lezer/css': 1.3.0 - '@lezer/highlight': 1.2.3 - '@lezer/html': 1.3.12 - '@lezer/javascript': 1.5.4 - '@lezer/markdown': 1.6.1 - '@lezer/python': 1.1.18 - '@observablehq/inspector': 5.0.1 - '@observablehq/parser': 6.1.0 - '@observablehq/runtime': 6.0.0 - '@sindresorhus/slugify': 2.2.1 + "@observablehq/notebook-kit@1.5.0(@types/markdown-it@14.1.2)(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)": + dependencies: + "@fontsource/inter": 5.2.8 + "@fontsource/source-serif-4": 5.2.9 + "@fontsource/spline-sans-mono": 5.2.8 + "@lezer/common": 1.4.0 + "@lezer/css": 1.3.0 + "@lezer/highlight": 1.2.3 + "@lezer/html": 1.3.12 + "@lezer/javascript": 1.5.4 + "@lezer/markdown": 1.6.1 + "@lezer/python": 1.1.18 + "@observablehq/inspector": 5.0.1 + "@observablehq/parser": 6.1.0 + "@observablehq/runtime": 6.0.0 + "@sindresorhus/slugify": 2.2.1 acorn: 8.15.0 acorn-walk: 8.3.4 jsdom: 26.1.0 @@ -4519,8 +5339,8 @@ snapshots: typescript: 5.9.3 vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: - - '@types/markdown-it' - - '@types/node' + - "@types/markdown-it" + - "@types/node" - bufferutil - canvas - jiti @@ -4536,130 +5356,130 @@ snapshots: - utf-8-validate - yaml - '@observablehq/parser@6.1.0': + "@observablehq/parser@6.1.0": dependencies: acorn: 8.15.0 acorn-walk: 8.3.4 - '@observablehq/runtime@6.0.0': {} + "@observablehq/runtime@6.0.0": {} - '@rolldown/pluginutils@1.0.0-beta.27': {} + "@rolldown/pluginutils@1.0.0-beta.27": {} - '@rollup/rollup-android-arm-eabi@4.53.3': + "@rollup/rollup-android-arm-eabi@4.53.3": optional: true - '@rollup/rollup-android-arm64@4.53.3': + "@rollup/rollup-android-arm64@4.53.3": optional: true - '@rollup/rollup-darwin-arm64@4.53.3': + "@rollup/rollup-darwin-arm64@4.53.3": optional: true - '@rollup/rollup-darwin-x64@4.53.3': + "@rollup/rollup-darwin-x64@4.53.3": optional: true - '@rollup/rollup-freebsd-arm64@4.53.3': + "@rollup/rollup-freebsd-arm64@4.53.3": optional: true - '@rollup/rollup-freebsd-x64@4.53.3': + "@rollup/rollup-freebsd-x64@4.53.3": optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + "@rollup/rollup-linux-arm-gnueabihf@4.53.3": optional: true - '@rollup/rollup-linux-arm-musleabihf@4.53.3': + "@rollup/rollup-linux-arm-musleabihf@4.53.3": optional: true - '@rollup/rollup-linux-arm64-gnu@4.53.3': + "@rollup/rollup-linux-arm64-gnu@4.53.3": optional: true - '@rollup/rollup-linux-arm64-musl@4.53.3': + "@rollup/rollup-linux-arm64-musl@4.53.3": optional: true - '@rollup/rollup-linux-loong64-gnu@4.53.3': + "@rollup/rollup-linux-loong64-gnu@4.53.3": optional: true - '@rollup/rollup-linux-ppc64-gnu@4.53.3': + "@rollup/rollup-linux-ppc64-gnu@4.53.3": optional: true - '@rollup/rollup-linux-riscv64-gnu@4.53.3': + "@rollup/rollup-linux-riscv64-gnu@4.53.3": optional: true - '@rollup/rollup-linux-riscv64-musl@4.53.3': + "@rollup/rollup-linux-riscv64-musl@4.53.3": optional: true - '@rollup/rollup-linux-s390x-gnu@4.53.3': + "@rollup/rollup-linux-s390x-gnu@4.53.3": optional: true - '@rollup/rollup-linux-x64-gnu@4.53.3': + "@rollup/rollup-linux-x64-gnu@4.53.3": optional: true - '@rollup/rollup-linux-x64-musl@4.53.3': + "@rollup/rollup-linux-x64-musl@4.53.3": optional: true - '@rollup/rollup-openharmony-arm64@4.53.3': + "@rollup/rollup-openharmony-arm64@4.53.3": optional: true - '@rollup/rollup-win32-arm64-msvc@4.53.3': + "@rollup/rollup-win32-arm64-msvc@4.53.3": optional: true - '@rollup/rollup-win32-ia32-msvc@4.53.3': + "@rollup/rollup-win32-ia32-msvc@4.53.3": optional: true - '@rollup/rollup-win32-x64-gnu@4.53.3': + "@rollup/rollup-win32-x64-gnu@4.53.3": optional: true - '@rollup/rollup-win32-x64-msvc@4.53.3': + "@rollup/rollup-win32-x64-msvc@4.53.3": optional: true - '@shikijs/core@3.19.0': + "@shikijs/core@3.19.0": dependencies: - '@shikijs/types': 3.19.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 + "@shikijs/types": 3.19.0 + "@shikijs/vscode-textmate": 10.0.2 + "@types/hast": 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@3.19.0': + "@shikijs/engine-javascript@3.19.0": dependencies: - '@shikijs/types': 3.19.0 - '@shikijs/vscode-textmate': 10.0.2 + "@shikijs/types": 3.19.0 + "@shikijs/vscode-textmate": 10.0.2 oniguruma-to-es: 4.3.4 - '@shikijs/engine-oniguruma@3.19.0': + "@shikijs/engine-oniguruma@3.19.0": dependencies: - '@shikijs/types': 3.19.0 - '@shikijs/vscode-textmate': 10.0.2 + "@shikijs/types": 3.19.0 + "@shikijs/vscode-textmate": 10.0.2 - '@shikijs/langs@3.19.0': + "@shikijs/langs@3.19.0": dependencies: - '@shikijs/types': 3.19.0 + "@shikijs/types": 3.19.0 - '@shikijs/themes@3.19.0': + "@shikijs/themes@3.19.0": dependencies: - '@shikijs/types': 3.19.0 + "@shikijs/types": 3.19.0 - '@shikijs/types@3.19.0': + "@shikijs/types@3.19.0": dependencies: - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 + "@shikijs/vscode-textmate": 10.0.2 + "@types/hast": 3.0.4 - '@shikijs/vscode-textmate@10.0.2': {} + "@shikijs/vscode-textmate@10.0.2": {} - '@sindresorhus/slugify@2.2.1': + "@sindresorhus/slugify@2.2.1": dependencies: - '@sindresorhus/transliterate': 1.6.0 + "@sindresorhus/transliterate": 1.6.0 escape-string-regexp: 5.0.0 - '@sindresorhus/transliterate@1.6.0': + "@sindresorhus/transliterate@1.6.0": dependencies: escape-string-regexp: 5.0.0 - '@swc/helpers@0.5.15': + "@swc/helpers@0.5.15": dependencies: tslib: 2.8.1 - '@tailwindcss/node@4.1.17': + "@tailwindcss/node@4.1.17": dependencies: - '@jridgewell/remapping': 2.3.5 + "@jridgewell/remapping": 2.3.5 enhanced-resolve: 5.18.3 jiti: 2.6.1 lightningcss: 1.30.2 @@ -4667,136 +5487,136 @@ snapshots: source-map-js: 1.2.1 tailwindcss: 4.1.17 - '@tailwindcss/oxide-android-arm64@4.1.17': + "@tailwindcss/oxide-android-arm64@4.1.17": optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.17': + "@tailwindcss/oxide-darwin-arm64@4.1.17": optional: true - '@tailwindcss/oxide-darwin-x64@4.1.17': + "@tailwindcss/oxide-darwin-x64@4.1.17": optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.17': + "@tailwindcss/oxide-freebsd-x64@4.1.17": optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17": optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': + "@tailwindcss/oxide-linux-arm64-gnu@4.1.17": optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.17': + "@tailwindcss/oxide-linux-arm64-musl@4.1.17": optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.17': + "@tailwindcss/oxide-linux-x64-gnu@4.1.17": optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.17': + "@tailwindcss/oxide-linux-x64-musl@4.1.17": optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.17': + "@tailwindcss/oxide-wasm32-wasi@4.1.17": optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': + "@tailwindcss/oxide-win32-arm64-msvc@4.1.17": optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.17': + "@tailwindcss/oxide-win32-x64-msvc@4.1.17": optional: true - '@tailwindcss/oxide@4.1.17': + "@tailwindcss/oxide@4.1.17": optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.17 - '@tailwindcss/oxide-darwin-arm64': 4.1.17 - '@tailwindcss/oxide-darwin-x64': 4.1.17 - '@tailwindcss/oxide-freebsd-x64': 4.1.17 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.17 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.17 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.17 - '@tailwindcss/oxide-linux-x64-musl': 4.1.17 - '@tailwindcss/oxide-wasm32-wasi': 4.1.17 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 - - '@tailwindcss/postcss@4.1.17': - dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.17 - '@tailwindcss/oxide': 4.1.17 + "@tailwindcss/oxide-android-arm64": 4.1.17 + "@tailwindcss/oxide-darwin-arm64": 4.1.17 + "@tailwindcss/oxide-darwin-x64": 4.1.17 + "@tailwindcss/oxide-freebsd-x64": 4.1.17 + "@tailwindcss/oxide-linux-arm-gnueabihf": 4.1.17 + "@tailwindcss/oxide-linux-arm64-gnu": 4.1.17 + "@tailwindcss/oxide-linux-arm64-musl": 4.1.17 + "@tailwindcss/oxide-linux-x64-gnu": 4.1.17 + "@tailwindcss/oxide-linux-x64-musl": 4.1.17 + "@tailwindcss/oxide-wasm32-wasi": 4.1.17 + "@tailwindcss/oxide-win32-arm64-msvc": 4.1.17 + "@tailwindcss/oxide-win32-x64-msvc": 4.1.17 + + "@tailwindcss/postcss@4.1.17": + dependencies: + "@alloc/quick-lru": 5.2.0 + "@tailwindcss/node": 4.1.17 + "@tailwindcss/oxide": 4.1.17 postcss: 8.5.6 tailwindcss: 4.1.17 - '@types/babel__core@7.20.5': + "@types/babel__core@7.20.5": dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 + "@babel/parser": 7.28.5 + "@babel/types": 7.28.5 + "@types/babel__generator": 7.27.0 + "@types/babel__template": 7.4.4 + "@types/babel__traverse": 7.28.0 - '@types/babel__generator@7.27.0': + "@types/babel__generator@7.27.0": dependencies: - '@babel/types': 7.28.5 + "@babel/types": 7.28.5 - '@types/babel__template@7.4.4': + "@types/babel__template@7.4.4": dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + "@babel/parser": 7.28.5 + "@babel/types": 7.28.5 - '@types/babel__traverse@7.28.0': + "@types/babel__traverse@7.28.0": dependencies: - '@babel/types': 7.28.5 + "@babel/types": 7.28.5 - '@types/chai@5.2.3': + "@types/chai@5.2.3": dependencies: - '@types/deep-eql': 4.0.2 + "@types/deep-eql": 4.0.2 assertion-error: 2.0.1 - '@types/deep-eql@4.0.2': {} + "@types/deep-eql@4.0.2": {} - '@types/estree@1.0.8': {} + "@types/estree@1.0.8": {} - '@types/hast@3.0.4': + "@types/hast@3.0.4": dependencies: - '@types/unist': 3.0.3 + "@types/unist": 3.0.3 - '@types/json-schema@7.0.15': {} + "@types/json-schema@7.0.15": {} - '@types/linkify-it@5.0.0': {} + "@types/linkify-it@5.0.0": {} - '@types/markdown-it@14.1.2': + "@types/markdown-it@14.1.2": dependencies: - '@types/linkify-it': 5.0.0 - '@types/mdurl': 2.0.0 + "@types/linkify-it": 5.0.0 + "@types/mdurl": 2.0.0 - '@types/mdast@4.0.4': + "@types/mdast@4.0.4": dependencies: - '@types/unist': 3.0.3 + "@types/unist": 3.0.3 - '@types/mdurl@2.0.0': {} + "@types/mdurl@2.0.0": {} - '@types/node@24.10.1': + "@types/node@24.10.1": dependencies: undici-types: 7.16.0 - '@types/react-dom@19.2.3(@types/react@19.2.6)': + "@types/react-dom@19.2.3(@types/react@19.2.6)": dependencies: - '@types/react': 19.2.6 + "@types/react": 19.2.6 - '@types/react@19.2.6': + "@types/react@19.2.6": dependencies: csstype: 3.2.3 - '@types/unist@3.0.3': {} + "@types/unist@3.0.3": {} - '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + "@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)": dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 + "@eslint-community/regexpp": 4.12.2 + "@typescript-eslint/parser": 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/scope-manager": 8.49.0 + "@typescript-eslint/type-utils": 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/utils": 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/visitor-keys": 8.49.0 eslint: 9.39.1(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 @@ -4805,41 +5625,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + "@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)": dependencies: - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 + "@typescript-eslint/scope-manager": 8.49.0 + "@typescript-eslint/types": 8.49.0 + "@typescript-eslint/typescript-estree": 8.49.0(typescript@5.9.3) + "@typescript-eslint/visitor-keys": 8.49.0 debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': + "@typescript-eslint/project-service@8.49.0(typescript@5.9.3)": dependencies: - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 + "@typescript-eslint/tsconfig-utils": 8.49.0(typescript@5.9.3) + "@typescript-eslint/types": 8.49.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.49.0': + "@typescript-eslint/scope-manager@8.49.0": dependencies: - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/visitor-keys': 8.49.0 + "@typescript-eslint/types": 8.49.0 + "@typescript-eslint/visitor-keys": 8.49.0 - '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': + "@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)": dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + "@typescript-eslint/type-utils@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)": dependencies: - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/types": 8.49.0 + "@typescript-eslint/typescript-estree": 8.49.0(typescript@5.9.3) + "@typescript-eslint/utils": 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) @@ -4847,14 +5667,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.49.0': {} + "@typescript-eslint/types@8.49.0": {} - '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': + "@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)": dependencies: - '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/visitor-keys': 8.49.0 + "@typescript-eslint/project-service": 8.49.0(typescript@5.9.3) + "@typescript-eslint/tsconfig-utils": 8.49.0(typescript@5.9.3) + "@typescript-eslint/types": 8.49.0 + "@typescript-eslint/visitor-keys": 8.49.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 @@ -4864,94 +5684,94 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + "@typescript-eslint/utils@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)": dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + "@eslint-community/eslint-utils": 4.9.0(eslint@9.39.1(jiti@2.6.1)) + "@typescript-eslint/scope-manager": 8.49.0 + "@typescript-eslint/types": 8.49.0 + "@typescript-eslint/typescript-estree": 8.49.0(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.49.0': + "@typescript-eslint/visitor-keys@8.49.0": dependencies: - '@typescript-eslint/types': 8.49.0 + "@typescript-eslint/types": 8.49.0 eslint-visitor-keys: 4.2.1 - '@uiw/codemirror-theme-github@4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)': + "@uiw/codemirror-theme-github@4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)": dependencies: - '@uiw/codemirror-themes': 4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4) + "@uiw/codemirror-themes": 4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4) transitivePeerDependencies: - - '@codemirror/language' - - '@codemirror/state' - - '@codemirror/view' + - "@codemirror/language" + - "@codemirror/state" + - "@codemirror/view" - '@uiw/codemirror-themes@4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)': + "@uiw/codemirror-themes@4.25.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)": dependencies: - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + "@codemirror/language": 6.11.3 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 - '@ungap/structured-clone@1.3.0': {} + "@ungap/structured-clone@1.3.0": {} - '@vercel/analytics@1.6.1(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)': + "@vercel/analytics@1.6.1(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)": optionalDependencies: next: 15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react: 19.2.1 - '@vitejs/plugin-react@4.7.0(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': + "@vitejs/plugin-react@4.7.0(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))": dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) - '@rolldown/pluginutils': 1.0.0-beta.27 - '@types/babel__core': 7.20.5 + "@babel/core": 7.28.5 + "@babel/plugin-transform-react-jsx-self": 7.27.1(@babel/core@7.28.5) + "@babel/plugin-transform-react-jsx-source": 7.27.1(@babel/core@7.28.5) + "@rolldown/pluginutils": 1.0.0-beta.27 + "@types/babel__core": 7.20.5 react-refresh: 0.17.0 vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: - supports-color - '@vitest/expect@3.2.4': + "@vitest/expect@3.2.4": dependencies: - '@types/chai': 5.2.3 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 + "@types/chai": 5.2.3 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': + "@vitest/mocker@3.2.4(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))": dependencies: - '@vitest/spy': 3.2.4 + "@vitest/spy": 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) - '@vitest/pretty-format@3.2.4': + "@vitest/pretty-format@3.2.4": dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@3.2.4': + "@vitest/runner@3.2.4": dependencies: - '@vitest/utils': 3.2.4 + "@vitest/utils": 3.2.4 pathe: 2.0.3 strip-literal: 3.1.0 - '@vitest/snapshot@3.2.4': + "@vitest/snapshot@3.2.4": dependencies: - '@vitest/pretty-format': 3.2.4 + "@vitest/pretty-format": 3.2.4 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@3.2.4': + "@vitest/spy@3.2.4": dependencies: tinyspy: 4.0.4 - '@vitest/utils@3.2.4': + "@vitest/utils@3.2.4": dependencies: - '@vitest/pretty-format': 3.2.4 + "@vitest/pretty-format": 3.2.4 loupe: 3.2.1 tinyrainbow: 2.0.0 @@ -5313,13 +6133,13 @@ snapshots: codemirror@6.0.2: dependencies: - '@codemirror/autocomplete': 6.20.0 - '@codemirror/commands': 6.10.0 - '@codemirror/language': 6.11.3 - '@codemirror/lint': 6.9.2 - '@codemirror/search': 6.5.11 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + "@codemirror/autocomplete": 6.20.0 + "@codemirror/commands": 6.10.0 + "@codemirror/language": 6.11.3 + "@codemirror/lint": 6.9.2 + "@codemirror/search": 6.5.11 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.39.4 color-convert@2.0.1: dependencies: @@ -5378,7 +6198,7 @@ snapshots: cssstyle@4.6.0: dependencies: - '@asamuzakjp/css-color': 3.2.0 + "@asamuzakjp/css-color": 3.2.0 rrweb-cssom: 0.8.0 csstype@3.2.3: {} @@ -5627,32 +6447,32 @@ snapshots: esbuild@0.25.12: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 + "@esbuild/aix-ppc64": 0.25.12 + "@esbuild/android-arm": 0.25.12 + "@esbuild/android-arm64": 0.25.12 + "@esbuild/android-x64": 0.25.12 + "@esbuild/darwin-arm64": 0.25.12 + "@esbuild/darwin-x64": 0.25.12 + "@esbuild/freebsd-arm64": 0.25.12 + "@esbuild/freebsd-x64": 0.25.12 + "@esbuild/linux-arm": 0.25.12 + "@esbuild/linux-arm64": 0.25.12 + "@esbuild/linux-ia32": 0.25.12 + "@esbuild/linux-loong64": 0.25.12 + "@esbuild/linux-mips64el": 0.25.12 + "@esbuild/linux-ppc64": 0.25.12 + "@esbuild/linux-riscv64": 0.25.12 + "@esbuild/linux-s390x": 0.25.12 + "@esbuild/linux-x64": 0.25.12 + "@esbuild/netbsd-arm64": 0.25.12 + "@esbuild/netbsd-x64": 0.25.12 + "@esbuild/openbsd-arm64": 0.25.12 + "@esbuild/openbsd-x64": 0.25.12 + "@esbuild/openharmony-arm64": 0.25.12 + "@esbuild/sunos-x64": 0.25.12 + "@esbuild/win32-arm64": 0.25.12 + "@esbuild/win32-ia32": 0.25.12 + "@esbuild/win32-x64": 0.25.12 escalade@3.2.0: {} @@ -5707,18 +6527,18 @@ snapshots: eslint@9.39.1(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.1 - '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 + "@eslint-community/eslint-utils": 4.9.0(eslint@9.39.1(jiti@2.6.1)) + "@eslint-community/regexpp": 4.12.2 + "@eslint/config-array": 0.21.1 + "@eslint/config-helpers": 0.4.2 + "@eslint/core": 0.17.0 + "@eslint/eslintrc": 3.3.3 + "@eslint/js": 9.39.1 + "@eslint/plugin-kit": 0.4.1 + "@humanfs/node": 0.16.7 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.3 + "@types/estree": 1.0.8 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -5766,7 +6586,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + "@types/estree": 1.0.8 esutils@2.0.3: {} @@ -5816,8 +6636,8 @@ snapshots: fast-glob@3.3.3: dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.8 @@ -5892,7 +6712,7 @@ snapshots: express: 4.22.1 lodash.samplesize: 4.2.0 transitivePeerDependencies: - - '@ava/typescript' + - "@ava/typescript" - supports-color fsevents@2.3.3: @@ -5994,8 +6814,8 @@ snapshots: hast-util-to-html@9.0.5: dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 + "@types/hast": 3.0.4 + "@types/unist": 3.0.3 ccount: 2.0.1 comma-separated-tokens: 2.0.3 hast-util-whitespace: 3.0.0 @@ -6008,7 +6828,7 @@ snapshots: hast-util-whitespace@3.0.0: dependencies: - '@types/hast': 3.0.4 + "@types/hast": 3.0.4 html-encoding-sniffer@4.0.0: dependencies: @@ -6237,9 +7057,9 @@ snapshots: jotai@2.15.1(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.6)(react@19.2.1): optionalDependencies: - '@babel/core': 7.28.5 - '@babel/template': 7.27.2 - '@types/react': 19.2.6 + "@babel/core": 7.28.5 + "@babel/template": 7.27.2 + "@types/react": 19.2.6 react: 19.2.1 js-string-escape@1.0.1: {} @@ -6401,7 +7221,7 @@ snapshots: magic-string@0.30.21: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + "@jridgewell/sourcemap-codec": 1.5.5 map-age-cleaner@0.1.3: dependencies: @@ -6409,7 +7229,7 @@ snapshots: markdown-it-anchor@9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.1.0): dependencies: - '@types/markdown-it': 14.1.2 + "@types/markdown-it": 14.1.2 markdown-it: 14.1.0 markdown-it@14.1.0: @@ -6433,9 +7253,9 @@ snapshots: mdast-util-to-hast@13.2.1: dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.0 + "@types/hast": 3.0.4 + "@types/mdast": 4.0.4 + "@ungap/structured-clone": 1.3.0 devlop: 1.1.0 micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 @@ -6512,25 +7332,25 @@ snapshots: next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: - '@next/env': 15.5.7 - '@swc/helpers': 0.5.15 + "@next/env": 15.5.7 + "@swc/helpers": 0.5.15 caniuse-lite: 1.0.30001760 postcss: 8.4.31 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.1) optionalDependencies: - '@next/swc-darwin-arm64': 15.5.7 - '@next/swc-darwin-x64': 15.5.7 - '@next/swc-linux-arm64-gnu': 15.5.7 - '@next/swc-linux-arm64-musl': 15.5.7 - '@next/swc-linux-x64-gnu': 15.5.7 - '@next/swc-linux-x64-musl': 15.5.7 - '@next/swc-win32-arm64-msvc': 15.5.7 - '@next/swc-win32-x64-msvc': 15.5.7 + "@next/swc-darwin-arm64": 15.5.7 + "@next/swc-darwin-x64": 15.5.7 + "@next/swc-linux-arm64-gnu": 15.5.7 + "@next/swc-linux-arm64-musl": 15.5.7 + "@next/swc-linux-x64-gnu": 15.5.7 + "@next/swc-linux-x64-musl": 15.5.7 + "@next/swc-win32-arm64-msvc": 15.5.7 + "@next/swc-win32-x64-msvc": 15.5.7 sharp: 0.34.5 transitivePeerDependencies: - - '@babel/core' + - "@babel/core" - babel-plugin-macros node-releases@2.0.27: {} @@ -6764,7 +7584,7 @@ snapshots: react-tooltip@5.30.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: - '@floating-ui/dom': 1.7.4 + "@floating-ui/dom": 1.7.4 classnames: 2.5.1 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) @@ -6827,30 +7647,30 @@ snapshots: rollup@4.53.3: dependencies: - '@types/estree': 1.0.8 + "@types/estree": 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.53.3 - '@rollup/rollup-android-arm64': 4.53.3 - '@rollup/rollup-darwin-arm64': 4.53.3 - '@rollup/rollup-darwin-x64': 4.53.3 - '@rollup/rollup-freebsd-arm64': 4.53.3 - '@rollup/rollup-freebsd-x64': 4.53.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 - '@rollup/rollup-linux-arm-musleabihf': 4.53.3 - '@rollup/rollup-linux-arm64-gnu': 4.53.3 - '@rollup/rollup-linux-arm64-musl': 4.53.3 - '@rollup/rollup-linux-loong64-gnu': 4.53.3 - '@rollup/rollup-linux-ppc64-gnu': 4.53.3 - '@rollup/rollup-linux-riscv64-gnu': 4.53.3 - '@rollup/rollup-linux-riscv64-musl': 4.53.3 - '@rollup/rollup-linux-s390x-gnu': 4.53.3 - '@rollup/rollup-linux-x64-gnu': 4.53.3 - '@rollup/rollup-linux-x64-musl': 4.53.3 - '@rollup/rollup-openharmony-arm64': 4.53.3 - '@rollup/rollup-win32-arm64-msvc': 4.53.3 - '@rollup/rollup-win32-ia32-msvc': 4.53.3 - '@rollup/rollup-win32-x64-gnu': 4.53.3 - '@rollup/rollup-win32-x64-msvc': 4.53.3 + "@rollup/rollup-android-arm-eabi": 4.53.3 + "@rollup/rollup-android-arm64": 4.53.3 + "@rollup/rollup-darwin-arm64": 4.53.3 + "@rollup/rollup-darwin-x64": 4.53.3 + "@rollup/rollup-freebsd-arm64": 4.53.3 + "@rollup/rollup-freebsd-x64": 4.53.3 + "@rollup/rollup-linux-arm-gnueabihf": 4.53.3 + "@rollup/rollup-linux-arm-musleabihf": 4.53.3 + "@rollup/rollup-linux-arm64-gnu": 4.53.3 + "@rollup/rollup-linux-arm64-musl": 4.53.3 + "@rollup/rollup-linux-loong64-gnu": 4.53.3 + "@rollup/rollup-linux-ppc64-gnu": 4.53.3 + "@rollup/rollup-linux-riscv64-gnu": 4.53.3 + "@rollup/rollup-linux-riscv64-musl": 4.53.3 + "@rollup/rollup-linux-s390x-gnu": 4.53.3 + "@rollup/rollup-linux-x64-gnu": 4.53.3 + "@rollup/rollup-linux-x64-musl": 4.53.3 + "@rollup/rollup-openharmony-arm64": 4.53.3 + "@rollup/rollup-win32-arm64-msvc": 4.53.3 + "@rollup/rollup-win32-ia32-msvc": 4.53.3 + "@rollup/rollup-win32-x64-gnu": 4.53.3 + "@rollup/rollup-win32-x64-msvc": 4.53.3 fsevents: 2.3.3 rrweb-cssom@0.8.0: {} @@ -6967,34 +7787,34 @@ snapshots: sharp@0.34.5: dependencies: - '@img/colour': 1.0.0 + "@img/colour": 1.0.0 detect-libc: 2.1.2 semver: 7.7.3 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 + "@img/sharp-darwin-arm64": 0.34.5 + "@img/sharp-darwin-x64": 0.34.5 + "@img/sharp-libvips-darwin-arm64": 1.2.4 + "@img/sharp-libvips-darwin-x64": 1.2.4 + "@img/sharp-libvips-linux-arm": 1.2.4 + "@img/sharp-libvips-linux-arm64": 1.2.4 + "@img/sharp-libvips-linux-ppc64": 1.2.4 + "@img/sharp-libvips-linux-riscv64": 1.2.4 + "@img/sharp-libvips-linux-s390x": 1.2.4 + "@img/sharp-libvips-linux-x64": 1.2.4 + "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 + "@img/sharp-libvips-linuxmusl-x64": 1.2.4 + "@img/sharp-linux-arm": 0.34.5 + "@img/sharp-linux-arm64": 0.34.5 + "@img/sharp-linux-ppc64": 0.34.5 + "@img/sharp-linux-riscv64": 0.34.5 + "@img/sharp-linux-s390x": 0.34.5 + "@img/sharp-linux-x64": 0.34.5 + "@img/sharp-linuxmusl-arm64": 0.34.5 + "@img/sharp-linuxmusl-x64": 0.34.5 + "@img/sharp-wasm32": 0.34.5 + "@img/sharp-win32-arm64": 0.34.5 + "@img/sharp-win32-ia32": 0.34.5 + "@img/sharp-win32-x64": 0.34.5 optional: true shebang-command@2.0.0: @@ -7005,14 +7825,14 @@ snapshots: shiki@3.19.0: dependencies: - '@shikijs/core': 3.19.0 - '@shikijs/engine-javascript': 3.19.0 - '@shikijs/engine-oniguruma': 3.19.0 - '@shikijs/langs': 3.19.0 - '@shikijs/themes': 3.19.0 - '@shikijs/types': 3.19.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 + "@shikijs/core": 3.19.0 + "@shikijs/engine-javascript": 3.19.0 + "@shikijs/engine-oniguruma": 3.19.0 + "@shikijs/langs": 3.19.0 + "@shikijs/themes": 3.19.0 + "@shikijs/types": 3.19.0 + "@shikijs/vscode-textmate": 10.0.2 + "@types/hast": 3.0.4 short-uuid@5.2.0: dependencies: @@ -7176,7 +7996,7 @@ snapshots: client-only: 0.0.1 react: 19.2.1 optionalDependencies: - '@babel/core': 7.28.5 + "@babel/core": 7.28.5 supertap@3.0.1: dependencies: @@ -7300,10 +8120,10 @@ snapshots: typescript-eslint@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/eslint-plugin": 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/parser": 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + "@typescript-eslint/typescript-estree": 8.49.0(typescript@5.9.3) + "@typescript-eslint/utils": 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -7326,24 +8146,24 @@ snapshots: unist-util-is@6.0.1: dependencies: - '@types/unist': 3.0.3 + "@types/unist": 3.0.3 unist-util-position@5.0.0: dependencies: - '@types/unist': 3.0.3 + "@types/unist": 3.0.3 unist-util-stringify-position@4.0.0: dependencies: - '@types/unist': 3.0.3 + "@types/unist": 3.0.3 unist-util-visit-parents@6.0.2: dependencies: - '@types/unist': 3.0.3 + "@types/unist": 3.0.3 unist-util-is: 6.0.1 unist-util-visit@5.0.0: dependencies: - '@types/unist': 3.0.3 + "@types/unist": 3.0.3 unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 @@ -7367,12 +8187,12 @@ snapshots: vfile-message@4.0.3: dependencies: - '@types/unist': 3.0.3 + "@types/unist": 3.0.3 unist-util-stringify-position: 4.0.0 vfile@6.0.3: dependencies: - '@types/unist': 3.0.3 + "@types/unist": 3.0.3 vfile-message: 4.0.3 vite-node@3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2): @@ -7383,7 +8203,7 @@ snapshots: pathe: 2.0.3 vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: - - '@types/node' + - "@types/node" - jiti - less - lightningcss @@ -7405,21 +8225,21 @@ snapshots: rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.1 + "@types/node": 24.10.1 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 vitest@3.2.4(@edge-runtime/vm@3.2.0)(@types/node@24.10.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2): dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 + "@types/chai": 5.2.3 + "@vitest/expect": 3.2.4 + "@vitest/mocker": 3.2.4(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) + "@vitest/pretty-format": 3.2.4 + "@vitest/runner": 3.2.4 + "@vitest/snapshot": 3.2.4 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 chai: 5.3.3 debug: 4.4.3 expect-type: 1.3.0 @@ -7436,8 +8256,8 @@ snapshots: vite-node: 3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) why-is-node-running: 2.3.0 optionalDependencies: - '@edge-runtime/vm': 3.2.0 - '@types/node': 24.10.1 + "@edge-runtime/vm": 3.2.0 + "@types/node": 24.10.1 jsdom: 26.1.0 transitivePeerDependencies: - jiti diff --git a/runtime/index.js b/runtime/index.js index dfe5112..61069ac 100644 --- a/runtime/index.js +++ b/runtime/index.js @@ -11,7 +11,7 @@ import {IntervalTree} from "../lib/IntervalTree.ts"; import {transpileRechoJavaScript} from "./transpile.js"; import {ButtonRegistry, makeButton} from "./controls/button.js"; import {addPrefix, makeOutput, OUTPUT_PREFIX, ERROR_PREFIX} from "./output.js"; -import { Transaction } from "@codemirror/state"; +import {Transaction} from "@codemirror/state"; function uid() { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); diff --git a/test/components/App.tsx b/test/components/App.tsx index d42e9c4..21e177a 100644 --- a/test/components/App.tsx +++ b/test/components/App.tsx @@ -8,10 +8,10 @@ import {BlockViewer} from "./BlockViewer.tsx"; import {Editor} from "./Editor.tsx"; import {TestSelector} from "./TestSelector.tsx"; import {TransactionViewer} from "./TransactionViewer.tsx"; -import { type TransactionData, extractTransactionData } from "./transaction-data.ts"; -import { List } from "immutable"; -import { extractBlockData, type BlockData } from "./block-data.ts"; -import { EditorSelection } from "@codemirror/state"; +import {type TransactionData, extractTransactionData} from "./transaction-data.ts"; +import {List} from "immutable"; +import {extractBlockData, type BlockData} from "./block-data.ts"; +import {EditorSelection} from "@codemirror/state"; export function App() { const [selectedTest, setSelectedTest] = useAtom(selectedTestAtom); @@ -28,8 +28,8 @@ export function App() { class { update(update: ViewUpdate) { if (update.transactions.length > 0) { - setTransactions(oldTransactions => { - const transactionData = update.transactions.map(tr => extractTransactionData(tr, nextIndex++)); + setTransactions((oldTransactions) => { + const transactionData = update.transactions.map((tr) => extractTransactionData(tr, nextIndex++)); const newTransactions = oldTransactions.concat(transactionData); if (newTransactions.size > MAX_HISTORY) { return newTransactions.slice(newTransactions.size - MAX_HISTORY); diff --git a/test/components/BlockItem.tsx b/test/components/BlockItem.tsx index dea28cc..03e28c4 100644 --- a/test/components/BlockItem.tsx +++ b/test/components/BlockItem.tsx @@ -56,31 +56,31 @@ export function BlockItem({block, onLocate}: {block: BlockData; onLocate: (from: Output Range: {block.outputFrom}-{block.outputTo} ({block.outputTo! - block.outputFrom!} chars) + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + onLocate(block.outputFrom!, block.outputTo!); + }} + className="p-1 hover:bg-gray-200 rounded transition-colors" + title="Locate block in editor" + > + +
) : (
Output Range: none + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + onLocate(block.outputFrom ?? block.sourceFrom, block.sourceTo); + }} + className="p-1 hover:bg-gray-200 rounded transition-colors" + title="Locate block in editor" + > + +
)} @@ -100,7 +100,6 @@ export function BlockItem({block, onLocate}: {block: BlockData; onLocate: (from:
-
Total Range: {hasOutput ? block.outputFrom : block.sourceFrom}-{block.sourceTo} diff --git a/test/components/BlockViewer.tsx b/test/components/BlockViewer.tsx index e8353b4..8de05db 100644 --- a/test/components/BlockViewer.tsx +++ b/test/components/BlockViewer.tsx @@ -1,7 +1,7 @@ import {useEffect, useRef, useState} from "react"; import type {BlockData} from "./block-data.ts"; import {BlockItem} from "./BlockItem.tsx"; -import type { List } from "immutable"; +import type {List} from "immutable"; interface BlockViewerProps { blocks: List; @@ -38,7 +38,10 @@ export function BlockViewer({blocks, onLocateBlock}: BlockViewerProps) { {blocks.size === 0 ? (
No blocks yet
) : ( - blocks.toSeq().map((block) => ).toArray() + blocks + .toSeq() + .map((block) => ) + .toArray() )}
diff --git a/test/components/TransactionViewer.tsx b/test/components/TransactionViewer.tsx index 8ff85e1..0c1074f 100644 --- a/test/components/TransactionViewer.tsx +++ b/test/components/TransactionViewer.tsx @@ -2,13 +2,12 @@ import {useState, useEffect, useRef, useMemo} from "react"; import {SelectionGroupItem} from "./SelectionGroupItem.tsx"; import type {TransactionData, TransactionGroup} from "./transaction-data.ts"; import {TransactionItem} from "./TransactionItem.tsx"; -import type { List } from "immutable"; +import type {List} from "immutable"; export type TransactionViewerProps = { transactions: List; onClear: () => void; -} - +}; export function TransactionViewer({transactions, onClear}: TransactionViewerProps) { const [autoScroll, setAutoScroll] = useState(true); @@ -41,7 +40,7 @@ export function TransactionViewer({transactions, onClear}: TransactionViewerProp groups.push({type: "individual", transaction: tr}); currentGroup = null; } - }) + }); return groups; }, [filteredTransactions]); diff --git a/test/components/block-data.ts b/test/components/block-data.ts index 81521b1..b2012e7 100644 --- a/test/components/block-data.ts +++ b/test/components/block-data.ts @@ -1,5 +1,5 @@ -import { blockMetadataField } from "../../editor/blockMetadata.ts"; -import type { EditorView } from "@codemirror/view"; +import {blockMetadataField} from "../../editor/blockMetadata.ts"; +import type {EditorView} from "@codemirror/view"; export interface BlockData { id: string; diff --git a/test/components/transaction-data.ts b/test/components/transaction-data.ts index 4bc17f4..ecf174c 100644 --- a/test/components/transaction-data.ts +++ b/test/components/transaction-data.ts @@ -1,6 +1,6 @@ -import { blockMetadataEffect } from "../../editor/blockMetadata.ts"; -import type { BlockMetadata } from "../../editor/blocks/BlockMetadata.ts"; -import { Transaction as Tr } from "@codemirror/state"; +import {blockMetadataEffect} from "../../editor/blockMetadata.ts"; +import type {BlockMetadata} from "../../editor/blocks/BlockMetadata.ts"; +import {Transaction as Tr} from "@codemirror/state"; export interface TransactionRange { from: number; @@ -108,4 +108,4 @@ export function extractTransactionData(tr: Tr, index: number): TransactionData { } return data; -} \ No newline at end of file +} diff --git a/types/observablehq__runtime.d.ts b/types/observablehq__runtime.d.ts index c0518a9..bba1d43 100644 --- a/types/observablehq__runtime.d.ts +++ b/types/observablehq__runtime.d.ts @@ -27,12 +27,7 @@ declare module "@observablehq/runtime" { // Variable class export class Variable { - constructor( - type: number, - module: Module, - observer?: Observer | boolean, - options?: VariableOptions - ); + constructor(type: number, module: Module, observer?: Observer | boolean, options?: VariableOptions); // Define a variable with optional name, inputs, and definition define(definition: DefinitionFunction | T): this; @@ -80,10 +75,7 @@ declare module "@observablehq/runtime" { variable(observer?: Observer | boolean, options?: VariableOptions): Variable; // Derive a new module with injected variables - derive( - injects: Array, - injectModule: Module - ): Module; + derive(injects: Array, injectModule: Module): Module; // Get the value of a variable by name value(name: string): Promise; @@ -110,7 +102,7 @@ declare module "@observablehq/runtime" { module(): Module; module( define: (runtime: Runtime, observer: (variable: Variable) => Observer) => void, - observer?: (variable: Variable) => Observer + observer?: (variable: Variable) => Observer, ): Module; // Dispose the runtime diff --git a/webpack-empty-module.js b/webpack-empty-module.js index 9d9393b..4fda643 100644 --- a/webpack-empty-module.js +++ b/webpack-empty-module.js @@ -14,4 +14,3 @@ export default emptyModule; // Also export as named export for compatibility export {emptyModule}; - From 239753d0c79bf3501fdd1a1977d6821c5028d5ae Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:01:29 +0800 Subject: [PATCH 28/30] Fix type errors --- test/components/SelectionGroupItem.tsx | 2 +- test/components/TransactionItem.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/components/SelectionGroupItem.tsx b/test/components/SelectionGroupItem.tsx index 6a49caa..f756b22 100644 --- a/test/components/SelectionGroupItem.tsx +++ b/test/components/SelectionGroupItem.tsx @@ -1,5 +1,5 @@ import {useState} from "react"; -import type {TransactionData} from "./types.ts"; +import type {TransactionData} from "./transaction-data.ts"; export function SelectionGroupItem({transactions}: {transactions: TransactionData[]}) { const [isOpen, setIsOpen] = useState(false); diff --git a/test/components/TransactionItem.tsx b/test/components/TransactionItem.tsx index 2a73387..2f3e60f 100644 --- a/test/components/TransactionItem.tsx +++ b/test/components/TransactionItem.tsx @@ -1,5 +1,5 @@ import {useState, type ReactNode} from "react"; -import type {TransactionData} from "./types.ts"; +import type {TransactionData} from "./transaction-data.ts"; import {ObjectInspector} from "react-inspector"; import {cn} from "../../app/cn.js"; import {UserEvent} from "./UserEvent.tsx"; From acd4f0b8814eaddd98e52c8029b95b56e599201b Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:22:03 +0800 Subject: [PATCH 29/30] Display indent and dedent transactions --- test/components/UserEvent.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/components/UserEvent.tsx b/test/components/UserEvent.tsx index a2fc6b8..0e451e6 100644 --- a/test/components/UserEvent.tsx +++ b/test/components/UserEvent.tsx @@ -5,6 +5,8 @@ import { DeleteIcon, KeyboardIcon, LanguagesIcon, + IndentIncreaseIcon, + IndentDecreaseIcon, ListPlusIcon, MoveIcon, RedoDotIcon, @@ -50,6 +52,14 @@ export function UserEvent({userEvent}: {userEvent: string}) { Icon = ListPlusIcon; text = "Complete"; break; + case "input.indent": + Icon = IndentIncreaseIcon; + text = "Indent"; + break; + case "delete.dedent": + Icon = IndentDecreaseIcon; + text = "Dedent"; + break; case "delete.selection": Icon = TextCursorInputIcon; text = "Delete Selection"; From 00b61e1b8933f8eca0dee281a291127500c06b6f Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Sat, 20 Dec 2025 22:49:13 +0800 Subject: [PATCH 30/30] Gather block tracking related source files --- editor/blocks/compact.ts | 49 +++++++ editor/blocks/debug.ts | 50 ++++++++ editor/blocks/{dedup.ts => deduplicate.ts} | 0 editor/blocks/effect.ts | 4 + editor/blocks/index.ts | 24 ++++ .../indicators.ts} | 4 +- editor/blocks/output.ts | 97 ++++++++++++++ editor/blocks/state.ts | 34 +++++ editor/{blockMetadata.ts => blocks/update.ts} | 93 ++++++-------- editor/decoration.js | 121 ------------------ editor/index.js | 11 +- editor/outputLines.js | 60 --------- editor/protection.js | 37 ------ runtime/index.js | 3 +- test/components/block-data.ts | 2 +- test/components/transaction-data.ts | 2 +- vite.config.js | 3 + 17 files changed, 310 insertions(+), 284 deletions(-) create mode 100644 editor/blocks/compact.ts create mode 100644 editor/blocks/debug.ts rename editor/blocks/{dedup.ts => deduplicate.ts} (100%) create mode 100644 editor/blocks/effect.ts create mode 100644 editor/blocks/index.ts rename editor/{blockIndicator.ts => blocks/indicators.ts} (96%) create mode 100644 editor/blocks/output.ts create mode 100644 editor/blocks/state.ts rename editor/{blockMetadata.ts => blocks/update.ts} (67%) delete mode 100644 editor/decoration.js delete mode 100644 editor/outputLines.js delete mode 100644 editor/protection.js diff --git a/editor/blocks/compact.ts b/editor/blocks/compact.ts new file mode 100644 index 0000000..4981816 --- /dev/null +++ b/editor/blocks/compact.ts @@ -0,0 +1,49 @@ +import {Decoration, ViewPlugin, ViewUpdate, EditorView, type DecorationSet} from "@codemirror/view"; +import {blockMetadataField} from "./state.ts"; +import {BlockMetadata} from "./BlockMetadata.ts"; +import {RangeSetBuilder} from "@codemirror/state"; + +const compactLineDecoration = Decoration.line({attributes: {class: "cm-output-line cm-compact-line"}}); + +export const compactDecoration = ViewPlugin.fromClass( + class { + #decorations: DecorationSet; + + get decorations() { + return this.#decorations; + } + + /** @param {EditorView} view */ + constructor(view: EditorView) { + const blockMetadata = view.state.field(blockMetadataField); + this.#decorations = this.createDecorations(blockMetadata, view.state); + } + + /** @param {ViewUpdate} update */ + update(update: ViewUpdate) { + const blockMetadata = update.state.field(blockMetadataField); + // A possible optimization would be to only update the changed lines. + this.#decorations = this.createDecorations(blockMetadata, update.state); + } + + createDecorations(blockMetadata: BlockMetadata[], state: EditorView["state"]) { + const builder = new RangeSetBuilder(); + // Add block attribute decorations + for (const {output, attributes} of blockMetadata) { + if (output === null) continue; + // Apply decorations to each line in the block range + const startLine = state.doc.lineAt(output.from); + const endLine = state.doc.lineAt(output.to); + const endLineNumber = endLine.from < output.to ? endLine.number + 1 : endLine.number; + if (attributes.compact === true) { + for (let lineNum = startLine.number; lineNum < endLineNumber; lineNum++) { + const line = state.doc.line(lineNum); + builder.add(line.from, line.from, compactLineDecoration); + } + } + } + return builder.finish(); + } + }, + {decorations: (v) => v.decorations}, +); diff --git a/editor/blocks/debug.ts b/editor/blocks/debug.ts new file mode 100644 index 0000000..47786b7 --- /dev/null +++ b/editor/blocks/debug.ts @@ -0,0 +1,50 @@ +import {RangeSetBuilder} from "@codemirror/state"; +import {Decoration, EditorView, ViewPlugin, ViewUpdate, type DecorationSet} from "@codemirror/view"; +import {blockMetadataField} from "./state.ts"; +import type {BlockMetadata} from "./BlockMetadata.ts"; + +const debugGreenDecoration = Decoration.mark({attributes: {class: "cm-debug-mark green"}}); +const debugRedDecoration = Decoration.mark({attributes: {class: "cm-debug-mark red"}}); +// const debugBlueDecoration = Decoration.mark({attributes: {class: "cm-debug-mark blue"}}); + +function createDebugMarks(blockMetadata: BlockMetadata[]): DecorationSet { + // Build mark decorations separately from line decorations to avoid conflicts + const builder = new RangeSetBuilder(); + + for (const {output, source} of blockMetadata) { + // Add red marks for output ranges + if (output !== null && output.from < output.to) { + // console.log(`Adding red decoration for output: ${output.from}-${output.to}`); + builder.add(output.from, output.to, debugRedDecoration); + } + + // Add green marks for source ranges + if (source.from < source.to) { + // console.log(`Adding green decoration for source: ${source.from}-${source.to}`); + builder.add(source.from, source.to, debugGreenDecoration); + } + } + + return builder.finish(); +} + +export const debugDecoration = ViewPlugin.fromClass( + class { + #decorations: DecorationSet; + + get decorations(): DecorationSet { + return this.#decorations; + } + + constructor(view: EditorView) { + const blockMetadata = view.state.field(blockMetadataField); + this.#decorations = createDebugMarks(blockMetadata); + } + + update(update: ViewUpdate) { + const blockMetadata = update.state.field(blockMetadataField); + this.#decorations = createDebugMarks(blockMetadata); + } + }, + {decorations: (v) => v.decorations}, +); diff --git a/editor/blocks/dedup.ts b/editor/blocks/deduplicate.ts similarity index 100% rename from editor/blocks/dedup.ts rename to editor/blocks/deduplicate.ts diff --git a/editor/blocks/effect.ts b/editor/blocks/effect.ts new file mode 100644 index 0000000..f6daf6e --- /dev/null +++ b/editor/blocks/effect.ts @@ -0,0 +1,4 @@ +import {StateEffect} from "@codemirror/state"; +import {BlockMetadata} from "./BlockMetadata.ts"; + +export const blockMetadataEffect = StateEffect.define(); diff --git a/editor/blocks/index.ts b/editor/blocks/index.ts new file mode 100644 index 0000000..756cb8f --- /dev/null +++ b/editor/blocks/index.ts @@ -0,0 +1,24 @@ +import {compactDecoration} from "./compact.ts"; +import {debugDecoration} from "./debug.ts"; +export {blockIndicator} from "./indicators.ts"; +import {outputDecoration, outputLines} from "./output.ts"; +import {blockMetadataExtension} from "./state.ts"; + +export const blockExtensions = [ + // This view plugin tracks the output lines. + outputLines, + outputDecoration, + // This extension tracks the metdata of each block. + blockMetadataExtension, + // This view plugin displays output lines in compact mode. + compactDecoration, + // This view plugin displays the block indicators. + // Note that block indicators are added by modifying the basic setup. + // Because we want to display the block indicators in the gutter. + // blockIndicator, +]; + +// Only enable debug decoration in the test environment. +if (process.env.NODE_ENV === "test") { + blockExtensions.push(debugDecoration); +} diff --git a/editor/blockIndicator.ts b/editor/blocks/indicators.ts similarity index 96% rename from editor/blockIndicator.ts rename to editor/blocks/indicators.ts index 5510b62..c0d6bb0 100644 --- a/editor/blockIndicator.ts +++ b/editor/blocks/indicators.ts @@ -1,6 +1,6 @@ import {GutterMarker, gutter} from "@codemirror/view"; -import {BlockMetadata} from "./blocks/BlockMetadata.js"; -import {blockMetadataField} from "./blockMetadata.ts"; +import {BlockMetadata} from "./BlockMetadata.ts"; +import {blockMetadataField} from "./state.ts"; export class BlockIndicator extends GutterMarker { constructor( diff --git a/editor/blocks/output.ts b/editor/blocks/output.ts new file mode 100644 index 0000000..9adeacf --- /dev/null +++ b/editor/blocks/output.ts @@ -0,0 +1,97 @@ +import {Decoration, ViewPlugin, ViewUpdate, EditorView, type DecorationSet} from "@codemirror/view"; +import {syntaxTree} from "@codemirror/language"; +import {StateField, RangeSetBuilder, type EditorState} from "@codemirror/state"; +import {OUTPUT_MARK, ERROR_MARK} from "../../runtime/constant.js"; + +const OUTPUT_MARK_CODE_POINT = OUTPUT_MARK.codePointAt(0)!; +const ERROR_MARK_CODE_POINT = ERROR_MARK.codePointAt(0)!; + +type OutputLine = { + number: number; + from: number; + to: number; + type: "output" | "error"; +}; + +function computeLineNumbers(state: EditorState): OutputLine[] { + const lineNumbers: OutputLine[] = []; + syntaxTree(state).iterate({ + enter: (node) => { + // Find top-level single-line comments. + if (node.name === "LineComment" && node.node.parent?.name === "Script") { + const line = state.doc.lineAt(node.from); + // Check if the line comment covers the entire line. + if (line.from !== node.from || line.to !== node.to) return; + if (line.text.codePointAt(2) === OUTPUT_MARK_CODE_POINT) { + lineNumbers.push({ + number: line.number, + from: line.from, + to: line.to, + type: "output", + }); + } + } + + // For error messages, it's Ok if the line is not top-level. + if (node.name === "LineComment") { + const line = state.doc.lineAt(node.from); + if (line.from !== node.from || line.to !== node.to) return; + if (line.text.codePointAt(2) === ERROR_MARK_CODE_POINT) { + lineNumbers.push({ + number: line.number, + from: line.from, + to: line.to, + type: "error", + }); + } + } + }, + }); + return lineNumbers; +} + +export const outputLinesField = StateField.define({ + create(state) { + return computeLineNumbers(state); + }, + update(value, tr) { + return tr.docChanged ? computeLineNumbers(tr.state) : value; + }, +}); + +export const outputLines = outputLinesField.extension; + +const highlight = Decoration.line({attributes: {class: "cm-output-line"}}); +const errorHighlight = Decoration.line({attributes: {class: "cm-output-line cm-error-line"}}); + +export const outputDecoration = ViewPlugin.fromClass( + class { + #decorations: DecorationSet; + + get decorations() { + return this.#decorations; + } + + constructor(view: EditorView) { + const outputLines = view.state.field(outputLinesField); + this.#decorations = this.createDecorations(outputLines); + } + + update(update: ViewUpdate) { + const newOutputLines = update.state.field(outputLinesField); + // A possible optimization would be to only update the changed lines. + this.#decorations = this.createDecorations(newOutputLines); + } + + createDecorations(lines: OutputLine[]) { + const builder = new RangeSetBuilder(); + // Add output line decorations + for (const {from, type} of lines) { + if (type === "output") builder.add(from, from, highlight); + else if (type === "error") builder.add(from, from, errorHighlight); + } + return builder.finish(); + } + }, + {decorations: (v) => v.decorations}, +); diff --git a/editor/blocks/state.ts b/editor/blocks/state.ts new file mode 100644 index 0000000..140539e --- /dev/null +++ b/editor/blocks/state.ts @@ -0,0 +1,34 @@ +import {StateField} from "@codemirror/state"; +import {BlockMetadata} from "./BlockMetadata.ts"; +import {blockMetadataEffect} from "./effect.ts"; +import {updateBlocks} from "./update.ts"; + +export const blockMetadataField = StateField.define({ + create() { + return []; + }, + update(blocks, tr) { + // Find if the block attributes effect is present. + let blocksFromEffect: BlockMetadata[] | null = null; + for (const effect of tr.effects) { + if (effect.is(blockMetadataEffect)) { + blocksFromEffect = effect.value; + break; + } + } + + if (blocksFromEffect === null) { + // If the block attributes effect is not present, then this transaction + // is made by the user, we need to update the block attributes accroding + // to the latest syntax tree. + return updateBlocks(blocks, tr); + } else { + // Otherwise, we need to update the block attributes according to the + // metadata sent from the runtime. Most importantly, we need to translate + // the position of each block after the changes has been made. + return blocksFromEffect.map((block) => block.map(tr)); + } + }, +}); + +export const blockMetadataExtension = blockMetadataField.extension; diff --git a/editor/blockMetadata.ts b/editor/blocks/update.ts similarity index 67% rename from editor/blockMetadata.ts rename to editor/blocks/update.ts index e6d2733..4c088a2 100644 --- a/editor/blockMetadata.ts +++ b/editor/blocks/update.ts @@ -1,10 +1,10 @@ -import {StateField, StateEffect, Transaction} from "@codemirror/state"; -import {blockRangeLength, findAffectedBlockRange, getOnlyOneBlock} from "../lib/blocks.ts"; -import {detectBlocksWithinRange} from "../lib/blocks/detect.ts"; +import {Transaction} from "@codemirror/state"; +import {blockRangeLength, findAffectedBlockRange, getOnlyOneBlock} from "../../lib/blocks.ts"; +import {detectBlocksWithinRange} from "../../lib/blocks/detect.ts"; import {syntaxTree} from "@codemirror/language"; -import {MaxHeap} from "../lib/containers/heap.ts"; -import {BlockMetadata} from "./blocks/BlockMetadata.ts"; -import {deduplicateNaive} from "./blocks/dedup.ts"; +import {MaxHeap} from "../../lib/containers/heap.ts"; +import {BlockMetadata} from "./BlockMetadata.ts"; +import {deduplicateNaive} from "./deduplicate.ts"; /** * Update block metadata according to the given transaction. @@ -12,7 +12,7 @@ import {deduplicateNaive} from "./blocks/dedup.ts"; * @param oldBlocks the current blocks * @param tr the editor transaction */ -function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadata[] { +export function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadata[] { // If the transaction does not change the document, then we return early. if (!tr.docChanged) return oldBlocks; @@ -23,14 +23,9 @@ function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadat console.groupCollapsed(`updateBlocks`); } - // Collect all ranges that need to be rescanned - type ChangedRange = {oldFrom: number; oldTo: number; newFrom: number; newTo: number}; - const changedRanges: ChangedRange[] = []; - tr.changes.iterChangedRanges((oldFrom, oldTo, newFrom, newTo) => { - changedRanges.push({oldFrom, oldTo, newFrom, newTo}); - }); + const isCopyingLines = userEvent === "input.copyline"; - if (changedRanges.length === 0) { + if (tr.changes.empty) { console.log("No changes detected"); console.groupEnd(); return oldBlocks; @@ -48,7 +43,7 @@ function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadat ); // Process changed ranges one by one, because ranges are disjoint. - for (const {oldFrom, oldTo, newFrom, newTo} of changedRanges) { + tr.changes.iterChanges((oldFrom, oldTo, newFrom, newTo, inserted) => { if (oldFrom === oldTo) { if (newFrom === oldFrom) { console.groupCollapsed(`Insert ${newTo - newFrom} characters at ${oldFrom}`); @@ -59,9 +54,28 @@ function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadat console.groupCollapsed(`Update: ${oldFrom}-${oldTo} -> ${newFrom}-${newTo}`); } + let changeFrom = oldFrom; + let changeTo = oldTo; + if (isCopyingLines) { + if (oldFrom === oldTo) { + const index = oldFrom; // The insertion point, should be either at the beginning or the end of the line. + const line = tr.startState.doc.lineAt(index); + if (index === line.from) { + changeFrom = changeTo = index + inserted.length; + } else if (index === line.to) { + // changeFrom = changeTo = index - inserted.length; + changeFrom = changeTo = index; + } else { + console.error("This is weird as the insertion point is not at the beginning or the end of the line"); + } + } else { + console.error("This is weird as the cursor is not at the same position"); + } + } + // Step 1: Find the blocks that are affected by the change. - const affectedBlockRange = findAffectedBlockRange(oldBlocks, oldFrom, oldTo); + const affectedBlockRange = findAffectedBlockRange(oldBlocks, changeFrom, changeTo); console.log(`Affected block range: ${affectedBlockRange[0]} to ${affectedBlockRange[1] ?? "the end"}`); @@ -72,7 +86,7 @@ function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadat // Check a corner case where the affected block range is empty but there are blocks. if (blockRangeLength(oldBlocks.length, affectedBlockRange) === 0 && oldBlocks.length > 0) { - reportError("This should never happen"); + console.error("This should never happen"); } // Now, we are going to compute the range which should be re-parsed. @@ -95,17 +109,22 @@ function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadat } console.groupEnd(); - } + }); // Step 3: Combine the array of old blocks and the heap of new blocks. const newBlocks: BlockMetadata[] = []; + console.group("Combining old blocks and new blocks"); + for (let i = 0, n = oldBlocks.length; i < n; i++) { const oldBlock = oldBlocks[i]!; // Skip affected blocks, as they have been updated. - if (affectedBlocks.has(oldBlock)) continue; + if (affectedBlocks.has(oldBlock)) { + console.log("Skipping affected old block:", oldBlock); + continue; + } const newBlock = oldBlock.map(tr); @@ -114,6 +133,7 @@ function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadat while (newlyCreatedBlocks.nonEmpty() && newlyCreatedBlocks.peek.from < newBlock.from) { newBlocks.push(newlyCreatedBlocks.peek); + console.log("Pushing new block from heap:", newlyCreatedBlocks.peek); newlyCreatedBlocks.extractMax(); } @@ -121,8 +141,11 @@ function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadat // the current old block's `from` position. newBlocks.push(newBlock); + console.log("Pushing mapped old block:", newBlock); } + console.groupEnd(); + // In the end, push any remaining blocks from the heap. while (newlyCreatedBlocks.nonEmpty()) { newBlocks.push(newlyCreatedBlocks.peek); @@ -138,35 +161,3 @@ function updateBlocks(oldBlocks: BlockMetadata[], tr: Transaction): BlockMetadat console.groupEnd(); return deduplicatedBlocks; } - -export const blockMetadataEffect = StateEffect.define(); - -export const blockMetadataField = StateField.define({ - create() { - return []; - }, - update(blocks, tr) { - // Find if the block attributes effect is present. - let blocksFromEffect: BlockMetadata[] | null = null; - for (const effect of tr.effects) { - if (effect.is(blockMetadataEffect)) { - blocksFromEffect = effect.value; - break; - } - } - - if (blocksFromEffect === null) { - // If the block attributes effect is not present, then this transaction - // is made by the user, we need to update the block attributes accroding - // to the latest syntax tree. - return updateBlocks(blocks, tr); - } else { - // Otherwise, we need to update the block attributes according to the - // metadata sent from the runtime. Most importantly, we need to translate - // the position of each block after the changes has been made. - return blocksFromEffect.map((block) => block.map(tr)); - } - }, -}); - -export const blockMetadataExtension = blockMetadataField.extension; diff --git a/editor/decoration.js b/editor/decoration.js deleted file mode 100644 index 09f3eb0..0000000 --- a/editor/decoration.js +++ /dev/null @@ -1,121 +0,0 @@ -import {Decoration, ViewPlugin, ViewUpdate, EditorView} from "@codemirror/view"; -import {outputLinesField} from "./outputLines"; -import {blockMetadataField} from "./blockMetadata"; -import {RangeSet, RangeSetBuilder} from "@codemirror/state"; - -const highlight = Decoration.line({attributes: {class: "cm-output-line"}}); -const errorHighlight = Decoration.line({attributes: {class: "cm-output-line cm-error-line"}}); -const compactLineDecoration = Decoration.line({attributes: {class: "cm-output-line cm-compact-line"}}); -const debugGreenDecoration = Decoration.mark({attributes: {class: "cm-debug-mark green"}}); -const debugRedDecoration = Decoration.mark({attributes: {class: "cm-debug-mark red"}}); -const debugBlueDecoration = Decoration.mark({attributes: {class: "cm-debug-mark blue"}}); -// const linePrefix = Decoration.mark({attributes: {class: "cm-output-line-prefix"}}); -// const lineContent = Decoration.mark({attributes: {class: "cm-output-line-content"}}); - -function createWidgets(lines, blockMetadata, state) { - // Build the range set for output lines. - const builder1 = new RangeSetBuilder(); - // Add output line decorations - for (const {from, type} of lines) { - if (type === "output") builder1.add(from, from, highlight); - else if (type === "error") builder1.add(from, from, errorHighlight); - // builder.add(from, from + 3, linePrefix); - // builder.add(from + 4, to, lineContent); - } - const set1 = builder1.finish(); - - // Build the range set for block attributes. - // console.groupCollapsed("Decorations for block attributes"); - const builder2 = new RangeSetBuilder(); - // Add block attribute decorations - for (const {output, attributes} of blockMetadata) { - if (output === null) continue; - // Apply decorations to each line in the block range - const startLine = state.doc.lineAt(output.from); - const endLine = state.doc.lineAt(output.to); - // console.log(`Make lines from ${startLine.number} to ${endLine.number} compact`); - const endLineNumber = endLine.from < output.to ? endLine.number + 1 : endLine.number; - if (attributes.compact === true) { - for (let lineNum = startLine.number; lineNum < endLineNumber; lineNum++) { - const line = state.doc.line(lineNum); - builder2.add(line.from, line.from, compactLineDecoration); - } - } - } - const set2 = builder2.finish(); - // console.groupEnd(); - - // Range sets are required to be sorted. Fortunately, they provide a method - // to merge multiple range sets into a single sorted range set. - return RangeSet.join([set1, set2]); -} - -function createDebugMarks(blockMetadata, state) { - // Build mark decorations separately from line decorations to avoid conflicts - const builder = new RangeSetBuilder(); - - for (const {output, source} of blockMetadata) { - // Add red marks for output ranges - if (output !== null && output.from < output.to) { - // console.log(`Adding red decoration for output: ${output.from}-${output.to}`); - builder.add(output.from, output.to, debugRedDecoration); - } - - // Add green marks for source ranges - if (source.from < source.to) { - // console.log(`Adding green decoration for source: ${source.from}-${source.to}`); - builder.add(source.from, source.to, debugGreenDecoration); - } - } - - return builder.finish(); -} - -export const outputDecoration = ViewPlugin.fromClass( - class { - #decorations; - - get decorations() { - return this.#decorations; - } - - /** @param {EditorView} view */ - constructor(view) { - const outputLines = view.state.field(outputLinesField); - const blockMetadata = view.state.field(blockMetadataField); - this.#decorations = createWidgets(outputLines, blockMetadata, view.state); - } - - /** @param {ViewUpdate} update */ - update(update) { - const newOutputLines = update.state.field(outputLinesField); - const blockMetadata = update.state.field(blockMetadataField); - // A possible optimization would be to only update the changed lines. - this.#decorations = createWidgets(newOutputLines, blockMetadata, update.state); - } - }, - {decorations: (v) => v.decorations}, -); - -export const debugDecoration = ViewPlugin.fromClass( - class { - #decorations; - - get decorations() { - return this.#decorations; - } - - /** @param {EditorView} view */ - constructor(view) { - const blockMetadata = view.state.field(blockMetadataField); - this.#decorations = createDebugMarks(blockMetadata, view.state); - } - - /** @param {ViewUpdate} update */ - update(update) { - const blockMetadata = update.state.field(blockMetadataField); - this.#decorations = createDebugMarks(blockMetadata, update.state); - } - }, - {decorations: (v) => v.decorations}, -); diff --git a/editor/index.js b/editor/index.js index be0b48d..1eb9ca4 100644 --- a/editor/index.js +++ b/editor/index.js @@ -9,16 +9,12 @@ import {indentWithTab} from "@codemirror/commands"; import {browser} from "globals"; import * as eslint from "eslint-linter-browserify"; import {createRuntime} from "../runtime/index.js"; -import {outputDecoration, debugDecoration} from "./decoration.js"; -import {outputLines} from "./outputLines.js"; -import {blockMetadataExtension} from "./blockMetadata.ts"; -// import {outputProtection} from "./protection.js"; import {dispatch as d3Dispatch} from "d3-dispatch"; import {controls} from "./controls/index.js"; import {rechoCompletion} from "./completion.js"; import {docStringTag} from "./docStringTag.js"; import {commentLink, commentLinkClickHandler} from "./commentLink.js"; -import {blockIndicator} from "./blockIndicator.ts"; +import {blockExtensions, blockIndicator} from "./blocks"; // @see https://github.com/UziTech/eslint-linter-browserify/blob/master/example/script.js // @see https://codemirror.net/examples/lint/ @@ -72,10 +68,7 @@ export function createEditor(container, options) { indentWithTab, ]), javascriptLanguage.data.of({autocomplete: rechoCompletion}), - outputLines, - blockMetadataExtension, - outputDecoration, - debugDecoration, + blockExtensions, controls(runtimeRef), // Disable this for now, because it prevents copying/pasting the code. // outputProtection(), diff --git a/editor/outputLines.js b/editor/outputLines.js deleted file mode 100644 index c9913a6..0000000 --- a/editor/outputLines.js +++ /dev/null @@ -1,60 +0,0 @@ -import {syntaxTree} from "@codemirror/language"; -import {StateField} from "@codemirror/state"; -import {OUTPUT_MARK, ERROR_MARK} from "../runtime/constant.js"; - -const OUTPUT_MARK_CODE_POINT = OUTPUT_MARK.codePointAt(0); - -const ERROR_MARK_CODE_POINT = ERROR_MARK.codePointAt(0); - -/** @type {StateField<{number: number, from: number, to: number}[]>} */ -export const outputLinesField = StateField.define({ - create(state) { - return computeLineNumbers(state); - }, - update(value, tr) { - return tr.docChanged ? computeLineNumbers(tr.state) : value; - }, -}); - -function computeLineNumbers(state) { - const lineNumbers = []; - // for (let {from, to} of view.visibleRanges) { - syntaxTree(state).iterate({ - // from, - // to, - enter: (node) => { - // Find top-level single-line comments. - if (node.name === "LineComment" && node.node.parent.name === "Script") { - const line = state.doc.lineAt(node.from); - // Check if the line comment covers the entire line. - if (line.from !== node.from || line.to !== node.to) return; - if (line.text.codePointAt(2) === OUTPUT_MARK_CODE_POINT) { - lineNumbers.push({ - number: line.number, - from: line.from, - to: line.to, - type: "output", - }); - } - } - - // For error messages, it's Ok if the line is not top-level. - if (node.name === "LineComment") { - const line = state.doc.lineAt(node.from); - if (line.from !== node.from || line.to !== node.to) return; - if (line.text.codePointAt(2) === ERROR_MARK_CODE_POINT) { - lineNumbers.push({ - number: line.number, - from: line.from, - to: line.to, - type: "error", - }); - } - } - }, - }); - // } - return lineNumbers; -} - -export const outputLines = outputLinesField.extension; diff --git a/editor/protection.js b/editor/protection.js deleted file mode 100644 index da32e17..0000000 --- a/editor/protection.js +++ /dev/null @@ -1,37 +0,0 @@ -import {EditorState} from "@codemirror/state"; -import {outputLinesField} from "./outputLines"; -import {Transaction} from "@codemirror/state"; - -export function outputProtection() { - return EditorState.transactionFilter.of((tr) => { - if (!tr.docChanged) return tr; - - const lines = tr.startState.field(outputLinesField); - if (lines.size === 0) return tr; - - if (tr.annotation(Transaction.remote) === "runtime") return tr; - - const lineNumberSet = new Set(lines.map((line) => line.number)); - - let shouldReject = false; - tr.changes.iterChanges((fromA, toA) => { - if (shouldReject) return; - - if (fromA === toA) { - const line = tr.startState.doc.lineAt(fromA); - if (lineNumberSet.has(line.number)) shouldReject = true; - } else { - const startLine = tr.startState.doc.lineAt(fromA); - const endLine = tr.startState.doc.lineAt(toA); - for (let line = startLine.number; line <= endLine.number; line++) { - if (lineNumberSet.has(line)) { - shouldReject = true; - break; - } - } - } - }); - - return shouldReject ? [] : tr; - }); -} diff --git a/runtime/index.js b/runtime/index.js index 61069ac..4a1c905 100644 --- a/runtime/index.js +++ b/runtime/index.js @@ -6,12 +6,11 @@ import {dispatch as d3Dispatch} from "d3-dispatch"; import * as stdlib from "./stdlib/index.js"; import {Inspector} from "./stdlib/inspect.js"; import {BlockMetadata} from "../editor/blocks/BlockMetadata.ts"; -import {blockMetadataEffect} from "../editor/blockMetadata.ts"; +import {blockMetadataEffect} from "../editor/blocks/effect.ts"; import {IntervalTree} from "../lib/IntervalTree.ts"; import {transpileRechoJavaScript} from "./transpile.js"; import {ButtonRegistry, makeButton} from "./controls/button.js"; import {addPrefix, makeOutput, OUTPUT_PREFIX, ERROR_PREFIX} from "./output.js"; -import {Transaction} from "@codemirror/state"; function uid() { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); diff --git a/test/components/block-data.ts b/test/components/block-data.ts index b2012e7..d876bfd 100644 --- a/test/components/block-data.ts +++ b/test/components/block-data.ts @@ -1,4 +1,4 @@ -import {blockMetadataField} from "../../editor/blockMetadata.ts"; +import {blockMetadataField} from "../../editor/blocks/state.ts"; import type {EditorView} from "@codemirror/view"; export interface BlockData { diff --git a/test/components/transaction-data.ts b/test/components/transaction-data.ts index ecf174c..d85db63 100644 --- a/test/components/transaction-data.ts +++ b/test/components/transaction-data.ts @@ -1,4 +1,4 @@ -import {blockMetadataEffect} from "../../editor/blockMetadata.ts"; +import {blockMetadataEffect} from "../../editor/blocks/effect.ts"; import type {BlockMetadata} from "../../editor/blocks/BlockMetadata.ts"; import {Transaction as Tr} from "@codemirror/state"; diff --git a/vite.config.js b/vite.config.js index 1abada3..63a5f88 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,6 +4,9 @@ import react from "@vitejs/plugin-react"; export default defineConfig({ root: "test", plugins: [react()], + define: { + "process.env.NODE_ENV": JSON.stringify("test"), + }, test: { environment: "jsdom", },