diff --git a/ab-testing/config/scripts/build/test-group-mvt-manager.test.ts b/ab-testing/config/scripts/build/test-group-mvt-manager.test.ts index 6baef85ad7a..8b711e4accb 100644 --- a/ab-testing/config/scripts/build/test-group-mvt-manager.test.ts +++ b/ab-testing/config/scripts/build/test-group-mvt-manager.test.ts @@ -331,7 +331,8 @@ test("TestGroupMVTManager - stress test with maximum MVTs", () => { const manager = new TestGroupMVTManager(emptyAudienceSpace); // Add a group that uses all available MVTs - manager.addTestGroup("test1:control", 1000); + manager.addTestGroup("test1:control", 200); + manager.resizeTestGroup("test1:control", 1000); const testGroup = manager.getTestGroup("test1:control"); equal(testGroup?.length, 1000); @@ -379,3 +380,33 @@ test("TestGroupMVTManager - constructor with invalid MVT key format", () => { false, ); }); + +test("TestGroupMVTManager - adding a test with size 1000 should throw an error", () => { + const emptyAudienceSpace = new Map< + string, + { name: string; type: string; exp: number } + >(); + const manager = new TestGroupMVTManager(emptyAudienceSpace); + + throws( + () => manager.addTestGroup("test1:control", 1000), + Error, + "Size for for new test, test1:control, cannot be 100%, please add it as a 20-50% first and scale up to 100% after a few hours, see https://github.com/guardian/dotcom-rendering/blob/9370061e00535d4e280c573ea27cc3095d7e2b8a/dotcom-rendering/docs/development/ab-testing-in-dcr.md#L43 for details on why", + ); +}); + +test("TestGroupMVTManager - resizing a test from 0 to 1000 should throw an error", () => { + const emptyAudienceSpace = new Map< + string, + { name: string; type: string; exp: number } + >(); + const manager = new TestGroupMVTManager(emptyAudienceSpace); + + manager.addTestGroup("test1:control", 10); + + throws( + () => manager.resizeTestGroup("test1:control", 1000), + Error, + "Cannot resize test test1:control from 10% to 100%, please scale it to 20-50% first then up to 100% after a few hours, see https://github.com/guardian/dotcom-rendering/blob/9370061e00535d4e280c573ea27cc3095d7e2b8a/dotcom-rendering/docs/development/ab-testing-in-dcr.md#L43 for details on why", + ); +}); diff --git a/ab-testing/config/scripts/build/test-group-mvt-manager.ts b/ab-testing/config/scripts/build/test-group-mvt-manager.ts index 0a5c7e461ec..b998de3d2ba 100644 --- a/ab-testing/config/scripts/build/test-group-mvt-manager.ts +++ b/ab-testing/config/scripts/build/test-group-mvt-manager.ts @@ -79,6 +79,11 @@ class TestGroupMVTManager { if (!Number.isInteger(size)) { throw new Error(`Size for test ${name} must be an integer`); } + if (size === 1000) { + throw new Error( + `Size for for new test, ${name}, cannot be 100%, please add it as a 20-50% first and scale up to 100% after a few hours, see https://github.com/guardian/dotcom-rendering/blob/9370061e00535d4e280c573ea27cc3095d7e2b8a/dotcom-rendering/docs/development/ab-testing-in-dcr.md#L43 for details on why`, + ); + } const mvts = this.availableMVTs.splice(0, size); this.testGroups.set(name, mvts); mvts.forEach((mvt) => { @@ -103,6 +108,12 @@ class TestGroupMVTManager { const currentMVTs = this.testGroups.get(name) ?? []; const currentSize = currentMVTs.length; + if (currentSize < 200 && newSize === 1000) { + throw new Error( + `Cannot resize test ${name} from ${Math.round((currentSize / 1000) * 100)}% to 100%, please scale it to 20-50% first then up to 100% after a few hours, see https://github.com/guardian/dotcom-rendering/blob/9370061e00535d4e280c573ea27cc3095d7e2b8a/dotcom-rendering/docs/development/ab-testing-in-dcr.md#L43 for details on why`, + ); + } + if (newSize > currentSize) { const additionalMVTsNeeded = newSize - currentSize; if (this.availableMVTs.length < additionalMVTsNeeded) { diff --git a/ab-testing/deploy-lambda/src/lib/custom-resource-response.ts b/ab-testing/deploy-lambda/src/lib/custom-resource-response.ts index c03c6fade61..65f4251cf9f 100644 --- a/ab-testing/deploy-lambda/src/lib/custom-resource-response.ts +++ b/ab-testing/deploy-lambda/src/lib/custom-resource-response.ts @@ -35,9 +35,6 @@ const send = async ( Data: responseData, }); - console.log("Response body:\n", responseBody); - console.log("Sending response to:", event.ResponseURL); - try { await fetch(event.ResponseURL, { method: "PUT", diff --git a/dotcom-rendering/fixtures/manual/puzzlesPage.ts b/dotcom-rendering/fixtures/manual/puzzlesPage.ts new file mode 100644 index 00000000000..700f5c369eb --- /dev/null +++ b/dotcom-rendering/fixtures/manual/puzzlesPage.ts @@ -0,0 +1,102 @@ +import type { + FEPuzzlesPageType, + PuzzlesLayoutType, +} from '../../src/types/puzzlesPage'; +import { Standard } from '../generated/fe-articles/Standard'; + +export const minimalPuzzlesLayout: PuzzlesLayoutType = { + filters: [], + containers: [], +}; + +export const fullPuzzlesLayout: PuzzlesLayoutType = { + filters: [ + { + id: 'word-games', + title: 'Word games', + target: '#word-games', + backgroundColour: '#f9d4e8', + }, + ], + containers: [ + { + title: 'Word games', + variant: 'standard', + filterId: 'word-games', + content: { + items: [ + [ + { + id: 'wordiply-daily', + title: 'Wordiply', + type: 'wordiply', + set: 'all', + cardVariant: 'primary', + cadence: 'Daily', + slug: 'wordiply', + url: 'https://www.wordiply.com/', + variant: 'iframe-page', + backgroundColour: '#f9d4e8', + filterId: 'word-games', + }, + ], + ], + nestedContainers: [ + { + title: 'Word wheel', + desktopSpan: 6, + content: { + items: [ + [ + { + id: 'word-wheel-daily', + title: 'Word wheel', + type: 'word-wheel', + set: 'all', + cardVariant: 'compact', + cadence: 'Daily', + index: 1, + }, + ], + ], + nestedContainers: [], + archive: { + id: 'word-wheel-archive', + title: 'Word wheel archive', + type: 'word-wheel', + set: 'all', + cardVariant: 'archive', + slug: 'word-wheel', + url: '/puzzles/word-wheel/archive', + variant: 'archive-page', + }, + }, + }, + ], + }, + }, + ], +}; + +export const createPuzzlesPage = ( + overrides: Partial = {}, +): FEPuzzlesPageType => ({ + id: 'puzzles', + editionId: Standard.editionId, + editionLongForm: Standard.editionLongForm, + contributionsServiceUrl: Standard.contributionsServiceUrl, + webTitle: 'Puzzles & Games', + description: 'Play the Guardian’s daily puzzles and games.', + config: { + ...Standard.config, + contentType: 'Puzzles', + serverSideABTests: {}, + }, + nav: Standard.nav, + pageFooter: Standard.pageFooter, + commercialProperties: Standard.commercialProperties, + isAdFreeUser: false, + canonicalUrl: 'https://www.theguardian.com/puzzles', + layout: minimalPuzzlesLayout, + ...overrides, +}); diff --git a/dotcom-rendering/package.json b/dotcom-rendering/package.json index f0a8941f020..7850706d26b 100644 --- a/dotcom-rendering/package.json +++ b/dotcom-rendering/package.json @@ -52,10 +52,10 @@ "@opentelemetry/sdk-node": "0.219.0", "@playwright/test": "1.60.0", "@sentry/browser": "10.65.0", - "@storybook/addon-a11y": "10.5.7", - "@storybook/addon-docs": "10.5.7", + "@storybook/addon-a11y": "10.5.10", + "@storybook/addon-docs": "10.5.10", "@storybook/addon-webpack5-compiler-swc": "4.0.3", - "@storybook/react-webpack5": "10.5.7", + "@storybook/react-webpack5": "10.5.10", "@svgr/webpack": "8.1.0", "@swc/cli": "0.8.1", "@swc/core": "1.16.1", @@ -148,7 +148,7 @@ "screenfull": "6.0.2", "semver": "7.5.4", "source-map": "0.7.4", - "storybook": "10.5.7", + "storybook": "10.5.10", "stylelint": "16.26.1", "stylelint-config-recommended": "14.0.0", "swc-loader": "0.2.7", diff --git a/dotcom-rendering/src/components/PuzzlesPage.tsx b/dotcom-rendering/src/components/PuzzlesPage.tsx new file mode 100644 index 00000000000..bdb28510783 --- /dev/null +++ b/dotcom-rendering/src/components/PuzzlesPage.tsx @@ -0,0 +1,59 @@ +import { Global } from '@emotion/react'; +import { StrictMode } from 'react'; +import { PuzzlesLayout } from '../layouts/PuzzlesLayout'; +import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat'; +import { rootStyles } from '../lib/rootStyles'; +import type { NavType } from '../model/extract-nav'; +import type { FEPuzzlesPageType } from '../types/puzzlesPage'; +import { AdmiralScript } from './AdmiralScript.island'; +import { AlreadyVisited } from './AlreadyVisited.island'; +import { useConfig } from './ConfigContext'; +import { FocusStyles } from './FocusStyles.island'; +import { Island } from './Island'; +import { Metrics } from './Metrics.island'; +import { SetABTests } from './SetABTests.island'; +import { SkipTo } from './SkipTo'; + +type Props = { + puzzlesPage: FEPuzzlesPageType; + NAV: NavType; +}; + +export const PuzzlesPage = ({ puzzlesPage, NAV }: Props) => { + const format = { + display: ArticleDisplay.Standard, + design: ArticleDesign.Standard, + theme: Pillar.Lifestyle, + }; + const { darkModeAvailable } = useConfig(); + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/dotcom-rendering/src/layouts/PuzzlesLayout.tsx b/dotcom-rendering/src/layouts/PuzzlesLayout.tsx new file mode 100644 index 00000000000..e3f85e641e9 --- /dev/null +++ b/dotcom-rendering/src/layouts/PuzzlesLayout.tsx @@ -0,0 +1,79 @@ +import { css } from '@emotion/react'; +import { headlineBold34, palette, space } from '@guardian/source/foundations'; +import { Footer } from '../components/Footer'; +import { HeaderAdSlot } from '../components/HeaderAdSlot'; +import { Masthead } from '../components/Masthead/Masthead'; +import { Section } from '../components/Section'; +import type { NavType } from '../model/extract-nav'; +import type { FEPuzzlesPageType } from '../types/puzzlesPage'; +import { Stuck } from './lib/stickiness'; + +type Props = { + puzzlesPage: FEPuzzlesPageType; + NAV: NavType; +}; + +const heading = css` + margin: 0; + padding: ${space[6]}px 0 ${space[12]}px; + ${headlineBold34}; +`; + +/** + * The base puzzles page shell. Blueprint-driven composition is intentionally + * deferred to the next task. + */ +export const PuzzlesLayout = ({ puzzlesPage, NAV }: Props) => ( + <> +
+ {!puzzlesPage.isAdFreeUser && ( + +
+ +
+
+ )} + +
+ +
+
+

{puzzlesPage.webTitle}

+
+
+ +
+
+
+ +); diff --git a/dotcom-rendering/src/lib/puzzlesHubExperiment.test.ts b/dotcom-rendering/src/lib/puzzlesHubExperiment.test.ts new file mode 100644 index 00000000000..5f332e66144 --- /dev/null +++ b/dotcom-rendering/src/lib/puzzlesHubExperiment.test.ts @@ -0,0 +1,29 @@ +import { + isPuzzlesHubEnabled, + puzzlesHubExperiment, + puzzlesHubParticipation, +} from './puzzlesHubExperiment'; + +describe('isPuzzlesHubEnabled', () => { + it('enables only the configured variant', () => { + expect( + isPuzzlesHubEnabled({ + serverSideABTests: puzzlesHubParticipation( + puzzlesHubExperiment.variant, + ), + }), + ).toBe(true); + }); + + it.each([ + puzzlesHubParticipation(puzzlesHubExperiment.control), + puzzlesHubParticipation('unknown'), + puzzlesHubParticipation('variant:extra'), + {}, + { 'another-test': 'variant' }, + ])('returns false for non-variant participation %#', (participations) => { + expect(isPuzzlesHubEnabled({ serverSideABTests: participations })).toBe( + false, + ); + }); +}); diff --git a/dotcom-rendering/src/lib/puzzlesHubExperiment.ts b/dotcom-rendering/src/lib/puzzlesHubExperiment.ts new file mode 100644 index 00000000000..19acfda115d --- /dev/null +++ b/dotcom-rendering/src/lib/puzzlesHubExperiment.ts @@ -0,0 +1,21 @@ +import type { ConfigType } from '../types/config'; + +export const puzzlesHubExperiment = { + name: 'puzzles-new-hub', + variant: 'variant', + control: 'control', +} as const; + +type PuzzlesExperimentConfig = Pick; + +export const isPuzzlesHubEnabled = ({ + serverSideABTests, +}: PuzzlesExperimentConfig): boolean => + serverSideABTests[puzzlesHubExperiment.name] === + puzzlesHubExperiment.variant; + +export const puzzlesHubParticipation = ( + group: string, +): Record => ({ + [puzzlesHubExperiment.name]: group, +}); diff --git a/dotcom-rendering/src/model/validate.puzzlesPage.test.ts b/dotcom-rendering/src/model/validate.puzzlesPage.test.ts new file mode 100644 index 00000000000..b1829ed1cd8 --- /dev/null +++ b/dotcom-rendering/src/model/validate.puzzlesPage.test.ts @@ -0,0 +1,173 @@ +import { + createPuzzlesPage, + fullPuzzlesLayout, +} from '../../fixtures/manual/puzzlesPage'; +import { + puzzlesHubExperiment, + puzzlesHubParticipation, +} from '../lib/puzzlesHubExperiment'; +import { validateAsPuzzlesPageType } from './validate'; + +const clone = (value: T): T => JSON.parse(JSON.stringify(value)) as T; + +const expectInvalid = (page: unknown) => + expect(() => validateAsPuzzlesPageType(page)).toThrow( + 'Unable to validate request body for puzzles page.', + ); + +describe('validateAsPuzzlesPageType', () => { + it('accepts a minimal payload and preserves server-side participation', () => { + const participation = puzzlesHubParticipation( + puzzlesHubExperiment.variant, + ); + const page = createPuzzlesPage({ + config: { + ...createPuzzlesPage().config, + serverSideABTests: participation, + }, + }); + + expect(validateAsPuzzlesPageType(page).config.serverSideABTests).toBe( + participation, + ); + }); + + it('accepts a full payload with rows, nested containers and an archive', () => { + expect( + validateAsPuzzlesPageType( + createPuzzlesPage({ layout: fullPuzzlesLayout }), + ).layout, + ).toEqual(fullPuzzlesLayout); + }); + + it.each(['id', 'editionLongForm', 'contributionsServiceUrl', 'webTitle'])( + 'rejects a missing required page field: %s', + (field) => { + const page = clone(createPuzzlesPage()) as unknown as Record< + string, + unknown + >; + delete page[field]; + expectInvalid(page); + }, + ); + + it('rejects a config without server-side participations', () => { + const page = clone(createPuzzlesPage()) as unknown as { + config: Record; + }; + delete page.config.serverSideABTests; + expectInvalid(page); + }); + + it('rejects navigation that is not an object', () => { + const page = clone(createPuzzlesPage()) as unknown as { + nav: unknown; + }; + page.nav = []; + expectInvalid(page); + }); + + it('rejects a malformed filter colour', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + page.layout.filters[0]!.backgroundColour = 'pink'; + expectInvalid(page); + }); + + it('rejects a navigation target without a matching section', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + page.layout.filters[0]!.target = '#missing'; + expectInvalid(page); + }); + + it('rejects duplicate filter IDs', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + page.layout.filters.push(clone(page.layout.filters[0]!)); + expectInvalid(page); + }); + + it('rejects an unknown required container variant', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + (page.layout.containers[0] as unknown as { variant: string }).variant = + 'unknown'; + expectInvalid(page); + }); + + it.each([0, 13, 1.5])('rejects invalid desktop span %s', (desktopSpan) => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + page.layout.containers[0]!.desktopSpan = desktopSpan; + expectInvalid(page); + }); + + it('rejects a structurally invalid row', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + page.layout.containers[0]!.content.items = [[]]; + expectInvalid(page); + }); + + it('rejects an invalid item in a nested container', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + const nestedItem = + page.layout.containers[0]!.content.nestedContainers[0]!.content + .items[0]![0]!; + nestedItem.id = 'Not Stable'; + expectInvalid(page); + }); + + it('rejects an unknown card variant', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + const item = page.layout.containers[0]!.content.items[0]![0]!; + (item as unknown as { cardVariant: string }).cardVariant = 'unknown'; + expectInvalid(page); + }); + + it('rejects a card without cadence metadata', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + const item = page.layout.containers[0]!.content.items[0]![0]!; + (item as unknown as { cadence?: string }).cadence = undefined; + expectInvalid(page); + }); + + it('rejects an unknown page presentation variant', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + const item = page.layout.containers[0]!.content.items[0]![0]!; + (item as unknown as { variant: string }).variant = 'unknown'; + expectInvalid(page); + }); + + it('rejects a malformed item colour', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + page.layout.containers[0]!.content.items[0]![0]!.backgroundColour = + '#12345'; + expectInvalid(page); + }); + + it('rejects duplicate puzzle IDs at different recursion levels', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + page.layout.containers[0]!.content.nestedContainers[0]!.content.items[0]![0]!.id = + page.layout.containers[0]!.content.items[0]![0]!.id; + expectInvalid(page); + }); + + it('rejects an undefined filter reference', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + page.layout.containers[0]!.filterId = 'missing'; + expectInvalid(page); + }); + + it('rejects archive presentation outside the archive slot', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + const item = page.layout.containers[0]!.content.items[0]![0]!; + (item as unknown as { cardVariant: string }).cardVariant = 'archive'; + expectInvalid(page); + }); + + it('rejects a non-archive presentation in the archive slot', () => { + const page = clone(createPuzzlesPage({ layout: fullPuzzlesLayout })); + const archive = + page.layout.containers[0]!.content.nestedContainers[0]!.content + .archive!; + (archive as unknown as { cardVariant: string }).cardVariant = 'compact'; + expectInvalid(page); + }); +}); diff --git a/dotcom-rendering/src/model/validate.ts b/dotcom-rendering/src/model/validate.ts index 3820dc1084c..cf82d176740 100644 --- a/dotcom-rendering/src/model/validate.ts +++ b/dotcom-rendering/src/model/validate.ts @@ -17,6 +17,12 @@ import tagPageSchema from '../frontend/schemas/feTagPage.json'; import type { Block } from '../types/blocks'; import type { FEEditionsCrosswords } from '../types/editionsCrossword'; import type { FENewslettersPageType } from '../types/newslettersPage'; +import { + type FEPuzzlesPageType, + puzzleCardVariants, + puzzleContainerVariants, + puzzlePageVariants, +} from '../types/puzzlesPage'; import blockSchema from './block-schema.json'; import editionsCrosswordSchema from './editions-crossword-schema.json'; import newslettersPageSchema from './newsletter-page-schema.json'; @@ -166,3 +172,208 @@ export const validateAsFootballMatchPageType = ( ${JSON.stringify(validateFootballMatchInfoPage.errors, null, 2)}`, ); }; +const stableIdPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; +const colourPattern = /^#[0-9a-f]{6}$/i; +const editions = new Set(['UK', 'US', 'AU', 'INT', 'EUR']); + +const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null && !Array.isArray(value); + +const isNonEmptyString = (value: unknown): value is string => + typeof value === 'string' && value.trim().length > 0; + +const isOptionalString = (value: unknown): boolean => + value === undefined || typeof value === 'string'; + +const isOptionalColour = (value: unknown): boolean => + value === undefined || + (typeof value === 'string' && colourPattern.test(value)); + +const isStringRecord = (value: unknown): boolean => + isRecord(value) && Object.values(value).every(isString); + +const isPuzzlesConfig = (value: unknown): boolean => + isRecord(value) && isStringRecord(value.serverSideABTests); + +const isPuzzleItem = (value: unknown, archiveSlot: boolean): boolean => { + if (!isRecord(value)) return false; + const cardVariant = value.cardVariant; + const pageVariant = value.variant; + + return ( + isNonEmptyString(value.id) && + stableIdPattern.test(value.id) && + isNonEmptyString(value.title) && + isNonEmptyString(value.type) && + isNonEmptyString(value.set) && + isString(cardVariant) && + puzzleCardVariants.includes( + cardVariant as (typeof puzzleCardVariants)[number], + ) && + (archiveSlot + ? cardVariant === 'archive' + : cardVariant !== 'archive' && isNonEmptyString(value.cadence)) && + isOptionalString(value.cadence) && + isOptionalString(value.url) && + isOptionalString(value.image) && + isOptionalString(value.slug) && + (value.index === undefined || Number.isInteger(value.index)) && + (pageVariant === undefined || + (isString(pageVariant) && + puzzlePageVariants.includes( + pageVariant as (typeof puzzlePageVariants)[number], + ))) && + isOptionalColour(value.backgroundColour) && + isOptionalString(value.filterId) + ); +}; + +type LayoutIds = { + itemIds: string[]; + sectionIds: string[]; + referencedFilterIds: string[]; +}; + +const validateContainer = (value: unknown, ids: LayoutIds): boolean => { + if (!isRecord(value) || !isRecord(value.content)) return false; + if ( + !isNonEmptyString(value.title) || + (value.variant !== undefined && + (!isString(value.variant) || + !puzzleContainerVariants.includes( + value.variant as (typeof puzzleContainerVariants)[number], + ))) || + (value.desktopSpan !== undefined && + (!Number.isInteger(value.desktopSpan) || + Number(value.desktopSpan) < 1 || + Number(value.desktopSpan) > 12)) || + !isOptionalString(value.filterId) + ) { + return false; + } + + if (isString(value.filterId)) { + ids.referencedFilterIds.push(value.filterId); + ids.sectionIds.push(value.filterId); + } + + const { items, nestedContainers, archive } = value.content; + if (!Array.isArray(items) || !Array.isArray(nestedContainers)) return false; + if ( + !items.every( + (row) => + Array.isArray(row) && + row.length > 0 && + row.every((item) => { + if (!isPuzzleItem(item, false) || !isRecord(item)) { + return false; + } + ids.itemIds.push(item.id as string); + if (isString(item.filterId)) { + ids.referencedFilterIds.push(item.filterId); + } + return true; + }), + ) + ) { + return false; + } + + if ( + !nestedContainers.every((container) => + validateContainer(container, ids), + ) + ) { + return false; + } + + if (archive !== undefined) { + if (!isPuzzleItem(archive, true) || !isRecord(archive)) { + return false; + } + ids.itemIds.push(archive.id as string); + if (isString(archive.filterId)) { + ids.referencedFilterIds.push(archive.filterId); + } + } + + return true; +}; + +const hasDuplicates = (values: string[]): boolean => + new Set(values).size !== values.length; + +const isPuzzlesLayout = (value: unknown): boolean => { + if ( + !isRecord(value) || + !Array.isArray(value.filters) || + !Array.isArray(value.containers) + ) { + return false; + } + + const filterIds: string[] = []; + const anchorTargets: string[] = []; + for (const filter of value.filters) { + if ( + !isRecord(filter) || + !isNonEmptyString(filter.id) || + !stableIdPattern.test(filter.id) || + !isNonEmptyString(filter.title) || + !isNonEmptyString(filter.target) || + (!filter.target.startsWith('#') && + !filter.target.startsWith('/puzzles')) || + !isOptionalColour(filter.backgroundColour) + ) { + return false; + } + filterIds.push(filter.id); + if (filter.target.startsWith('#')) { + anchorTargets.push(filter.target.slice(1)); + } + } + + const ids: LayoutIds = { + itemIds: [], + sectionIds: [], + referencedFilterIds: [], + }; + if ( + !value.containers.every((container) => + validateContainer(container, ids), + ) + ) { + return false; + } + + return ( + !hasDuplicates(filterIds) && + !hasDuplicates(ids.itemIds) && + anchorTargets.every((target) => ids.sectionIds.includes(target)) && + ids.referencedFilterIds.every((id) => filterIds.includes(id)) + ); +}; + +export const validateAsPuzzlesPageType = (data: unknown): FEPuzzlesPageType => { + if ( + isRecord(data) && + isNonEmptyString(data.id) && + isString(data.editionId) && + editions.has(String(data.editionId)) && + isNonEmptyString(data.editionLongForm) && + isNonEmptyString(data.contributionsServiceUrl) && + isNonEmptyString(data.webTitle) && + isOptionalString(data.description) && + isPuzzlesConfig(data.config) && + isRecord(data.nav) && + isRecord(data.pageFooter) && + isRecord(data.commercialProperties) && + typeof data.isAdFreeUser === 'boolean' && + isNonEmptyString(data.canonicalUrl) && + isPuzzlesLayout(data.layout) + ) { + return data as unknown as FEPuzzlesPageType; + } + + throw new TypeError('Unable to validate request body for puzzles page.'); +}; diff --git a/dotcom-rendering/src/server/handler.puzzlesPage.web.test.ts b/dotcom-rendering/src/server/handler.puzzlesPage.web.test.ts new file mode 100644 index 00000000000..96ecdf6f4f1 --- /dev/null +++ b/dotcom-rendering/src/server/handler.puzzlesPage.web.test.ts @@ -0,0 +1,98 @@ +import type { Request, Response } from 'express'; +import { createPuzzlesPage } from '../../fixtures/manual/puzzlesPage'; +import { + puzzlesHubExperiment, + puzzlesHubParticipation, +} from '../lib/puzzlesHubExperiment'; +import { handlePuzzlesPage } from './handler.puzzlesPage.web'; +import { renderPuzzlesPage } from './render.puzzlesPage.web'; + +jest.mock('./render.puzzlesPage.web', () => ({ + renderPuzzlesPage: jest.fn(), +})); + +const mockedRenderPuzzlesPage = jest.mocked(renderPuzzlesPage); + +const response = () => { + const res = { + status: jest.fn(), + set: jest.fn(), + send: jest.fn(), + sendStatus: jest.fn(), + }; + res.status.mockReturnValue(res); + res.set.mockReturnValue(res); + res.send.mockReturnValue(res); + res.sendStatus.mockReturnValue(res); + return res; +}; + +const pageWithParticipations = (serverSideABTests: Record) => + createPuzzlesPage({ + config: { + ...createPuzzlesPage().config, + serverSideABTests, + }, + }); + +const invokeHandler = (body: unknown, res: ReturnType) => + handlePuzzlesPage( + { body } as Request, + res as unknown as Response, + jest.fn(), + ); + +describe('handlePuzzlesPage', () => { + beforeEach(() => { + jest.resetAllMocks(); + mockedRenderPuzzlesPage.mockReturnValue({ + html: 'Puzzles', + prefetchScripts: ['/assets/index.js'], + }); + }); + + it('renders the page for the configured variant', () => { + const res = response(); + const page = pageWithParticipations( + puzzlesHubParticipation(puzzlesHubExperiment.variant), + ); + + invokeHandler(page, res); + + expect(mockedRenderPuzzlesPage).toHaveBeenCalledWith({ + puzzlesPage: page, + }); + expect(res.status).toHaveBeenCalledWith(200); + expect(res.set).toHaveBeenCalledWith( + 'Link', + expect.stringContaining('/assets/index.js'), + ); + expect(res.send).toHaveBeenCalledWith('Puzzles'); + }); + + it.each([ + ['control', puzzlesHubParticipation(puzzlesHubExperiment.control)], + ['absent', {}], + ['malformed', puzzlesHubParticipation('variant:extra')], + ['unknown group', puzzlesHubParticipation('unknown')], + ['unrelated', { 'another-test': 'variant' }], + ])('returns 404 and does not render for %s participation', (_, tests) => { + const res = response(); + + invokeHandler(pageWithParticipations(tests), res); + + expect(res.sendStatus).toHaveBeenCalledWith(404); + expect(mockedRenderPuzzlesPage).not.toHaveBeenCalled(); + }); + + it('rejects an invalid payload without invoking the renderer', () => { + const res = response(); + const invalidPage = pageWithParticipations( + puzzlesHubParticipation(puzzlesHubExperiment.variant), + ) as unknown as Record; + delete invalidPage.layout; + + expect(() => invokeHandler(invalidPage, res)).toThrow(TypeError); + expect(mockedRenderPuzzlesPage).not.toHaveBeenCalled(); + }); +}); diff --git a/dotcom-rendering/src/server/handler.puzzlesPage.web.ts b/dotcom-rendering/src/server/handler.puzzlesPage.web.ts new file mode 100644 index 00000000000..580a90a28e6 --- /dev/null +++ b/dotcom-rendering/src/server/handler.puzzlesPage.web.ts @@ -0,0 +1,17 @@ +import type { RequestHandler } from 'express'; +import { isPuzzlesHubEnabled } from '../lib/puzzlesHubExperiment'; +import { validateAsPuzzlesPageType } from '../model/validate'; +import { makePrefetchHeader } from './lib/header'; +import { renderPuzzlesPage } from './render.puzzlesPage.web'; + +export const handlePuzzlesPage: RequestHandler = ({ body }, res) => { + const puzzlesPage = validateAsPuzzlesPageType(body); + + if (!isPuzzlesHubEnabled(puzzlesPage.config)) { + res.sendStatus(404); + return; + } + + const { html, prefetchScripts } = renderPuzzlesPage({ puzzlesPage }); + res.status(200).set('Link', makePrefetchHeader(prefetchScripts)).send(html); +}; diff --git a/dotcom-rendering/src/server/render.puzzlesPage.web.tsx b/dotcom-rendering/src/server/render.puzzlesPage.web.tsx new file mode 100644 index 00000000000..6898a37a1f0 --- /dev/null +++ b/dotcom-rendering/src/server/render.puzzlesPage.web.tsx @@ -0,0 +1,87 @@ +import { ConfigProvider } from '../components/ConfigContext'; +import { PuzzlesPage } from '../components/PuzzlesPage'; +import { + ASSET_ORIGIN, + generateScriptTags, + getModulesBuild, + getPathFromManifest, +} from '../lib/assets'; +import { renderToStringWithEmotion } from '../lib/emotion'; +import { polyfillIO } from '../lib/polyfill.io'; +import { extractNAV } from '../model/extract-nav'; +import { createGuardian } from '../model/guardian'; +import type { Config } from '../types/configContext'; +import type { FEPuzzlesPageType } from '../types/puzzlesPage'; +import { htmlPageTemplate } from './htmlPageTemplate'; + +type Props = { puzzlesPage: FEPuzzlesPageType }; + +export const renderPuzzlesPage = ({ + puzzlesPage, +}: Props): { html: string; prefetchScripts: string[] } => { + const NAV = extractNAV(puzzlesPage.nav); + const darkModeAvailable = + puzzlesPage.config.serverSideABTests['webx-dark-mode-web'] === 'enable'; + const config = { + renderingTarget: 'Web', + darkModeAvailable, + assetOrigin: ASSET_ORIGIN, + editionId: puzzlesPage.editionId, + } satisfies Config; + + const { html, extractedCss } = renderToStringWithEmotion( + + + , + ); + + const build = getModulesBuild(); + const prefetchScripts = [ + polyfillIO, + getPathFromManifest(build, 'frameworks.js'), + getPathFromManifest(build, 'index.js'), + process.env.COMMERCIAL_BUNDLE_URL ?? + puzzlesPage.config.commercialBundleUrl, + ]; + const scriptTags = generateScriptTags(prefetchScripts); + const guardian = createGuardian({ + editionId: puzzlesPage.editionId, + stage: puzzlesPage.config.stage, + frontendAssetsFullURL: puzzlesPage.config.frontendAssetsFullURL, + revisionNumber: puzzlesPage.config.revisionNumber, + sentryPublicApiKey: puzzlesPage.config.sentryPublicApiKey, + sentryHost: puzzlesPage.config.sentryHost, + keywordIds: puzzlesPage.config.keywordIds, + dfpAccountId: puzzlesPage.config.dfpAccountId, + adUnit: puzzlesPage.config.adUnit, + ajaxUrl: puzzlesPage.config.ajaxUrl, + shouldHideReaderRevenue: puzzlesPage.config.shouldHideReaderRevenue, + isPaidContent: puzzlesPage.config.isPaidContent, + googletagUrl: puzzlesPage.config.googletagUrl, + switches: puzzlesPage.config.switches, + serverSideABTests: puzzlesPage.config.serverSideABTests, + contentType: puzzlesPage.config.contentType, + brazeApiKey: puzzlesPage.config.brazeApiKey, + googleRecaptchaSiteKey: puzzlesPage.config.googleRecaptchaSiteKey, + googleRecaptchaSiteKeyVisible: + puzzlesPage.config.googleRecaptchaSiteKeyVisible, + unknownConfig: puzzlesPage.config, + }); + + return { + html: htmlPageTemplate({ + scriptTags, + css: extractedCss, + html, + title: puzzlesPage.webTitle, + description: puzzlesPage.description ?? '', + guardian, + section: puzzlesPage.config.section, + renderingTarget: 'Web', + weAreHiring: !!puzzlesPage.config.switches.weAreHiring, + config, + canonicalUrl: puzzlesPage.canonicalUrl, + }), + prefetchScripts, + }; +}; diff --git a/dotcom-rendering/src/server/server.dev.ts b/dotcom-rendering/src/server/server.dev.ts index 250847b361b..08836483a16 100644 --- a/dotcom-rendering/src/server/server.dev.ts +++ b/dotcom-rendering/src/server/server.dev.ts @@ -18,6 +18,7 @@ import { handleAppsAssets } from './handler.assets.apps'; import { handleEditionsCrossword } from './handler.editionsCrossword'; import { handleFootballMatchDayEmbed } from './handler.footballMatchDayEmbed'; import { handleFront, handleTagPage } from './handler.front.web'; +import { handlePuzzlesPage } from './handler.puzzlesPage.web'; import { handleAppsFootballMatchPage, handleFootballMatchListPage, @@ -112,6 +113,7 @@ renderer.get('/Interactive/*url', handleInteractive); renderer.get('/Blocks/*url', handleBlocks); renderer.get('/Front/*url', handleFront); renderer.get('/TagPage/*url', handleTagPage); +renderer.get('/PuzzlesPage/*url', handlePuzzlesPage); renderer.get('/EmailNewsletters/*url', handleAllEditorialNewslettersPage); renderer.get('/AppsArticle/*url', handleAppsArticle); renderer.get('/AppsInteractive/*url', handleAppsInteractive); @@ -132,6 +134,7 @@ renderer.post('/Interactive', handleInteractive); renderer.post('/Blocks', handleBlocks); renderer.post('/Front', handleFront); renderer.post('/TagPage', handleTagPage); +renderer.post('/PuzzlesPage', handlePuzzlesPage); renderer.post('/EmailNewsletters', handleAllEditorialNewslettersPage); renderer.post('/AppsArticle', handleAppsArticle); renderer.post('/AppsInteractive', handleAppsInteractive); diff --git a/dotcom-rendering/src/server/server.prod.ts b/dotcom-rendering/src/server/server.prod.ts index e89a76f6e9b..821df4b58f6 100644 --- a/dotcom-rendering/src/server/server.prod.ts +++ b/dotcom-rendering/src/server/server.prod.ts @@ -19,6 +19,7 @@ import { handleAppsAssets } from './handler.assets.apps'; import { handleEditionsCrossword } from './handler.editionsCrossword'; import { handleFootballMatchDayEmbed } from './handler.footballMatchDayEmbed'; import { handleFront, handleTagPage } from './handler.front.web'; +import { handlePuzzlesPage } from './handler.puzzlesPage.web'; import { handleAppsFootballMatchPage, handleFootballMatchListPage, @@ -70,6 +71,7 @@ export const prodServer = (): void => { app.post('/Blocks', handleBlocks); app.post('/Front', handleFront); app.post('/TagPage', handleTagPage); + app.post('/PuzzlesPage', handlePuzzlesPage); app.post('/FootballMatchListPage', handleFootballMatchListPage); app.post('/FootballTablesPage', handleFootballTablesPage); app.post('/FootballMatchSummaryPage', handleFootballMatchPage); diff --git a/dotcom-rendering/src/types/puzzlesPage.ts b/dotcom-rendering/src/types/puzzlesPage.ts new file mode 100644 index 00000000000..6c4e53128d1 --- /dev/null +++ b/dotcom-rendering/src/types/puzzlesPage.ts @@ -0,0 +1,76 @@ +import type { EditionId } from '../lib/edition'; +import type { ConfigType } from './config'; +import type { FooterType } from './footer'; +import type { FENavType } from './frontend'; + +export const puzzleCardVariants = [ + 'large', + 'primary', + 'compact', + 'archive', +] as const; +export type PuzzleCardVariant = (typeof puzzleCardVariants)[number]; + +export const puzzleContainerVariants = ['featured', 'standard'] as const; +export type PuzzleContainerVariant = (typeof puzzleContainerVariants)[number]; + +export const puzzlePageVariants = ['iframe-page', 'archive-page'] as const; +export type PuzzlePageVariant = (typeof puzzlePageVariants)[number]; + +export type PuzzleItem = { + id: string; + title: string; + type: string; + set: string; + cardVariant: PuzzleCardVariant; + cadence?: string; + url?: string; + image?: string; + slug?: string; + index?: number; + variant?: PuzzlePageVariant; + backgroundColour?: string; + filterId?: string; +}; + +export type PuzzleContent = { + items: PuzzleItem[][]; + nestedContainers: PuzzleContainer[]; + archive?: PuzzleItem; +}; + +export type PuzzleContainer = { + title: string; + variant?: PuzzleContainerVariant; + content: PuzzleContent; + filterId?: string; + desktopSpan?: number; +}; + +export type PuzzleFilter = { + id: string; + title: string; + target: string; + backgroundColour?: string; +}; + +export type PuzzlesLayoutType = { + containers: PuzzleContainer[]; + filters: PuzzleFilter[]; +}; + +export interface FEPuzzlesPageType { + id: string; + editionId: EditionId; + editionLongForm: string; + contributionsServiceUrl: string; + webTitle: string; + description?: string; + config: ConfigType; + nav: FENavType; + pageFooter: FooterType; + commercialProperties: Record; + isAdFreeUser: boolean; + canonicalUrl: string; + layout: PuzzlesLayoutType; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c97e03b8f97..73f93b58247 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -219,16 +219,16 @@ importers: version: link:../config '@sveltejs/adapter-auto': specifier: 7.0.1 - version: 7.0.1(@sveltejs/kit@2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0))) + version: 7.0.1(@sveltejs/kit@2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0))) '@sveltejs/adapter-static': specifier: 3.0.10 - version: 3.0.10(@sveltejs/kit@2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0))) + version: 3.0.10(@sveltejs/kit@2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0))) '@sveltejs/kit': specifier: 2.70.3 - version: 2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)) + version: 2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)) '@sveltejs/vite-plugin-svelte': specifier: 7.3.0 - version: 7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)) + version: 7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)) svelte: specifier: 5.57.0 version: 5.57.0(@typescript-eslint/types@8.59.2) @@ -240,7 +240,7 @@ importers: version: 6.0.3 vite: specifier: 6.4.3 - version: 6.4.3(@types/node@24.12.4)(terser@5.49.2)(tsx@4.6.2)(yaml@2.9.0) + version: 6.4.3(@types/node@24.12.4)(terser@5.51.2)(tsx@4.6.2)(yaml@2.9.0) ab-testing/notification-lambda: dependencies: @@ -387,17 +387,17 @@ importers: specifier: 10.65.0 version: 10.65.0 '@storybook/addon-a11y': - specifier: 10.5.7 - version: 10.5.7(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1)) + specifier: 10.5.10 + version: 10.5.10(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1)) '@storybook/addon-docs': - specifier: 10.5.7 - version: 10.5.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(esbuild@0.28.2)(rollup@4.62.0)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(tsx@4.6.2)(yaml@2.9.0))(webpack@5.109.2) + specifier: 10.5.10 + version: 10.5.10(@types/react-dom@18.3.0)(@types/react@18.3.1)(esbuild@0.28.2)(rollup@4.62.0)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(tsx@4.6.2)(yaml@2.9.0))(webpack@5.109.2) '@storybook/addon-webpack5-compiler-swc': specifier: 4.0.3 - version: 4.0.3(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(webpack@5.109.2) + version: 4.0.3(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(webpack@5.109.2) '@storybook/react-webpack5': - specifier: 10.5.7 - version: 10.5.7(@swc/core@1.16.1)(@types/react-dom@18.3.0)(@types/react@18.3.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2) + specifier: 10.5.10 + version: 10.5.10(@swc/core@1.16.1)(@types/react-dom@18.3.0)(@types/react@18.3.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2) '@svgr/webpack': specifier: 8.1.0 version: 8.1.0(typescript@6.0.3) @@ -675,8 +675,8 @@ importers: specifier: 0.7.4 version: 0.7.4 storybook: - specifier: 10.5.7 - version: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + specifier: 10.5.10 + version: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) stylelint: specifier: 16.26.1 version: 16.26.1(typescript@6.0.3) @@ -1859,8 +1859,8 @@ packages: '@dual-bundle/import-meta-resolve@4.2.1': resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} - '@emnapi/core@1.11.2': - resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + '@emnapi/core@1.11.0': + resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} '@emnapi/core@1.11.3': resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} @@ -1868,8 +1868,8 @@ packages: '@emnapi/core@1.9.2': resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - '@emnapi/runtime@1.11.2': - resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + '@emnapi/runtime@1.11.0': + resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} '@emnapi/runtime@1.11.3': resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} @@ -3168,12 +3168,12 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@napi-rs/wasm-runtime@1.2.2': - resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==} + '@napi-rs/wasm-runtime@1.2.3': + resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==} engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} peerDependencies: - '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 - '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 '@noble/hashes@1.4.0': resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} @@ -3495,106 +3495,106 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} - '@oxc-resolver/binding-android-arm-eabi@11.24.2': - resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} + '@oxc-resolver/binding-android-arm-eabi@11.21.2': + resolution: {integrity: sha512-xQoCRv+gKax9KTdwdaQNnAFOai8neay7g3jExDIORzhbrejwGSJaZNTdOJHR5ziLg2joMxOCMOFMo4zuxza2uQ==} cpu: [arm] os: [android] - '@oxc-resolver/binding-android-arm64@11.24.2': - resolution: {integrity: sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==} + '@oxc-resolver/binding-android-arm64@11.21.2': + resolution: {integrity: sha512-HF5oiE2L05yInPYCFD/4uxSrEZW4SuIfn99Y6L1xnJnzl066JR+MJs2rIdstw8A2MPlAKH+13dpFPNycjqzvGg==} cpu: [arm64] os: [android] - '@oxc-resolver/binding-darwin-arm64@11.24.2': - resolution: {integrity: sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==} + '@oxc-resolver/binding-darwin-arm64@11.21.2': + resolution: {integrity: sha512-UX4u49CVCAD8QZNELaW8eMGgMAGwFWYEPbvNsh+3r/gs4NX3KfpiACMVwRQT0EuH3uat9hM5Zl+Ppm9pJD8tgg==} cpu: [arm64] os: [darwin] - '@oxc-resolver/binding-darwin-x64@11.24.2': - resolution: {integrity: sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==} + '@oxc-resolver/binding-darwin-x64@11.21.2': + resolution: {integrity: sha512-J9xPx7YBkrRmJ+xl561ztnMWEc1aOyjEIxBiGX1dVb3u7bGSnfObfcZk+Pd+uM0HZAPNsQ1xvD8j52A/uOSqNQ==} cpu: [x64] os: [darwin] - '@oxc-resolver/binding-freebsd-x64@11.24.2': - resolution: {integrity: sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==} + '@oxc-resolver/binding-freebsd-x64@11.21.2': + resolution: {integrity: sha512-fRlt7OvSaQkWj6+EDTVxawVxOlqJB2QSnBfkeCyK4RTvsGctbw3BiH2Tb7DzMs7bikc4BRBpvWP5zF9K8b54Zg==} cpu: [x64] os: [freebsd] - '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': - resolution: {integrity: sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==} + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.2': + resolution: {integrity: sha512-gK+vPUcPQITkGwBKpZGrcDHSlU6eDGl7AQacxS2CEKAZIBHWkOVFeJwLZ4tYnA1acJqRM5lt7yYwPCVqGHIJ7A==} cpu: [arm] os: [linux] - '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': - resolution: {integrity: sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==} + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.2': + resolution: {integrity: sha512-L/Rgas7SrOKy/z7IH+HxSFRqVO4PuLDKLEGKvnhoCBBq3UJ0YzGBou3qMzaAGgkJwsIvX2pBF+7ojzfUhLiZxQ==} cpu: [arm] os: [linux] - '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': - resolution: {integrity: sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==} + '@oxc-resolver/binding-linux-arm64-gnu@11.21.2': + resolution: {integrity: sha512-CUEYvlX1Fk7E9kUMzuswru1J9HLxMwnpDeQGjQuI4ZH+iNCoa2X9T+pvyzrbsgl7WnIeTFTlNlHsRfVdf5g9/g==} cpu: [arm64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-arm64-musl@11.24.2': - resolution: {integrity: sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==} + '@oxc-resolver/binding-linux-arm64-musl@11.21.2': + resolution: {integrity: sha512-ViN1ZibQyxwC67GpoP33oo+S9UyUnkog13vzQb9+v9bCNvrVzJuk0MRdacjtv/9xfRMVF/eqHFyl8YOeykI10Q==} cpu: [arm64] os: [linux] libc: [musl] - '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': - resolution: {integrity: sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==} + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.2': + resolution: {integrity: sha512-CU1sCqWnhGqYD5I1HedHk5pujrn7ssDkNB/AEQd/pd3D/EojVSgJUlpbafwIbyhia3PgIfkvdFpRPWSALzVumA==} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': - resolution: {integrity: sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==} + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.2': + resolution: {integrity: sha512-jWVyZtIHca4Gb96x7dag+y69vlei7ffjrsveLkmf2ZhqEAz6ZSBnY1GWvgZUaZlwZP62A3xD092BW97Q5VGc+g==} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': - resolution: {integrity: sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==} + '@oxc-resolver/binding-linux-riscv64-musl@11.21.2': + resolution: {integrity: sha512-LF29obFqNgBUgDX7rmUK7M4D0JQG5LxhYzn3xXmECcHU9aQAdWG7NiY052qybtesEdwHQXKNTWYQ7mTsybNvWg==} cpu: [riscv64] os: [linux] libc: [musl] - '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': - resolution: {integrity: sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==} + '@oxc-resolver/binding-linux-s390x-gnu@11.21.2': + resolution: {integrity: sha512-+NYcm+cCHBbtdQQ3A4phQTSuVRYnNHz7wrl9XRAPEovcdoqi0mb1K5ZOl+jN54ZD+q1zz3V0vltbFJmzecJKmw==} cpu: [s390x] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-x64-gnu@11.24.2': - resolution: {integrity: sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==} + '@oxc-resolver/binding-linux-x64-gnu@11.21.2': + resolution: {integrity: sha512-UQqZDdG2r2HhAOsZEgufkIWHPQ886IUyuJQkoZByvzhW8j51R4UNzGBJFkTiTnLhQnggwJRdJgFWK4uY6ZVIMw==} cpu: [x64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-x64-musl@11.24.2': - resolution: {integrity: sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==} + '@oxc-resolver/binding-linux-x64-musl@11.21.2': + resolution: {integrity: sha512-3Q9PMRjalWkT6NZ4jfujuqTCFwoWErg3y3BnOgb544B8IMw4PktiwWOigMfOHNRLMghZeJ7hpfpZf4CP7rV7Og==} cpu: [x64] os: [linux] libc: [musl] - '@oxc-resolver/binding-openharmony-arm64@11.24.2': - resolution: {integrity: sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==} + '@oxc-resolver/binding-openharmony-arm64@11.21.2': + resolution: {integrity: sha512-Eljeq3ndtyKhM+Es8LITi4Zl2htzuRZcrPMF3kMCsrILztvU6AjZ3FuEhHHKobPUB9rMpzLpJP5bDqXI7r+iog==} cpu: [arm64] os: [openharmony] - '@oxc-resolver/binding-wasm32-wasi@11.24.2': - resolution: {integrity: sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==} + '@oxc-resolver/binding-wasm32-wasi@11.21.2': + resolution: {integrity: sha512-HGDbNsIywqc4LxU38+CTJNnB/6BF7rheWOJ259b4eE0aEnelYblC8x+1tEd63bp31fYne63RQz9Jb4yVnC7Yig==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': - resolution: {integrity: sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==} + '@oxc-resolver/binding-win32-arm64-msvc@11.21.2': + resolution: {integrity: sha512-iWx25CBEgH49iE9q5coEGI/jb1jl5kkCY9z6U5Og67xCkQ/WFMDc2J5U78+AE91SUxM2NqSLhJC8/PLfWnImww==} cpu: [arm64] os: [win32] - '@oxc-resolver/binding-win32-x64-msvc@11.24.2': - resolution: {integrity: sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==} + '@oxc-resolver/binding-win32-x64-msvc@11.21.2': + resolution: {integrity: sha512-VPoCAhKvCQTlG7vxqaBXcmuvbh77BfnGXj8g0pbvVXpm1F/R8rDVwqIWcEbMzrI1JvJlm8v7T9uMVQb6UctMRg==} cpu: [x64] os: [win32] @@ -4287,16 +4287,16 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@storybook/addon-a11y@10.5.7': - resolution: {integrity: sha512-I30rsNz6aA3xg3811MEry40uJDHP3l5SOkfqtmNkp7y4NdqTDdKhmbhhDAZQX1WWEk6GeMBGr3AJ3TCz7r7JmQ==} + '@storybook/addon-a11y@10.5.10': + resolution: {integrity: sha512-RpRQV5xUbrl6hCiNrd5FSMIo6pnRZ0VZxWvEW/ASLcreGkKUW5jl2AeLCe5YROE2i80s/dU+6VPzOYKrwWNFbQ==} peerDependencies: - storybook: ^10.5.7 + storybook: ^10.5.10 - '@storybook/addon-docs@10.5.7': - resolution: {integrity: sha512-KNARJfjICaizinsR3INMEiipZm1ObYo+xw+E26gteu50Bcy2dIZUtk5uHY5XdtardU3AXX6yRXoBZ2HCY3lbHA==} + '@storybook/addon-docs@10.5.10': + resolution: {integrity: sha512-06JoK3/a7FWI/6GzuidJP9iHp1/Vejboe6lzS1jW+d8ItpecriBt+oXh1VNmUM7i7PjI6pZnet+j51QnLyeOoQ==} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.5.7 + storybook: ^10.5.10 peerDependenciesMeta: '@types/react': optional: true @@ -4307,26 +4307,26 @@ packages: peerDependencies: storybook: ^9.0.0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 - '@storybook/builder-webpack5@10.5.7': - resolution: {integrity: sha512-4n4c60LihFivZnjAcXGO5+XbgZthoUtKb/nPKVgypj3MpEetzjq6XR83A4UNnRsXYmjqfn6bsDWNgEJ/RvQg5A==} + '@storybook/builder-webpack5@10.5.10': + resolution: {integrity: sha512-9ZBRusBwZStoCj3Wm4wVpSpKV8OlLMpadEQ+vvOVrtDQdVJWM5xdLQpqrG21r/cJC4ZX53ZpbrDL+NMsIp1Rkw==} peerDependencies: - storybook: ^10.5.7 + storybook: ^10.5.10 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@storybook/core-webpack@10.5.7': - resolution: {integrity: sha512-0dtDw/FNPREoeCHX2RgZz0OecxaAGol1R7bCobFevArxyFIPJisTfjDMUFHKr+3B7BilTd3vnatl7Nlvgs0EiA==} + '@storybook/core-webpack@10.5.10': + resolution: {integrity: sha512-CiUo4AZRKm3nVb6KWw+yfV6g6VSTA97msiVN9bJlBOFS1dax2ulrRNJnHJxR/U1AT51hONfU4dZOpCXRwaobTA==} peerDependencies: - storybook: ^10.5.7 + storybook: ^10.5.10 - '@storybook/csf-plugin@10.5.7': - resolution: {integrity: sha512-IaX8FlM0H36HNFhJ2+4L9bCldqfvHGqcLg841SJNyK/DhfMlM7JsvY/GDH2ZFuWrUf8FSOx96GRRnHq6XfRKag==} + '@storybook/csf-plugin@10.5.10': + resolution: {integrity: sha512-TaCLBrqVEr767+w58QDotDUiCTuE5cyRJuRcDlsKQyUIyGBv+lYD3lu8wBiVCYxgIjB/gu9HmqiC+0yx1rHzaw==} peerDependencies: esbuild: '*' rollup: '*' - storybook: ^10.5.7 + storybook: ^10.5.10 vite: '*' webpack: '*' peerDependenciesMeta: @@ -4347,12 +4347,12 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@storybook/preset-react-webpack@10.5.7': - resolution: {integrity: sha512-xwNRcoVlIDx1/YYCFBAxfh/91vFiOgrVI+0Ir4u9eO87SH2leehRnJh619QEOrlQEU5px487y2BmL2ZVtmTpYA==} + '@storybook/preset-react-webpack@10.5.10': + resolution: {integrity: sha512-erAyPdNKqzOQOQVHm/kMSO4Nrr/g279KoA5LyzMOTJAQJHjDdsqmYdGXRDVfcnDL+H+Qm9pWaP+re2S8YG6JNg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.5.7 + storybook: ^10.5.10 typescript: '*' peerDependenciesMeta: typescript: @@ -4364,39 +4364,39 @@ packages: typescript: '>= 4.x' webpack: '>= 4' - '@storybook/react-dom-shim@10.5.7': - resolution: {integrity: sha512-lxOkyh+wu/MiBXvYQHjZfD+DRKOa4bHBzbuGuiHXnHXmdOcTRdcrQTsoeN2FPtfugmmOG66cZUEgDwNX+k5eRA==} + '@storybook/react-dom-shim@10.5.10': + resolution: {integrity: sha512-rbu62ILo/VE3iXKmu+kWXFpD1H1Lwi0f19q/x7JnDsD2dxKS9w5znLEqPIq2qxpzi/wjjIb2iUP1cRG1d/9W5A==} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.5.7 + storybook: ^10.5.10 peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - '@storybook/react-webpack5@10.5.7': - resolution: {integrity: sha512-vvl07oXp2qfmHJHZ77Aw1F3LFOo7XubOta+lC8UmlEw3rDDJhQxJN3erJJVHavNhdA2jBTK6VUXQKdqQh7X7nQ==} + '@storybook/react-webpack5@10.5.10': + resolution: {integrity: sha512-PpC2CYbeqKGDYpG64PR/wkjo+Sm9EKWvdE0ZaKHBQb1PfC0p/TUxHy8XfXQp/4atjhET0VqSUDc0TP/ii1bGxw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.5.7 + storybook: ^10.5.10 typescript: '>= 4.9.x' peerDependenciesMeta: typescript: optional: true - '@storybook/react@10.5.7': - resolution: {integrity: sha512-uFvty2MMdFXzW5PcQe1JqDAZkz6cQq7q/9G/cbGVnBEvP6zsOVeL+bmrQ0/WBlFQN0Ko9+ZoCTvaQ9s65zBa5g==} + '@storybook/react@10.5.10': + resolution: {integrity: sha512-4MBV5e1SXIMfPynLHzr+Mp0dwGv/FW1bklWAsS4ynBOAbC98W9p/I9vqBnUctsvE3BJkhzHQQyPwMHL5tTcHVA==} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.5.7 + storybook: ^10.5.10 typescript: '>= 4.9.x' peerDependenciesMeta: '@types/react': @@ -6102,6 +6102,10 @@ packages: resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} engines: {node: '>=18'} + default-browser@5.5.1: + resolution: {integrity: sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==} + engines: {node: '>=18'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -8230,8 +8234,8 @@ packages: resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} engines: {node: ^20.19.0 || >=22.12.0} - oxc-resolver@11.24.2: - resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==} + oxc-resolver@11.21.2: + resolution: {integrity: sha512-w5tLwYN3Zo24w5EeWJjJWZOwhYqTtC8PS2B1tIt7BZUuqTIcU07sQValbDw+rq7+AuAGzOHklgK+ifsy4lpXfw==} p-cancelable@4.0.1: resolution: {integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==} @@ -9077,8 +9081,8 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - storybook@10.5.7: - resolution: {integrity: sha512-oiKvWIwIoOhFP1i6dASYyMXwPHKEtVZMshqSB7EvIVYjWRh0l9H7gHEt1z4Gh2rLGFMekWdsm4s94rvwpR7gkg==} + storybook@10.5.10: + resolution: {integrity: sha512-Rz8k9ejFHsi7lbtJTaxZlhCUz4GkbJIKEoKDjXeLfr/ZhXip73E6keKxW0KH8iGeKiCqHAbJCV4YIQrxTOLiig==} hasBin: true peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -9354,6 +9358,11 @@ packages: engines: {node: '>=10'} hasBin: true + terser@5.51.2: + resolution: {integrity: sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==} + engines: {node: '>=10'} + hasBin: true + test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -12027,7 +12036,7 @@ snapshots: '@dual-bundle/import-meta-resolve@4.2.1': {} - '@emnapi/core@1.11.2': + '@emnapi/core@1.11.0': dependencies: '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 @@ -12045,7 +12054,7 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.11.2': + '@emnapi/runtime@1.11.0': dependencies: tslib: 2.8.1 optional: true @@ -13136,14 +13145,14 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': dependencies: - '@emnapi/core': 1.11.2 - '@emnapi/runtime': 1.11.2 + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: '@emnapi/core': 1.9.2 '@emnapi/runtime': 1.9.2 @@ -13464,7 +13473,7 @@ snapshots: dependencies: '@emnapi/core': 1.9.2 '@emnapi/runtime': 1.9.2 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) optional: true '@oxc-parser/binding-win32-arm64-msvc@0.127.0': @@ -13478,65 +13487,65 @@ snapshots: '@oxc-project/types@0.127.0': {} - '@oxc-resolver/binding-android-arm-eabi@11.24.2': + '@oxc-resolver/binding-android-arm-eabi@11.21.2': optional: true - '@oxc-resolver/binding-android-arm64@11.24.2': + '@oxc-resolver/binding-android-arm64@11.21.2': optional: true - '@oxc-resolver/binding-darwin-arm64@11.24.2': + '@oxc-resolver/binding-darwin-arm64@11.21.2': optional: true - '@oxc-resolver/binding-darwin-x64@11.24.2': + '@oxc-resolver/binding-darwin-x64@11.21.2': optional: true - '@oxc-resolver/binding-freebsd-x64@11.24.2': + '@oxc-resolver/binding-freebsd-x64@11.21.2': optional: true - '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.2': optional: true - '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.2': optional: true - '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + '@oxc-resolver/binding-linux-arm64-gnu@11.21.2': optional: true - '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + '@oxc-resolver/binding-linux-arm64-musl@11.21.2': optional: true - '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.2': optional: true - '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.2': optional: true - '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + '@oxc-resolver/binding-linux-riscv64-musl@11.21.2': optional: true - '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + '@oxc-resolver/binding-linux-s390x-gnu@11.21.2': optional: true - '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + '@oxc-resolver/binding-linux-x64-gnu@11.21.2': optional: true - '@oxc-resolver/binding-linux-x64-musl@11.24.2': + '@oxc-resolver/binding-linux-x64-musl@11.21.2': optional: true - '@oxc-resolver/binding-openharmony-arm64@11.24.2': + '@oxc-resolver/binding-openharmony-arm64@11.21.2': optional: true - '@oxc-resolver/binding-wasm32-wasi@11.24.2': + '@oxc-resolver/binding-wasm32-wasi@11.21.2': dependencies: - '@emnapi/core': 1.11.2 - '@emnapi/runtime': 1.11.2 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) optional: true - '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + '@oxc-resolver/binding-win32-arm64-msvc@11.21.2': optional: true - '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + '@oxc-resolver/binding-win32-x64-msvc@11.21.2': optional: true '@package-json/types@0.0.12': {} @@ -14288,21 +14297,21 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@storybook/addon-a11y@10.5.7(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))': + '@storybook/addon-a11y@10.5.10(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))': dependencies: '@storybook/global': 5.0.0 axe-core: 4.13.0 - storybook: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + storybook: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) - '@storybook/addon-docs@10.5.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(esbuild@0.28.2)(rollup@4.62.0)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(tsx@4.6.2)(yaml@2.9.0))(webpack@5.109.2)': + '@storybook/addon-docs@10.5.10(@types/react-dom@18.3.0)(@types/react@18.3.1)(esbuild@0.28.2)(rollup@4.62.0)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(tsx@4.6.2)(yaml@2.9.0))(webpack@5.109.2)': dependencies: '@mdx-js/react': 3.1.1(@types/react@18.3.1)(react@18.3.1) - '@storybook/csf-plugin': 10.5.7(esbuild@0.28.2)(rollup@4.62.0)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(tsx@4.6.2)(yaml@2.9.0))(webpack@5.109.2) + '@storybook/csf-plugin': 10.5.10(esbuild@0.28.2)(rollup@4.62.0)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(tsx@4.6.2)(yaml@2.9.0))(webpack@5.109.2) '@storybook/icons': 2.1.0(react@18.3.1) - '@storybook/react-dom-shim': 10.5.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1)) + '@storybook/react-dom-shim': 10.5.10(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + storybook: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) ts-dedent: 2.3.0 optionalDependencies: '@types/react': 18.3.1 @@ -14313,18 +14322,18 @@ snapshots: - vite - webpack - '@storybook/addon-webpack5-compiler-swc@4.0.3(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(webpack@5.109.2)': + '@storybook/addon-webpack5-compiler-swc@4.0.3(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(webpack@5.109.2)': dependencies: '@swc/core': 1.16.1 - storybook: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + storybook: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) swc-loader: 0.2.7(@swc/core@1.16.1)(webpack@5.109.2) transitivePeerDependencies: - '@swc/helpers' - webpack - '@storybook/builder-webpack5@10.5.7(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2)': + '@storybook/builder-webpack5@10.5.10(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2)': dependencies: - '@storybook/core-webpack': 10.5.7(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1)) + '@storybook/core-webpack': 10.5.10(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1)) case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.3 css-loader: 7.1.4(webpack@5.109.2) @@ -14333,7 +14342,7 @@ snapshots: html-webpack-plugin: 5.6.8(webpack@5.109.2) magic-string: 0.30.21 semver: 7.8.5 - storybook: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + storybook: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) style-loader: 4.0.0(webpack@5.109.2) terser-webpack-plugin: 5.6.1(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(webpack@5.109.2) ts-dedent: 2.3.0 @@ -14359,19 +14368,19 @@ snapshots: - uglify-js - webpack-cli - '@storybook/core-webpack@10.5.7(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))': + '@storybook/core-webpack@10.5.10(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))': dependencies: - storybook: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + storybook: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) ts-dedent: 2.3.0 - '@storybook/csf-plugin@10.5.7(esbuild@0.28.2)(rollup@4.62.0)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(tsx@4.6.2)(yaml@2.9.0))(webpack@5.109.2)': + '@storybook/csf-plugin@10.5.10(esbuild@0.28.2)(rollup@4.62.0)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(tsx@4.6.2)(yaml@2.9.0))(webpack@5.109.2)': dependencies: - storybook: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + storybook: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) unplugin: 2.3.11 optionalDependencies: esbuild: 0.28.2 rollup: 4.62.0 - vite: 6.4.3(@types/node@24.12.4)(terser@5.49.2)(tsx@4.6.2)(yaml@2.9.0) + vite: 6.4.3(@types/node@24.12.4)(terser@5.51.2)(tsx@4.6.2)(yaml@2.9.0) webpack: 5.109.2(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(webpack-cli@7.2.2) '@storybook/global@5.0.0': {} @@ -14380,9 +14389,9 @@ snapshots: dependencies: react: 18.3.1 - '@storybook/preset-react-webpack@10.5.7(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2)': + '@storybook/preset-react-webpack@10.5.10(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2)': dependencies: - '@storybook/core-webpack': 10.5.7(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1)) + '@storybook/core-webpack': 10.5.10(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1)) '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@6.0.3)(webpack@5.109.2) '@types/semver': 7.8.0 magic-string: 0.30.21 @@ -14391,7 +14400,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) resolve: 1.22.12 semver: 7.8.5 - storybook: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + storybook: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) tsconfig-paths: 4.2.0 webpack: 5.109.2(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(webpack-cli@7.2.2) optionalDependencies: @@ -14420,29 +14429,29 @@ snapshots: flat-cache: 3.2.0 micromatch: 4.0.8 react-docgen-typescript: 2.4.0(typescript@6.0.3) - tslib: 2.8.1 + tslib: 2.6.2 typescript: 6.0.3 webpack: 5.109.2(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(webpack-cli@7.2.2) transitivePeerDependencies: - supports-color - '@storybook/react-dom-shim@10.5.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))': + '@storybook/react-dom-shim@10.5.10(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + storybook: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) optionalDependencies: '@types/react': 18.3.1 '@types/react-dom': 18.3.0 - '@storybook/react-webpack5@10.5.7(@swc/core@1.16.1)(@types/react-dom@18.3.0)(@types/react@18.3.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2)': + '@storybook/react-webpack5@10.5.10(@swc/core@1.16.1)(@types/react-dom@18.3.0)(@types/react@18.3.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2)': dependencies: - '@storybook/builder-webpack5': 10.5.7(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2) - '@storybook/preset-react-webpack': 10.5.7(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2) - '@storybook/react': 10.5.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3) + '@storybook/builder-webpack5': 10.5.10(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2) + '@storybook/preset-react-webpack': 10.5.10(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)(webpack-cli@7.2.2) + '@storybook/react': 10.5.10(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + storybook: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: @@ -14464,15 +14473,15 @@ snapshots: - uglify-js - webpack-cli - '@storybook/react@10.5.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)': + '@storybook/react@10.5.10(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1))(typescript@6.0.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 10.5.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1)) + '@storybook/react-dom-shim': 10.5.10(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1)) react: 18.3.1 react-docgen: 8.0.3 react-docgen-typescript: 2.4.0(typescript@6.0.3) react-dom: 18.3.1(react@18.3.1) - storybook: 10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) + storybook: 10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1) optionalDependencies: '@types/react': 18.3.1 '@types/react-dom': 18.3.0 @@ -14494,19 +14503,19 @@ snapshots: dependencies: acorn: 8.18.0 - '@sveltejs/adapter-auto@7.0.1(@sveltejs/kit@2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)))': + '@sveltejs/adapter-auto@7.0.1(@sveltejs/kit@2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)))': dependencies: - '@sveltejs/kit': 2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)) + '@sveltejs/kit': 2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)) - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)))': dependencies: - '@sveltejs/kit': 2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)) + '@sveltejs/kit': 2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)) - '@sveltejs/kit@2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0))': + '@sveltejs/kit@2.70.3(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)))(svelte@5.57.0(@typescript-eslint/types@8.59.2))(typescript@6.0.3)(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.13(acorn@8.18.0) - '@sveltejs/vite-plugin-svelte': 7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.18.0 cookie: 0.6.0 @@ -14518,21 +14527,21 @@ snapshots: set-cookie-parser: 3.1.2 sirv: 3.0.2 svelte: 5.57.0(@typescript-eslint/types@8.59.2) - vite: 6.4.3(@types/node@24.12.4)(terser@5.49.2)(tsx@4.6.2)(yaml@2.9.0) + vite: 6.4.3(@types/node@24.12.4)(terser@5.51.2)(tsx@4.6.2)(yaml@2.9.0) optionalDependencies: '@opentelemetry/api': 1.9.1 typescript: 6.0.3 '@sveltejs/load-config@0.2.3': {} - '@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0(@typescript-eslint/types@8.59.2))(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0))': dependencies: deepmerge: 4.3.1 magic-string: 1.2.2 obug: 2.1.4 svelte: 5.57.0(@typescript-eslint/types@8.59.2) - vite: 6.4.3(@types/node@24.12.4)(terser@5.49.2)(tsx@4.6.2)(yaml@2.9.0) - vitefu: 1.1.3(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)) + vite: 6.4.3(@types/node@24.12.4)(terser@5.51.2)(tsx@4.6.2)(yaml@2.9.0) + vitefu: 1.1.3(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)) '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.0)': dependencies: @@ -16296,6 +16305,11 @@ snapshots: bundle-name: 4.1.0 default-browser-id: 5.0.1 + default-browser@5.5.1: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -16401,7 +16415,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.8.1 + tslib: 2.6.2 dunder-proto@1.0.1: dependencies: @@ -17588,7 +17602,7 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.49.2 + terser: 5.51.2 html-minifier-terser@7.2.0: dependencies: @@ -17968,7 +17982,7 @@ snapshots: istanbul-lib-instrument@6.0.1: dependencies: '@babel/core': 7.29.7 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.8 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.5.4 @@ -19007,7 +19021,7 @@ snapshots: open@10.2.0: dependencies: - default-browser: 5.5.0 + default-browser: 5.5.1 define-lazy-prop: 3.0.0 is-inside-container: 1.0.0 wsl-utils: 0.1.0 @@ -19063,27 +19077,27 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 '@oxc-parser/binding-win32-x64-msvc': 0.127.0 - oxc-resolver@11.24.2: + oxc-resolver@11.21.2: optionalDependencies: - '@oxc-resolver/binding-android-arm-eabi': 11.24.2 - '@oxc-resolver/binding-android-arm64': 11.24.2 - '@oxc-resolver/binding-darwin-arm64': 11.24.2 - '@oxc-resolver/binding-darwin-x64': 11.24.2 - '@oxc-resolver/binding-freebsd-x64': 11.24.2 - '@oxc-resolver/binding-linux-arm-gnueabihf': 11.24.2 - '@oxc-resolver/binding-linux-arm-musleabihf': 11.24.2 - '@oxc-resolver/binding-linux-arm64-gnu': 11.24.2 - '@oxc-resolver/binding-linux-arm64-musl': 11.24.2 - '@oxc-resolver/binding-linux-ppc64-gnu': 11.24.2 - '@oxc-resolver/binding-linux-riscv64-gnu': 11.24.2 - '@oxc-resolver/binding-linux-riscv64-musl': 11.24.2 - '@oxc-resolver/binding-linux-s390x-gnu': 11.24.2 - '@oxc-resolver/binding-linux-x64-gnu': 11.24.2 - '@oxc-resolver/binding-linux-x64-musl': 11.24.2 - '@oxc-resolver/binding-openharmony-arm64': 11.24.2 - '@oxc-resolver/binding-wasm32-wasi': 11.24.2 - '@oxc-resolver/binding-win32-arm64-msvc': 11.24.2 - '@oxc-resolver/binding-win32-x64-msvc': 11.24.2 + '@oxc-resolver/binding-android-arm-eabi': 11.21.2 + '@oxc-resolver/binding-android-arm64': 11.21.2 + '@oxc-resolver/binding-darwin-arm64': 11.21.2 + '@oxc-resolver/binding-darwin-x64': 11.21.2 + '@oxc-resolver/binding-freebsd-x64': 11.21.2 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.21.2 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.21.2 + '@oxc-resolver/binding-linux-arm64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-arm64-musl': 11.21.2 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-riscv64-musl': 11.21.2 + '@oxc-resolver/binding-linux-s390x-gnu': 11.21.2 + '@oxc-resolver/binding-linux-x64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-x64-musl': 11.21.2 + '@oxc-resolver/binding-openharmony-arm64': 11.21.2 + '@oxc-resolver/binding-wasm32-wasi': 11.21.2 + '@oxc-resolver/binding-win32-arm64-msvc': 11.21.2 + '@oxc-resolver/binding-win32-x64-msvc': 11.21.2 p-cancelable@4.0.1: {} @@ -19173,7 +19187,7 @@ snapshots: pascal-case@3.1.2: dependencies: no-case: 3.0.4 - tslib: 2.8.1 + tslib: 2.6.2 path-equal@1.2.5: {} @@ -19984,7 +19998,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.8.1 + tslib: 2.6.2 sort-keys-length@1.0.1: dependencies: @@ -20045,7 +20059,7 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - storybook@10.5.7(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1): + storybook@10.5.10(@types/react@18.3.1)(prettier@3.8.3)(react@18.3.1): dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 2.1.0(react@18.3.1) @@ -20059,7 +20073,7 @@ snapshots: jsonc-parser: 3.3.1 open: 10.2.0 oxc-parser: 0.127.0 - oxc-resolver: 11.24.2 + oxc-resolver: 11.21.2 recast: 0.23.21 semver: 7.8.5 use-sync-external-store: 1.6.0(react@18.3.1) @@ -20371,7 +20385,7 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.49.2 + terser: 5.51.2 webpack: 5.109.2(@swc/core@1.16.1)(clean-css@5.3.3)(esbuild@0.28.2)(html-minifier-terser@7.2.0)(postcss@8.5.25)(webpack-cli@7.2.2) optionalDependencies: '@swc/core': 1.16.1 @@ -20394,6 +20408,13 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + terser@5.51.2: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.18.0 + commander: 2.20.3 + source-map-support: 0.5.21 + test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 @@ -20856,7 +20877,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(tsx@4.6.2)(yaml@2.9.0): + vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(tsx@4.6.2)(yaml@2.9.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) @@ -20867,13 +20888,13 @@ snapshots: optionalDependencies: '@types/node': 24.12.4 fsevents: 2.3.3 - terser: 5.49.2 + terser: 5.51.2 tsx: 4.6.2 yaml: 2.9.0 - vitefu@1.1.3(vite@6.4.3(@types/node@24.12.4)(terser@5.49.2)(yaml@2.9.0)): + vitefu@1.1.3(vite@6.4.3(@types/node@24.12.4)(terser@5.51.2)(yaml@2.9.0)): optionalDependencies: - vite: 6.4.3(@types/node@24.12.4)(terser@5.49.2)(tsx@4.6.2)(yaml@2.9.0) + vite: 6.4.3(@types/node@24.12.4)(terser@5.51.2)(tsx@4.6.2)(yaml@2.9.0) w3c-xmlserializer@4.0.0: dependencies: