diff --git a/docs/printing.md b/docs/printing.md index aa6e492a2..e280dd345 100644 --- a/docs/printing.md +++ b/docs/printing.md @@ -149,3 +149,55 @@ after the tab's data has loaded; it excludes the initial dashboard load. The timeout is a bounded wait with an explicit retry path, not a guarantee that every device or connection finishes within 15 seconds. Safari native printing remains unverified, and Firefox page numbering still needs its native print setting. + +## Port to the interactive dashboard (27 September 2026) + +The shadcn/ui dashboard (#490) replaced the PNG galleries with interactive charts +drawn from each variant's JSON dataset, so the print feature was carried over +onto the new components rather than merged line by line. Its behaviour is +unchanged: the live tree is printed under the same transient print context, with +selections stated as text, every table row up to the 500-row cap, and provenance +on every page. What changed is how each piece is implemented: + +- **Readiness.** Charts are SVG drawn from JSON, so preparation no longer decodes + images. A chart still loading (its module or its dataset) carries + `data-print-pending`; a chart whose dataset failed carries `data-print-error` + and names itself ("Could not load chart data: (<variant>)"). Print tab + waits, within the same 15-second bound, until nothing is pending, then refuses + to print if anything failed. Native printing before that point prints the named + loading or error notice, never a silent gap. +- **Collapsed content.** Section groups, cards and the tab explainer are Radix + collapsibles, which unmount closed content. Each holds its open state and is + forced open while printing; the reader's own collapse returns afterwards. The + native `<details>` that remain (chart methodology) are still opened and + restored by the print controller. Group names print as headings. +- **Selections.** Variant, role and period switches are toggle groups. While + printing they stay mounted but hidden, and a line such as "Time range: 1 month" + or "Pipeline view: By month" states the choice. +- **Controls on paper.** `print.css` selects data attributes and shadcn + `data-slot`s, never utility classes. It hides the header, sidebar, dialogs, + tooltips, inputs and every button except those marked `data-print-keep`: a + chart's legend doubles as its series switch, and some table rows name + themselves with a focus button, so those print as text. +- **Slideshows** keep every slide mounted (each keeps its chosen variant) and + print them all in order; an expanded chart prints in place of its dialog. +- **Light output.** Dark mode is one Tailwind `dark` variant, now scoped to + `screen`, so paper always gets the light palette. + +The component tests were ported to the new roles (toggle items are radios) and +now cover chart readiness through the pending and error markers instead of image +decoding. The Playwright suite was ported to the new DOM with real chart datasets +in its fixtures; avatar requests to GitHub, which the dashboard makes by design +(see `web/README.md`), are answered locally so the suite stays offline. + +Two behaviours needed new handling. An open Radix dialog (an expanded chart or a +figure's explanation) ignores Escape and outside clicks while printing, so the +key that dismisses the browser's print dialog cannot also close it; the dialog is +a scroll box and opts in to scroll restore. An expanded chart prints in place +while its dialog keeps its own copy mounted, so its open explanation and scroll +position survive. + +Local verification after the port: 167 Vitest tests, and 21 Playwright tests in +each of Chromium and Firefox. Lint, Prettier and the typecheck/build pass; lint +keeps its five existing warnings and adds none. Printed output from real data was +not re-reviewed in this pass. diff --git a/src/hiero_analytics/analysis/scorecard_analysis.py b/src/hiero_analytics/analysis/scorecard_analysis.py index 642280c5b..c2a383c35 100644 --- a/src/hiero_analytics/analysis/scorecard_analysis.py +++ b/src/hiero_analytics/analysis/scorecard_analysis.py @@ -34,11 +34,12 @@ def scorecard_to_dataframe(scorecards: list[ScorecardRecord]) -> pd.DataFrame: ) -def scorecard_stacked_dataframe(scorecards: list[ScorecardRecord]) -> pd.DataFrame: +def scorecard_stacked_dataframe(scorecards: list[ScorecardRecord], missing: float | None = 0.0) -> pd.DataFrame: """ Convert ScorecardRecord list into a dataframe with checks as columns. - Missing checks are filled with 0. + Checks a scorecard does not report are filled with ``missing``: 0 for the + stacked chart, None where "not reported" must stay distinct from a zero. """ if not scorecards: return pd.DataFrame(columns=["repo", "score", "date", *CHECK_COLUMNS]) @@ -52,7 +53,7 @@ def scorecard_stacked_dataframe(scorecards: list[ScorecardRecord]) -> pd.DataFra "repo": s.repo, "score": s.score, "date": s.date, - **{check: checks.get(check, 0.0) for check in CHECK_COLUMNS}, + **{check: checks.get(check, missing) for check in CHECK_COLUMNS}, } ) diff --git a/src/hiero_analytics/dashboard_spec/__init__.py b/src/hiero_analytics/dashboard_spec/__init__.py index da39b4032..0e81c8e4a 100644 --- a/src/hiero_analytics/dashboard_spec/__init__.py +++ b/src/hiero_analytics/dashboard_spec/__init__.py @@ -40,6 +40,7 @@ "MACRO_GLOSSARIES", "MACRO_GROUP_ORDER", "MACRO_PARENTS", + "MACRO_SUMMARIES", "METRIC_ANNOTATIONS", "PROJECT_ISSUES_URL", "TABLE_FAMILIES", @@ -54,7 +55,9 @@ # The frontend implements exactly these (web/src/components/FormattedCell.tsx); # an unlisted value would fall through to plain text, so a typo is caught by # tests/dashboard_spec instead of shipping as a silently unformatted column. -COLUMN_FORMATS = frozenset({"hip", "date", "link", "evidence", "status", "flag", "presence", "number", "staleness"}) +COLUMN_FORMATS = frozenset( + {"hip", "date", "link", "evidence", "status", "flag", "presence", "number", "percent", "staleness"} +) # The families that carry table sections, keyed by their macro name — the # dashboard pipeline reads SECTION_SPECS / SECTION_ORDER / SECTION_GROUP_OF @@ -89,6 +92,11 @@ } +# The one-line purpose shown under each tab's title. Every family declares one, +# so a new tab cannot ship with the frontend's generic fallback by accident. +MACRO_SUMMARIES = {family.CHART_MACRO["name"]: family.SUMMARY for family in _FAMILIES} + + def _group_order(family) -> list[str]: """The family's section-group display order, chart-only groups included. diff --git a/src/hiero_analytics/dashboard_spec/community.py b/src/hiero_analytics/dashboard_spec/community.py index 79033db9d..d6845356e 100644 --- a/src/hiero_analytics/dashboard_spec/community.py +++ b/src/hiero_analytics/dashboard_spec/community.py @@ -5,6 +5,11 @@ from __future__ import annotations +from hiero_analytics.dashboard_spec.interactive import DISCORD_SOURCES + +# One line under the tab's title saying what the tab is for. +SUMMARY = "Discover how the Hiero community connects and grows." + # Shown when the selected org has no content for this tab. ABSENT_NOTE = ( "Discord serves the whole Hiero ecosystem, so community activity is published " @@ -17,6 +22,7 @@ "hiero-ledger": [ { "id": "discord", + "interactive_sources": DISCORD_SOURCES, "group": "Discord", "title": "Discord activity", "description": "Discord channel categories, monthly traffic, and recent activity.", diff --git a/src/hiero_analytics/dashboard_spec/contributors.py b/src/hiero_analytics/dashboard_spec/contributors.py index 1c1c61f86..8247c3d5c 100644 --- a/src/hiero_analytics/dashboard_spec/contributors.py +++ b/src/hiero_analytics/dashboard_spec/contributors.py @@ -9,6 +9,16 @@ from __future__ import annotations from hiero_analytics.dashboard_spec.glossary import glossary_of +from hiero_analytics.dashboard_spec.interactive import ( + ACTIVITY_HEATMAP_SOURCES, + CONTRIBUTOR_HEATMAP, + CONTRIBUTOR_NETWORK, + OVERVIEW_SOURCES, + REPO_GROWTH_SOURCES, +) + +# One line under the tab's title saying what the tab is for. +SUMMARY = "Explore the people behind Hiero. Follow contribution activity, collaboration, and community growth." CHART_MACRO = { "name": "Contributors", @@ -27,6 +37,7 @@ "(maintainers, committers, triage) live in the Governance tab. Click to enlarge." ), "files": [("Repositories linked by shared contributors", "all_network.png")], + "interactive_sources": {"all_network.png": CONTRIBUTOR_NETWORK}, }, { "id": "activity-heatmap", @@ -34,7 +45,7 @@ "group": "Activity & networks", "slideshow": True, "description": ( - "Weighted monthly activity over the last six months (greener = more active). Slide " + "Weighted monthly activity over the last six months (stronger colour = more active). Slide " "through the same activity zooming steadily out — from each individual contributor, " "to their governance team, to their employer, and finally to the repositories the " "work lands in. For employer concentration and authority risk, see the Governance tab." @@ -45,6 +56,7 @@ ("By organisation", "org_activity_heatmap.png"), ("By repository", "repo_activity_heatmap.png"), ], + "interactive_sources": ACTIVITY_HEATMAP_SOURCES, }, { "id": "repo-growth", @@ -59,11 +71,13 @@ ("New repos per month", "repos_created_per_month.png"), ("Cumulative repo count", "cumulative_repo_count.png"), ], + "interactive_sources": REPO_GROWTH_SOURCES, }, ], "hiero-hackers": [ { "id": "org-overview", + "interactive_sources": OVERVIEW_SOURCES, "title": "Organization overview (org-wide)", "group": "Org overview", "description": ( @@ -85,6 +99,7 @@ "linked when they share contributors. Colour = repository type. Click to enlarge." ), "files": [("Repositories linked by shared contributors", "all_network.png")], + "interactive_sources": {"all_network.png": CONTRIBUTOR_NETWORK}, }, { "id": "activity-heatmap", @@ -95,6 +110,7 @@ "months (greener = more active that month)." ), "files": [("Activity heatmap", "contributor_activity_heatmap.png")], + "interactive_sources": {"contributor_activity_heatmap.png": CONTRIBUTOR_HEATMAP}, }, { "id": "repo-growth", @@ -109,6 +125,7 @@ ("New repos per month", "repos_created_per_month.png"), ("Cumulative repo count", "cumulative_repo_count.png"), ], + "interactive_sources": REPO_GROWTH_SOURCES, }, ], }, @@ -178,21 +195,22 @@ "share contributors. Bubble colour is the repo's category; the link threshold scales with org size.", "contributor_activity_heatmap.png": "Rows are the 25 busiest contributors over the last six months; columns are those months. The " "colour and number in each cell are a weighted activity score (issues ×2, reviews ×3, PRs opened " - "×3, merges ×2) for that month — greener = more active, redder = less. Bots are excluded.", + "×3, merges ×2) for that month — darker blue = more active. Bots are excluded.", "org_activity_heatmap.png": "Rows are organisations, columns are the last six months; each cell is that org's people's " - "weighted activity that month (issues ×2, reviews ×3, PRs opened ×3, merges ×2) — greener = more " - "active. It shows which employers carry the work over time, the activity counterpart to the " + "weighted activity that month (issues ×2, reviews ×3, PRs opened ×3, merges ×2) — darker blue = " + "more active. It shows which employers carry the work over time, the activity counterpart to the " "head-count chart. Bots and contributors not mapped to an organisation are excluded.", "team_activity_heatmap.png": "Rows are governance teams, columns are the last six months; each cell is the weighted activity of " "the team's members that month (same weights as the other heatmaps). A contributor on several teams " "counts toward each, so team totals overlap — this measures each team's activity, not a partition. " "The 25 busiest teams are shown; bots are excluded.", "repo_activity_heatmap.png": "Rows are repositories, columns are the last six months; each cell is the repo's weighted activity " - "that month (issues ×2, reviews ×3, PRs opened ×3, merges ×2) — greener = more active. Aggregated " + "that month (issues ×2, reviews ×3, PRs opened ×3, merges ×2) — darker blue = more active. Aggregated " "straight from the events, so each counts once. The 25 busiest repositories are shown; bots are " "excluded.", - "contributor_counts.png": "The 20 repositories with the most distinct contributors over the last six months; bar height is " - "the number of unique contributors.", + "contributor_counts.png": "Repositories ranked by distinct contributors over the last six months; bar height is the " + "number of unique contributors. The static image shows the top 20; the interactive view and CSV hold every " + "repository.", "language_distribution.png": "How many repositories use each primary language (current snapshot). Repositories with no " "detected language are grouped as 'Unknown'.", "push_activity.png": "The share of repositories that received a push in the last 30 days (active) versus those that " @@ -227,7 +245,7 @@ "contributor_counts.png": [ "Take the org-wide contributor activity records for the Hiero Hackers organisation.", "Count the distinct contributors active in each repository.", - "Keep the top 20 repositories by that count.", + "Rank the repositories by that count; the static image keeps the top 20, the CSV keeps them all.", ], "language_distribution.png": [ "List the organisation's repositories and read GitHub's primary-language field for each.", diff --git a/src/hiero_analytics/dashboard_spec/governance.py b/src/hiero_analytics/dashboard_spec/governance.py index c88437477..b2bbf19c4 100644 --- a/src/hiero_analytics/dashboard_spec/governance.py +++ b/src/hiero_analytics/dashboard_spec/governance.py @@ -11,6 +11,15 @@ from hiero_analytics.dashboard_spec.constants import PROJECT_ISSUES_URL from hiero_analytics.dashboard_spec.glossary import GLOSSARY_NOTE, glossary_of +from hiero_analytics.dashboard_spec.interactive import ( + ROLE_ACTIVITY, + ROLE_BY_REPO, + SINGLE_EMPLOYER_SOURCES, + SPAN_WINDOWS, + affiliation_share, + composition, + role_network, +) # "Suggest a correction" target for the affiliations reference table. That table # earns a contextual link because its data is hand-curated: a correction is either @@ -19,6 +28,9 @@ # "this looks wrong" everywhere else. AFFILIATION_ISSUE_URL = PROJECT_ISSUES_URL +# One line under the tab's title saying what the tab is for. +SUMMARY = "Understand project stewardship, role coverage, and the people guiding the ecosystem." + # Shown when the selected org has no content for this tab (see the manifest's # macro_absent_notes): say *why*, so absence reads as a property of the data, # not a bug. @@ -34,6 +46,22 @@ "hiero-ledger": [ { "id": "maintainer-pipeline", + # Same windows as the PNGs: all years, 12 months, 5 ISO weeks, 7 days. + "interactive_sources": { + f"maintainer_pipeline_{name}.png": { + **ROLE_ACTIVITY, + "file": f"maintainer_pipeline_{name}.csv", + "category": frequency, + "frequency": frequency, + **({"buckets": buckets} if buckets else {}), + } + for name, frequency, buckets in [ + ("yearly", "year", None), + ("monthly", "month", 12), + ("weekly", "week", 5), + ("daily", "day", 7), + ] + }, "title": "Maintainer pipeline over time", "group": "Maintainer pipeline", "description": ( @@ -56,6 +84,14 @@ }, { "id": "maintainer-pipeline-by-repo", + "interactive_sources": { + f"maintainer_pipeline_by_repo{suffix}.png": { + **ROLE_BY_REPO, + "file": f"maintainer_pipeline_by_repo{suffix}.csv", + "window": window, + } + for suffix, window in SPAN_WINDOWS + }, "title": "Maintainer pipeline by repository", "group": "Maintainer pipeline", "description": ( @@ -93,6 +129,14 @@ ("Committers", "committer_network.png"), ("Triage", "triage_network.png"), ], + "interactive_sources": { + f"{key}_network.png": role_network(key, label) + for key, label in [ + ("maintainer", "maintainers"), + ("committer", "committers"), + ("triage", "triage holders"), + ] + }, }, { "id": "org-diversity", @@ -114,6 +158,33 @@ "repository's and each team's mix. The team charts are membership-based and so have no " "role tabs. See the affiliations and repo-diversity tables below for the underlying detail." ), + "interactive_sources": { + **SINGLE_EMPLOYER_SOURCES, + "affiliation_donut.png": affiliation_share("maintainer", "maintainers", ""), + "affiliation_donut_committers.png": affiliation_share("committer", "committers", "_committers"), + **{ + f"repo_affiliation_composition{suffix}.png": composition( + "repo", + "Repository", + f"repo_affiliation_composition{suffix}.csv", + f"{plural.capitalize()} (share of repository)", + f"People holding the {role} role in each repository, by curated employer; " + "a person is counted in every repository where they hold it.", + ) + for suffix, role, plural in [ + ("", "maintainer", "maintainers"), + ("_committers", "committer", "committers"), + ] + }, + "team_affiliation_composition.png": composition( + "team", + "Team", + "team_affiliation_composition.csv", + "Members (share of team)", + "Members of each governance team with at least four resolved members, by curated " + "employer; a person is counted in every team they belong to.", + ), + }, # Deliberately not time-filterable (charts and tables alike): # diversity is a property of the roster, and windowing it mostly # re-measures activity, which the activity views already show. @@ -209,8 +280,8 @@ ("repo", "repo"), ("top_carrier", "top carrier"), ("top_role", "top role"), - ("top_pct", "top %", "number"), - ("top2_pct", "top-2 %", "number"), + ("top_pct", "top %", "percent"), + ("top2_pct", "top-2 %", "percent"), ], }, { @@ -385,7 +456,7 @@ ("maintainers", "maintainers", "number"), ("distinct_orgs", "distinct orgs", "number"), ("top_org", "largest org"), - ("top_org_pct", "largest org %", "number"), + ("top_org_pct", "largest org %", "percent"), ("independent", "independent"), ("unknown", "unknown"), ("organisations", "organisations"), @@ -415,7 +486,7 @@ ("committers", "committers", "number"), ("distinct_orgs", "distinct orgs", "number"), ("top_org", "largest org"), - ("top_org_pct", "largest org %", "number"), + ("top_org_pct", "largest org %", "percent"), ("independent", "independent"), ("unknown", "unknown"), ("organisations", "organisations"), @@ -443,7 +514,7 @@ ("resolved", "resolved"), ("distinct_orgs", "distinct orgs"), ("top_org", "largest org"), - ("top_org_pct", "largest org %"), + ("top_org_pct", "largest org %", "percent"), ("hhi", "HHI"), ("unknown", "unknown"), ("single_employer", "single employer"), diff --git a/src/hiero_analytics/dashboard_spec/hips.py b/src/hiero_analytics/dashboard_spec/hips.py index 476cabd01..7c2b67157 100644 --- a/src/hiero_analytics/dashboard_spec/hips.py +++ b/src/hiero_analytics/dashboard_spec/hips.py @@ -10,6 +10,11 @@ from __future__ import annotations +from hiero_analytics.dashboard_spec.interactive import HIP_STATUS_SOURCE + +# One line under the tab's title saying what the tab is for. +SUMMARY = "Follow improvement proposals from discussion to implementation." + # Shown when the selected org has no content for this tab. ABSENT_NOTE = ( "The HIP process — specs in the hiero-ledger governance repo and the PRs that " @@ -33,9 +38,37 @@ ), "files": [("Funnel", "hip_adoption_funnel.png")], "csv": "hip_adoption_funnel.csv", + "interactive_sources": { + "hip_adoption_funnel.png": { + "kind": "categories", + "file": "hip_adoption_funnel.csv", + "category": "stage", + "category_label": "Stage", + "group": {"key": "cohort", "label": "Cohort", "default": "created since 2024-09"}, + "series": [{"key": "hips", "label": "HIPs", "color": "var(--heat-4)"}], + "details": [{"key": "pct_of_proposed", "label": "Share of proposed", "format": "percent"}], + # Each stage is a subset of the one before, so it draws as a funnel. + "mark": "funnel", + "orientation": "horizontal", + "window": "snapshot", + "metric": "hip_adoption_funnel", + "unit": "HIP specs", + "population": ( + "Each HIP spec in the cohort is counted at every stage it has reached, so each " + "stage is a subset of the one before. Shares are of the cohort's proposed specs. " + "Citation-based stages undercount specs created before September 2024." + ), + "note": ( + "Stages run top to bottom: proposed, approved by the TSC, merged implementation " + "evidence found, and merged citing PRs in five or more repositories. Switch the " + "cohort to compare recent specs with all specs; the two are never combined." + ), + } + }, }, { "id": "hip-activity-by-status", + "interactive_sources": {"hip_activity_by_status.png": HIP_STATUS_SOURCE}, "group": "Adoption", "title": "Implementation evidence — approved & final specs", "description": ( @@ -59,6 +92,30 @@ ), "files": [("Repositories by distinct HIPs", "hip_repo_engagement.png")], "csv": "hip_repo_engagement.csv", + "interactive_sources": { + "hip_repo_engagement.png": { + "kind": "categories", + "file": "hip_repo_engagement.csv", + "category": "repo", + "category_label": "Repository", + "strip_org_prefix": True, + "series": [{"key": "distinct_hips_merged", "label": "Distinct HIPs", "color": "var(--heat-4)"}], + "details": [ + {"key": "matched_prs", "label": "Merged PRs citing a HIP"}, + {"key": "total_prs", "label": "All merged PRs"}, + ], + "orientation": "horizontal", + "rank": True, + "top_n": 15, + "window": "all", + "metric": "hip_repo_engagement", + "unit": "Distinct HIPs", + "population": ( + "Distinct HIP specs cited by merged pull requests in each repository, over all " + "recorded history. Repositories with no citing PRs keep their zero rows." + ), + } + }, }, ], }, diff --git a/src/hiero_analytics/dashboard_spec/interactive.py b/src/hiero_analytics/dashboard_spec/interactive.py new file mode 100644 index 000000000..07b4863f3 --- /dev/null +++ b/src/hiero_analytics/dashboard_spec/interactive.py @@ -0,0 +1,486 @@ +"""Shared presets for chart specs' ``interactive_sources`` (read by ``export/chart_data.py``). + +They keep a series' label and colour identical on every card, and each counting rule written once. +""" + +from __future__ import annotations + +from hiero_analytics.analysis.scorecard_analysis import CHECK_COLUMNS +from hiero_analytics.config.analysis import ACTIVITY_WEIGHTS, HEATMAP_MONTHS, HEATMAP_TOP_ROWS +from hiero_analytics.config.charts import DIFFICULTY_COLORS +from hiero_analytics.domain.labels import DIFFICULTY_ORDER + +# The span tabs every windowed card shares, widest first: (PNG/CSV suffix, window). +SPAN_WINDOWS = [("", "all"), ("_365d", 365), ("_30d", 30), ("_7d", 7)] + +ROLE_SERIES = [ + {"key": "general_user", "label": "General contributors", "color": "var(--chart-general)"}, + {"key": "triage", "label": "Triage", "color": "var(--chart-triage)"}, + {"key": "committer", "label": "Committers", "color": "var(--chart-committer)"}, + {"key": "maintainer", "label": "Maintainers", "color": "var(--chart-maintainer)"}, +] + +# Keyed by the display names the by-repo CSV uses as columns. +DIFFICULTY_SERIES = [{"key": name, "label": name, "color": DIFFICULTY_COLORS[name]} for name in DIFFICULTY_ORDER] + +# The over-time CSVs use short column keys for the same levels. +DIFFICULTY_OVER_TIME_SERIES = [ + {"key": key, "label": name, "color": DIFFICULTY_COLORS[name]} + for key, name in [ + ("unknown", "Unknown"), + ("gfi", "Good First Issue"), + ("beginner", "Beginner"), + ("intermediate", "Intermediate"), + ("advanced", "Advanced"), + ] +] + +ROLE_ACTIVITY = { + "kind": "timeseries", + "series": ROLE_SERIES, + "mark": "bar", + "stacked": True, + "metric": "active_contributors_by_role", + "unit": "Unique active contributors", + "population": ( + "Each person is counted once per bucket under the highest governance role they hold in any " + "repository. Bots are excluded. Counts across periods must not be added to obtain unique people." + ), +} + +ROLE_BY_REPO = { + "kind": "categories", + "category": "repo", + "category_label": "Repository", + "series": ROLE_SERIES, + "stacked": True, + "orientation": "horizontal", + "rank": True, + "top_n": 10, + "metric": "active_contributors_by_role", + "unit": "Unique active contributors", + "population": ( + "Each person is counted once per repository under the role they hold there. Someone active in " + "several repositories is counted in each, so repository counts must not be added to obtain " + "unique people. Bots are excluded." + ), + "note": ( + "Each bar is a repository, counting people active there over the selected span, grouped by the " + "governance role they hold in that repo (general → triage → committer → maintainer). The chart " + "shows the busiest repositories first; the data view lists every repository." + ), +} + + +def affiliation_share(role: str, plural: str, suffix: str) -> dict: + """Ranked employer bars for one role.""" + return { + "kind": "categories", + "file": f"affiliation_distribution{suffix}.csv", + "category": "organisation", + "category_label": "Organisation", + "series": [{"key": plural, "label": plural.capitalize(), "color": "var(--chart-maintainer)"}], + "orientation": "horizontal", + "rank": True, + "top_n": 10, + "window": "snapshot", + "metric": f"{role}_affiliations", + "unit": plural.capitalize(), + "population": ( + f"People whose highest role anywhere is {role}, by curated employer. Solo contributors appear " + "as 'Independent'; people with no curated affiliation are excluded, so shares are of resolved " + "people only." + ), + "note": ( + "Every employer with at least one resolved role-holder, largest first. Check the " + "affiliations-known tile before reading the bars as the whole population." + ), + } + + +def composition(category: str, label: str, file: str, unit: str, population: str) -> dict: + """Each row's employer mix, drawn as 100% bars in the analysis's concentration order.""" + return { + "kind": "categories", + "file": file, + "category": category, + "category_label": label, + "series": "columns", + "palette": "organisation", + "stacked": True, + "normalize": True, + "orientation": "horizontal", + "top_n": 15, + "reference": {"value": 50, "label": "Majority"}, + "window": "snapshot", + "metric": "employer_composition", + "unit": unit, + "population": population, + "note": ( + "Each bar is normalised to 100%, so segments are each employer's share of that row's " + "role-holders; the dashed line marks 50%. Rows are ordered most-concentrated first. The six " + "largest employers get their own colour; smaller ones pool into 'Other orgs'. The data view " + "shows the underlying counts." + ), + } + + +REPO_GROWTH = { + "kind": "timeseries", + "file": "repo_growth_timeline.csv", + "category": "month", + "category_label": "Month (UTC)", + "frequency": "month", + # The CSV stores each month as its first day ("2024-01-01"). + "normalize_dates": True, + "metric": "repositories_created", + "population": ( + "Repositories currently in the organisation, by the month GitHub records them as created. " + "Deleted or transferred-out repositories are not counted." + ), +} + +# New repositories are a flow (bars, empty months are zero); the running total is +# a stock (a line that holds its level through empty months). +REPO_GROWTH_SOURCES = { + "repos_created_per_month.png": { + **REPO_GROWTH, + "series": [{"key": "repos_created", "label": "New repositories", "color": "var(--chart-committer)"}], + "unit": "Repositories created", + }, + "cumulative_repo_count.png": { + **REPO_GROWTH, + "series": [ + {"key": "cumulative_repos", "label": "Repositories", "color": "var(--chart-committer)", "fill": "carry"} + ], + "mark": "line", + "unit": "Total repositories", + }, +} + + +_WEIGHTS = ", ".join(f"{name} ×{weight}" for name, weight in ACTIVITY_WEIGHTS.items()) + + +def activity_heatmap( + file: str, row: str, label: str, *, rule: str, avatars: bool = False, sublabel: dict | None = None +) -> dict: + """A weighted-activity heatmap (one of contributors, teams, organisations, repositories).""" + return { + "kind": "matrix", + "file": file, + "row": row, + "row_label": label, + **({"sublabel": sublabel} if sublabel else {}), + "total": {"key": "activity score", "label": "Six-month score"}, + "columns": "months", + "value_label": "Weighted activity score", + "avatars": avatars, + "top_n": HEATMAP_TOP_ROWS, + "window": "snapshot", + "metric": "weighted_activity", + "unit": "Weighted monthly activity score", + "population": ( + f"A weighted score, not a count: {_WEIGHTS}. Other activity (comments, labels) is not scored. " + f"Columns are the last {HEATMAP_MONTHS} complete calendar months (UTC); the current month is " + f"excluded. Bots are excluded. {rule}" + ), + "note": ( + f"The {HEATMAP_TOP_ROWS} highest six-month scores are shown first; search or show all to see every " + "row. Stronger colour means a higher score; the scale lists each shade's range, and every cell " + "states its exact value." + ), + } + + +def role_network(key: str, label: str) -> dict: + """Repositories linked by people who hold one governance role.""" + return { + "kind": "network", + "file": f"{key}_network_nodes.csv", + "edges_file": f"{key}_network_edges.csv", + "member_label": label, + "window": "snapshot", + "metric": f"{key}_comembership", + "unit": f"Repositories linked by shared {label}", + "population": ( + f"A node is a repository where someone holds the {key} role (their highest role in that " + f"repository); its size is how many of those {label} were active there in the last 90 days. " + f"A link joins two repositories that share at least one {key}, counted whether active or not." + ), + } + + +CONTRIBUTOR_HEATMAP = activity_heatmap( + "contributor_activity_heatmap.csv", + "contributor name", + "Contributor", + avatars=True, + sublabel={"key": "role", "label": "Highest role"}, + rule="Each row is one person.", +) + +ACTIVITY_HEATMAP_SOURCES = { + "contributor_activity_heatmap.png": CONTRIBUTOR_HEATMAP, + "team_activity_heatmap.png": activity_heatmap( + "team_activity_heatmap.csv", + "team", + "Team", + rule="A person on several teams counts toward each, so team rows overlap and must not be added.", + ), + "org_activity_heatmap.png": activity_heatmap( + "org_activity_heatmap.csv", + "organisation", + "Organisation", + rule="Each person counts toward their curated employer; people with no curated affiliation are excluded.", + ), + "repo_activity_heatmap.png": activity_heatmap( + "repo_activity_heatmap.csv", + "repo", + "Repository", + rule="Each event counts once, in the repository where it happened.", + ), +} + +CONTRIBUTOR_NETWORK = { + "kind": "network", + "file": "all_network_nodes.csv", + "edges_file": "all_network_edges.csv", + "member_label": "contributors", + "window": "snapshot", + "metric": "contributor_comembership", + "unit": "Repositories linked by shared contributors", + "population": ( + "A node is a repository with recorded contributors (bots excluded); its size is how many were active " + "there in the last 90 days. A link joins two repositories that share enough contributors: the " + "threshold is one shared contributor per six repositories in the organisation, at least one." + ), +} + +# 548 days mirrors the PNG's "Last 18 months" tab. +RELEASE_TIMELINE_SOURCES = { + f"release_timeline{suffix}.png": { + "kind": "events", + "file": "release_timeline.csv", + "category": "repo", + "category_label": "Repository", + "strip_org_prefix": True, + "time": "published_at", + "label": "tag_name", + "label_label": "Tag", + "flag": { + "key": "is_prerelease", + "false": {"key": "release", "label": "Release", "color": "var(--chart-2)"}, + "true": {"key": "prerelease", "label": "Prerelease", "color": "var(--chart-1)"}, + }, + "window": days, + "metric": "releases", + "unit": "Published releases", + "population": ( + "Each published GitHub release, by repository and publication time (UTC). Drafts are excluded; " + "prereleases are marked. Repositories with no release in the window are not shown." + ), + } + for suffix, days in [("", 548), ("_365d", 365), ("_30d", 30), ("_7d", 7)] +} + +SCORECARD_CHECKS = { + "kind": "matrix", + "file": "org_scorecard_checks.csv", + "row": "repo", + "row_label": "Repository", + "strip_org_prefix": True, + "total": {"key": "score", "label": "Aggregate score"}, + "columns": [{"key": check, "label": check} for check in CHECK_COLUMNS], + "values": "number", + "scale_max": 10, + "missing": {"value": -1, "label": "Not scored"}, + "value_label": "Check score (0–10)", + "window": "snapshot", + "metric": "openssf_scorecard_checks", + "unit": "OpenSSF Scorecard check scores", + "population": ( + "Each repository's per-check OpenSSF Scorecard results (0–10), as published by the Scorecard API. " + "'Not scored' means Scorecard reported the check as inconclusive or did not report it. The " + "aggregate is Scorecard's own " + "weighted score, not the sum or mean of the checks." + ), + "note": ( + "One row per repository, one column per check; stronger colour means a higher check score on a fixed " + "0–10 scale. Scorecard weights the checks into its score, so they do not add up to it." + ), +} + + +def status_meter(file: str, label: str, unit: str, population: str) -> dict: + """A snapshot split into a few statuses, headline status first, as one part-to-whole meter.""" + return { + "kind": "categories", + "file": file, + "category": "status", + "category_label": label, + "series": [{"key": "count", "label": unit}], + "mark": "meter", + "window": "snapshot", + "metric": file.removesuffix(".csv"), + "unit": unit, + "population": population, + } + + +def ranked_counts(file: str, category: str, label: str, key: str, unit: str, population: str) -> dict: + """An explorable ranking backed by the complete analysis table.""" + return { + "kind": "categories", + "file": file, + "category": category, + "category_label": label, + "series": [{"key": key, "label": unit}], + "orientation": "horizontal", + "rank": True, + "top_n": 15, + "window": "snapshot", + "metric": file.removesuffix(".csv"), + "unit": unit, + "population": population, + } + + +OVERVIEW_SOURCES = { + "contributor_counts.png": { + **ranked_counts( + "contributor_counts.csv", + "repo", + "Repository", + "contributors", + "Contributors", + "Distinct contributors recorded in each repository. People active in several repositories count in each.", + ), + "strip_org_prefix": True, + }, + "language_distribution.png": ranked_counts( + "language_distribution.csv", + "language", + "Language", + "count", + "Repositories", + "Repositories grouped by their primary language reported by GitHub; not lines of code.", + ), + "push_activity.png": status_meter( + "push_activity.csv", + "Push activity", + "Repositories", + "Repositories with a push in the 30 days before collection versus other repositories.", + ), +} + +SINGLE_EMPLOYER_SOURCES = { + f"{stem}.png": ranked_counts( + f"{stem}.csv", + "organisation", + "Organisation", + key, + unit, + f"{population} Independent and unresolved affiliations follow the diversity table's counting rules.", + ) + for stem, key, unit, population in [ + ( + "single_employer_teams_by_org", + "teams", + "Teams", + "Governance teams whose resolved members share one employer.", + ), + ( + "single_employer_repos_by_org", + "repos", + "Repositories", + "Repositories whose resolved maintainers share one employer.", + ), + ( + "single_employer_repos_by_org_committers", + "repos", + "Repositories", + "Repositories whose resolved committers share one employer.", + ), + ] +} + +OWNERSHIP_SOURCES = { + "org_codeowner_summary.png": status_meter( + "org_codeowner_summary.csv", + "CODEOWNERS file", + "Repositories", + "Repositories with or without a CODEOWNERS file in the standard GitHub locations.", + ), + "org_runner_chart.png": { + **ranked_counts( + "org_runner_summary.csv", + "repo", + "Repository", + "Standard", + "Workflow jobs", + "Workflow job runner declarations, grouped by repository. Indeterminate declarations cannot be classified.", + ), + "series": [{"key": key, "label": key} for key in ["Self-Hosted", "Standard", "Indeterminate"]], + "stacked": True, + "strip_org_prefix": True, + }, +} + +HIP_STATUS_SOURCE = { + **ranked_counts( + "hip_activity_by_status.csv", + "bucket", + "HIP status", + "merged", + "HIPs", + "Each HIP counts once under its strongest PR evidence. No citing PR for a Final/Active HIP is a citation gap, not proof it is unimplemented.", + ), + "rank": False, + "stacked": True, + "series": [ + {"key": "merged", "label": "With merged implementation PRs"}, + {"key": "open_only", "label": "With open PRs only"}, + {"key": "none_awaiting", "label": "Awaiting implementation evidence"}, + {"key": "citation_gap", "label": "Citation gap (Final/Active)"}, + ], +} + +DISCORD_SOURCES = { + "hiero_discord_channel_categories.png": { + **ranked_counts( + "hiero_discord_channel_categories.csv", + "category", + "Topic", + "earlier", + "Messages", + "Messages from a manual Discord snapshot, split into its last 90 days and earlier history.", + ), + "series": [{"key": "last_90d", "label": "Last 90 days"}, {"key": "earlier", "label": "Earlier history"}], + "stacked": True, + }, + "hiero_discord_recent_activity_30d.png": { + **ranked_counts( + "hiero_discord_recent_activity_30d.csv", + "channel_label", + "Channel", + "d30", + "Messages", + "Messages in the 30 days before the manual Discord snapshot. Show all to inspect every channel.", + ), + "top_n": 5, + }, + "hiero_discord_monthly_traffic.png": { + "kind": "timeseries", + "file": "hiero_discord_monthly_traffic.csv", + "category": "month", + "category_label": "Month (UTC)", + "frequency": "month", + "normalize_dates": True, + "series": [{"key": "messages", "label": "Messages"}], + "mark": "area", + "metric": "discord_messages", + "unit": "Messages", + "population": "Monthly messages from a manual Discord snapshot.", + }, +} diff --git a/src/hiero_analytics/dashboard_spec/onboarding.py b/src/hiero_analytics/dashboard_spec/onboarding.py index d2e1a229b..00788180b 100644 --- a/src/hiero_analytics/dashboard_spec/onboarding.py +++ b/src/hiero_analytics/dashboard_spec/onboarding.py @@ -5,6 +5,15 @@ from __future__ import annotations +from hiero_analytics.dashboard_spec.interactive import ( + DIFFICULTY_OVER_TIME_SERIES, + DIFFICULTY_SERIES, + SPAN_WINDOWS, +) + +# One line under the tab's title saying what the tab is for. +SUMMARY = "Track the path from first issue to meaningful contribution." + # Shown when the selected org has no content for this tab. ABSENT_NOTE = ( "Nothing generated for this org yet: the issue-difficulty and onboarding " @@ -19,6 +28,52 @@ "*": [ { "id": "issue-difficulty", + "interactive_sources": { + **{ + f"difficulty_by_repo{suffix}.png": { + "kind": "categories", + "file": f"difficulty_by_repo{suffix}.csv", + "category": "repo", + "category_label": "Repository", + "series": DIFFICULTY_SERIES, + "stacked": True, + "orientation": "horizontal", + "rank": True, + "top_n": 12, + "window": window, + "metric": "open_issues_by_difficulty", + "unit": "Open issues", + "population": ( + "Open issues in each repository, by difficulty label. 'Unknown' is an " + "open issue with no difficulty label yet. Each issue is counted once." + ), + } + for suffix, window in SPAN_WINDOWS + }, + **{ + f"{stem}.png": { + "kind": "timeseries", + "file": f"{stem}.csv", + "category": "date", + "category_label": "Date (UTC)", + "frequency": "snapshot", + "series": series, + "mark": "area", + "stacked": True, + "metric": "open_issues_by_difficulty", + "unit": "Open issues", + "population": ( + "Open issues on each date, by the difficulty label they carried then, " + "reconstructed from label events. Each point is a count at that moment, " + "not activity during the week." + ), + } + for stem, series in [ + ("difficulty_over_time_all_event_based_weekly", DIFFICULTY_OVER_TIME_SERIES), + ("difficulty_over_time_event_based_weekly", DIFFICULTY_OVER_TIME_SERIES[1:]), + ] + }, + }, "group": "Issue difficulty", "title": "Issue difficulty", "description": ( diff --git a/src/hiero_analytics/dashboard_spec/releases.py b/src/hiero_analytics/dashboard_spec/releases.py index b92e42b95..0d698bb9f 100644 --- a/src/hiero_analytics/dashboard_spec/releases.py +++ b/src/hiero_analytics/dashboard_spec/releases.py @@ -9,6 +9,11 @@ from __future__ import annotations +from hiero_analytics.dashboard_spec.interactive import RELEASE_TIMELINE_SOURCES + +# One line under the tab's title saying what the tab is for. +SUMMARY = "Follow release activity and delivery across the ecosystem." + # Shown when the selected org has no content for this tab. ABSENT_NOTE = "No releases pipeline data for this org yet." @@ -41,6 +46,7 @@ ], ), ], + "interactive_sources": RELEASE_TIMELINE_SOURCES, }, ], }, diff --git a/src/hiero_analytics/dashboard_spec/security.py b/src/hiero_analytics/dashboard_spec/security.py index 2ef25315b..2ad39df88 100644 --- a/src/hiero_analytics/dashboard_spec/security.py +++ b/src/hiero_analytics/dashboard_spec/security.py @@ -5,6 +5,11 @@ from __future__ import annotations +from hiero_analytics.dashboard_spec.interactive import OWNERSHIP_SOURCES, SCORECARD_CHECKS + +# One line under the tab's title saying what the tab is for. +SUMMARY = "Explore repository health, security practices, and scorecard results." + # Shown when the selected org has no content for this tab. ABSENT_NOTE = ( "Nothing generated for this org yet: the scorecard and repo-compliance " @@ -26,9 +31,37 @@ ("Org scorecard", "org_scorecard.png"), ("Score breakdown", "org_scorecard_breakdown.png"), ], + # The breakdown is a checks matrix, not stacked bars: stacking + # per-check scores implies they add up to the aggregate, which + # Scorecard weights instead. + "interactive_sources": { + "org_scorecard_breakdown.png": SCORECARD_CHECKS, + "org_scorecard.png": { + "kind": "categories", + "file": "org_scorecard.csv", + "category": "repo", + "category_label": "Repository", + "strip_org_prefix": True, + "series": [{"key": "score", "label": "Score", "color": "var(--chart-committer)"}], + "values": "number", + "orientation": "horizontal", + "rank": True, + "top_n": 15, + "reference": {"stat": "median", "label": "Org median"}, + "value_max": 10, + "window": "snapshot", + "metric": "openssf_scorecard", + "unit": "OpenSSF Scorecard score (0–10)", + "population": ( + "Each repository's aggregate OpenSSF Scorecard score, as published by the " + "Scorecard API. Repositories without a published scorecard are absent." + ), + }, + }, }, { "id": "ownership", + "interactive_sources": OWNERSHIP_SOURCES, "group": "Ownership", "title": "Code owners & CI runners", "description": ( diff --git a/src/hiero_analytics/data_sources/scorecard.py b/src/hiero_analytics/data_sources/scorecard.py index 58ebb52ea..3bb5b5cd4 100644 --- a/src/hiero_analytics/data_sources/scorecard.py +++ b/src/hiero_analytics/data_sources/scorecard.py @@ -15,15 +15,27 @@ logger = logging.getLogger(__name__) -SCORECARD_API = os.getenv("SCORECARD_API", "https://api.scorecard.dev/projects/github.com/hiero-ledger") +# Override for a mirror or test server. The value must carry an ``{org}`` +# placeholder: an older org-less value (``…/github.com/hiero-ledger``) would +# query that one org's scorecards for every org, so it is rejected, not reused. +SCORECARD_API = os.getenv("SCORECARD_API", "https://api.scorecard.dev/projects/github.com/{org}") -def fetch_repo_scorecard(repo: str) -> ScorecardRecord | None: +def scorecard_url(repo: str, org: str, template: str | None = None) -> str: + """The Scorecard API URL for ``org/repo``; raises when the template has no ``{org}``.""" + template = SCORECARD_API if template is None else template + if "{org}" not in template: + raise ValueError(f"SCORECARD_API must contain an {{org}} placeholder, got {template!r}") + return f"{template.replace('{org}', org)}/{repo}" + + +def fetch_repo_scorecard(repo: str, *, org: str = "hiero-ledger") -> ScorecardRecord | None: """ Fetch latest OpenSSF Scorecard for a repository. Args: repo: Repository in format `eg: hiero-python-sdk` + org: GitHub organisation that owns the repository. Returns: ScorecardRecord, or None when the repository has no scorecard (404). @@ -32,7 +44,7 @@ def fetch_repo_scorecard(repo: str) -> ScorecardRecord | None: requests.RequestException: On network failures or non-404 HTTP errors, so a transient outage is never silently recorded as a missing scorecard. """ - url = f"{SCORECARD_API}/{repo}" + url = scorecard_url(repo, org) try: response = requests.get(url, timeout=HTTP_TIMEOUT_SECONDS) diff --git a/src/hiero_analytics/export/chart_data.py b/src/hiero_analytics/export/chart_data.py new file mode 100644 index 000000000..3f040bbf2 --- /dev/null +++ b/src/hiero_analytics/export/chart_data.py @@ -0,0 +1,487 @@ +"""Validated numeric datasets for interactive charts. + +Reads the CSV a dashboard spec's ``interactive_sources`` entry names, checks it, +and emits the JSON document the web app renders. The analysis's values pass +through unchanged, never re-aggregated; the only statistic derived here is an +optional median reference line (``reference: {"stat": "median"}``). Calendar +gaps are filled with zero rows and categories are ordered for display. Kinds: +timeseries, categories, matrix, network, events. +""" + +from __future__ import annotations + +import math +import re +from datetime import UTC, datetime +from pathlib import Path + +import pandas as pd + +from hiero_analytics.analysis.maintainer_pipeline import last_calendar_buckets +from hiero_analytics.config.charts import REPO_CATEGORY_COLORS +from hiero_analytics.domain.repo_categories import CATEGORY_ORDER + +FORMATS = {"year": "%Y", "month": "%Y-%m", "week": "%G-W%V-%u", "day": "%Y-%m-%d", "snapshot": "%Y-%m-%d"} +MARKS = {"bar", "line", "area"} +# Only valid over categories: a meter splits a snapshot, a funnel draws nested stages. +CATEGORY_MARKS = {"meter", "funnel"} +DETAIL_FORMATS = {"number", "decimal", "percent"} + +# Cycled for series with no declared colour; mirrors the web `--chart-1`..`--chart-5` tokens. +FALLBACK_COLORS = [f"var(--chart-{index})" for index in range(1, 6)] + + +def bucket_start(value: str, frequency: str) -> datetime: + """Parse a canonical UTC calendar bucket, including ISO week years.""" + text = f"{value}-1" if frequency == "week" else value + parsed = datetime.strptime(text, FORMATS[frequency]).replace(tzinfo=UTC) + if parsed.strftime(FORMATS[frequency]) != text: + raise ValueError(f"Invalid {frequency} bucket: {value}") + return parsed + + +def _parse_time(value: str | None) -> datetime | None: + """An ISO timestamp as aware UTC, or None when absent or unparseable.""" + if not value: + return None + try: + parsed = datetime.fromisoformat(value) + except ValueError: + return None + return parsed.replace(tzinfo=UTC) if parsed.tzinfo is None else parsed.astimezone(UTC) + + +def _numbers(frame: pd.DataFrame, column: str, values: str, name: str) -> pd.Series: + """One column as finite numbers; ``count`` also demands safe nonnegative integers.""" + series = pd.to_numeric(frame[column], errors="raise") + if series.isna().any() or not series.map(math.isfinite).all(): + raise ValueError(f"{name}: {column} must contain finite numbers") + if values == "count": + if ((series < 0) | (series > 2**53 - 1) | (series % 1 != 0)).any(): + raise ValueError(f"{name}: {column} must contain safe nonnegative integer counts") + return series.astype("int64") + return series.astype("float64") + + +def _integral(values: pd.Series) -> pd.Series: + """Whole-number values as integers (4957, not 4957.0); anything else unchanged.""" + return values.astype("int64") if (values % 1 == 0).all() else values + + +def _series(source: dict, frame: pd.DataFrame, category: str, group: str | None) -> list[dict]: + """The declared series, or every remaining CSV column for ``series: "columns"``.""" + declared = source["series"] + if declared == "columns": + excluded = {category, group, *(detail["key"] for detail in source.get("details", []))} + declared = [{"key": column, "label": column} for column in frame.columns if column not in excluded] + palette = source.get("palette") or {} + if palette == "organisation": + # Deferred: the affiliation pipeline pulls in plotting, which the rest of the export never needs. + from hiero_analytics.pipelines.affiliation import segment_colors + + palette = segment_colors([entry["key"] for entry in declared]) + series = [] + for index, entry in enumerate(declared): + color = entry.get("color") or palette.get(entry["key"]) or FALLBACK_COLORS[index % len(FALLBACK_COLORS)] + series.append({"key": entry["key"], "label": entry["label"], "color": color}) + if not series: + raise ValueError("a chart needs at least one series") + return series + + +def _read(source: dict, csv_path: Path, org: str) -> tuple[pd.DataFrame, list[dict], list[dict]]: + """Read and validate the CSV columns a source declares.""" + category = source["category"] + group = source.get("group", {}).get("key") + frame = pd.read_csv(csv_path, dtype={category: str, **({group: str} if group else {})}) + series = _series(source, frame, category, group) + details = [{"format": "number", **detail} for detail in source.get("details", [])] + required = [category, *([group] if group else []), *(s["key"] for s in series), *(d["key"] for d in details)] + missing = set(required) - set(frame.columns) + if missing: + raise ValueError(f"{csv_path.name}: missing columns {sorted(missing)}") + frame = frame[required].copy() + keys = [group, category] if group else [category] + if frame[keys].isna().any().any() or frame.duplicated(keys).any(): + raise ValueError(f"{csv_path.name}: missing or duplicate {category} values") + if source.get("strip_org_prefix"): + frame[category] = frame[category].str.removeprefix(f"{org}/") + for entry in series: + frame[entry["key"]] = _numbers(frame, entry["key"], source.get("values", "count"), csv_path.name) + for detail in details: + if detail["format"] not in DETAIL_FORMATS: + raise ValueError(f"{csv_path.name}: unknown detail format {detail['format']}") + frame[detail["key"]] = _integral(_numbers(frame, detail["key"], "number", csv_path.name)) + return frame, series, details + + +def _calendar(frame: pd.DataFrame, source: dict, series: list[dict], generated_at: str | None) -> pd.DataFrame: + """Complete a calendar series up to its generation bucket and window it. + + Anchored to the later of its latest bucket and generation time, never to + today: a missing sidecar is no evidence that a historic dataset is current. + """ + category, frequency = source["category"], source["frequency"] + anchor = bucket_start(frame.iloc[-1][category], frequency) + if (generated := _parse_time(generated_at)) is not None: + anchor = max(anchor, generated) + if buckets := source.get("buckets"): + labels = last_calendar_buckets(anchor, buckets, frequency) + else: + first = bucket_start(frame.iloc[0][category], frequency) + labels = list(dict.fromkeys(_bucket_labels(first, anchor, frequency))) + filled = frame.set_index(category).reindex(labels) + declared = source["series"] if isinstance(source["series"], list) else [] + carried = {entry["key"] for entry in declared if entry.get("fill") == "carry"} + for entry in series: + if entry["key"] in carried: + # A stock (a running total) holds its level through empty buckets. + earlier = frame[frame[category] < labels[0]][entry["key"]] + start = earlier.iloc[-1] if len(earlier) else 0 + filled[entry["key"]] = filled[entry["key"]].ffill().fillna(start) + else: + filled[entry["key"]] = filled[entry["key"]].fillna(0) + filled = filled.reset_index(names=category).fillna(0) + # Reindexing turns integer columns float (NaN in the new rows), so restore + # every one — series and detail alike — or a count serialises as 12.0. + for column in frame.columns: + if pd.api.types.is_integer_dtype(frame[column]): + filled[column] = filled[column].astype(frame[column].dtype) + return filled + + +def _bucket_labels(first: datetime, last: datetime, frequency: str) -> list[str]: + """Every calendar bucket label from ``first`` through ``last``'s bucket.""" + if frequency == "year": + return [str(year) for year in range(first.year, last.year + 1)] + pandas_freq = {"month": "MS", "week": "W-MON", "day": "D"}[frequency] + stamps = pd.date_range(first, last, freq=pandas_freq, tz=UTC) + fmt = FORMATS[frequency].removesuffix("-%u") + return [stamp.strftime(fmt) for stamp in stamps] or [first.strftime(fmt)] + + +def _timeseries(source: dict, csv_path: Path, org: str, generated_at: str | None) -> dict: + """Rows in time order, with calendar gaps completed and partial buckets flagged.""" + frame, series, details = _read(source, csv_path, org) + category, frequency = source["category"], source["frequency"] + if source.get("normalize_dates"): + frame[category] = pd.to_datetime(frame[category], utc=True).dt.strftime(FORMATS[frequency].removesuffix("-%u")) + for value in frame[category]: + bucket_start(value, frequency) + frame = frame.sort_values(category) + calendar = frequency != "snapshot" and not frame.empty + if calendar: + frame = _calendar(frame, source, series, generated_at) + rows = [] + last = frame.iloc[-1][category] if len(frame) else None + for record in frame.to_dict(orient="records"): + bucket = record.pop(category) + rows.append({"bucket": bucket, **record, "partial": calendar and bucket == last}) + return { + "frequency": frequency, + "timezone": "UTC", + "comparison": _comparison(rows, frequency) if source.get("compare", True) else None, + "category": {"key": "bucket", "label": source.get("category_label", "Period (UTC)")}, + "window": { + "kind": "calendar", + "first": rows[0]["bucket"] if rows else None, + "last": rows[-1]["bucket"] if rows else None, + }, + "series": series, + "details": details, + "rows": rows, + } + + +def _categories(source: dict, csv_path: Path, org: str, generated_at: str | None) -> dict: + """Category rows in the source's order (the analysis's ranking or sequence).""" + frame, series, details = _read(source, csv_path, org) + category = source["category"] + group = source.get("group") + if group: + values = list(dict.fromkeys(frame[group["key"]])) + if group["default"] not in values: + raise ValueError(f"{csv_path.name}: default {group['key']} {group['default']!r} is absent") + group = {**group, "values": values} + return { + "category": {"key": category, "label": source["category_label"]}, + "window": _window(source, generated_at), + "group": group, + "series": series, + "details": details, + "rows": frame.to_dict(orient="records"), + } + + +def _reference(reference: dict | None, body: dict) -> dict | None: + """A fixed line (``value``), or the published rows' median of the one series (``stat``).""" + if reference is None or "value" in reference: + return reference + if reference.get("stat") != "median" or len(body["series"]) != 1 or body.get("group"): + raise ValueError(f"Unsupported chart reference: {reference}") + values = [row[body["series"][0]["key"]] for row in body["rows"]] + if not values: + return None + median = round(float(pd.Series(values).median()), 2) + return {"value": median, "label": f"{reference.get('label', 'Median')} {median:g}"} + + +def _window(source: dict, generated_at: str | None) -> dict: + """``snapshot``: the state at ``end``; ``all``: every recorded event; an integer: trailing days to ``end``.""" + window = source.get("window", "all") + return { + "kind": "trailing" if isinstance(window, int) else window, + "days": window if isinstance(window, int) else None, + "end": generated_at, + } + + +MONTH_COLUMN = re.compile(r"^\d{4}-\d{2}$") + + +def _matrix(source: dict, csv_path: Path, org: str, generated_at: str | None) -> dict: + """A wide table as heatmap rows, in the source's order (the analysis's ranking). + + ``columns: "months"`` takes every ``YYYY-MM`` column, oldest first. A + ``missing`` sentinel (Scorecard's -1 "inconclusive") becomes null rather + than a value, so it can never be coloured as a score. + """ + row_key = source["row"] + frame = pd.read_csv(csv_path, dtype={row_key: str}) + if source["columns"] == "months": + columns = [{"key": c, "label": c} for c in sorted(c for c in frame.columns if MONTH_COLUMN.match(c))] + else: + columns = source["columns"] + extras = [source[key] for key in ("sublabel", "total") if source.get(key)] + required = [row_key, *(extra["key"] for extra in extras), *(column["key"] for column in columns)] + missing = set(required) - set(frame.columns) + if missing: + raise ValueError(f"{csv_path.name}: missing columns {sorted(missing)}") + if not columns: + raise ValueError(f"{csv_path.name}: no matrix columns") + frame = frame[required].copy() + if frame[row_key].isna().any() or frame[row_key].duplicated().any(): + raise ValueError(f"{csv_path.name}: missing or duplicate {row_key} values") + if source.get("strip_org_prefix"): + frame[row_key] = frame[row_key].str.removeprefix(f"{org}/") + values = source.get("values", "count") + sentinel = source.get("missing", {}).get("value") + for column in columns: + cells = frame[column["key"]] + # A blank cell is "not reported"; the sentinel is the source's own "could not score". + absent = cells.isna() | (cells == sentinel if sentinel is not None else False) + checked = _numbers(frame[~absent], column["key"], values, csv_path.name) + # Object before reindexing: an int64 column reindexed with gaps turns float first. + frame[column["key"]] = checked.astype(object).reindex(frame.index).where(~absent, None) + if total := source.get("total"): + frame[total["key"]] = _integral(_numbers(frame, total["key"], "number", csv_path.name)) + if sublabel := source.get("sublabel"): + frame[sublabel["key"]] = frame[sublabel["key"]].fillna("").astype(str) + observed = [v for column in columns for v in frame[column["key"]] if v is not None] + # Linear from zero like the PNG's colour bar; a declared maximum (Scorecard's + # 10) keeps the scale meaningful when no repository reaches it. + maximum = source.get("scale_max") or max([*observed, 1]) + return { + "row": {"key": row_key, "label": source["row_label"]}, + "sublabel": source.get("sublabel"), + "total": source.get("total"), + "columns": columns, + "value_label": source["value_label"], + "scale": {"min": 0, "max": maximum, "steps": 5}, + "missing": source.get("missing", {}).get("label"), + "avatars": bool(source.get("avatars", False)), + "top_n": source.get("top_n"), + "value_format": "integer" if values == "count" else "decimal", + "window": _window(source, generated_at), + "rows": frame.to_dict(orient="records"), + } + + +def _network(source: dict, csv_path: Path, _org: str, generated_at: str | None) -> dict: + """Nodes (with the PNG's layout) and the links between them, from two CSVs.""" + nodes = pd.read_csv(csv_path, dtype={"repo": str, "category": str}) + edges_path = csv_path.with_name(source["edges_file"]) + edges = pd.read_csv(edges_path, dtype={"repo_a": str, "repo_b": str}) + for frame, required, name in [ + (nodes, ["repo", "active_members", "total_members", "category", "x", "y"], csv_path.name), + (edges, ["repo_a", "repo_b", "shared"], edges_path.name), + ]: + missing = set(required) - set(frame.columns) + if missing: + raise ValueError(f"{name}: missing columns {sorted(missing)}") + if nodes["repo"].isna().any() or nodes["repo"].duplicated().any(): + raise ValueError(f"{csv_path.name}: missing or duplicate repositories") + for column in ["active_members", "total_members"]: + nodes[column] = _numbers(nodes, column, "count", csv_path.name) + for column in ["x", "y"]: + nodes[column] = _numbers(nodes, column, "number", csv_path.name) + edges["shared"] = _numbers(edges, "shared", "count", edges_path.name) + known = set(nodes["repo"]) + if not (edges["repo_a"].isin(known) & edges["repo_b"].isin(known)).all(): + raise ValueError(f"{edges_path.name}: links a repository that is not a node") + if (edges["repo_a"] == edges["repo_b"]).any() or edges[["repo_a", "repo_b"]].apply( + frozenset, axis=1 + ).duplicated().any(): + raise ValueError(f"{edges_path.name}: self or duplicate links") + present = set(nodes["category"]) + categories = [ + {"key": name, "label": name, "color": REPO_CATEGORY_COLORS[name]} + for name in [*CATEGORY_ORDER, *sorted(present - set(CATEGORY_ORDER))] + if name in present and name in REPO_CATEGORY_COLORS + ] + if unknown := present - {c["key"] for c in categories}: + raise ValueError(f"{csv_path.name}: unknown repository categories {sorted(unknown)}") + member = source["member_label"] + return { + "member_label": member, + "categories": categories, + "window": _window(source, generated_at), + "nodes": [ + { + "id": row.repo, + "active": int(row.active_members), + "total": int(row.total_members), + "category": row.category, + "x": float(row.x), + "y": float(row.y), + } + for row in nodes.itertuples() + ], + "edges": [ + {"source": row.repo_a, "target": row.repo_b, "shared": int(row.shared)} for row in edges.itertuples() + ], + } + + +def _events(source: dict, csv_path: Path, org: str, generated_at: str | None) -> dict: + """Timestamped events inside the source's trailing window, one row each. + + The window ends at the source's generation time (the pipeline's "now"), + or at the latest event when no sidecar says when that was. + """ + category, time, label, flag = source["category"], source["time"], source["label"], source["flag"] + frame = pd.read_csv(csv_path, dtype={category: str, label: str}) + missing = {category, time, label, flag["key"]} - set(frame.columns) + if missing: + raise ValueError(f"{csv_path.name}: missing columns {sorted(missing)}") + stamps = pd.to_datetime(frame[time], utc=True, errors="coerce") + if stamps.isna().any() or frame[category].isna().any(): + raise ValueError(f"{csv_path.name}: every event needs a {category} and a valid {time}") + flags = frame[flag["key"]].map( + {True: True, False: False, "True": True, "False": False, "true": True, "false": False} + ) + if flags.isna().any(): + raise ValueError(f"{csv_path.name}: {flag['key']} must be true or false") + if source.get("strip_org_prefix"): + frame[category] = frame[category].str.removeprefix(f"{org}/") + end = _parse_time(generated_at) or (stamps.max().to_pydatetime() if len(stamps) else None) + days = source["window"] + keep = ( + (stamps > pd.Timestamp(end) - pd.Timedelta(days=days)) & (stamps <= pd.Timestamp(end)) + if end + else stamps.notna() + ) + events = pd.DataFrame( + { + category: frame[category], + "time": stamps.dt.strftime("%Y-%m-%dT%H:%M:%SZ"), + label: frame[label].fillna(""), + "type": flags.map({True: flag["true"]["key"], False: flag["false"]["key"]}), + } + )[keep].sort_values(["time", category]) + counts = events[category].value_counts() + return { + "category": {"key": category, "label": source["category_label"]}, + "label": {"key": label, "label": source["label_label"]}, + "types": [flag["false"], flag["true"]], + "categories": sorted(counts.index, key=lambda name: (-counts[name], name)), + "window": {"kind": "trailing", "days": days, "end": end.isoformat() if end else None}, + "rows": events.to_dict(orient="records"), + } + + +# Dimension names as the dashboard's focus knows them: one name per concept, +# whatever the source CSV calls its column. +CANONICAL_DIMENSIONS = { + "contributor name": "contributor", + "login": "contributor", + "user": "contributor", + "repository": "repo", +} + + +def _dimension(key: str) -> str: + return CANONICAL_DIMENSIONS.get(key, key) + + +def _comparison(rows: list[dict], frequency: str) -> dict | None: + """The last complete bucket against the one before it; the partial current bucket never compares.""" + if frequency == "snapshot": + return None + complete = [row["bucket"] for row in rows if not row["partial"]] + if len(complete) < 2: + return None + return {"current": complete[-1], "previous": complete[-2]} + + +SERIES_KINDS = {"timeseries": _timeseries, "categories": _categories} +OTHER_KINDS = {"matrix": _matrix, "network": _network, "events": _events} + + +def chart_document(source: dict, csv_path: Path, org: str, generated_at: str | None = None) -> dict: + """Build the interactive document a dashboard spec source declares.""" + kind = source.get("kind") + if kind not in SERIES_KINDS and kind not in OTHER_KINDS: + raise ValueError(f"Unknown interactive chart kind: {kind}") + document = { + "schema_version": 1, + "id": csv_path.stem, + "kind": kind, + "org": org, + "source": csv_path.name, + "metric": source["metric"], + "unit": source["unit"], + "population": source["population"], + } + if kind in OTHER_KINDS: + body = OTHER_KINDS[kind](source, csv_path, org, generated_at) + # What the rows can be sliced by; the UI must not offer anything else. + document["dimensions"] = { + "matrix": [_dimension(body.get("row", {}).get("key")), "column"], + "network": ["repo", "link"], + "events": [_dimension(body.get("category", {}).get("key")), "type"], + }[kind] + document.update(body) + if note := source.get("note"): + document["note"] = note + return document + mark = source.get("mark", "bar") + if mark not in MARKS and not (kind == "categories" and mark in CATEGORY_MARKS): + raise ValueError(f"Unknown chart mark for {kind}: {mark}") + body = SERIES_KINDS[kind](source, csv_path, org, generated_at) + if mark in CATEGORY_MARKS and len(body["series"]) != 1: + raise ValueError(f"{csv_path.name}: a {mark} draws exactly one series") + document = { + **document, + "dimensions": [ + "period" if kind == "timeseries" else _dimension(body["category"]["key"]), + *([body["group"]["key"]] if body.get("group") else []), + *(["series"] if len(body["series"]) > 1 else []), + ], + "mark": mark, + "stacked": bool(source.get("stacked", False)), + "normalize": bool(source.get("normalize", False)), + "orientation": source.get("orientation", "vertical"), + "value_format": "integer" if source.get("values", "count") == "count" else "decimal", + # Re-rank by the series on show; otherwise keep the source's order. + "rank": bool(source.get("rank", False)), + "top_n": source.get("top_n"), + "reference": _reference(source.get("reference"), body), + # A bounded scale's ceiling (a 0–10 score), so the axis never implies more. + "value_max": source.get("value_max"), + "group": None, + **body, + } + if note := source.get("note"): + document["note"] = note + return document diff --git a/src/hiero_analytics/export/data_api.py b/src/hiero_analytics/export/data_api.py index 44f815ff7..29c657a24 100644 --- a/src/hiero_analytics/export/data_api.py +++ b/src/hiero_analytics/export/data_api.py @@ -56,6 +56,7 @@ MACRO_GLOSSARIES, MACRO_GROUP_ORDER, MACRO_PARENTS, + MACRO_SUMMARIES, METRIC_ANNOTATIONS, PROJECT_ISSUES_URL, TABLE_FAMILIES, @@ -63,6 +64,7 @@ table_variants, ) from hiero_analytics.domain.periods import ACTIVITY_PERIODS +from hiero_analytics.export.chart_data import chart_document from hiero_analytics.export.csv_safety import sanitize_csv_text from hiero_analytics.export.macro_metrics import macro_metrics from hiero_analytics.provenance import resolve_provenance @@ -296,7 +298,9 @@ def _org_chart_sections(org: str, org_data_dir: Path, org_dir: Path) -> list[dic a title and description; each chart inside carries its variant tabs (e.g. All / Active 90d), its "how to read this" note, its step-by-step methodology, and the wide flag (rendered as a horizontal scroll). Only - variants whose PNG was actually produced are listed. + variants whose PNG was actually produced are listed; one whose dataset + CSV also exists gains an ``interactive`` reference (an additive field, so + a consumer that only knows ``file`` still renders every listed variant). """ chart_dir = paths.ORG_CHARTS_DIR / org sections = [] @@ -307,11 +311,29 @@ def _org_chart_sections(org: str, org_data_dir: Path, org_dir: Path) -> list[dic for spec in macro["charts"].get(org) or macro["charts"].get("*", []): charts = [] for caption, variant_specs in spec["files"]: - variants = [ - _chart_variant(org, chart_dir, label, filename) - for label, filename in variant_specs - if (chart_dir / filename).exists() - ] + variants = [] + for label, filename in variant_specs: + # v1 is additive-only: ``file`` has always named a PNG that + # exists, so a variant without one is not listed at all. + if not (chart_dir / filename).exists(): + continue + variant = _chart_variant(org, chart_dir, label, filename) + source = spec.get("interactive_sources", {}).get(filename) + if source and (csv_path := org_data_dir / source["file"]).exists(): + try: + document = chart_document(source, csv_path, org, _read_meta(csv_path).get("generated_at")) + except (ValueError, TypeError) as exc: + raise DataApiContractError(f"Invalid chart dataset {org}/{source['file']}: {exc}") from exc + _stamp_freshness(document, csv_path) + # A source's own note describes the interactive view and wins over the PNG's. + document = {**variant_annotations(filename), **document} + # Keyed by the PNG stem, not the CSV: two charts can share one CSV. + document["id"] = Path(filename).stem + target = org_dir / "charts" / f"{document['id']}.json" + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(json.dumps(document, indent=1, allow_nan=False), encoding="utf-8") + variant["interactive"] = {"kind": document["kind"], "path": f"{org}/charts/{target.name}"} + variants.append(variant) if not variants: continue filenames = [filename for _label, filename in variant_specs] @@ -477,6 +499,8 @@ def emit_data_api() -> Path: "macro_parents": MACRO_PARENTS, # Why a tab may be empty for an org — shown in place of a blank tab. "macro_absent_notes": MACRO_ABSENT_NOTES, + # Each tab's one-line purpose, shown under its title. + "macro_summaries": MACRO_SUMMARIES, # Macro name -> ordered section-group names; the frontend renders each # tab as this sequence of named sections (views + charts + tables). "group_order": MACRO_GROUP_ORDER, diff --git a/src/hiero_analytics/pipelines/affiliation.py b/src/hiero_analytics/pipelines/affiliation.py index d4b6dea9b..62049352e 100644 --- a/src/hiero_analytics/pipelines/affiliation.py +++ b/src/hiero_analytics/pipelines/affiliation.py @@ -129,6 +129,12 @@ def _chart_colors(segments: list[str], organisation_colors: dict[str, str] | Non return colors +def segment_colors(segments: list[str]) -> dict[str, str]: + """The colours the composition PNGs give ``segments``, for the interactive charts.""" + colors = _chart_colors(segments, _composition_colors(list(load_affiliations().values()))) + return {segment: colors[segment] for segment in segments if segment in colors} + + def _percent_rows(df, value_cols): """Copy of ``df`` with each row's ``value_cols`` rescaled to sum to 100 (percent).""" out = df.copy() @@ -264,13 +270,14 @@ def _team_composition_chart(team_membership, affiliations, data_dir, charts_dir, ) -def _single_employer_chart(team_membership, affiliations, charts_dir, *, suffix, title, organisation_colors): +def _single_employer_chart(team_membership, affiliations, data_dir, charts_dir, *, suffix, title, organisation_colors): """Single-employer teams by controlling org, as a bar (possibly active-filtered).""" diversity = build_team_affiliation_diversity(team_membership, affiliations) plot_and_save( build_single_employer_team_counts(diversity), plot_bar, output_path=charts_dir / f"single_employer_teams_by_org{suffix}.png", + csv_path=data_dir / f"single_employer_teams_by_org{suffix}.csv", x_col="organisation", y_col="teams", title=title, @@ -286,6 +293,7 @@ def _repo_diversity_views(role_lookup, affiliations, data_dir, charts_dir, *, ro build_single_employer_repo_counts(diversity, count_col=role_column(role)), plot_bar, output_path=charts_dir / f"single_employer_repos_by_org{suffix}.png", + csv_path=data_dir / f"single_employer_repos_by_org{suffix}.csv", x_col="organisation", y_col="repos", title=title, @@ -484,6 +492,7 @@ def main(org: str = ORG) -> None: _single_employer_chart( team_membership, affiliations, + org_data_dir, org_charts_dir, suffix="", title=f"{org} — single-employer governance teams, by controlling organisation", diff --git a/src/hiero_analytics/pipelines/codeowner_and_runner.py b/src/hiero_analytics/pipelines/codeowner_and_runner.py index c0bedd0bb..0ac6654e0 100644 --- a/src/hiero_analytics/pipelines/codeowner_and_runner.py +++ b/src/hiero_analytics/pipelines/codeowner_and_runner.py @@ -129,6 +129,7 @@ def main(org: str = ORG) -> None: codeowners_summary_df = prepare_org_codeowners_summary(codeowners) if not codeowners_summary_df.empty: + save_dataframe(codeowners_summary_df, org_data_dir / "org_codeowner_summary.csv") plot_bar( df=codeowners_summary_df, x_col="status", @@ -158,6 +159,7 @@ def main(org: str = ORG) -> None: runner_stacked_df = prepare_stacked_runner_summary(runners) if not runner_stacked_df.empty: + save_dataframe(runner_stacked_df, org_data_dir / "org_runner_summary.csv") plot_stacked_bar( df=runner_stacked_df, x_col="repo", diff --git a/src/hiero_analytics/pipelines/contributor_activity.py b/src/hiero_analytics/pipelines/contributor_activity.py index fa2ab6f54..13bd6966b 100644 --- a/src/hiero_analytics/pipelines/contributor_activity.py +++ b/src/hiero_analytics/pipelines/contributor_activity.py @@ -31,12 +31,12 @@ from hiero_analytics.domain.periods import ACTIVITY_PERIODS from hiero_analytics.export.save import save_dataframe from hiero_analytics.pipelines._shared import load_contributor_activity, load_issue_label_events, org_context -from hiero_analytics.plotting.network import render_comembership_network +from hiero_analytics.plotting.network import network_tables, render_comembership_network logger = logging.getLogger(__name__) -def _build_contributor_network(records, label_events, by_repo, org_charts_dir, org: str) -> None: +def _build_contributor_network(records, label_events, by_repo, org_data_dir, org_charts_dir, org: str) -> None: """Render the all-contributors co-membership network for the org. Governance-independent (no roles needed), so it runs for every org. Repos are @@ -52,6 +52,12 @@ def _build_contributor_network(records, label_events, by_repo, org_charts_dir, o membership = build_active_membership(by_repo, recent_by_repo) min_shared = max(1, round(len(by_repo) / CONTRIBUTOR_NETWORK_REPOS_PER_LINK)) nodes, edges = build_comembership_network(membership, min_shared=min_shared) + if nodes.empty: + return + # The interactive network reads these: same nodes, edges and layout as the PNG. + node_table, edge_table = network_tables(nodes, edges) + save_dataframe(node_table, org_data_dir / "all_network_nodes.csv") + save_dataframe(edge_table, org_data_dir / "all_network_edges.csv") if render_comembership_network( nodes, edges, @@ -123,6 +129,6 @@ def main(org: str = ORG) -> None: logger.info("Wrote per-repo profiles for %d repositories", len(by_repo)) # All-contributors network (no governance needed, so every org gets it). - _build_contributor_network(records, label_events, by_repo, org_charts_dir, org) + _build_contributor_network(records, label_events, by_repo, org_data_dir, org_charts_dir, org) logger.info("Contributor activity tables complete") diff --git a/src/hiero_analytics/pipelines/discord_analytics.py b/src/hiero_analytics/pipelines/discord_analytics.py index a163f4c6d..15eb65dfe 100644 --- a/src/hiero_analytics/pipelines/discord_analytics.py +++ b/src/hiero_analytics/pipelines/discord_analytics.py @@ -29,13 +29,14 @@ import logging import os -from datetime import date +from datetime import UTC, date, datetime from pathlib import Path import pandas as pd from hiero_analytics.config.charts import MUTED_HISTORICAL_COLOR, PRIMARY_PALETTE from hiero_analytics.config.paths import INPUTS_DIR, ensure_org_dirs +from hiero_analytics.export.save import save_dataframe, write_output_meta from hiero_analytics.plotting.bars import plot_bar, plot_stacked_bar from hiero_analytics.plotting.lines import plot_date_line @@ -137,17 +138,22 @@ def plot_recent_activity_30d(channels: pd.DataFrame, output_path: Path, top_n: i ) -def plot_category_breakdown(channels: pd.DataFrame, output_path: Path) -> None: - """Channel grouping by topical category — total vs last-90-day activity.""" +def category_breakdown(channels: pd.DataFrame) -> pd.DataFrame: + """Messages per topical category: all-time, last 90 days, and the earlier remainder, busiest first.""" grouped = ( channels.groupby("category", as_index=False) .agg(total=("total", "sum"), last_90d=("d90", "sum")) - .sort_values("total", ascending=False) + .sort_values("total", ascending=False, kind="stable") + .reset_index(drop=True) ) + grouped["earlier"] = grouped["total"] - grouped["last_90d"] + return grouped + + +def plot_category_breakdown(categories: pd.DataFrame, output_path: Path) -> None: + """Channel grouping by topical category — total vs last-90-day activity.""" plot_stacked_bar( - df=grouped.rename(columns={"total": "earlier", "last_90d": "last 90 days"}).assign( - earlier=lambda d: d["earlier"] - d["last 90 days"] - ), + df=categories.rename(columns={"last_90d": "last 90 days"}), x_col="category", stack_cols=["last 90 days", "earlier"], labels=["Last 90 days", "Earlier history"], @@ -178,13 +184,24 @@ def plot_monthly_traffic(series: pd.DataFrame, output_path: Path) -> None: def main() -> None: """Generate the Hiero Discord chart bundle.""" - _, charts_dir = ensure_org_dirs(ORG) + data_dir, charts_dir = ensure_org_dirs(ORG) channels = load_channels_df() monthly = load_monthly_df() + categories = category_breakdown(channels) + save_dataframe(categories, data_dir / "hiero_discord_channel_categories.csv") + save_dataframe(channels[["channel_label", "d30"]], data_dir / "hiero_discord_recent_activity_30d.csv") + save_dataframe(monthly, data_dir / "hiero_discord_monthly_traffic.csv") + + for name in ("channel_categories", "recent_activity_30d", "monthly_traffic"): + write_output_meta( + data_dir / f"hiero_discord_{name}.csv", + generated_at=datetime.combine(SNAPSHOT_DATE, datetime.min.time(), tzinfo=UTC), + ) + plot_monthly_traffic(monthly, charts_dir / "hiero_discord_monthly_traffic.png") plot_recent_activity_30d(channels, charts_dir / "hiero_discord_recent_activity_30d.png") - plot_category_breakdown(channels, charts_dir / "hiero_discord_channel_categories.png") + plot_category_breakdown(categories, charts_dir / "hiero_discord_channel_categories.png") logger.info("Hiero Discord charts written to %s", charts_dir) diff --git a/src/hiero_analytics/pipelines/hiero_hackers.py b/src/hiero_analytics/pipelines/hiero_hackers.py index ae2e23c5f..7da23bbcb 100644 --- a/src/hiero_analytics/pipelines/hiero_hackers.py +++ b/src/hiero_analytics/pipelines/hiero_hackers.py @@ -77,9 +77,9 @@ def main(org: str = ORG) -> None: # Generate contributor counts chart and CSV (only if data exists) contributors_df = build_contributor_counts(activity_records) if not contributors_df.empty: - contributors_df = contributors_df.sort_values("contributors", ascending=False).head(20) + contributors_df = contributors_df.sort_values("contributors", ascending=False) plot_bar( - df=contributors_df, + df=contributors_df.head(20), x_col="repo", y_col="contributors", title=f"{title_prefix} — Top 20 Repositories by Contributors", diff --git a/src/hiero_analytics/pipelines/hip_implementation.py b/src/hiero_analytics/pipelines/hip_implementation.py index 3576eacba..a9aae7b94 100644 --- a/src/hiero_analytics/pipelines/hip_implementation.py +++ b/src/hiero_analytics/pipelines/hip_implementation.py @@ -129,7 +129,7 @@ def _plot_adoption_funnel(funnel: pd.DataFrame, org: str, charts_dir) -> None: ) -def _plot_activity_by_status(summary: pd.DataFrame, org: str, charts_dir) -> None: +def activity_by_status(summary: pd.DataFrame) -> pd.DataFrame: """Implementation evidence for the statuses where implementation is expected. "No evidence" splits by what HIP-1 implies: for an approved spec it means @@ -138,7 +138,7 @@ def _plot_activity_by_status(summary: pd.DataFrame, org: str, charts_dir) -> Non """ summary = summary[summary["status_bucket"].isin(_BUCKET_LABELS)] if summary.empty: - return + return pd.DataFrame(columns=["bucket", "merged", "open_only", "none_awaiting", "citation_gap"]) counts = summary.groupby(["status_bucket", "evidence_class"]).size().unstack(fill_value=0).reindex(_BUCKET_LABELS) for column in ("merged", "open_only", "none"): if column not in counts: @@ -148,6 +148,13 @@ def _plot_activity_by_status(summary: pd.DataFrame, org: str, charts_dir) -> Non is_approved = frame["status_bucket"] == "approved_accepted" frame["none_awaiting"] = frame["none"].where(is_approved, 0) frame["citation_gap"] = frame["none"].where(~is_approved, 0) + return frame + + +def _plot_activity_by_status(summary: pd.DataFrame, org: str, charts_dir) -> None: + frame = activity_by_status(summary) + if frame.empty: + return labels = [ "Merged implementation PRs", "Open PRs only", @@ -207,6 +214,9 @@ def main(org: str = ORG) -> None: } for filename, frame in tables.items(): save_dataframe(frame, org_data_dir / filename) + # Chart data only, unlike the tables above: no rows means no chart, so no file. + if not (activity := activity_by_status(summary)).empty: + save_dataframe(activity, org_data_dir / "hip_activity_by_status.csv") _plot_engagement(engagement, org, org_charts_dir) _plot_adoption_funnel(tables["hip_adoption_funnel.csv"], org, org_charts_dir) diff --git a/src/hiero_analytics/pipelines/role_coverage.py b/src/hiero_analytics/pipelines/role_coverage.py index e6ed895cd..50281b37b 100644 --- a/src/hiero_analytics/pipelines/role_coverage.py +++ b/src/hiero_analytics/pipelines/role_coverage.py @@ -62,7 +62,7 @@ from hiero_analytics.domain.repos import bare_repo from hiero_analytics.export.save import save_dataframe from hiero_analytics.pipelines._shared import load_contributor_activity, load_issue_label_events, org_context -from hiero_analytics.plotting.network import render_comembership_network +from hiero_analytics.plotting.network import network_tables, render_comembership_network logger = logging.getLogger(__name__) # Thresholds live in config.analysis (ROLE_ACTIVE_DAYS, the network link @@ -162,7 +162,7 @@ def output_path(stem): logger.info("Review load share: %d repos (>=%d review+merge in window)", len(load_share), LOAD_SHARE_MIN_ACTIONS) -def _write_role_networks(combined, org_charts_dir, org: str = ORG): +def _write_role_networks(combined, org_data_dir, org_charts_dir, org: str = ORG): """Co-membership networks per governance role (maintainer, committer, triage). The Governance tab shows how each permission tier connects the repos; the @@ -177,6 +177,12 @@ def _write_role_networks(combined, org_charts_dir, org: str = ORG): ] for key, label, membership, min_shared in groups: nodes, edges = build_comembership_network(membership, min_shared=min_shared) + if nodes.empty: + continue + # The interactive network reads these: same nodes, edges and layout as the PNG. + node_table, edge_table = network_tables(nodes, edges) + save_dataframe(node_table, org_data_dir / f"{key}_network_nodes.csv") + save_dataframe(edge_table, org_data_dir / f"{key}_network_edges.csv") if render_comembership_network( nodes, edges, @@ -260,7 +266,7 @@ def main(org: str = ORG) -> None: if not combined.empty: save_dataframe(combined, org_data_dir / "role_coverage_all.csv") _write_repo_summaries(combined, org_data_dir) - _write_role_networks(combined, org_charts_dir, org) + _write_role_networks(combined, org_data_dir, org_charts_dir, org) for period in ACTIVITY_PERIODS: # Every ACTIVITY_PERIODS entry is a bounded window (all-time is the diff --git a/src/hiero_analytics/pipelines/scorecard.py b/src/hiero_analytics/pipelines/scorecard.py index a98469bdb..87e7b220b 100644 --- a/src/hiero_analytics/pipelines/scorecard.py +++ b/src/hiero_analytics/pipelines/scorecard.py @@ -17,7 +17,7 @@ from hiero_analytics.data_sources.github_ingest import fetch_org_repos_graphql from hiero_analytics.data_sources.models import ScorecardRecord from hiero_analytics.data_sources.scorecard import fetch_repo_scorecard -from hiero_analytics.export.save import plot_and_save +from hiero_analytics.export.save import plot_and_save, save_dataframe from hiero_analytics.pipelines._shared import org_context from hiero_analytics.plotting.bars import plot_bar, plot_stacked_bar @@ -29,7 +29,7 @@ def fetch_org_repos(client: GitHubClient, org: str): return fetch_org_repos_graphql(client, org) -def fetch_all_scorecards(repos) -> list[ScorecardRecord]: +def fetch_all_scorecards(repos, *, org: str = ORG) -> list[ScorecardRecord]: """Fetch scorecards for each repository in the organization. A transient failure is retried once; a second failure propagates, so the @@ -41,10 +41,10 @@ def fetch_all_scorecards(repos) -> list[ScorecardRecord]: logger.info("Fetching scorecard (%d/%d): %s", i, len(repos), repo.name) try: - sc = fetch_repo_scorecard(repo.name) + sc = fetch_repo_scorecard(repo.name, org=org) except requests.RequestException: logger.warning("Scorecard fetch failed for %s; retrying once", repo.name) - sc = fetch_repo_scorecard(repo.name) + sc = fetch_repo_scorecard(repo.name, org=org) if sc: scorecards.append(sc) @@ -53,7 +53,7 @@ def fetch_all_scorecards(repos) -> list[ScorecardRecord]: def main(org: str = ORG): """Fetch scorecards for all organisation repos and generate bar charts.""" - client, _, org_charts_dir = org_context(org) + client, org_data_dir, org_charts_dir = org_context(org) repos = fetch_org_repos(client, org) @@ -61,13 +61,14 @@ def main(org: str = ORG): logger.warning("No repositories found for org: %s", org) return - scorecards = fetch_all_scorecards(repos) + scorecards = fetch_all_scorecards(repos, org=org) if not scorecards: logger.warning("No scorecards fetched") return df = scorecard_to_dataframe(scorecards) + save_dataframe(df, org_data_dir / "org_scorecard.csv") plot_and_save( df, plot_bar, @@ -78,6 +79,8 @@ def main(org: str = ORG): ) df_stacked = scorecard_stacked_dataframe(scorecards) + # -1 (inconclusive) is kept and unreported checks stay blank, never a zero score. + save_dataframe(scorecard_stacked_dataframe(scorecards, missing=None), org_data_dir / "org_scorecard_checks.csv") plot_and_save( df_stacked, plot_stacked_bar, diff --git a/src/hiero_analytics/plotting/network.py b/src/hiero_analytics/plotting/network.py index 51cb88b29..d3686d507 100644 --- a/src/hiero_analytics/plotting/network.py +++ b/src/hiero_analytics/plotting/network.py @@ -71,6 +71,60 @@ def _packed_layout(graph: nx.Graph, seed: int) -> tuple[dict, list]: return pos, isolated +def _graph(nodes: pd.DataFrame, edges: pd.DataFrame) -> nx.Graph: + """Repositories as nodes (with active members), shared members as weighted edges.""" + graph = nx.Graph() + for row in nodes.itertuples(): + graph.add_node(row.repo, active=int(row.active_members)) + for row in edges.itertuples(): + if row.repo_a in graph and row.repo_b in graph: + graph.add_edge(row.repo_a, row.repo_b, weight=int(row.shared)) + return graph + + +# Isolated bubbles sit in rows of up to eight, at a fixed spacing, under the clusters. +_ISOLATE_GAP = 0.9 +_ISOLATE_COLS = 8 + + +def network_layout(graph: nx.Graph, seed: int = 42) -> tuple[dict, list, tuple[float, float] | None]: + """Node positions and the "not linked" caption point, shared by the PNG and the interactive network. + + Returns ``(pos, isolated, caption)``; ``caption`` is None when every node is linked. + """ + pos, isolated = _packed_layout(graph, seed) + if not isolated: + return pos, isolated, None + if pos: + xs = [p[0] for p in pos.values()] + ys = [p[1] for p in pos.values()] + x_min, x_max, y_min = min(xs), max(xs), min(ys) + else: + x_min, x_max, y_min = -1.0, 1.0, -1.0 + # Fixed bubble spacing (not span-based) so a single isolate doesn't get + # flung far below; centre the row under the clusters, just beneath them. + cols = min(len(isolated), _ISOLATE_COLS) + row_width = (cols - 1) * _ISOLATE_GAP + x_centre = (x_min + x_max) / 2 + top = y_min - _ISOLATE_GAP * 1.8 + for i, node in enumerate(isolated): + row, col = divmod(i, cols) + pos[node] = (x_centre - row_width / 2 + col * _ISOLATE_GAP, top - row * _ISOLATE_GAP) + return pos, isolated, (x_centre, top + _ISOLATE_GAP * 0.8) + + +def network_tables(nodes: pd.DataFrame, edges: pd.DataFrame, seed: int = 42) -> tuple[pd.DataFrame, pd.DataFrame]: + """The drawn network as node and edge tables, with each node's ``category`` and PNG ``x``/``y``.""" + graph = _graph(nodes, edges) + pos, _isolated, _caption = network_layout(graph, seed) + node_table = nodes[["repo", "active_members", "total_members"]].copy() + node_table["category"] = node_table["repo"].map(categorize_repo) + node_table["x"] = node_table["repo"].map(lambda repo: round(pos[repo][0], 4)) + node_table["y"] = node_table["repo"].map(lambda repo: round(pos[repo][1], 4)) + edge_table = edges[edges["repo_a"].isin(graph) & edges["repo_b"].isin(graph)][["repo_a", "repo_b", "shared"]] + return node_table.reset_index(drop=True), edge_table.reset_index(drop=True) + + def render_comembership_network( nodes: pd.DataFrame, edges: pd.DataFrame, @@ -92,12 +146,7 @@ def render_comembership_network( return False apply_style() - graph = nx.Graph() - for row in nodes.itertuples(): - graph.add_node(row.repo, active=int(row.active_members)) - for row in edges.itertuples(): - if row.repo_a in graph and row.repo_b in graph: - graph.add_edge(row.repo_a, row.repo_b, weight=int(row.shared)) + graph = _graph(nodes, edges) categories = {node: categorize_repo(node) for node in graph.nodes()} node_colors = [REPO_CATEGORY_COLORS.get(categories[node], _OTHER_COLOR) for node in graph.nodes()] @@ -106,27 +155,10 @@ def render_comembership_network( # clusters sit side by side, not flung apart); isolated bubbles tuck below. fig, ax = plt.subplots(figsize=(16, 12)) try: - pos, isolated = _packed_layout(graph, seed) - if isolated: - if pos: - xs = [p[0] for p in pos.values()] - ys = [p[1] for p in pos.values()] - x_min, x_max, y_min = min(xs), max(xs), min(ys) - else: - x_min, x_max, y_min = -1.0, 1.0, -1.0 - # Fixed bubble spacing (not span-based) so a single isolate doesn't get - # flung far below; centre the row under the clusters, just beneath them. - gap = 0.9 - cols = min(len(isolated), 8) - row_width = (cols - 1) * gap - x_centre = (x_min + x_max) / 2 - top = y_min - gap * 1.8 - for i, node in enumerate(isolated): - row, col = divmod(i, cols) - pos[node] = (x_centre - row_width / 2 + col * gap, top - row * gap) + pos, _isolated, caption = network_layout(graph, seed) + if caption: ax.text( - x_centre, - top + gap * 0.8, + *caption, f"not linked — no shared {member_label}", ha="center", va="bottom", diff --git a/tests/contracts/test_output_contract.py b/tests/contracts/test_output_contract.py index 65594caa0..51c15a8bc 100644 --- a/tests/contracts/test_output_contract.py +++ b/tests/contracts/test_output_contract.py @@ -383,7 +383,9 @@ def outputs_root(tmp_path_factory) -> Path: mp.setattr( scorecard_mod, "fetch_repo_scorecard", - lambda name: ScorecardRecord(repo=name, score=7.5, checks={"Maintained": 10, "Code-Review": 8}, date=_NOW), + lambda name, **_kwargs: ScorecardRecord( + repo=name, score=7.5, checks={"Maintained": 10, "Code-Review": 8}, date=_NOW + ), ) mp.setattr( codeowner_mod, @@ -526,6 +528,39 @@ def test_data_api_ships_every_declared_chart_csv(outputs_root: Path): assert any("download" in section for section in hip_charts) +def test_data_api_ships_every_interactive_chart(outputs_root: Path): + """Every interactive reference resolves, and every source whose CSV was produced publishes.""" + api_dir = outputs_root / "data" / "api" / "v1" + manifest = json.loads((api_dir / "manifest.json").read_text()) + + published, missing = set(), [] + for entry in manifest["orgs"].values(): + for section in entry["chart_sections"]: + for chart in section["charts"]: + for variant in chart["variants"]: + if reference := variant.get("interactive"): + path = api_dir / reference["path"] + if not path.exists(): + missing.append(reference["path"]) + continue + document = json.loads(path.read_text()) + assert document["kind"] == reference["kind"] + content = {"network": "nodes"}.get(document["kind"], "rows") + assert document["population"] and content in document + published.add(Path(reference["path"]).name) + assert not missing, f"interactive charts referenced but not written: {missing}" + + org_data = outputs_root / "data" / "org" / PRIMARY + expected = { + f"{Path(filename).stem}.json" + for macro in CHART_MACROS + for spec in macro["charts"].get(PRIMARY) or macro["charts"].get("*", []) + for filename, source in spec.get("interactive_sources", {}).items() + if (org_data / source["file"]).exists() + } + assert expected <= published, f"sources with data but no interactive chart: {sorted(expected - published)}" + + def test_every_spec_table_csv_is_produced(outputs_root: Path): """Each section's CSV (and every derived period variant) exists for the primary org.""" org_data = outputs_root / "data" / "org" / PRIMARY @@ -559,6 +594,16 @@ def test_no_orphan_org_level_outputs(outputs_root: Path): if spec.get("periods"): stem = Path(spec["file"]).stem spec_csvs.update(period.filename(stem) for period in ACTIVITY_PERIODS) + # CSVs an interactive chart reads are spec-listed through its source. + spec_csvs.update( + name + for macro in CHART_MACROS + for specs in macro["charts"].values() + for spec in specs + for source in spec.get("interactive_sources", {}).values() + for name in (source["file"], source.get("edges_file")) + if name + ) period_suffixes = tuple(f"_{period.key}.csv" for period in ACTIVITY_PERIODS) orphans = [] @@ -601,3 +646,30 @@ def test_every_emitted_kpi_tile_explains_itself(outputs_root: Path): if not tile.get("note") or not tile.get("methodology") ] assert not unexplained, f"KPI tiles with no explanation: {unexplained}" + + +def test_every_chart_variant_has_a_data_source(): + """Every chart variant a spec lists declares an interactive source.""" + for macro in CHART_MACROS: + for specs in macro["charts"].values(): + for spec in specs: + for _caption, variants in spec["files"]: + for _label, filename in variants: + assert filename in spec.get("interactive_sources", {}), filename + + +def test_every_produced_chart_has_interactive_data(outputs_root: Path): + """Every published chart variant has its PNG and an interactive document on disk. + + The PNG half is the v1 additive-only guarantee: ``file`` has always named an + image that exists, so a consumer built before interactive charts still works. + """ + manifest = json.loads((outputs_root / "data/api/v1/manifest.json").read_text()) + for org in manifest["orgs"].values(): + for section in org["chart_sections"]: + for chart in section["charts"]: + for variant in chart["variants"]: + assert (outputs_root / variant["file"]).is_file(), variant["file"] + assert variant.get("interactive"), variant["file"] + target = outputs_root / "data/api/v1" / variant["interactive"]["path"] + assert target.exists() diff --git a/tests/dashboard_spec/test_spec_consistency.py b/tests/dashboard_spec/test_spec_consistency.py index 4bf372888..8f95c564e 100644 --- a/tests/dashboard_spec/test_spec_consistency.py +++ b/tests/dashboard_spec/test_spec_consistency.py @@ -11,6 +11,7 @@ COLUMN_FORMATS, CUSTOM_VIEW_MODULES, MACRO_GLOSSARIES, + MACRO_SUMMARIES, TABLE_FAMILIES, WIDE_CHARTS, table_variants, @@ -119,6 +120,15 @@ def test_macro_glossaries_belong_to_real_macros(): assert "<" not in str(glossary), f"{macro_name}: glossary must not carry HTML" +def test_every_macro_has_a_plain_one_line_summary(): + """Each tab states its purpose under its title: one line of text, no markup.""" + assert set(MACRO_SUMMARIES) == _macro_names() + for macro_name, summary in MACRO_SUMMARIES.items(): + assert summary.strip(), f"{macro_name}: summary is empty" + assert "\n" not in summary, f"{macro_name}: summary must be one line" + assert "<" not in summary, f"{macro_name}: summary must not carry HTML" + + def test_activity_specs_use_the_shared_period_set(): """The tabbed activity tables opt in via the flag; filenames derive centrally.""" all_specs = [spec for family in TABLE_FAMILIES.values() for spec in family.SECTION_SPECS] diff --git a/tests/data_sources/test_scorecard.py b/tests/data_sources/test_scorecard.py index 823a29ac9..2b94643b4 100644 --- a/tests/data_sources/test_scorecard.py +++ b/tests/data_sources/test_scorecard.py @@ -7,7 +7,7 @@ import requests from hiero_analytics.data_sources.models import ScorecardRecord -from hiero_analytics.data_sources.scorecard import fetch_repo_scorecard +from hiero_analytics.data_sources.scorecard import fetch_repo_scorecard, scorecard_url MOCK_RESPONSE = { "score": 7.5, @@ -79,3 +79,23 @@ def test_fetch_repo_scorecard_server_error_raises(mock_get): with pytest.raises(requests.exceptions.HTTPError): fetch_repo_scorecard("hiero-sdk-python") + + +@patch("hiero_analytics.data_sources.scorecard.requests.get") +def test_scorecard_uses_requested_organisation(mock_get): + """Secondary organisations must not query the primary organisation's scorecards.""" + mock_get.return_value.json.return_value = MOCK_RESPONSE + fetch_repo_scorecard("analytics", org="hiero-hackers") + assert mock_get.call_args.args[0] == "https://api.scorecard.dev/projects/github.com/hiero-hackers/analytics" + + +def test_scorecard_url_substitutes_the_org(): + """Each org queries its own repos, through the default or an override.""" + assert scorecard_url("sdk", "hiero-hackers") == "https://api.scorecard.dev/projects/github.com/hiero-hackers/sdk" + assert scorecard_url("sdk", "o", "https://mirror.test/{org}") == "https://mirror.test/o/sdk" + + +def test_scorecard_url_rejects_an_org_less_override(): + """An old-style override would silently query one org for all; fail loudly instead.""" + with pytest.raises(ValueError, match=r"\{org\} placeholder"): + scorecard_url("sdk", "hiero-hackers", "https://mirror.test/projects/github.com/hiero-ledger") diff --git a/tests/export/test_chart_data.py b/tests/export/test_chart_data.py new file mode 100644 index 000000000..b6b6df9ae --- /dev/null +++ b/tests/export/test_chart_data.py @@ -0,0 +1,463 @@ +"""Interactive charts retain the source counts and calendar semantics.""" + +import json + +import pandas as pd +import pytest + +from hiero_analytics.analysis.scorecard_analysis import CHECK_COLUMNS +from hiero_analytics.dashboard_spec import CHART_MACROS +from hiero_analytics.dashboard_spec.interactive import ( + ACTIVITY_HEATMAP_SOURCES, + RELEASE_TIMELINE_SOURCES, + REPO_GROWTH_SOURCES, + ROLE_ACTIVITY, + ROLE_BY_REPO, + SCORECARD_CHECKS, + role_network, +) +from hiero_analytics.export import data_api +from hiero_analytics.export.chart_data import chart_document +from hiero_analytics.plotting.network import _graph, network_layout, network_tables + + +def write_counts(tmp_path, frequency, buckets, counts): + """Write a small role dataset with one nonzero series.""" + path = tmp_path / "counts.csv" + pd.DataFrame({frequency: buckets, "general_user": counts, "triage": 0, "committer": 2, "maintainer": 3}).to_csv( + path, index=False + ) + return path + + +def roles(frequency, **extra): + """The role-activity preset for one calendar resolution.""" + return {**ROLE_ACTIVITY, "category": frequency, "frequency": frequency, **extra} + + +def test_monthly_window_fills_gaps_and_preserves_counts(tmp_path): + """Missing calendar months are zero, while populated buckets keep exact values.""" + path = write_counts(tmp_path, "month", ["2026-03", "2026-01"], [7, 4]) + data = chart_document(roles("month", buckets=3), path, "org", "2026-03-15T12:00:00Z") + assert [row["bucket"] for row in data["rows"]] == ["2026-01", "2026-02", "2026-03"] + assert [row["general_user"] for row in data["rows"]] == [4, 0, 7] + assert [row["maintainer"] for row in data["rows"]] == [3, 0, 3] + assert [row["partial"] for row in data["rows"]] == [False, False, True] + assert data["dimensions"] == ["period", "series"] + + +def test_gap_filling_keeps_integer_details_integral(tmp_path): + """A gap-filled bucket must not turn integer detail counts into 12.0 in the JSON.""" + path = tmp_path / "ts.csv" + path.write_text("month,prs,reviewers\n2026-01,5,12\n2026-03,7,3\n") + source = { + "kind": "timeseries", + "file": "ts.csv", + "category": "month", + "frequency": "month", + "series": [{"key": "prs", "label": "PRs"}], + "details": [{"key": "reviewers", "label": "Reviewers"}], + "metric": "m", + "unit": "u", + "population": "p", + } + data = chart_document(source, path, "org", "2026-03-15T00:00:00+00:00") + assert [row["reviewers"] for row in data["rows"]] == [12, 0, 3] + assert '"reviewers": 12,' in json.dumps(data["rows"][0]) + + +@pytest.mark.parametrize( + ("frequency", "buckets", "expected"), + [ + ("day", ["2025-12-31", "2026-01-02"], ["2025-12-31", "2026-01-01", "2026-01-02"]), + ("week", ["2025-W52", "2026-W02"], ["2025-W52", "2026-W01", "2026-W02"]), + ("year", ["2024", "2026"], ["2024", "2025", "2026"]), + ], +) +def test_calendar_boundaries(tmp_path, frequency, buckets, expected): + """Year boundaries and missing sidecars do not shift historic data to today.""" + path = write_counts(tmp_path, frequency, buckets, [4, 7]) + extra = {} if frequency == "year" else {"buckets": 3} + data = chart_document(roles(frequency, **extra), path, "org") + assert [row["bucket"] for row in data["rows"]] == expected + + +@pytest.mark.parametrize("count", [-1, 1.5, float("inf"), float("nan"), "bad"]) +def test_invalid_counts_fail(tmp_path, count): + """Corrupt numeric data must never silently become chart values.""" + path = write_counts(tmp_path, "year", ["2026"], [count]) + with pytest.raises(ValueError): + chart_document(roles("year"), path, "org") + + +def test_duplicate_buckets_fail(tmp_path): + """Two rows for a single month violate the unique-count contract.""" + path = write_counts(tmp_path, "month", ["2026-01", "2026-01"], [1, 2]) + with pytest.raises(ValueError, match="duplicate"): + chart_document(roles("month", buckets=12), path, "org") + + +def test_empty_dataset(tmp_path): + """Empty source data stays empty instead of inventing a zero history.""" + path = write_counts(tmp_path, "month", [], []) + data = chart_document(roles("month", buckets=12), path, "org") + assert data["rows"] == [] + assert data["window"] == {"kind": "calendar", "first": None, "last": None} + + +def test_cumulative_series_carry_forward_and_flows_fill_zero(tmp_path): + """A running total holds its level through empty months; new repos in them are zero.""" + path = tmp_path / "repo_growth_timeline.csv" + pd.DataFrame({"month": ["2026-01-01", "2026-03-01"], "repos_created": [2, 1], "cumulative_repos": [5, 6]}).to_csv( + path, index=False + ) + created, total = ( + chart_document(REPO_GROWTH_SOURCES[name], path, "org", "2026-05-02T00:00:00Z") + for name in ["repos_created_per_month.png", "cumulative_repo_count.png"] + ) + assert [row["bucket"] for row in created["rows"]] == ["2026-01", "2026-02", "2026-03", "2026-04", "2026-05"] + assert [row["repos_created"] for row in created["rows"]] == [2, 0, 1, 0, 0] + assert [row["cumulative_repos"] for row in total["rows"]] == [5, 5, 6, 6, 6] + assert total["mark"] == "line" + + +def test_snapshot_series_keep_their_own_dates(tmp_path): + """Point-in-time counts are neither gap-filled nor flagged as partial buckets.""" + path = tmp_path / "open.csv" + pd.DataFrame({"date": ["2026-01-04", "2026-01-11", "2026-01-13"], "gfi": [1, 2, 3]}).to_csv(path, index=False) + source = { + **ROLE_ACTIVITY, + "category": "date", + "frequency": "snapshot", + "series": [{"key": "gfi", "label": "Good First Issue"}], + "mark": "area", + } + data = chart_document(source, path, "org", "2026-01-13T09:00:00Z") + assert [row["bucket"] for row in data["rows"]] == ["2026-01-04", "2026-01-11", "2026-01-13"] + assert not any(row["partial"] for row in data["rows"]) + assert data["series"][0]["color"] == "var(--chart-1)" + + +def test_chart_note_does_not_replace_counting_rule(tmp_path, monkeypatch): + """A PNG's "how to read this" note sits beside the population rule, never over it.""" + source = tmp_path / "source" + source.mkdir() + write_counts(source, "year", ["2026"], [9]) + spec = { + "name": "Governance", + "charts": { + "org": [ + { + "id": "maintainer-pipeline", + "title": "Pipeline", + "description": "Roles", + "files": [("Role counts", [("All time", "roles.png")])], + "interactive_sources": {"roles.png": {**roles("year"), "file": "counts.csv"}}, + } + ] + }, + } + monkeypatch.setattr(data_api, "CHART_MACROS", [spec]) + monkeypatch.setattr(data_api, "CHART_NOTES", {"roles.png": "Read the bars left to right."}) + monkeypatch.setattr(data_api.paths, "ORG_CHARTS_DIR", tmp_path / "images") + (tmp_path / "images" / "org").mkdir(parents=True) + (tmp_path / "images" / "org" / "roles.png").write_bytes(b"\x89PNG") + output = tmp_path / "api" / "org" + result = data_api._org_chart_sections("org", source, output) + variant = result[0]["charts"][0]["variants"][0] + assert variant["file"] == "charts/org/org/roles.png" + assert variant["interactive"] == {"kind": "timeseries", "path": "org/charts/roles.json"} + document = json.loads((output / "charts/roles.json").read_text()) + assert document["rows"][0]["general_user"] == 9 + assert document["note"] == "Read the bars left to right." + assert "Bots are excluded" in document["population"] + + +def test_a_dataset_without_its_png_lists_no_variant(tmp_path, monkeypatch): + """v1 is additive-only: every listed ``file`` is a PNG that exists, dataset or not.""" + source = tmp_path / "source" + source.mkdir() + write_counts(source, "year", ["2026"], [9]) + spec = { + "name": "Governance", + "charts": { + "org": [ + { + "id": "maintainer-pipeline", + "title": "Pipeline", + "description": "Roles", + "files": [("Role counts", [("All time", "roles.png")])], + "interactive_sources": {"roles.png": {**roles("year"), "file": "counts.csv"}}, + } + ] + }, + } + monkeypatch.setattr(data_api, "CHART_MACROS", [spec]) + monkeypatch.setattr(data_api.paths, "ORG_CHARTS_DIR", tmp_path / "images") + output = tmp_path / "api" / "org" + assert data_api._org_chart_sections("org", source, output) == [] + assert not (output / "charts").exists() + + +def test_ranked_categories_keep_every_row(tmp_path): + """No pooling: summing people across repos would double-count, so every row survives.""" + path = tmp_path / "repos.csv" + pd.DataFrame( + {"repo": ["org/big", "org/small"], "general_user": [9, 1], "triage": 0, "committer": 0, "maintainer": 0} + ).to_csv(path, index=False) + data = chart_document({**ROLE_BY_REPO, "window": 30, "strip_org_prefix": True}, path, "org", "2026-03-15T12:00Z") + assert data["kind"] == "categories" + assert [row["repo"] for row in data["rows"]] == ["big", "small"] + assert data["window"] == {"kind": "trailing", "days": 30, "end": "2026-03-15T12:00Z"} + assert (data["rank"], data["top_n"], data["orientation"]) == (True, 10, "horizontal") + assert data["dimensions"] == ["repo", "series"] + + +def test_groups_keep_cohorts_apart(tmp_path): + """A funnel's cohorts share stage names, so uniqueness is per cohort, and the default must exist.""" + path = tmp_path / "funnel.csv" + pd.DataFrame( + { + "cohort": ["all", "all", "recent", "recent"], + "stage": ["proposed", "approved", "proposed", "approved"], + "hips": [10, 5, 4, 1], + "pct_of_proposed": [100, 50, 100, 25], + } + ).to_csv(path, index=False) + source = { + **ROLE_BY_REPO, + "category": "stage", + "category_label": "Stage", + "group": {"key": "cohort", "label": "Cohort", "default": "recent"}, + "series": [{"key": "hips", "label": "HIPs"}], + "details": [{"key": "pct_of_proposed", "label": "Share", "format": "percent"}], + } + data = chart_document(source, path, "org") + assert data["group"]["values"] == ["all", "recent"] + assert data["rows"][1] == {"stage": "approved", "cohort": "all", "hips": 5, "pct_of_proposed": 50} + assert data["dimensions"] == ["stage", "cohort"] + with pytest.raises(ValueError, match="absent"): + chart_document({**source, "group": {**source["group"], "default": "none"}}, path, "org") + + +def test_decimal_values_and_column_series(tmp_path): + """Scores may be fractional; ``series: columns`` takes every non-category column in order.""" + path = tmp_path / "scores.csv" + pd.DataFrame({"repo": ["a", "b"], "score": [7.5, 3.25]}).to_csv(path, index=False) + data = chart_document({**ROLE_BY_REPO, "series": "columns", "values": "number"}, path, "org") + assert [row["score"] for row in data["rows"]] == [7.5, 3.25] + assert data["value_format"] == "decimal" + assert [series["key"] for series in data["series"]] == ["score"] + + +def test_duplicates_and_undeclared_renderers_fail(tmp_path): + """A repo listed twice, or an unknown kind or mark, fails the export.""" + path = tmp_path / "repos.csv" + pd.DataFrame({"repo": ["a", "a"], "general_user": 1, "triage": 0, "committer": 0, "maintainer": 0}).to_csv( + path, index=False + ) + with pytest.raises(ValueError, match="duplicate"): + chart_document(ROLE_BY_REPO, path, "org") + with pytest.raises(ValueError, match="Unknown"): + chart_document({**ROLE_BY_REPO, "kind": "pie"}, path, "org") + with pytest.raises(ValueError, match="mark"): + chart_document({**ROLE_BY_REPO, "mark": "donut"}, path, "org") + + +def test_every_interactive_source_replaces_a_declared_chart(): + """A source keyed by a filename the card never lists would silently never render.""" + for macro in CHART_MACROS: + for specs in macro["charts"].values(): + for spec in specs: + listed = {filename for _caption, variants in spec["files"] for _label, filename in variants} + assert set(spec.get("interactive_sources", {})) <= listed, spec["id"] + + +def test_matrix_takes_month_columns_in_order_and_nulls_inconclusive(tmp_path): + """Months are found by shape, oldest first; Scorecard's -1 is absent, never a score.""" + heat = tmp_path / "heat.csv" + pd.DataFrame( + { + "contributor name": ["ann", "bo"], + "role": ["Maintainer", None], + "activity score": [30, 3], + "2026-02": [20, 0], + "2026-01": [10, 3], + } + ).to_csv(heat, index=False) + data = chart_document(ACTIVITY_HEATMAP_SOURCES["contributor_activity_heatmap.png"], heat, "org") + assert [column["key"] for column in data["columns"]] == ["2026-01", "2026-02"] + assert data["rows"][1] == {"contributor name": "bo", "role": "", "activity score": 3, "2026-02": 0, "2026-01": 3} + assert data["scale"] == {"min": 0, "max": 20, "steps": 5} + assert data["avatars"] is True + assert "not a count" in data["population"] + + checks = tmp_path / "checks.csv" + pd.DataFrame( + {"repo": ["org/a"], "score": [6.4], **{check: [-1 if check == "Fuzzing" else 5] for check in CHECK_COLUMNS}} + ).to_csv(checks, index=False) + data = chart_document(SCORECARD_CHECKS, checks, "org") + row = data["rows"][0] + assert row["repo"] == "a" + assert row["Fuzzing"] is None and row["Maintained"] == 5 + assert (data["scale"]["max"], data["missing"], data["value_format"]) == (10, "Not scored", "decimal") + + +def test_count_matrix_with_a_blank_cell_keeps_integer_counts(tmp_path): + """One unreported cell must not turn the rest of its column into floats.""" + path = tmp_path / "mx.csv" + path.write_text("repo,2026-01,2026-02\na,12,\nb,3,4\n") + source = { + "kind": "matrix", + "file": "mx.csv", + "row": "repo", + "row_label": "Repo", + "columns": "months", + "value_label": "v", + "metric": "m", + "unit": "u", + "population": "p", + } + data = chart_document(source, path, "org") + assert json.dumps(data["rows"]) == json.dumps( + [{"repo": "a", "2026-01": 12, "2026-02": None}, {"repo": "b", "2026-01": 3, "2026-02": 4}] + ) + + +def test_network_tables_carry_the_png_layout(tmp_path): + """The saved positions are the renderer's own, so both views draw one picture.""" + nodes = pd.DataFrame( + { + "repo": ["hiero-sdk-js", "hiero-sdk-go", "governance"], + "active_members": [3, 1, 0], + "total_members": [4, 2, 1], + } + ) + edges = pd.DataFrame({"repo_a": ["hiero-sdk-js"], "repo_b": ["hiero-sdk-go"], "shared": [2]}) + node_table, edge_table = network_tables(nodes, edges) + pos, isolated, caption = network_layout(_graph(nodes, edges)) + assert isolated == ["governance"] and caption is not None + for row in node_table.itertuples(): + assert (row.x, row.y) == (round(pos[row.repo][0], 4), round(pos[row.repo][1], 4)) + assert list(node_table["category"]) == ["SDKs", "SDKs", "Governance"] + + node_table.to_csv(tmp_path / "net_nodes.csv", index=False) + edge_table.to_csv(tmp_path / "net_edges.csv", index=False) + source = role_network("maintainer", "maintainers") | {"edges_file": "net_edges.csv"} + data = chart_document(source, tmp_path / "net_nodes.csv", "org") + assert data["edges"] == [{"source": "hiero-sdk-js", "target": "hiero-sdk-go", "shared": 2}] + assert [category["key"] for category in data["categories"]] == ["SDKs", "Governance"] + governance = data["nodes"][2] + assert (governance["id"], governance["active"], governance["total"]) == ("governance", 0, 1) + assert (governance["x"], governance["y"]) == (round(pos["governance"][0], 4), round(pos["governance"][1], 4)) + + pd.DataFrame({"repo_a": ["hiero-sdk-js"], "repo_b": ["elsewhere"], "shared": [1]}).to_csv( + tmp_path / "net_edges.csv", index=False + ) + with pytest.raises(ValueError, match="not a node"): + chart_document(source, tmp_path / "net_nodes.csv", "org") + + +def test_events_keep_the_trailing_window_busiest_first(tmp_path): + """Only releases inside the window survive; rows are ordered by release count.""" + path = tmp_path / "release_timeline.csv" + pd.DataFrame( + { + "repo": ["org/a", "org/a", "org/b", "org/c"], + "tag_name": ["v1", "v2-rc", "v9", "old"], + "published_at": [ + "2026-03-01T00:00:00Z", + "2026-03-10T00:00:00Z", + "2026-03-09T00:00:00Z", + "2025-01-01T00:00:00Z", + ], + "is_prerelease": [False, True, False, False], + } + ).to_csv(path, index=False) + week = RELEASE_TIMELINE_SOURCES["release_timeline_30d.png"] + data = chart_document(week, path, "org", "2026-03-15T00:00:00+00:00") + assert data["categories"] == ["a", "b"] + assert [(row["repo"], row["tag_name"], row["type"]) for row in data["rows"]] == [ + ("a", "v1", "release"), + ("b", "v9", "release"), + ("a", "v2-rc", "prerelease"), + ] + assert data["window"] == {"kind": "trailing", "days": 30, "end": "2026-03-15T00:00:00+00:00"} + pd.DataFrame( + {"repo": ["a"], "tag_name": ["v1"], "published_at": ["2026-03-01"], "is_prerelease": ["maybe"]} + ).to_csv(path, index=False) + with pytest.raises(ValueError, match="true or false"): + chart_document(week, path, "org") + + +def test_comparison_skips_the_partial_bucket(tmp_path): + """The comparable pair is the last two complete buckets, never the current one.""" + path = write_counts(tmp_path, "month", ["2026-01", "2026-02", "2026-03"], [4, 5, 6]) + data = chart_document(roles("month", buckets=3), path, "org", "2026-03-15T12:00:00Z") + assert data["comparison"] == {"current": "2026-02", "previous": "2026-01"} + # One complete bucket has nothing to compare against. + short = chart_document(roles("month", buckets=2), path, "org", "2026-03-15T12:00:00Z") + assert short["comparison"] is None + + +def test_snapshots_and_opted_out_series_have_no_comparison(tmp_path): + """Point-in-time counts on irregular dates are not comparable windows.""" + path = tmp_path / "open.csv" + pd.DataFrame({"date": ["2026-01-04", "2026-01-11", "2026-01-13"], "gfi": [1, 2, 3]}).to_csv(path, index=False) + source = {**roles("snapshot"), "category": "date", "series": [{"key": "gfi", "label": "GFI"}]} + assert chart_document(source, path, "org")["comparison"] is None + counts = write_counts(tmp_path, "year", ["2024", "2025", "2026"], [1, 2, 3]) + assert chart_document(roles("year", compare=False), counts, "org", "2026-05-01T00:00:00Z")["comparison"] is None + + +def test_dimensions_use_the_focus_vocabulary(tmp_path): + """Whatever a CSV calls its column, the document names the concept once.""" + heat = tmp_path / "heat.csv" + pd.DataFrame({"contributor name": ["ann"], "role": ["Maintainer"], "activity score": [3], "2026-01": [3]}).to_csv( + heat, index=False + ) + assert chart_document(ACTIVITY_HEATMAP_SOURCES["contributor_activity_heatmap.png"], heat, "org")["dimensions"] == [ + "contributor", + "column", + ] + + +def test_meter_and_funnel_marks_are_category_only_and_single_series(tmp_path): + """Part-to-whole and staged forms draw one series over categories, never over time.""" + from hiero_analytics.dashboard_spec.interactive import OWNERSHIP_SOURCES + + path = tmp_path / "org_codeowner_summary.csv" + pd.DataFrame({"status": ["Present", "Missing"], "count": [29, 15]}).to_csv(path, index=False) + data = chart_document(OWNERSHIP_SOURCES["org_codeowner_summary.png"], path, "org") + assert data["mark"] == "meter" + # The headline status stays first: a meter reads its first segment as the share. + assert [row["status"] for row in data["rows"]] == ["Present", "Missing"] + assert data["rank"] is False + + counts = write_counts(tmp_path, "month", ["2026-01"], [1]) + with pytest.raises(ValueError, match="mark for timeseries"): + chart_document(roles("month", buckets=1, mark="funnel"), counts, "org", "2026-01-15T00:00:00Z") + repos = tmp_path / "repos.csv" + pd.DataFrame({"repo": ["a"], "general_user": 1, "triage": 0, "committer": 0, "maintainer": 0}).to_csv( + repos, index=False + ) + with pytest.raises(ValueError, match="exactly one series"): + chart_document({**ROLE_BY_REPO, "mark": "meter"}, repos, "org") + + +def test_median_reference_is_computed_from_the_published_rows(tmp_path): + """The org median follows the data and names its value; unsupported stats fail.""" + path = tmp_path / "org_scorecard.csv" + pd.DataFrame({"repo": ["a", "b", "c", "d"], "score": [2.0, 5.5, 6.5, 9.0]}).to_csv(path, index=False) + source = { + **ROLE_BY_REPO, + "series": [{"key": "score", "label": "Score"}], + "values": "number", + "reference": {"stat": "median", "label": "Org median"}, + } + assert chart_document(source, path, "org")["reference"] == {"value": 6.0, "label": "Org median 6"} + with pytest.raises(ValueError, match="reference"): + chart_document({**source, "reference": {"stat": "mean"}}, path, "org") + assert chart_document({**source, "value_max": 10}, path, "org")["value_max"] == 10 + fixed = {"value": 50, "label": "Majority"} + assert chart_document({**source, "reference": fixed}, path, "org")["reference"] == fixed diff --git a/tests/pipelines/test_discord_analytics.py b/tests/pipelines/test_discord_analytics.py index fb3b713d8..a974b494a 100644 --- a/tests/pipelines/test_discord_analytics.py +++ b/tests/pipelines/test_discord_analytics.py @@ -170,10 +170,27 @@ def test_plot_recent_activity_30d_filters_zero_d30_and_caps_top_n(channels_csv: assert "#hiero-hips" not in passed_df["channel_label"].tolist() +def test_category_breakdown_ranks_categories_and_splits_recent_from_earlier() -> None: + """The chart and its CSV share one frame: busiest category first, earlier = total - last 90 days.""" + channels = pd.DataFrame( + { + "category": ["Dev", "General", "Dev", "Events"], + "total": [100, 300, 50, 10], + "d90": [40, 20, 10, 10], + } + ) + categories = runner.category_breakdown(channels) + assert categories.to_dict("records") == [ + {"category": "General", "total": 300, "last_90d": 20, "earlier": 280}, + {"category": "Dev", "total": 150, "last_90d": 50, "earlier": 100}, + {"category": "Events", "total": 10, "last_90d": 10, "earlier": 0}, + ] + + def test_plot_category_breakdown_writes_png(tmp_path: Path, channels_csv: Path) -> None: """Test that plot_category_breakdown writes a PNG file.""" output = tmp_path / "categories.png" - runner.plot_category_breakdown(runner.load_channels_df(), output) + runner.plot_category_breakdown(runner.category_breakdown(runner.load_channels_df()), output) assert output.exists() and output.stat().st_size > 0 @@ -215,6 +232,31 @@ def test_main_writes_three_charts( assert png.stat().st_size > 0 +def test_main_exports_chart_data_at_manual_snapshot_date(channels_csv, monthly_csv, tmp_path, monkeypatch): + """Rerendering a manual archive must not extend its calendar series to today.""" + import json + + from hiero_analytics.dashboard_spec.interactive import DISCORD_SOURCES + from hiero_analytics.export.chart_data import chart_document + + data_dir = tmp_path / "data" + charts_dir = tmp_path / "charts" + monkeypatch.setattr(runner, "ensure_org_dirs", lambda _org: (data_dir, charts_dir)) + for name in ("plot_monthly_traffic", "plot_recent_activity_30d", "plot_category_breakdown"): + monkeypatch.setattr(runner, name, lambda *_args: None) + runner.main() + documents = {} + for name, source in DISCORD_SOURCES.items(): + path = data_dir / source["file"] + stamp = json.loads(Path(f"{path}.meta.json").read_text())["generated_at"] + documents[name] = chart_document(source, path, "hiero-ledger", stamp) + assert stamp.startswith("2026-05-12") + assert documents["hiero_discord_monthly_traffic.png"]["rows"][-1]["bucket"] == "2026-05" + categories = documents["hiero_discord_channel_categories.png"]["rows"] + assert sum(row["earlier"] + row["last_90d"] for row in categories) == 911 + assert len(documents["hiero_discord_recent_activity_30d.png"]["rows"]) == 6 + + # --------------------------------------------------------------------------- # # Invariant Validation tests # --------------------------------------------------------------------------- # diff --git a/web/.oxlintrc.json b/web/.oxlintrc.json index 6fa991dad..3b3169fa8 100644 --- a/web/.oxlintrc.json +++ b/web/.oxlintrc.json @@ -4,5 +4,16 @@ "rules": { "react/rules-of-hooks": "error", "react/only-export-components": ["warn", { "allowConstantExport": true }] - } + }, + "overrides": [ + { + // shadcn/ui source, generated by its CLI: exporting variants and hooks + // next to components (buttonVariants, useSidebar) is the upstream + // pattern. Local edits there are marked "Local change". + "files": ["src/components/ui/**"], + "rules": { + "react/only-export-components": "off" + } + } + ] } diff --git a/web/README.md b/web/README.md index ba3be9c9c..d75d94d53 100644 --- a/web/README.md +++ b/web/README.md @@ -6,8 +6,9 @@ sections, chart sections, bespoke views, and metrics the API lists, so adding analytics on the Python side rarely requires frontend changes. Three kinds of content arrive from the API. _Sections_ are tables, rendered -generically from their column specs. _Chart sections_ are PNG galleries with -their notes and step-by-step methodology. _Views_ are the bespoke cases a table +generically from their column specs. _Chart sections_ are galleries of +interactive charts, drawn from each variant's JSON dataset, with their notes +and step-by-step methodology. _Views_ are the bespoke cases a table cannot express — today the HIP coverage matrix and governance board — which the Python side ships as pure data (`export/hip_views.py`) so the component owns only the rendering. @@ -25,19 +26,52 @@ npm run dev # the app, on http://localhost:5173 ## Styling conventions -- **Tailwind v4 utilities, composed from the semantic tokens** declared in - `src/app.css` (`bg-surface`, `text-muted`, `border-edge`, `text-ink`, - `bg-page`, `bg-raise`, `text-soft`, `bg-accent`, …). The tokens flip for - dark mode automatically — never write per-component dark colors when a - token exists. -- **No raw hex values in components.** If a design genuinely needs a new - color, add a token to the palette in `src/app.css` and use it by name. -- **Border utilities need `border-solid`** — Tailwind's preflight reset is - deliberately not imported (the layout predates it), so border-width - utilities alone won't render. -- The `@layer components` classes in `src/app.css` are the pre-Tailwind - vocabulary (`.card`, `.tsec`, `.lightbox`, …). They are being migrated to - utilities opportunistically; don't add new ones. +- **UI is built from [shadcn/ui](https://ui.shadcn.com) components** (Radix + base, Mira style) in `src/components/ui/`, added with + `npx shadcn@latest add <name>`. Reach for a component before writing + markup: `Card` for any content card, `Button`, `Badge`, `ToggleGroup` for a + 2–7 option switch, `Collapsible`, `Dialog`, `Table`, `Alert`, `Empty`, + `Skeleton`. Compose them fully (`CardHeader`/`CardTitle`/…), and use + `className` for layout, not to restyle a component. +- **Edits to generated components** are allowed but marked `Local change` + with the reason (e.g. `badge.tsx` has the status tones + `ok`/`warn`/`neg`/`info`/`neutral`). Preview upstream changes with + `npx shadcn@latest add <name> --diff` before overwriting one. +- **All colour comes from semantic tokens** in `src/app.css`. Components use + shadcn's names — `bg-background`, `bg-card`, `text-foreground`, + `text-muted-foreground`, `border-border`, `bg-primary`, `ring-ring` — plus + our own where shadcn has no slot (`text-soft`, `text-link`, `text-ok-ink`, + `bg-chart-ground`, …). shadcn `muted` and `accent` are _backgrounds_ + (secondary text is `text-muted-foreground`). No raw hex values in + components; a new colour gets a token in `src/app.css`, with its dark + value. +- **Dark mode is one token flip.** The OS preference is the default; + `data-theme="light"|"dark"` on `<html>` forces one (`src/theme.ts`, applied + before first paint by `public/theme-init.js`). Components rarely need + `dark:` — tokens already flip. +- **Custom CSS is the exception**, only for information design no component + expresses: the HIP coverage matrix (`.hipmx*`) and governance board lanes + (`.hipboard*`) in `src/app.css`. +- **Type:** one self-hosted family, Public Sans (the CSP allows fonts from + `'self'` only, so it ships in the bundle via `@fontsource-variable`). Numbers + that line up in columns use `tabular-nums`. + +## Third-party requests + +The CSP in `index.html` keeps everything on `'self'` except images from +`https://avatars.githubusercontent.com` (contributor avatars, `ContributorCell`) +and `https://github.com` (org avatars, `OrgSwitcher`, which redirect to the +avatar host). This is a deliberate trade-off: the pictures make long contributor +tables scannable, but GitHub sees the reader's IP address and which avatars, +and therefore which contributor lists, they load. To keep that exposure small, the +images load lazily (only rows scrolled into view) with +`referrerPolicy="no-referrer"`, so GitHub is not told which page asked. A +failed or blocked image falls back to initials, so a browser or extension that +blocks third-party images loses nothing but the pictures. + +If that exposure is no longer acceptable, download the avatars in the pipeline, +serve them from `'self'`, and drop both origins from `img-src`. Don't add any +other origin without the same written reasoning here. ## Adding to the dashboard @@ -54,7 +88,9 @@ npm run dev # the app, on http://localhost:5173 `src/test/` holds the Vitest suite: `fixtures.ts` is a miniature but structurally complete data API served through a fetch stub (it doubles as -documentation of the manifest contract), `app.test.tsx` covers the app shell -and table/chart behavior, `csv.test.ts` covers the provenance-stamped export. -Query by role and text, not by class name — styling refactors shouldn't break -tests. +documentation of the manifest contract), `app.test.tsx` covers tabs, tables +and charts, `shell.test.tsx` the header, sidebar, theme switch and phone +layout, `theme.test.ts` the theme plumbing, `csv.test.ts` the +provenance-stamped export. Query by role and text, not by class name — +styling refactors shouldn't break tests. (A single-select `ToggleGroup` has +`radiogroup`/`radio` roles; cards are labelled `region`s.) diff --git a/web/components.json b/web/components.json new file mode 100644 index 000000000..d9964b1c8 --- /dev/null +++ b/web/components.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "radix-mira", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": {} +} diff --git a/web/e2e/README.md b/web/e2e/README.md index 29ad7e6a8..364ae3e6a 100644 --- a/web/e2e/README.md +++ b/web/e2e/README.md @@ -8,10 +8,12 @@ npm run test:e2e The suite builds the app, stages `dist` and the typed component-test API fixtures under `.e2e/site`, then serves them at `/analytics/`. Every browser -request must remain on that local server. No live API, credentials, Python -pipeline, or duplicate JSON fixtures are needed. The print cases derive larger -tables and a slideshow from the shared fixtures to exercise virtualisation, -wrapping, and explicit row limits. +request must remain on that local server, except the GitHub avatar images the +dashboard loads by design (see `web/README.md`), which `browser.ts` answers +locally with a placeholder pixel. No live API, credentials, Python pipeline, or +duplicate JSON fixtures are needed. The print cases derive larger tables, a +slideshow and its interactive chart datasets from the shared fixtures to exercise +virtualisation, wrapping, chart readiness and explicit row limits. `npm run build` also typechecks these tests and their fixtures. Vitest is scoped to `src` so the two runners do not collect each other's files. This follows the @@ -27,6 +29,7 @@ npm run test:e2e:firefox Chromium produces a `governance.pdf` in its test-results directory for visual review of running footers and page boundaries. Firefox supports print-media -checks but Playwright cannot export its native PDF. Fixture chart PNGs are -one-pixel loading placeholders: chart legibility and effective resolution must -also be reviewed with real dashboard images, as described in the print notes. +checks but Playwright cannot export its native PDF. Charts are drawn from small +synthetic datasets, and the PNGs the manifest still names are one-pixel +placeholders the dashboard never shows: printed chart legibility must also be +reviewed with real dashboard data, as described in the print notes. diff --git a/web/e2e/browser.ts b/web/e2e/browser.ts index b1ba63dbb..01aa11a56 100644 --- a/web/e2e/browser.ts +++ b/web/e2e/browser.ts @@ -1,4 +1,18 @@ import { test as base, expect } from '@playwright/test'; +import { PIXEL_PNG } from './fixtures'; + +/** + * The dashboard's documented third-party requests (web/README.md): GitHub + * avatars for people and `github.com/<org>.png` for organisations. They are + * answered locally so the suite stays offline; any other origin fails the test. + */ +function isAvatar(url: URL) { + return ( + url.protocol === 'https:' && + (url.hostname === 'avatars.githubusercontent.com' || + (url.hostname === 'github.com' && /^\/[^/]+\.png$/.test(url.pathname))) + ); +} export const test = base.extend<{ browserErrors: string[] }>({ browserErrors: [ @@ -13,11 +27,13 @@ export const test = base.extend<{ browserErrors: string[] }>({ page.on('console', (message) => { if (message.type() === 'error') errors.push(message.text()); }); - // Use real local fetches, never the live API or remote chart hosts. + // Use real local fetches, never the live API or remote hosts. await page.route('**/*', async (route) => { const url = new URL(route.request().url()); if (url.origin === allowed || url.protocol === 'data:' || url.protocol === 'blob:') { await route.continue(); + } else if (isAvatar(url)) { + await route.fulfill({ contentType: 'image/png', body: PIXEL_PNG }); } else { errors.push(`Unexpected external request: ${url.href}`); await route.abort(); diff --git a/web/e2e/fixtures.ts b/web/e2e/fixtures.ts index e6b30ebf5..5676c5547 100644 --- a/web/e2e/fixtures.ts +++ b/web/e2e/fixtures.ts @@ -1,6 +1,12 @@ -import type { Manifest, SectionDoc } from '../src/api'; +import type { ChartSpec, Manifest, SectionDoc, TimeseriesDocument } from '../src/api'; import { CONTRIB_DOC, GOV_DOC, MANIFEST, ROUTES } from '../src/test/fixtures.ts'; +/** A decodable one-pixel PNG: chart files on disk and the stubbed avatar hosts. */ +export const PIXEL_PNG = Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGP4DwQACfsD/fteaysAAAAASUVORK5CYII=', + 'base64', +); + // Exercise real virtualisation, wrapping, and deliberate truncation without // a second hand-maintained API schema. All shapes derive from the unit data. export const PRINT_GOV_DOC: SectionDoc = { @@ -23,6 +29,105 @@ export const PRINT_CONTRIB_DOC: SectionDoc = { row_count: 620, }; +/** A valid role-stacked timeseries (see src/chartData.ts), one row per bucket. */ +export function pipelineDocument( + id: string, + frequency: 'year' | 'month', + buckets: string[], +): TimeseriesDocument { + return { + schema_version: 1, + id, + org: 'hiero-ledger', + kind: 'timeseries', + source: 'roles.csv', + metric: 'active_contributors_by_role', + unit: 'Unique active contributors', + population: 'Each person is counted once per bucket, at their most senior role.', + dimensions: ['period', 'series'], + note: 'Read the bars left to right.', + methodology: ['Resolve roles per bucket.', 'Count each person once.'], + generated_at: '2026-07-25T10:00:00+00:00', + mark: 'bar', + stacked: true, + normalize: false, + orientation: 'vertical', + value_format: 'integer', + rank: false, + top_n: null, + reference: null, + category: { key: 'bucket', label: 'Period (UTC)' }, + series: [ + { key: 'general_user', label: 'General contributors', color: 'var(--chart-general)' }, + { key: 'triage', label: 'Triage', color: 'var(--chart-triage)' }, + { key: 'committer', label: 'Committers', color: 'var(--chart-committer)' }, + { key: 'maintainer', label: 'Maintainers', color: 'var(--chart-maintainer)' }, + ], + details: [], + frequency, + timezone: 'UTC', + comparison: null, + group: null, + window: { kind: 'calendar', first: buckets[0], last: buckets[buckets.length - 1] }, + rows: buckets.map((bucket, index) => ({ + bucket, + general_user: 40 + index, + triage: 3 + (index % 4), + committer: 10 + (index % 5), + maintainer: 12 + (index % 3), + partial: index === buckets.length - 1, + })), + }; +} + +const months = (count: number) => + Array.from({ length: count }, (_, index) => { + const month = 2026 * 12 + 6 - (count - 1) + index; + return `${Math.floor(month / 12)}-${String((month % 12) + 1).padStart(2, '0')}`; + }); + +/** Chart documents by API path; each pipeline variant draws from one of them. */ +export const PIPELINE_DOCS = { + 'hiero-ledger/charts/pipeline_yearly.json': pipelineDocument('pipeline_yearly', 'year', [ + '2021', + '2022', + '2023', + '2024', + '2025', + '2026', + ]), + 'hiero-ledger/charts/pipeline_monthly.json': pipelineDocument( + 'pipeline_monthly', + 'month', + months(24), + ), + 'hiero-ledger/charts/pipeline_next.json': pipelineDocument('pipeline_next', 'month', months(12)), +} satisfies Record<string, TimeseriesDocument>; + +const interactive = (path: keyof typeof PIPELINE_DOCS) => ({ kind: 'timeseries' as const, path }); + +const pipeline = MANIFEST.orgs['hiero-ledger'].chart_sections.find( + (section) => section.id === 'pipeline', +)!; +const [yearly, monthly] = pipeline.charts[0].variants; + +/** The first slide switches By year / By month; the second, off-screen slide has its own data. */ +const PIPELINE_CHARTS: ChartSpec[] = [ + { + ...pipeline.charts[0], + variants: [ + { ...yearly, interactive: interactive('hiero-ledger/charts/pipeline_yearly.json') }, + { ...monthly, interactive: interactive('hiero-ledger/charts/pipeline_monthly.json') }, + ], + }, + { + ...pipeline.charts[0], + title: 'The next pipeline chart', + wide: true, + variants: [{ ...monthly, interactive: interactive('hiero-ledger/charts/pipeline_next.json') }], + }, +]; + export const PRINT_MANIFEST: Manifest = { ...MANIFEST, orgs: { @@ -40,19 +145,7 @@ export const PRINT_MANIFEST: Manifest = { })), chart_sections: MANIFEST.orgs['hiero-ledger'].chart_sections.map((section) => section.id === 'pipeline' - ? { - ...section, - slideshow: true, - charts: [ - ...section.charts, - { - ...section.charts[0], - title: 'The next pipeline chart', - wide: true, - variants: [section.charts[0].variants[1]], - }, - ], - } + ? { ...section, slideshow: true, charts: PIPELINE_CHARTS } : section, ), }, @@ -61,6 +154,7 @@ export const PRINT_MANIFEST: Manifest = { export const PRINT_ROUTES: Record<string, unknown> = { ...ROUTES, + ...PIPELINE_DOCS, 'manifest.json': PRINT_MANIFEST, 'hiero-ledger/roles.json': PRINT_GOV_DOC, 'hiero-ledger/profiles.json': PRINT_CONTRIB_DOC, diff --git a/web/e2e/prepare.ts b/web/e2e/prepare.ts index 10a44571e..c699039d0 100644 --- a/web/e2e/prepare.ts +++ b/web/e2e/prepare.ts @@ -1,7 +1,7 @@ /** Stage the built app beside the same typed API fixtures used by Vitest. */ import { cp, mkdir, rm, writeFile, access } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; -import { PRINT_MANIFEST, PRINT_ROUTES } from './fixtures.ts'; +import { PIXEL_PNG, PRINT_MANIFEST, PRINT_ROUTES } from './fixtures.ts'; const site = resolve('.e2e/site'); await rm(site, { recursive: true, force: true }); @@ -18,17 +18,19 @@ for (const [path, value] of Object.entries(PRINT_ROUTES)) { await write(`data/api/v1/${path}`, typeof value === 'string' ? value : JSON.stringify(value)); } -// Images only need a real decoded pixel for loading/lightbox smoke checks. -// Layout and printed chart readability are also checked against real PNGs -// during the manual PDF review; no network is needed by this suite. -const png = Buffer.from( - 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGP4DwQACfsD/fteaysAAAAASUVORK5CYII=', - 'base64', -); +// The v1 manifest still names a PNG for every variant, so each one must exist +// on disk. The dashboard draws the interactive JSON documents instead; printed +// chart readability is reviewed against real data, as described in the print +// notes. No network is needed by this suite. for (const entry of Object.values(PRINT_MANIFEST.orgs)) { for (const section of entry.chart_sections) { for (const chart of section.charts) { - for (const variant of chart.variants) await write(variant.file, png); + for (const variant of chart.variants) { + await write(variant.file, PIXEL_PNG); + if (variant.interactive) { + await access(resolve(site, 'data/api/v1', variant.interactive.path)); + } + } } } // Every reference must resolve on disk. A new manifest entry without data diff --git a/web/e2e/print.spec.ts b/web/e2e/print.spec.ts index 021ed4a7e..cc0c4037c 100644 --- a/web/e2e/print.spec.ts +++ b/web/e2e/print.spec.ts @@ -1,8 +1,17 @@ import type { Locator, Page } from '@playwright/test'; -import { PRINT_CONTRIB_DOC, PRINT_GOV_DOC, PRINT_MANIFEST } from './fixtures'; +import { + PIPELINE_DOCS, + PRINT_CONTRIB_DOC, + PRINT_GOV_DOC, + PRINT_MANIFEST, + pipelineDocument, +} from './fixtures'; import { BOARD_DOC, HIP_EVIDENCE_DOC, MATRIX_DOC } from '../src/test/fixtures'; import { test, expect } from './browser'; +const CHART = 'Unique active contributors by role'; +const NEXT_CHART = 'The next pipeline chart'; + async function enterNativePrint(page: Page) { await page.evaluate(() => window.dispatchEvent(new Event('beforeprint'))); await page.emulateMedia({ media: 'print' }); @@ -20,6 +29,10 @@ async function openGovernance(page: Page) { await expect(page.locator('#roles')).toBeVisible(); } +/** A tab in the sidebar's page list. */ +const tab = (page: Page, name: string) => + page.getByRole('navigation', { name: 'Dashboard' }).getByRole('button', { name, exact: true }); + async function setScrollOffset(scroller: Locator, top: number, left: number) { await scroller.evaluate( (element, offset) => { @@ -36,30 +49,48 @@ const scrollOffset = (scroller: Locator) => left: Math.round(element.scrollLeft), })); -async function decodedCharts(page: Page) { - await expect - .poll(() => - page.locator('figure img').evaluateAll((images) => - images.every((image) => { - const img = image as HTMLImageElement; - return img.complete && img.naturalWidth > 0; - }), - ), - ) - .toBe(true); +/** Per pipeline figure: whether its Recharts SVG is laid out and has drawn bars. */ +const drawnCharts = (page: Page) => + page.locator('#pipeline figure').evaluateAll((figures) => + figures.map((figure) => { + const svg = figure.querySelector('[data-slot="chart"] svg.recharts-surface'); + const box = svg?.getBoundingClientRect(); + return Boolean( + box && box.width > 0 && box.height > 0 && svg!.querySelector('.recharts-bar-rectangle'), + ); + }), + ); + +/** Both slides printed as drawn charts: nothing still loading, nothing failed. */ +async function chartsReady(page: Page) { + await expect(page.locator('[data-print-pending]')).toHaveCount(0); + await expect(page.locator('[data-print-error]')).toHaveCount(0); + await expect.poll(() => drawnCharts(page)).toEqual([true, true]); } +interface PrintObservation { + pending: number; + failed: number; + drawn: number; + rowCount: number; +} + +/** Replace the native dialog with a probe of what the document holds when it would open. */ async function observePrintDialog(page: Page) { await page.addInitScript(() => { - const observed = window as unknown as Window & { - printObservations: { imagesReady: boolean; rowCount: number }[]; - }; + const observed = window as unknown as Window & { printObservations: PrintObservation[] }; observed.printObservations = []; window.print = () => { observed.printObservations.push({ - imagesReady: [...document.querySelectorAll<HTMLImageElement>('figure img')].every( - (image) => image.complete && image.naturalWidth > 0, - ), + pending: document.querySelectorAll('[data-print-pending]').length, + failed: document.querySelectorAll('[data-print-error]').length, + drawn: [...document.querySelectorAll('#pipeline figure')].filter((figure) => { + const svg = figure.querySelector('[data-slot="chart"] svg.recharts-surface'); + const box = svg?.getBoundingClientRect(); + return ( + box && box.width > 0 && box.height > 0 && svg!.querySelector('.recharts-bar-rectangle') + ); + }).length, rowCount: document.querySelectorAll('#roles tbody tr').length, }); }; @@ -69,13 +100,23 @@ async function observePrintDialog(page: Page) { const printCalls = (page: Page) => page.evaluate( () => - ( - window as unknown as Window & { - printObservations: { imagesReady: boolean; rowCount: number }[]; - } - ).printObservations, + (window as unknown as Window & { printObservations: PrintObservation[] }).printObservations, ); +/** Hold one chart document's response until the returned release is called. */ +async function holdChart(page: Page, file: string) { + let release: () => void = () => {}; + const pending = new Promise<void>((resolve) => (release = resolve)); + let requested: () => void = () => {}; + const started = new Promise<void>((resolve) => (requested = resolve)); + await page.route(`**/data/api/v1/hiero-ledger/charts/${file}`, async (route) => { + requested(); + await pending; + await route.continue(); + }); + return { release, started }; +} + test('native printing renders every virtualised row, opens sections, and restores screen state', async ({ page, browserErrors, @@ -83,24 +124,66 @@ test('native printing renders every virtualised row, opens sections, and restore await openGovernance(page); const rows = page.locator('#roles tbody tr'); expect(await rows.count()).toBeLessThan(PRINT_GOV_DOC.rows.length); - const group = page.locator('details.group').last(); - await group.locator(':scope > summary').click(); - await expect(group).not.toHaveAttribute('open', ''); + // The reader folds a whole group, one card, and leaves the glossary folded. + // The group's own trigger, not the sidebar's table-of-contents entry of the same name. + const groupToggle = page + .locator('[data-slot="collapsible-trigger"]') + .filter({ hasText: 'Roles & teams' }); + const group = groupToggle.locator('xpath=..'); + await groupToggle.click(); + await expect(group).toHaveAttribute('data-state', 'closed'); + await expect(page.locator('#roles')).toHaveCount(0); + await page.getByRole('button', { name: 'Collapse Maintainer pipeline', exact: true }).click(); + await expect(page.locator('#pipeline figure')).toHaveCount(0); + const glossary = page.getByText('pull requests opened;'); + await expect(glossary).toBeHidden(); await enterNativePrint(page); + await expect(page.locator('h2.print-group')).toHaveText(['Pipeline charts', 'Roles & teams']); await expect(rows).toHaveCount(PRINT_GOV_DOC.rows.length); await expect(page.getByRole('cell', { name: 'member-125', exact: true })).toBeVisible(); await expect(page.locator('#roles')).toBeVisible(); await expect(page.locator('#pipeline figure')).toHaveCount(2); - await decodedCharts(page); + await chartsReady(page); + // The explanations under each chart print open, as does the glossary. + await expect(page.locator('#pipeline').getByText('Resolve roles per bucket.')).toHaveCount(2); + for (const step of await page.locator('#pipeline').getByText('Resolve roles per bucket.').all()) { + await expect(step).toBeVisible(); + } + await expect(glossary).toBeVisible(); await leavePrint(page); - await expect(group).not.toHaveAttribute('open', ''); - await group.locator(':scope > summary').click(); + await expect(group).toHaveAttribute('data-state', 'closed'); + await expect(page.locator('#roles')).toHaveCount(0); + await expect( + page.getByRole('button', { name: 'Expand Maintainer pipeline', exact: true }), + ).toBeVisible(); + await expect(page.locator('#pipeline figure')).toHaveCount(0); + await expect(glossary).toBeHidden(); + await groupToggle.click(); + await expect(page.locator('#roles')).toBeVisible(); await expect.poll(() => rows.count()).toBeLessThan(PRINT_GOV_DOC.rows.length); expect(browserErrors).toEqual([]); }); +// The chart palette tokens (src/app.css) in each scheme, as computed colours. +const LIGHT_SERIES = [ + 'rgb(100, 116, 139)', + 'rgb(183, 121, 19)', + 'rgb(22, 133, 117)', + 'rgb(57, 117, 220)', +]; +const DARK_SERIES = [ + 'rgb(148, 163, 184)', + 'rgb(233, 180, 76)', + 'rgb(61, 197, 173)', + 'rgb(121, 168, 250)', +]; +const barFills = (page: Page) => + page + .locator('#pipeline [data-slot="chart"] .recharts-bar-rectangle path') + .evaluateAll((paths) => [...new Set(paths.map((path) => getComputedStyle(path).fill))].sort()); + test('print media hides controls, uses light colours, and keeps long cells inside the page', async ({ page, browserErrors, @@ -108,23 +191,34 @@ test('print media hides controls, uses light colours, and keeps long cells insid }, testInfo) => { await page.emulateMedia({ colorScheme: 'dark' }); await openGovernance(page); + await expect.poll(() => barFills(page)).toEqual([...DARK_SERIES].sort()); await enterNativePrint(page); - await decodedCharts(page); + await chartsReady(page); for (const locator of [ - page.locator('nav'), - page.locator('.jump'), + page.locator('header'), + page.locator('[data-slot="sidebar"]'), + page.getByRole('button', { name: 'Print tab', includeHidden: true }), page.getByRole('button', { name: 'Copy link', includeHidden: true }), page.getByRole('button', { name: 'Download CSV', includeHidden: true }), + page.getByRole('button', { name: /^Expand interactive chart/, includeHidden: true }), page.getByRole('textbox', { name: 'Filter rows', includeHidden: true }), - page.getByRole('group', { name: 'Time range', includeHidden: true }), + // Single-select toggle groups are radiogroups. + page.getByRole('radiogroup', { name: 'Time range', includeHidden: true }), + page.getByRole('radiogroup', { name: `${CHART} view`, includeHidden: true }), ]) { + expect(await locator.count()).toBeGreaterThan(0); for (const element of await locator.all()) await expect(element).toBeHidden(); } + // Only buttons that carry data (a legend entry, the glossary title) print, as text. + await expect(page.locator('button:not([data-print-keep])').locator('visible=true')).toHaveCount( + 0, + ); + await expect(page.locator('input').locator('visible=true')).toHaveCount(0); await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 255, 255)'); - for (const img of await page.locator('figure img').all()) { - await expect(img).toHaveCSS('filter', 'none'); - } + await expect(page.locator('body')).toHaveCSS('color', 'rgb(27, 27, 27)'); + // The charts draw in the light palette even though the reader's screen is dark. + expect(await barFills(page)).toEqual([...LIGHT_SERIES].sort()); await expect(page.locator('#roles tbody td').last()).toHaveCSS('white-space', 'normal'); const width = await page.locator('#roles').evaluate((section) => { const bounds = section.getBoundingClientRect(); @@ -159,8 +253,10 @@ test('large tables print a deliberate row cap and an explicit omission notice', const printed = await page.locator('#profiles tbody tr:not([hidden])').count(); expect(printed).toBe(500); expect(printed).toBeLessThan(PRINT_CONTRIB_DOC.row_count); - await expect(page.locator('#profiles')).toContainText(/500.*620/); - await expect(page.locator('#profiles')).toContainText(/omitted|truncated|remaining|not printed/i); + const notice = page.locator('#profiles [data-print-truncated]'); + await expect(notice).toBeVisible(); + await expect(notice).toContainText(/500.*620/); + await expect(notice).toContainText(/omitted|truncated|remaining|not printed/i); await expect(page.getByRole('cell', { name: 'contributor-0500', exact: true })).toBeVisible(); await expect(page.getByRole('cell', { name: 'contributor-0501', exact: true })).toHaveCount(0); }); @@ -181,7 +277,7 @@ test('printing a capped table preserves scrolling and focus beyond the printed r }), ); await page.goto('./#tab=Contributors'); - const scroller = page.locator('#profiles .tablewrap'); + const scroller = page.locator('#profiles [data-slot="table-container"]'); await setScrollOffset(scroller, 100000, 0); const link = scroller.locator('a[href="https://example.test/profile/619"]'); await link.focus(); @@ -219,8 +315,9 @@ test('a page opened with print media active prepares all rows and slideshow char await page.goto('./#tab=Governance'); await expect(page.locator('#roles')).toBeVisible(); await expect(page.locator('html')).toHaveAttribute('data-printing', 'true'); - await expect(page.locator('#pipeline figure').nth(1)).toBeVisible(); + await expect(page.getByRole('figure', { name: `${NEXT_CHART} — By month` })).toBeVisible(); await expect(page.locator('#roles tbody tr')).toHaveCount(PRINT_GOV_DOC.row_count); + await chartsReady(page); }); test('printing respects the selected period and keeps its label after hiding tabs', async ({ @@ -228,18 +325,16 @@ test('printing respects the selected period and keeps its label after hiding tab }) => { await openGovernance(page); const section = page.locator('#roles'); - await section.getByRole('button', { name: '1 month', exact: true }).click(); + const month = section.getByRole('radio', { name: '1 month', exact: true }); + await month.click(); await expect(section.getByRole('cell', { name: 'alice', exact: true })).toBeVisible(); await enterNativePrint(page); await expect(section.locator('tbody tr')).toHaveCount(1); await expect(section.getByRole('cell', { name: 'alice', exact: true })).toBeVisible(); await expect(section.getByText('Time range: 1 month', { exact: true })).toBeVisible(); - await expect(section.getByRole('button', { name: '1 month', includeHidden: true })).toBeHidden(); + await expect(section.getByRole('radio', { name: '1 month', includeHidden: true })).toBeHidden(); await leavePrint(page); - await expect(section.getByRole('button', { name: '1 month', exact: true })).toHaveAttribute( - 'aria-pressed', - 'true', - ); + await expect(month).toHaveAttribute('aria-checked', 'true'); }); test('a failed section remains an explicit gap in printed output', async ({ page }) => { @@ -281,12 +376,19 @@ test('printing preserves filtered and sorted rows and makes the filter explicit' await section.getByRole('textbox', { name: 'Filter rows' }).fill('member-12'); await expect(section.locator('tbody tr')).toHaveCount(6); await section.getByRole('button', { name: 'count', exact: true }).click(); + await expect(section.getByRole('columnheader', { name: 'count' })).toHaveAttribute( + 'aria-sort', + /ascending|descending/, + ); const order = await section.locator('tbody tr td:first-child').allTextContents(); await enterNativePrint(page); await expect(section.locator('tbody tr')).toHaveCount(6); expect(await section.locator('tbody tr td:first-child').allTextContents()).toEqual(order); await expect(section).toContainText(/filter.*member-12/i); + // The header keeps its label on paper while the sort button is hidden. + await expect(section.getByRole('columnheader', { name: 'count' })).toBeVisible(); + await expect(section.getByRole('button', { name: 'count', includeHidden: true })).toBeHidden(); await leavePrint(page); await expect(section.getByRole('textbox', { name: 'Filter rows' })).toHaveValue('member-12'); @@ -305,46 +407,60 @@ test('printing preserves filtered and sorted rows and makes the filter explicit' test('the selected role variant remains selected in the printed table', async ({ page }) => { await page.goto('./#tab=Diversity'); const section = page.locator('#affiliations'); - await section.getByRole('button', { name: 'Committers', exact: true }).click(); + const committers = section.getByRole('radio', { name: 'Committers', exact: true }); + await committers.click(); await expect(section.getByRole('cell', { name: 'dave', exact: true })).toBeVisible(); await enterNativePrint(page); await expect(section.locator('tbody tr')).toHaveCount(1); await expect(section.getByRole('cell', { name: 'dave', exact: true })).toBeVisible(); await expect(section.getByRole('cell', { name: 'alice', exact: true })).toHaveCount(0); + await expect(section.getByText('Role: Committers', { exact: true })).toBeVisible(); await leavePrint(page); - await expect(section.getByRole('button', { name: 'Committers', exact: true })).toHaveAttribute( - 'aria-pressed', - 'true', - ); + await expect(committers).toHaveAttribute('aria-checked', 'true'); }); -test('Print tab waits for all slideshow charts to decode before opening the dialog', async ({ +test('the selected chart variant is the one printed, and says so', async ({ page }) => { + await openGovernance(page); + const pipeline = page.locator('#pipeline'); + const month = pipeline.getByRole('radio', { name: 'By month', exact: true }); + await month.click(); + await expect(page.getByRole('figure', { name: `${CHART} — By month` })).toBeVisible(); + await enterNativePrint(page); + await chartsReady(page); + await expect(page.getByRole('figure', { name: `${CHART} — By month` })).toBeVisible(); + await expect(page.getByRole('figure', { name: `${CHART} — By year` })).toHaveCount(0); + await expect(pipeline.getByText(`${CHART} view: By month`, { exact: true })).toBeVisible(); + await leavePrint(page); + await expect(month).toHaveAttribute('aria-checked', 'true'); +}); + +test('Print tab waits for every slideshow chart to load before opening the dialog', async ({ page, browserErrors, }) => { await observePrintDialog(page); - let release: () => void = () => {}; - const pending = new Promise<void>((resolve) => (release = resolve)); - let requested: () => void = () => {}; - const started = new Promise<void>((resolve) => (requested = resolve)); - await page.route('**/pipeline_monthly.png', async (route) => { - requested(); - await pending; - await route.continue(); - }); + // The second slide is off-screen: its data still has to arrive before printing. + const next = await holdChart(page, 'pipeline_next.json'); try { await page.goto('./#tab=Governance', { waitUntil: 'domcontentloaded' }); await expect(page.locator('#roles')).toBeVisible(); + await next.started; await page.getByRole('button', { name: 'Print tab', exact: true }).click(); - await started; await expect(page.locator('html')).toHaveAttribute('data-printing', 'true'); + await expect(page.getByRole('status').getByText('Loading charts for printing…')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Preparing print…' })).toBeDisabled(); + await expect( + page + .getByRole('figure', { name: `${NEXT_CHART} — By month` }) + .locator('[data-print-pending]'), + ).toBeVisible(); expect(await printCalls(page)).toHaveLength(0); } finally { - release(); + next.release(); } await expect .poll(() => printCalls(page)) - .toEqual([{ imagesReady: true, rowCount: PRINT_GOV_DOC.row_count }]); + .toEqual([{ pending: 0, failed: 0, drawn: 2, rowCount: PRINT_GOV_DOC.row_count }]); await leavePrint(page); await expect(page.getByRole('button', { name: 'Print tab', exact: true })).toBeEnabled(); expect(browserErrors).toEqual([]); @@ -354,18 +470,22 @@ test('an unavailable chart stops automatic printing and remains a named native-p page, }) => { await observePrintDialog(page); - await page.route('**/pipeline_monthly.png', (route) => + await page.route('**/data/api/v1/hiero-ledger/charts/pipeline_yearly.json', (route) => route.fulfill({ status: 503, body: 'Unavailable chart for test' }), ); await openGovernance(page); + const failed = page + .locator('#pipeline') + .getByRole('alert') + .filter({ hasText: `Could not load chart data: ${CHART} (By year).` }); + await expect(failed).toBeVisible(); await page.getByRole('button', { name: 'Print tab', exact: true }).click(); - const warning = page.getByText( - /chart.*unavailable|could not.*chart|chart.*could not|chart.*failed/i, - ); - await expect(warning.first()).toBeVisible(); + await expect(page.getByText('Could not prepare every chart for printing.')).toBeVisible(); + await expect(page.locator('html')).not.toHaveAttribute('data-printing', 'true'); expect(await printCalls(page)).toHaveLength(0); await enterNativePrint(page); - await expect(page.locator('#pipeline')).toContainText(/unavailable|could not|failed/i); + await expect(failed).toBeVisible(); + await expect(page.getByRole('button', { name: 'Retry chart', includeHidden: true })).toBeHidden(); await expect(page.locator('#roles tbody tr')).toHaveCount(PRINT_GOV_DOC.row_count); }); @@ -373,39 +493,35 @@ test('changing tabs cancels pending print preparation instead of printing the ne page, }) => { await observePrintDialog(page); - let release: () => void = () => {}; - const pending = new Promise<void>((resolve) => (release = resolve)); - await page.route('**/pipeline_monthly.png', async (route) => { - await pending; - await route.continue(); - }); + const next = await holdChart(page, 'pipeline_next.json'); try { await page.goto('./#tab=Governance', { waitUntil: 'domcontentloaded' }); await expect(page.locator('#roles')).toBeVisible(); await page.getByRole('button', { name: 'Print tab', exact: true }).click(); await expect(page.locator('html')).toHaveAttribute('data-printing', 'true'); - await page.getByRole('button', { name: 'Contributors', exact: true }).click(); + await tab(page, 'Contributors').click(); await expect(page.locator('#profiles')).toBeVisible(); await expect(page.locator('html')).not.toHaveAttribute('data-printing', 'true'); expect(await printCalls(page)).toHaveLength(0); } finally { - release(); + next.release(); } - // The old image can still finish after its component unmounts. Let that - // response and rendering frame settle before checking for a stale dialog. + // The old chart request can still finish after its component unmounts. Let + // that response and a rendering frame settle before checking for a stale dialog. await page.waitForLoadState('load'); await page.evaluate(() => new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))); expect(await printCalls(page)).toHaveLength(0); await expect(page.getByRole('button', { name: 'Print tab', exact: true })).toBeEnabled(); }); -test('native print restores keyboard focus to period, sort, KPI and role controls', async ({ +test('native print restores keyboard focus to period, sort, chart, KPI and role controls', async ({ page, }) => { await openGovernance(page); for (const control of [ - page.locator('#roles').getByRole('button', { name: '1 month', exact: true }), + page.locator('#roles').getByRole('radio', { name: '1 month', exact: true }), page.locator('#roles').getByRole('button', { name: 'count', exact: true }), + page.locator('#pipeline').getByRole('radio', { name: 'By month', exact: true }), page.getByRole('button', { name: /maintainers 103/i }), ]) { await control.focus(); @@ -414,10 +530,10 @@ test('native print restores keyboard focus to period, sort, KPI and role control await leavePrint(page); await expect(control).toBeFocused(); } - await page.getByRole('button', { name: 'Diversity', exact: true }).click(); + await tab(page, 'Diversity').click(); const role = page .locator('#affiliations') - .getByRole('button', { name: 'Committers', exact: true }); + .getByRole('radio', { name: 'Committers', exact: true }); await role.focus(); await expect(role).toBeFocused(); await enterNativePrint(page); @@ -425,26 +541,19 @@ test('native print restores keyboard focus to period, sort, KPI and role control await expect(role).toBeFocused(); }); -test('native print preserves table, wide-chart and period-tab scrolling', async ({ page }) => { - const org = PRINT_MANIFEST.orgs['hiero-ledger']; - const periods = { '30d': '1 month', '90d': '3 months', '180d': '6 months', '365d': '1 year' }; +test('native print preserves table, chart-data and period-tab scrolling', async ({ page }) => { + // Enough windows that the period tabs overflow and scroll at phone width. + const periods = { + '7d': '1 week', + '14d': '2 weeks', + '30d': '1 month', + '90d': '3 months', + '180d': '6 months', + '365d': '1 year', + '730d': '2 years', + }; await page.route('**/manifest.json', (route) => - route.fulfill({ - json: { - ...PRINT_MANIFEST, - period_labels: periods, - orgs: { - ...PRINT_MANIFEST.orgs, - 'hiero-ledger': { - ...org, - // A gallery's wide chart scrolls; the slideshow scales its images to fit. - chart_sections: org.chart_sections.map((section) => - section.id === 'pipeline' ? { ...section, slideshow: false } : section, - ), - }, - }, - }, - }), + route.fulfill({ json: { ...PRINT_MANIFEST, period_labels: periods } }), ); await page.route('**/hiero-ledger/roles.json', (route) => route.fulfill({ @@ -454,34 +563,35 @@ test('native print preserves table, wide-chart and period-tab scrolling', async }, }), ); - // The shared one-pixel image cannot exercise a genuinely wide chart layout. - await page.route('**/pipeline_monthly.png', (route) => - route.fulfill({ - contentType: 'image/svg+xml', - body: '<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="400"><rect width="1600" height="400" fill="white"/></svg>', - }), + // Enough years for the chart's Data view to scroll inside its own box. + const years = Array.from({ length: 30 }, (_, index) => String(1997 + index)); + await page.route('**/charts/pipeline_yearly.json', (route) => + route.fulfill({ json: pipelineDocument('pipeline_yearly', 'year', years) }), ); await page.setViewportSize({ width: 390, height: 844 }); await openGovernance(page); - await decodedCharts(page); - const table = page.locator('#roles .tablewrap'); - const chart = page.locator('#pipeline .chartscroll'); - const periodTabs = page.locator('#roles').getByRole('group', { name: 'Time range' }); + const figure = page.getByRole('figure', { name: `${CHART} — By year` }); + await figure.getByRole('radio', { name: 'Data', exact: true }).click(); + const table = page.locator('#roles [data-slot="table-container"]'); + const chartData = figure.locator('[data-slot="table-container"]'); + const periodTabs = page.locator('#roles').getByRole('radiogroup', { name: 'Time range' }); + await expect(chartData.locator('tbody tr')).toHaveCount(years.length); await table.scrollIntoViewIfNeeded(); - await setScrollOffset(table, 700, 200); - await setScrollOffset(chart, 0, 200); + await setScrollOffset(table, 600, 200); + await setScrollOffset(chartData, 300, 100); await setScrollOffset(periodTabs, 0, 40); - const offsets = () => Promise.all([table, chart, periodTabs].map(scrollOffset)); + const offsets = () => Promise.all([table, chartData, periodTabs].map(scrollOffset)); const expected = [ - { top: 700, left: 200 }, - { top: 0, left: 200 }, + { top: 600, left: 200 }, + { top: 300, left: 100 }, { top: 0, left: 40 }, ]; await expect.poll(offsets).toEqual(expected); await enterNativePrint(page); await expect(page.locator('#roles tbody tr')).toHaveCount(PRINT_GOV_DOC.row_count); - await expect(chart).toHaveCSS('overflow-x', 'visible'); + await expect(chartData).toHaveCSS('overflow-x', 'visible'); + await expect(chartData.locator('tbody tr')).toHaveCount(years.length); await expect(periodTabs).toBeHidden(); await leavePrint(page); await expect.poll(offsets).toEqual(expected); @@ -526,7 +636,7 @@ test('native print preserves matrix and nested board scrolling', async ({ page } ); await page.setViewportSize({ width: 390, height: 844 }); await page.goto('./#tab=HIPs'); - const matrixScroller = page.locator('.hipmx-wrap'); + const matrixScroller = page.locator('#hip-matrix .hipmx-wrap'); const board = page.locator('#hip-board .hipboard'); const chips = page.locator('#hip-board .hipboard-chips').first(); await matrixScroller.scrollIntoViewIfNeeded(); @@ -549,8 +659,9 @@ test('native print preserves matrix and nested board scrolling', async ({ page } test('native print preserves open chart and metric explanations', async ({ page }) => { const org = PRINT_MANIFEST.orgs['hiero-ledger']; + // Long enough that both dialogs scroll, even the explanation's at phone height. const methodology = Array.from( - { length: 20 }, + { length: 40 }, (_, index) => `Step ${index + 1}: count the matching contributors.`, ); await page.route('**/manifest.json', (route) => @@ -561,10 +672,6 @@ test('native print preserves open chart and metric explanations', async ({ page ...PRINT_MANIFEST.orgs, 'hiero-ledger': { ...org, - chart_sections: org.chart_sections.map((section) => ({ - ...section, - charts: section.charts.map((chart) => ({ ...chart, methodology })), - })), metrics: { ...org.metrics, Governance: org.metrics!.Governance.map((tile) => ({ ...tile, methodology })), @@ -574,33 +681,46 @@ test('native print preserves open chart and metric explanations', async ({ page }, }), ); + const yearly = PIPELINE_DOCS['hiero-ledger/charts/pipeline_yearly.json']; + await page.route('**/charts/pipeline_yearly.json', (route) => + route.fulfill({ json: { ...yearly, methodology } }), + ); await page.setViewportSize({ width: 390, height: 844 }); await openGovernance(page); - for (const opener of [ - page.locator('#pipeline figure img').first(), + const openers = [ + page + .getByRole('figure', { name: `${CHART} — By year` }) + .getByRole('button', { name: `Expand interactive chart: ${CHART}` }), page.getByRole('button', { name: /maintainers 103/i }), - ]) { + ]; + for (const opener of openers) { await opener.click(); const dialog = page.getByRole('dialog'); + await expect(dialog).toBeVisible(); + // The expanded chart explains itself in a <details>; the tile's dialog lists its steps. const details = dialog.locator('details'); - const summary = details.locator('summary'); - await summary.focus(); - await summary.press('Enter'); - await expect(details).toHaveAttribute('open', ''); - const caption = dialog.locator('.lbcap'); - await setScrollOffset(caption, 100, 0); - await expect.poll(() => scrollOffset(caption)).toEqual({ top: 100, left: 0 }); + if (await details.count()) { + const summary = details.locator('summary'); + await summary.focus(); + await summary.press('Enter'); + await expect(details).toHaveAttribute('open', ''); + } + await expect(dialog.getByText('Step 40: count the matching contributors.')).toBeVisible(); + // Focus first: focusing the close button scrolls it (top of the dialog) into view. const close = dialog.getByRole('button', { name: 'Close', exact: true }); await close.focus(); + await setScrollOffset(dialog, 100, 0); + await expect.poll(() => scrollOffset(dialog)).toEqual({ top: 100, left: 0 }); await enterNativePrint(page); - await expect(page.locator('.lightbox')).toBeHidden(); + await expect(dialog).toBeHidden(); + // The key that dismisses the print dialog must not also close this one. await page.keyboard.press('Escape'); await leavePrint(page); await expect(dialog).toBeVisible(); - await expect(details).toHaveAttribute('open', ''); + if (await details.count()) await expect(details).toHaveAttribute('open', ''); await expect(close).toBeFocused(); - await expect.poll(() => scrollOffset(caption)).toEqual({ top: 100, left: 0 }); + await expect.poll(() => scrollOffset(dialog)).toEqual({ top: 100, left: 0 }); await page.keyboard.press('Escape'); await expect(dialog).toHaveCount(0); } @@ -620,17 +740,20 @@ test('native print restores an open evidence panel and keeps raw numbers unbroke ); await page.goto('./#tab=HIPs'); await page.locator('#hip-matrix').getByRole('button', { name: '3', exact: true }).click(); - const panel = page.locator('.hipev'); + const heading = page.getByRole('heading', { name: 'HIP-1200 · hiero-ledger/consensus' }); + const panel = heading.locator('xpath=../..'); + await expect(panel).toBeVisible(); const list = panel.locator('ol'); await setScrollOffset(list, 200, 0); await expect.poll(() => scrollOffset(list)).toEqual({ top: 200, left: 0 }); const close = panel.getByRole('button', { name: 'Close', exact: true }); await close.focus(); const number = page.locator('#hip-evidence').getByRole('cell', { name: '1000', exact: true }); - await expect(number).toHaveCSS('text-align', 'center'); + await expect(number).toBeVisible(); await enterNativePrint(page); await expect(panel).toBeHidden(); + // Numbers read right-aligned on paper and never wrap mid-figure. await expect(number).toHaveCSS('text-align', 'right'); await expect(number).toHaveCSS('white-space', 'nowrap'); await page.keyboard.press('Escape'); diff --git a/web/e2e/smoke.spec.ts b/web/e2e/smoke.spec.ts index 60ef53887..86dfbb5b8 100644 --- a/web/e2e/smoke.spec.ts +++ b/web/e2e/smoke.spec.ts @@ -6,7 +6,8 @@ test('the built app boots offline under a subpath and each macro renders', async browserErrors, }) => { await page.goto('./'); - await expect(page.getByRole('heading', { name: 'Hiero — analytics dashboard' })).toBeVisible(); + await expect(page).toHaveTitle('Hiero — analytics dashboard'); + const pages = page.getByRole('navigation', { name: 'Dashboard' }); const macros = new Set( Object.values(MANIFEST.orgs).flatMap((entry) => [ ...entry.sections.map((section) => section.macro), @@ -15,15 +16,22 @@ test('the built app boots offline under a subpath and each macro renders', async ]), ); for (const macro of macros) { - await page.getByRole('button', { name: macro, exact: true }).click(); + await pages.getByRole('button', { name: macro, exact: true }).click(); + await expect(page.getByRole('heading', { level: 1, name: macro, exact: true })).toBeVisible(); await expect(page.getByRole('table').first()).toBeVisible(); await expect(page.getByRole('table').first().locator('tbody tr').first()).toBeVisible(); } - await page.getByRole('button', { name: 'Governance', exact: true }).click(); - const chart = page.getByRole('img', { name: 'Unique active contributors by role' }); - await chart.click(); - await expect(page.getByRole('dialog')).toBeVisible(); + await pages.getByRole('button', { name: 'Governance', exact: true }).click(); + // Charts draw from their JSON documents with Recharts, not from the PNGs. + const figure = page.getByRole('figure', { name: 'Unique active contributors by role — By year' }); + await expect(figure.locator('svg.recharts-surface').first()).toBeVisible(); + await figure + .getByRole('button', { name: 'Expand interactive chart: Unique active contributors by role' }) + .click(); + const dialog = page.getByRole('dialog'); + await expect(dialog).toBeVisible(); + await expect(dialog.locator('svg.recharts-surface').first()).toBeVisible(); await page.keyboard.press('Escape'); - await expect(page.getByRole('dialog')).toHaveCount(0); + await expect(dialog).toHaveCount(0); expect(browserErrors).toEqual([]); }); diff --git a/web/index.html b/web/index.html index e4929f8f5..6dfe95bb7 100644 --- a/web/index.html +++ b/web/index.html @@ -4,7 +4,7 @@ <meta charset="UTF-8" /> <!-- The dashboard is self-contained: its bundle, JSON, and chart PNGs are all - same-origin, so nothing legitimate needs an outside origin. 'unsafe-inline' + same-origin; only GitHub avatars come from outside. 'unsafe-inline' is granted to styles only (React writes inline style attributes); scripts get no such exemption. Note frame-ancestors cannot be set from a meta tag — clickjacking protection would need a response header, which GitHub Pages @@ -12,9 +12,12 @@ --> <meta http-equiv="Content-Security-Policy" - content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self'; object-src 'none'; base-uri 'none'; form-action 'none'" + content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://github.com https://avatars.githubusercontent.com; connect-src 'self'; font-src 'self'; object-src 'none'; base-uri 'none'; form-action 'none'" /> - <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> + <link rel="icon" type="image/svg+xml" href="/brand/hiero-mark.svg" /> + <!-- Render-blocking on purpose: the CSP allows no inline script, and a + deferred one would flash the OS theme before the stored choice. --> + <script src="/theme-init.js"></script> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Hiero — analytics dashboard diff --git a/web/package-lock.json b/web/package-lock.json index d1d1e0a57..5429eff92 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,12 +8,20 @@ "name": "web", "version": "0.0.0", "dependencies": { + "@fontsource-variable/bricolage-grotesque": "^5.3.0", + "@fontsource-variable/public-sans": "^5.3.0", "@tailwindcss/vite": "^4.3.3", "@tanstack/react-table": "^9.2.4", "@tanstack/react-virtual": "^3.14.13", + "class-variance-authority": "^0.7.1", + "cn": "^0.4.0", + "lucide-react": "^1.48.0", + "radix-ui": "^1.6.7", "react": "^19.2.7", "react-dom": "^19.3.0", - "tailwindcss": "^4.3.3" + "recharts": "^3.8.0", + "tailwindcss": "^4.3.3", + "tw-animate-css": "^1.4.0" }, "devDependencies": { "@playwright/test": "^1.63.0", @@ -27,6 +35,7 @@ "jsdom": "^30.1.1", "oxlint": "^1.85.0", "prettier": "^3.9.9", + "shadcn": "^4.21.0", "typescript": "~7.0.2", "vite": "^8.3.1", "vitest": "^5.0.2" @@ -78,7 +87,6 @@ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", @@ -88,17 +96,386 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.9.tgz", + "integrity": "sha512-FFwIwzU+7SCOuxxV4YtJql6T9981ZVTm+FHO5GhVsRqCTdy0WwrEZh3l42ARpXruJUDGOptdduHW6Zr7pNPLNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", @@ -109,6 +486,54 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -262,13 +687,218 @@ "node": ">=20.19.0" } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "license": "MIT", - "optional": true, - "dependencies": { + "node_modules/@dotenvx/dotenvx": { + "version": "1.75.1", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.75.1.tgz", + "integrity": "sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@dotenvx/primitives": "^0.8.0", + "commander": "^11.1.0", + "conf": "^10.2.0", + "dotenv": "^17.2.1", + "enquirer": "^2.4.1", + "env-paths": "^2.2.1", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "open": "^8.4.2", + "picomatch": "^4.0.4", + "systeminformation": "^5.22.11", + "undici": "^7.11.0", + "which": "^4.0.0", + "yocto-spinner": "^1.1.0" + }, + "bin": { + "dotenvx": "src/cli/dotenvx.js" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/undici": { + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", + "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/@dotenvx/primitives": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@dotenvx/primitives/-/primitives-0.8.0.tgz", + "integrity": "sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } @@ -311,6 +941,75 @@ } } }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@fontsource-variable/bricolage-grotesque": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/bricolage-grotesque/-/bricolage-grotesque-5.3.0.tgz", + "integrity": "sha512-TLi9Q4hJjS2UvoTMRSS2nHu6c4R56lAw60NR9QYtVRCHn0XtsFpiEhNffZ8Glsoxu6wEEwLKBP8lb94J52PNBA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/public-sans": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/public-sans/-/public-sans-5.3.0.tgz", + "integrity": "sha512-AVfkmAt50BMXWpOO21FAntiJFKGX6xTc2dSL8dxtDteONe9IuRXJWGbs0EbG955vAMCq23ENeuopuW87cGWDSQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@hono/node-server": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.3.tgz", + "integrity": "sha512-TA//nWMqPhbfdfneACk6t5a9eqbS9lABEPyKn0/xZTah3H3U2XaVg85rJFl0/Fyit0I552YDHgXGVSf3GwqbUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -356,6 +1055,47 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.31.0.tgz", + "integrity": "sha512-UvTMgnNlnIBO/22ob2RcVGDlcvOslQs8T59+FTGdA0L27a39fdGF/EDETNtDVK4DZGpwomlsYpRdA8UXcVL/pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -374,6 +1114,44 @@ "@emnapi/runtime": "^1.7.1" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@oxc-project/types": { "version": "0.151.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz", @@ -746,2417 +1524,7527 @@ "node": ">=20" } }, - "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", - "integrity": "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "node_modules/@radix-ui/number": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", + "license": "MIT" }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.11.tgz", - "integrity": "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==", - "cpu": [ - "arm64" - ], + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.15.tgz", + "integrity": "sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.11.tgz", - "integrity": "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==", - "cpu": [ - "arm64" - ], + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.20.tgz", + "integrity": "sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.11.tgz", - "integrity": "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==", - "cpu": [ - "x64" - ], + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.23.tgz", + "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==", "license": "MIT", - "optional": true, - "os": [ + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.15.tgz", + "integrity": "sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.6.tgz", + "integrity": "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.11.tgz", + "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz", + "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.7.tgz", + "integrity": "sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.16.tgz", + "integrity": "sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-label": "2.1.15", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.23.tgz", + "integrity": "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz", + "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.24.tgz", + "integrity": "sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.22", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.22.tgz", + "integrity": "sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.16.tgz", + "integrity": "sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.11.tgz", + "integrity": "sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-is-hydrated": "0.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.16.tgz", + "integrity": "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.7.tgz", + "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", + "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz", + "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.7.tgz", + "integrity": "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz", + "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.23.tgz", + "integrity": "sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.18.tgz", + "integrity": "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.19.tgz", + "integrity": "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.19.tgz", + "integrity": "sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-separator": "1.1.15", + "@radix-ui/react-toggle-group": "1.1.19" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.5.tgz", + "integrity": "sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", + "license": "MIT" + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.13.0.tgz", + "integrity": "sha512-f6jwVRfwW30+tnllz4YcsHHpPYYOE7abcHkyUCovD+P7DyhdAI5prz2Fh/8TLhakjpN+7aIEion1s5L/tvlYlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", + "integrity": "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.11.tgz", + "integrity": "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.11.tgz", + "integrity": "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.11.tgz", + "integrity": "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.11.tgz", + "integrity": "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.11.tgz", + "integrity": "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.11.tgz", + "integrity": "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.11.tgz", + "integrity": "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.11.tgz", + "integrity": "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.11.tgz", + "integrity": "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.11.tgz", + "integrity": "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.11.tgz", + "integrity": "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.11.tgz", + "integrity": "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.11.tgz", + "integrity": "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.11.tgz", + "integrity": "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.11.1.tgz", + "integrity": "sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.11.1", + "use-sync-external-store": "^1.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "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" + } + }, + "node_modules/@tanstack/react-table": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-9.2.4.tgz", + "integrity": "sha512-Rzp1Q4e0/nIgEjmISYR5HeEgLTNtrG+C7NFZf/AbCxPO5hg+zC3yNGwcoECigUk8SFzzvhXbd2rFdtP2ZiGrfA==", + "license": "MIT", + "dependencies": { + "@tanstack/react-store": "^0.11.1", + "@tanstack/table-core": "9.2.4" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.14.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.13.tgz", + "integrity": "sha512-JbDTAwtzZ99aOeCrAfW5EsE5KSq5RWh6Af2dtFwyLIIk48Ja7vm6n4axu/43T3vfjFnEGJalxJ1wyYjSQD6bSg==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "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" + } + }, + "node_modules/@tanstack/store": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.11.1.tgz", + "integrity": "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/table-core": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-9.2.4.tgz", + "integrity": "sha512-GwdDyGGr6UXAtubF14yAwcXvdaogqfsQgIk89Suebjxob/Rjq2xvfmXsjDF1rQmKmhHsJm9TOY7myxv3i6geJw==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "^0.11.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.11", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.11.tgz", + "integrity": "sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.1.tgz", + "integrity": "sha512-oMDTC3oA+6CXSO2JZnvOI7CA6oVub6kij5ggk9ohwye5slmkwxYDXcPOVxgMw/RQlticjtO0C1RZkR97HgrWMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=22", + "npm": ">=6", + "yarn": ">=1" + }, + "peerDependencies": { + "@testing-library/dom": ">=10 <11", + "vitest": ">= 0.32" + }, + "peerDependenciesMeta": { + "vitest": { + "optional": true + } + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.3.tgz", + "integrity": "sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.7", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz", + "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", + "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.3", + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz", + "integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@types/validate-npm-package-name": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", + "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.2.tgz", + "integrity": "sha512-Z5FS00Q1SJHkB35xATsmWGdQ5WA1/0MV3CDjqyv7GavHv1OfOj145MNfHOlHk7QLes21dKFDHr8EO2zvL+9WGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.2", + "estree-walker": "^3.0.3", + "magic-string": "^1.2.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.2.tgz", + "integrity": "sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.2.tgz", + "integrity": "sha512-Ijc7T1nT9efNb5LxvjaBrEqw3f/QwUv5EE0nKqZxgqsaV/FxAAZ8baGylA8X/Z2oS4Lp+K74Jr6dTJsDKxJDeg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.3.tgz", + "integrity": "sha512-FvWoWYfSCM6kRxCSH+MGLHIKKGRL6A6AW7Zek2O32REPQRdg131428uRTKMBYAeRd3XXAaHDS60Wpri7CdKDrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.26", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.26.tgz", + "integrity": "sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.1.0.tgz", + "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001813", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001813.tgz", + "integrity": "sha512-zfjJo4rM0+fUomGDBW/xcDjhIwz/210DGvip2MAMDZ8KHcRPnOHmEgHPZP0UHlZoxr8fYKVJOqcORhYQcG4FKQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cn": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cn/-/cn-0.4.0.tgz", + "integrity": "sha512-qOAeUhPwPzBxx6jaeQdo646XIKliKHc2j1ZF7Ngb1o0pJZKH5gq8FkA0aVdUdPFUDXNXyUCXpCtoJISo6dhizg==", + "license": "MIT", + "bin": { + "cn": "bin/cn.mjs" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/conf": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz", + "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.6.3", + "ajv-formats": "^2.1.1", + "atomically": "^1.7.0", + "debounce-fn": "^4.0.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "json-schema-typed": "^7.0.3", + "onetime": "^5.1.2", + "pkg-up": "^3.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conf/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/conf/node_modules/json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/conf/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.440", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.440.tgz", + "integrity": "sha512-SghDzqdJokdz8zP8YNlvS74+CwLRoUPDGvP/gFA+HrKVw+pOshUAgx8pXR37xl/u16zGae746rFNWvZ/22kU2Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.24.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz", + "integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ "darwin" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuzzysort": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", + "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.11.tgz", - "integrity": "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==", - "cpu": [ - "x64" - ], + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.11.tgz", - "integrity": "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==", - "cpu": [ - "arm" - ], + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.11.tgz", - "integrity": "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6" } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.11.tgz", - "integrity": "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.11.tgz", - "integrity": "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==", - "cpu": [ - "ppc64" - ], - "libc": [ - "glibc" - ], + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 0.4" } }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.11.tgz", - "integrity": "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==", - "cpu": [ - "s390x" - ], - "libc": [ - "glibc" - ], + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.13.11", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.11.tgz", + "integrity": "sha512-/SMX/RQNJn7oNmFwH6DtwDqcrzZU2otm1FD6OJe2cWF6cfy/F8hq0XVBv7xW3FTJshRQwuBnXHDq2kNsdZnshg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-7.0.0.tgz", + "integrity": "sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "11.1.18", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.18.tgz", + "integrity": "sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ip-address": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.11.tgz", - "integrity": "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=0.10.0" } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.11.tgz", - "integrity": "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=0.10.0" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.11.tgz", - "integrity": "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==", - "cpu": [ - "arm64" - ], + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.11.tgz", - "integrity": "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==", - "cpu": [ - "arm64" - ], + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.11.tgz", - "integrity": "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==", - "cpu": [ - "x64" - ], + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "license": "MIT" + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } }, - "node_modules/@tailwindcss/node": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", - "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.24.1", - "jiti": "^2.7.0", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.3.3" + "engines": { + "node": ">=8" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 12.0.0" - }, + "node": ">=18" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "dev": true, + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/panva" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } ], - "engines": { - "node": ">= 12.0.0" + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/jsdom": { + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.1.tgz", + "integrity": "sha512-FahmoPK5vbPc+jxV1iErMHmAZypCZ942NHF4+qqaWAuvaKKTBZxawnmAtrbGWLU7MtlxfqIP0qw6aSI+aWGtLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^7.0.0", + "@asamuzakjp/dom-selector": "^9.2.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.13", + "@exodus/bytes": "^1.15.1", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^7.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "tough-cookie": "^6.0.2", + "undici": "^8.10.2", + "w3c-xmlserializer": "^6.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.1", + "xml-name-validator": "^5.0.0" + }, "engines": { - "node": ">= 12.0.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.1.tgz", + "integrity": "sha512-ohjk1mdUebJVadRt3bAhQhx8lSnISq+GDttK79LFl8EHQkAPvzwctoasC4hs8tBt6kLAncBWWyq1N52qEfKvDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, "engines": { - "node": ">= 12.0.0" + "node": "^22.14.0 || >=24.0.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">=6" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=6" } }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "detect-libc": "^2.0.3" + }, "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", - "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", - "license": "MIT", - "engines": { - "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.3", - "@tailwindcss/oxide-darwin-arm64": "4.3.3", - "@tailwindcss/oxide-darwin-x64": "4.3.3", - "@tailwindcss/oxide-freebsd-x64": "4.3.3", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", - "@tailwindcss/oxide-linux-x64-musl": "4.3.3", - "@tailwindcss/oxide-wasm32-wasi": "4.3.3", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", - "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "android" ], "engines": { - "node": ">= 20" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", - "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">= 20" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", - "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">= 20" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", - "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">= 20" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", - "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 20" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", - "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 20" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", - "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 20" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", - "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 20" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", - "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", - "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.11.1", - "@emnapi/runtime": "^1.11.1", - "@emnapi/wasi-threads": "^1.2.2", - "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.2", - "tslib": "^2.8.1" + "node": ">= 12.0.0" }, - "engines": { - "node": ">=14.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", - "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "win32" ], "engines": { - "node": ">= 20" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", - "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "win32" ], "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", - "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.3.3", - "@tailwindcss/oxide": "4.3.3", - "tailwindcss": "4.3.3" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" - } - }, - "node_modules/@tanstack/react-store": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.11.1.tgz", - "integrity": "sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==", - "license": "MIT", - "dependencies": { - "@tanstack/store": "0.11.1", - "use-sync-external-store": "^1.6.0" + "node": ">= 12.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "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" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tanstack/react-table": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-9.2.4.tgz", - "integrity": "sha512-Rzp1Q4e0/nIgEjmISYR5HeEgLTNtrG+C7NFZf/AbCxPO5hg+zC3yNGwcoECigUk8SFzzvhXbd2rFdtP2ZiGrfA==", - "license": "MIT", - "dependencies": { - "@tanstack/react-store": "^0.11.1", - "@tanstack/table-core": "9.2.4" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": ">=18" - } + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" }, - "node_modules/@tanstack/react-virtual": { - "version": "3.14.13", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.13.tgz", - "integrity": "sha512-JbDTAwtzZ99aOeCrAfW5EsE5KSq5RWh6Af2dtFwyLIIk48Ja7vm6n4axu/43T3vfjFnEGJalxJ1wyYjSQD6bSg==", + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.17.11" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "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" - } - }, - "node_modules/@tanstack/store": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.11.1.tgz", - "integrity": "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" + "engines": { + "node": ">=6" } }, - "node_modules/@tanstack/table-core": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-9.2.4.tgz", - "integrity": "sha512-GwdDyGGr6UXAtubF14yAwcXvdaogqfsQgIk89Suebjxob/Rjq2xvfmXsjDF1rQmKmhHsJm9TOY7myxv3i6geJw==", + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, "license": "MIT", "dependencies": { - "@tanstack/store": "^0.11.1" + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" }, "engines": { - "node": ">=20" + "node": ">=18" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tanstack/virtual-core": { - "version": "3.17.11", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.11.tgz", - "integrity": "sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g==", + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, "license": "MIT", + "engines": { + "node": ">=12" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lucide-react": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.48.0.tgz", + "integrity": "sha512-R0CIKY/fXiC6y9xRBADgsK+VW2p/pcTJOMhLZf1T+uG+vJUvyUR02nGOgmIIC7MPkiNK4Ox8QDnbqF8rJYWPZQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/@testing-library/jest-dom": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.1.tgz", - "integrity": "sha512-oMDTC3oA+6CXSO2JZnvOI7CA6oVub6kij5ggk9ohwye5slmkwxYDXcPOVxgMw/RQlticjtO0C1RZkR97HgrWMw==", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", - "dependencies": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "picocolors": "^1.1.1", - "redent": "^3.0.0" - }, "engines": { - "node": ">=22", - "npm": ">=6", - "yarn": ">=1" - }, - "peerDependencies": { - "@testing-library/dom": ">=10 <11", - "vitest": ">= 0.32" - }, - "peerDependenciesMeta": { - "vitest": { - "optional": true - } + "node": ">= 0.4" } }, - "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, - "license": "MIT" + "license": "CC0-1.0" }, - "node_modules/@testing-library/react": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.3.tgz", - "integrity": "sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==", + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, "engines": { - "node": ">=18" + "node": ">= 0.8" }, - "peerDependencies": { - "@testing-library/dom": "^10.0.0", - "@types/react": "^18.0.0 || ^19.0.0", - "@types/react-dom": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@testing-library/user-event": { - "version": "14.6.7", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz", - "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==", + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", "engines": { - "node": ">=12", - "npm": ">=6" + "node": ">=18" }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">= 8" + } }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" } }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz", - "integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==", - "devOptional": true, "license": "MIT", - "dependencies": { - "undici-types": "~8.9.0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@types/react": { - "version": "19.3.0", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", - "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", "dependencies": { - "csstype": "^3.2.2" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@types/react-dom": { - "version": "19.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", - "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", "dev": true, "license": "MIT", - "peerDependencies": { - "@types/react": "^19.3.0" + "engines": { + "node": ">=8" } }, - "node_modules/@typescript/typescript-aix-ppc64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", - "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", - "cpu": [ - "ppc64" - ], + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "aix" - ], + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript/typescript-darwin-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", - "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", - "cpu": [ - "arm64" - ], + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">=4" } }, - "node_modules/@typescript/typescript-darwin-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", - "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", - "cpu": [ - "x64" - ], + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, "engines": { - "node": ">=16.20.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript/typescript-freebsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", - "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", - "cpu": [ - "arm64" - ], + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=16.20.0" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript/typescript-freebsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", - "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", - "cpu": [ - "x64" - ], + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": ">=16.20.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/@typescript/typescript-linux-arm": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", - "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", - "cpu": [ - "arm" - ], + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, "engines": { - "node": ">=16.20.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@typescript/typescript-linux-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", - "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", - "cpu": [ - "arm64" - ], + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@typescript/typescript-linux-loong64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", - "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", - "cpu": [ - "loong64" - ], + "node_modules/node-releases": { + "version": "2.0.57", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz", + "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-linux-mips64el": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", - "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", - "cpu": [ - "mips64el" - ], + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, "engines": { - "node": ">=16.20.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript/typescript-linux-ppc64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", - "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", - "cpu": [ - "ppc64" - ], + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript/typescript-linux-riscv64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", - "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", - "cpu": [ - "riscv64" - ], + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">=0.10.0" } }, - "node_modules/@typescript/typescript-linux-s390x": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", - "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", - "cpu": [ - "s390x" - ], + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript/typescript-linux-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", - "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", - "cpu": [ - "x64" - ], + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">= 10" } }, - "node_modules/@typescript/typescript-netbsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", - "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", - "cpu": [ - "arm64" - ], + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "netbsd" + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" ], + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">=12.20.0" } }, - "node_modules/@typescript/typescript-netbsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", - "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", - "cpu": [ - "x64" - ], + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "netbsd" - ], + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, "engines": { - "node": ">=16.20.0" + "node": ">= 0.8" } }, - "node_modules/@typescript/typescript-openbsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", - "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", - "cpu": [ - "arm64" - ], + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=16.20.0" + "license": "ISC", + "dependencies": { + "wrappy": "1" } }, - "node_modules/@typescript/typescript-openbsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", - "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", - "cpu": [ - "x64" - ], + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "openbsd" - ], + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">=6" } }, - "node_modules/@typescript/typescript-sunos-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", - "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", - "cpu": [ - "x64" - ], + "node_modules/open": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", + "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "sunos" - ], + "license": "MIT", + "dependencies": { + "default-browser": "^5.5.1", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.2.1", + "wsl-utils": "^1.0.0" + }, "engines": { - "node": ">=16.20.0" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript/typescript-win32-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", - "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", - "cpu": [ - "arm64" - ], + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, "engines": { - "node": ">=16.20.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript/typescript-win32-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", - "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", - "cpu": [ - "x64" - ], + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], + "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@vitejs/plugin-react": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", - "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "^1.0.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "oxc-transform-react": "^0.145.0", - "vite": "^8.0.0" + "node": ">=12" }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "oxc-transform-react": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@vitest/mocker": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.2.tgz", - "integrity": "sha512-Z5FS00Q1SJHkB35xATsmWGdQ5WA1/0MV3CDjqyv7GavHv1OfOj145MNfHOlHk7QLes21dKFDHr8EO2zvL+9WGA==", + "node_modules/oxlint": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.85.0.tgz", + "integrity": "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.31", - "@vitest/spy": "5.0.2", - "estree-walker": "^3.0.3", - "magic-string": "^1.2.3" + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.85.0", + "@oxlint/binding-android-arm64": "1.85.0", + "@oxlint/binding-darwin-arm64": "1.85.0", + "@oxlint/binding-darwin-x64": "1.85.0", + "@oxlint/binding-freebsd-x64": "1.85.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", + "@oxlint/binding-linux-arm-musleabihf": "1.85.0", + "@oxlint/binding-linux-arm64-gnu": "1.85.0", + "@oxlint/binding-linux-arm64-musl": "1.85.0", + "@oxlint/binding-linux-ppc64-gnu": "1.85.0", + "@oxlint/binding-linux-riscv64-gnu": "1.85.0", + "@oxlint/binding-linux-riscv64-musl": "1.85.0", + "@oxlint/binding-linux-s390x-gnu": "1.85.0", + "@oxlint/binding-linux-x64-gnu": "1.85.0", + "@oxlint/binding-linux-x64-musl": "1.85.0", + "@oxlint/binding-openharmony-arm64": "1.85.0", + "@oxlint/binding-win32-arm64-msvc": "1.85.0", + "@oxlint/binding-win32-ia32-msvc": "1.85.0", + "@oxlint/binding-win32-x64-msvc": "1.85.0" }, "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "oxlint-tsgolint": ">=7.0.2001", + "vite-plus": "*" }, "peerDependenciesMeta": { - "msw": { + "oxlint-tsgolint": { "optional": true }, - "vite": { + "vite-plus": { "optional": true } } }, - "node_modules/@vitest/mocker/node_modules/magic-string": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.2.tgz", - "integrity": "sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==", + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.6.0" - } - }, - "node_modules/@vitest/spy": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.2.tgz", - "integrity": "sha512-Ijc7T1nT9efNb5LxvjaBrEqw3f/QwUv5EE0nKqZxgqsaV/FxAAZ8baGylA8X/Z2oS4Lp+K74Jr6dTJsDKxJDeg==", - "dev": true, - "license": "MIT", + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "p-limit": "^2.0.0" + }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" + "node": ">=6" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/bidi-js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.1.0.tgz", - "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { - "require-from-string": "^2.0.2" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, "license": "MIT", "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" + "entities": "^8.0.0" }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true, - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" - }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">= 0.8" } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", "dev": true, "license": "MIT" }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "dev": true, "license": "MIT", - "peer": true - }, - "node_modules/enhanced-resolve": { - "version": "5.24.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz", - "integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=12" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/es-module-lexer": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", - "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/expect-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", - "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, "engines": { - "node": ">=12.0.0" + "node": ">=20" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "engines": { + "node": ">=20" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": "^10 || ^12 || >=14" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/html-encoding-sniffer": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-7.0.0.tgz", - "integrity": "sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==", + "node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", "dev": true, "license": "MIT", "dependencies": { - "@exodus/bytes": "^1.15.1" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": "^22.13.0 || >=24.0.0" + "node": ">=4" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/powershell-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "node_modules/prettier": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", "dev": true, - "license": "MIT" - }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", "bin": { - "jiti": "lib/jiti-cli.mjs" + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } }, - "node_modules/jsdom": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.1.tgz", - "integrity": "sha512-FahmoPK5vbPc+jxV1iErMHmAZypCZ942NHF4+qqaWAuvaKKTBZxawnmAtrbGWLU7MtlxfqIP0qw6aSI+aWGtLg==", + "node_modules/pretty-ms": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz", + "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^7.0.0", - "@asamuzakjp/dom-selector": "^9.2.1", - "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.13", - "@exodus/bytes": "^1.15.1", - "css-tree": "^3.2.1", - "data-urls": "^7.0.0", - "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^7.0.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.5.2", - "parse5": "^8.0.1", - "saxes": "^6.0.0", - "tough-cookie": "^6.0.2", - "undici": "^8.10.2", - "w3c-xmlserializer": "^6.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^17.1.1", - "xml-name-validator": "^5.0.0" + "parse-ms": "^4.0.0" }, "engines": { - "node": "^22.22.2 || ^24.15.0 || >=26.0.0" - }, - "peerDependencies": { - "canvas": "^3.2.3" + "node": ">=18" }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jsdom/node_modules/whatwg-url": { - "version": "17.1.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.1.tgz", - "integrity": "sha512-ohjk1mdUebJVadRt3bAhQhx8lSnISq+GDttK79LFl8EHQkAPvzwctoasC4hs8tBt6kLAncBWWyq1N52qEfKvDw==", + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, "license": "MIT", "dependencies": { - "@exodus/bytes": "^1.15.1", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" }, "engines": { - "node": "^22.14.0 || >=24.0.0" + "node": ">= 6" } }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "license": "MPL-2.0", + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "dev": true, + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.3" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.10" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" + "url": "https://opencollective.com/express" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=6" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=0.6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } ], - "engines": { - "node": ">= 12.0.0" + "license": "MIT" + }, + "node_modules/radix-ui": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.7.tgz", + "integrity": "sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-accessible-icon": "1.1.15", + "@radix-ui/react-accordion": "1.2.20", + "@radix-ui/react-alert-dialog": "1.1.23", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-aspect-ratio": "1.1.15", + "@radix-ui/react-avatar": "1.2.6", + "@radix-ui/react-checkbox": "1.3.11", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-context-menu": "2.3.7", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-dropdown-menu": "2.1.24", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-form": "0.1.16", + "@radix-ui/react-hover-card": "1.1.23", + "@radix-ui/react-label": "2.1.15", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-menubar": "1.1.24", + "@radix-ui/react-navigation-menu": "1.2.22", + "@radix-ui/react-one-time-password-field": "0.1.16", + "@radix-ui/react-password-toggle-field": "0.1.11", + "@radix-ui/react-popover": "1.1.23", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-progress": "1.1.16", + "@radix-ui/react-radio-group": "1.4.7", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-scroll-area": "1.2.18", + "@radix-ui/react-select": "2.3.7", + "@radix-ui/react-separator": "1.1.15", + "@radix-ui/react-slider": "1.4.7", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-switch": "1.3.7", + "@radix-ui/react-tabs": "1.1.21", + "@radix-ui/react-toast": "1.2.23", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-toggle-group": "1.1.19", + "@radix-ui/react-toolbar": "1.1.19", + "@radix-ui/react-tooltip": "1.2.16", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-escape-keydown": "1.1.5", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">= 0.6" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/express" } }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">= 0.10" } }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "react": "^19.3.0" } }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } } }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=10" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=10" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/recast": { + "version": "0.23.21", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.21.tgz", + "integrity": "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">= 4" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" + "node_modules/recharts": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", + "license": "MIT", + "workspaces": [ + "www" ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^11.1.8", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.2.0", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/lru-cache": { - "version": "11.5.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", - "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, "engines": { - "node": "20 || >=22" + "node": ">=8" } }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", "license": "MIT", - "peer": true, - "bin": { - "lz-string": "bin/bin.js" + "peerDependencies": { + "redux": "^5.0.0" } }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "dev": true, - "license": "CC0-1.0" + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/obug": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", - "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, "engines": { - "node": ">=12.20.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/oxlint": { - "version": "1.85.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.85.0.tgz", - "integrity": "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg==", + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", + "integrity": "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.151.0", + "@rolldown/pluginutils": "^1.0.0" + }, "bin": { - "oxlint": "bin/oxlint" + "rolldown": "bin/cli.mjs" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, - "funding": { - "url": "https://github.com/sponsors/oxc-project" - }, "optionalDependencies": { - "@oxlint/binding-android-arm-eabi": "1.85.0", - "@oxlint/binding-android-arm64": "1.85.0", - "@oxlint/binding-darwin-arm64": "1.85.0", - "@oxlint/binding-darwin-x64": "1.85.0", - "@oxlint/binding-freebsd-x64": "1.85.0", - "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", - "@oxlint/binding-linux-arm-musleabihf": "1.85.0", - "@oxlint/binding-linux-arm64-gnu": "1.85.0", - "@oxlint/binding-linux-arm64-musl": "1.85.0", - "@oxlint/binding-linux-ppc64-gnu": "1.85.0", - "@oxlint/binding-linux-riscv64-gnu": "1.85.0", - "@oxlint/binding-linux-riscv64-musl": "1.85.0", - "@oxlint/binding-linux-s390x-gnu": "1.85.0", - "@oxlint/binding-linux-x64-gnu": "1.85.0", - "@oxlint/binding-linux-x64-musl": "1.85.0", - "@oxlint/binding-openharmony-arm64": "1.85.0", - "@oxlint/binding-win32-arm64-msvc": "1.85.0", - "@oxlint/binding-win32-ia32-msvc": "1.85.0", - "@oxlint/binding-win32-x64-msvc": "1.85.0" + "@rolldown/binding-android-arm-eabi": "1.2.11", + "@rolldown/binding-android-arm64": "1.2.11", + "@rolldown/binding-darwin-arm64": "1.2.11", + "@rolldown/binding-darwin-x64": "1.2.11", + "@rolldown/binding-freebsd-x64": "1.2.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", + "@rolldown/binding-linux-arm64-gnu": "1.2.11", + "@rolldown/binding-linux-arm64-musl": "1.2.11", + "@rolldown/binding-linux-ppc64-gnu": "1.2.11", + "@rolldown/binding-linux-s390x-gnu": "1.2.11", + "@rolldown/binding-linux-x64-gnu": "1.2.11", + "@rolldown/binding-linux-x64-musl": "1.2.11", + "@rolldown/binding-openharmony-arm64": "1.2.11", + "@rolldown/binding-win32-arm64-msvc": "1.2.11", + "@rolldown/binding-win32-x64-msvc": "1.2.11" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, - "peerDependencies": { - "oxlint-tsgolint": ">=7.0.2001", - "vite-plus": "*" + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "oxlint-tsgolint": { - "optional": true + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "vite-plus": { - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", - "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, "license": "MIT", "dependencies": { - "entities": "^8.0.0" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" }, "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, "engines": { - "node": ">=12" + "node": ">= 18" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/playwright": { - "version": "1.63.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", - "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, - "license": "Apache-2.0", + "license": "ISC" + }, + "node_modules/shadcn": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.21.0.tgz", + "integrity": "sha512-UU2mFNusW8C5rvadKdH69vERYZqUlOOlXBcf0MYhYLdTGP6DPti7X4qovCu+RTfCqsAgq/T+YfE0Vnttxh9aiw==", + "dev": true, + "license": "MIT", "dependencies": { - "playwright-core": "1.63.0" + "@babel/core": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/plugin-transform-typescript": "^7.28.0", + "@babel/preset-typescript": "^7.27.1", + "@dotenvx/dotenvx": "^1.48.4", + "@modelcontextprotocol/sdk": "^1.26.0", + "@types/validate-npm-package-name": "^4.0.2", + "browserslist": "^4.26.2", + "cn": "^0.2.4", + "commander": "^14.0.0", + "cosmiconfig": "^9.0.0", + "dedent": "^1.6.0", + "deepmerge": "^4.3.1", + "diff": "^8.0.2", + "execa": "^9.6.0", + "fast-glob": "^3.3.3", + "fs-extra": "^11.3.1", + "fuzzysort": "^3.1.0", + "kleur": "^4.1.5", + "open": "^11.0.0", + "ora": "^8.2.0", + "postcss": "^8.5.6", + "postcss-selector-parser": "^7.1.0", + "prompts": "^2.4.2", + "recast": "^0.23.11", + "socks": "^2.8.8", + "stringify-object": "^5.0.0", + "ts-morph": "^26.0.0", + "tsconfig-paths": "^4.2.0", + "undici": "^7.27.2", + "validate-npm-package-name": "^7.0.1", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.6" }, "bin": { - "playwright": "cli.js" + "shadcn": "dist/index.js" }, "engines": { - "node": ">=20" + "node": ">=20.18.1" } }, - "node_modules/playwright-core": { - "version": "1.63.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", - "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "node_modules/shadcn/node_modules/cn": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/cn/-/cn-0.2.6.tgz", + "integrity": "sha512-+i4L0zUGgRcEnhsxueVrP7iBGxBx5iD0WOTYg1MwFEu2ZyCmH5Ov2V1cul2Ht5UchRQQgftCf4be/RxspuW6QQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "bin": { - "playwright-core": "cli.js" + "cn": "bin/cn.mjs" }, "engines": { "node": ">=20" } }, - "node_modules/postcss": { - "version": "8.5.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", - "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/shadcn/node_modules/undici": { + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", + "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { - "nanoid": "^3.3.18", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "shebang-regex": "^3.0.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=8" } }, - "node_modules/prettier": { - "version": "3.9.9", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", - "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/react": { - "version": "19.3.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", - "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 6.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/react-dom": { - "version": "19.3.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", - "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "node_modules/socks": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "dev": true, "license": "MIT", "dependencies": { - "scheduler": "^0.28.0" + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" }, - "peerDependencies": { - "react": "^19.3.0" + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">= 0.8" + } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/rolldown": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", - "integrity": "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==", + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.151.0", - "@rolldown/pluginutils": "^1.0.0" + "ansi-regex": "^6.2.2" }, - "bin": { - "rolldown": "bin/cli.mjs" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stringify-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", + "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-keys": "^1.0.0", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14.16" }, - "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.11", - "@rolldown/binding-android-arm64": "1.2.11", - "@rolldown/binding-darwin-arm64": "1.2.11", - "@rolldown/binding-darwin-x64": "1.2.11", - "@rolldown/binding-freebsd-x64": "1.2.11", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", - "@rolldown/binding-linux-arm64-gnu": "1.2.11", - "@rolldown/binding-linux-arm64-musl": "1.2.11", - "@rolldown/binding-linux-ppc64-gnu": "1.2.11", - "@rolldown/binding-linux-s390x-gnu": "1.2.11", - "@rolldown/binding-linux-x64-gnu": "1.2.11", - "@rolldown/binding-linux-x64-musl": "1.2.11", - "@rolldown/binding-openharmony-arm64": "1.2.11", - "@rolldown/binding-win32-arm64-msvc": "1.2.11", - "@rolldown/binding-win32-x64-msvc": "1.2.11" + "funding": { + "url": "https://github.com/yeoman/stringify-object?sponsor=1" + } + }, + "node_modules/stringify-object/node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "xmlchars": "^2.2.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=v12.22.7" + "node": ">=8" } }, - "node_modules/scheduler": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", - "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", - "license": "MIT" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/std-env": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", - "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/strip-indent": { "version": "3.0.0", @@ -3171,6 +9059,33 @@ "node": ">=8" } }, + "node_modules/systeminformation": { + "version": "5.33.14", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.33.14.tgz", + "integrity": "sha512-9liS0hutibZOitbibm90AK3WZe/1rGhNGZSw+/4TbY5moax6KkWwv1TKJmk9uSOD4bvPPg57VAuuNN+4edIpBg==", + "dev": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, "node_modules/tailwindcss": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", @@ -3190,6 +9105,12 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinybench": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", @@ -3246,6 +9167,29 @@ "dev": true, "license": "MIT" }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tough-cookie": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", @@ -3272,12 +9216,79 @@ "node": ">=20" } }, + "node_modules/ts-morph": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", + "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.27.0", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/typescript": { "version": "7.0.2", @@ -3331,6 +9342,113 @@ "devOptional": true, "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -3340,6 +9458,55 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "8.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.1.tgz", @@ -3558,6 +9725,22 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/why-is-node-running": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.2.tgz", @@ -3571,6 +9754,43 @@ "node": ">=20.11" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/wsl-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.0.tgz", + "integrity": "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -3587,6 +9807,62 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-spinner": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.2.2.tgz", + "integrity": "sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } } } } diff --git a/web/package.json b/web/package.json index 8ae287fba..0dad2b51b 100644 --- a/web/package.json +++ b/web/package.json @@ -18,12 +18,20 @@ "nanoid": "3.3.18" }, "dependencies": { + "@fontsource-variable/bricolage-grotesque": "^5.3.0", + "@fontsource-variable/public-sans": "^5.3.0", "@tailwindcss/vite": "^4.3.3", "@tanstack/react-table": "^9.2.4", "@tanstack/react-virtual": "^3.14.13", + "class-variance-authority": "^0.7.1", + "cn": "^0.4.0", + "lucide-react": "^1.48.0", + "radix-ui": "^1.6.7", "react": "^19.2.7", "react-dom": "^19.3.0", - "tailwindcss": "^4.3.3" + "recharts": "^3.8.0", + "tailwindcss": "^4.3.3", + "tw-animate-css": "^1.4.0" }, "devDependencies": { "@playwright/test": "^1.63.0", @@ -37,6 +45,7 @@ "jsdom": "^30.1.1", "oxlint": "^1.85.0", "prettier": "^3.9.9", + "shadcn": "^4.21.0", "typescript": "~7.0.2", "vite": "^8.3.1", "vitest": "^5.0.2" diff --git a/web/public/brand/README.md b/web/public/brand/README.md new file mode 100644 index 000000000..203c21248 --- /dev/null +++ b/web/public/brand/README.md @@ -0,0 +1,10 @@ +# Hiero brand assets + +Unmodified SVG artwork from the official Hiero website repository: +https://github.com/hiero-ledger/hiero-website/tree/main/public/images + +- `hiero-logo.svg`: `Hiero-Logo-wText.svg` +- `hiero-logo-dark.svg`: `Hiero-Logo-wText-white.svg` +- `hiero-mark.svg`: `Hiero-Favicon.svg` + +Retrieved September 26, 2026. Hiero branding remains the property of its respective owners. These files identify the Hiero project in its analytics dashboard. diff --git a/web/public/brand/hiero-logo-dark.svg b/web/public/brand/hiero-logo-dark.svg new file mode 100644 index 000000000..66478f9e9 --- /dev/null +++ b/web/public/brand/hiero-logo-dark.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + diff --git a/web/public/brand/hiero-logo.svg b/web/public/brand/hiero-logo.svg new file mode 100644 index 000000000..f46b556d3 --- /dev/null +++ b/web/public/brand/hiero-logo.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + diff --git a/web/public/brand/hiero-mark.svg b/web/public/brand/hiero-mark.svg new file mode 100644 index 000000000..b4039ba14 --- /dev/null +++ b/web/public/brand/hiero-mark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/web/public/favicon.svg b/web/public/favicon.svg deleted file mode 100644 index 0906f9ca5..000000000 --- a/web/public/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/web/public/theme-init.js b/web/public/theme-init.js new file mode 100644 index 000000000..a91bc832c --- /dev/null +++ b/web/public/theme-init.js @@ -0,0 +1,19 @@ +/* + * Applies a stored explicit theme before first paint, so a reader who chose + * light or dark never sees a flash of the OS theme. Loaded as an external + * file because the CSP forbids inline scripts (script-src 'self'). + * + * Mirrors src/theme.ts: same storage key, same values. No stored choice (or + * "system", or unreadable storage) leaves without data-theme, which + * means "follow the OS" to the stylesheet. + */ +(function () { + try { + var choice = window.localStorage.getItem('hiero-analytics-theme'); + if (choice === 'light' || choice === 'dark') { + document.documentElement.setAttribute('data-theme', choice); + } + } catch { + // Storage blocked (privacy mode, sandboxed frame): follow the OS. + } +})(); diff --git a/web/src/App.tsx b/web/src/App.tsx index 5eabea293..fe7b40d51 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,32 +1,58 @@ /** - * The analytics dashboard, driven entirely by the data-API manifest: metric - * tiles, the "how to read this" glossary, a jump bar over collapsible section - * groups, chart-section cards, then the table sections. + * The analytics dashboard, driven entirely by the data-API manifest: a sticky + * header, a sidebar of tabs, and the active tab's tiles, glossary and section groups. */ import { useEffect, useMemo, useRef, useState } from 'react'; +import { CircleAlertIcon, RotateCwIcon } from 'lucide-react'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Button } from '@/components/ui/button'; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; +import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from '@/components/ui/empty'; +import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; import { fetchManifest, type ChartSection, type Manifest } from './api'; +import { AppHeader, FreshnessStatus } from './components/AppHeader'; +import { OrgAvatar } from './components/OrgSwitcher'; +import { OrgContext } from './orgContext'; +import { PAGE_TITLE_ID } from './hooks/use-header-scroll'; +import { NARROW_HEADER, useMediaQuery } from './hooks/use-media-query'; +import { AppSidebar } from './components/AppSidebar'; import { ChartSectionCard } from './components/ChartSectionCard'; +import { SectionBoundary } from './components/ErrorBoundary'; import { Glossary } from './components/Glossary'; import { MetricTiles } from './components/MetricTiles'; import { ProvenanceFooter } from './components/ProvenanceFooter'; -import { SectionGroups, type Group } from './components/SectionGroups'; +import { SectionGroups } from './components/SectionGroups'; import { SectionTable } from './components/SectionTable'; import { Skeleton } from './components/Skeleton'; -import { TabBar } from './components/TabBar'; import { WipFooter } from './components/WipFooter'; -import { stamp } from './format'; +import { navModel, type NavModel } from './nav'; +import { tocEntries, type Group, type TocEntry } from './toc'; import { useHashState } from './useHashState'; +import { writeParams } from './urlState'; +import { FocusBar } from './components/FocusBar'; import { useSectionDocs } from './useSectionDocs'; import { useViewDocs } from './useViewDocs'; import { ViewCards } from './components/ViewCards'; -import { PrintControls, PrintProvider } from './printing'; import { PrintLayout } from './components/PrintFooter'; +import { PrintControls, PrintProvider } from './printing'; +import { stamp } from './format'; import './print.css'; const FLASH_MS = 1800; // shared link jump: flash the target for this long, then remove the highlight -function OrgPanel({ org, manifest, macro }: { org: string; manifest: Manifest; macro: string }) { +function OrgPanel({ + org, + manifest, + macro, + onToc, +}: { + org: string; + manifest: Manifest; + macro: string; + /** Reports this tab's table of contents to the sidebar. */ + onToc: (entries: TocEntry[]) => void; +}) { const entry = manifest.orgs[org]; // An absorbed section is a role variant another card renders as a tab. Its // document still exists (v1 may not withdraw an id), but its rows travel @@ -117,23 +143,36 @@ function OrgPanel({ org, manifest, macro }: { org: string; manifest: Manifest; m )} {groupCharts.map((section) => ( - + + + ))} {groupDocs.map((doc) => ( - + + + ))} , ], ]; }); + // Keyed by content so a re-render with the same groups doesn't re-report them. + const toc = tocEntries(groups); + const tocKey = JSON.stringify(toc); + useEffect(() => { + onToc(toc); + // eslint-disable-next-line react-hooks/exhaustive-deps -- tocKey is toc's identity + }, [tocKey, onToc]); + useEffect(() => () => onToc([]), [onToc]); + return ( <> + {/* Keyed by tab and org: navigating away cancels a pending preparation. */} {!settled && (

@@ -145,10 +184,16 @@ function OrgPanel({ org, manifest, macro }: { org: string; manifest: Manifest; m {/* A section that could not load leaves a named gap rather than blanking the tab — the rest of the page is still worth reading. */} {unavailable.length > 0 && ( -

- Could not load {unavailable.length === 1 ? 'this section' : 'these sections'}:{' '} - {unavailable.join(', ')}. Everything else on this tab is unaffected — reload to try again. -

+ + + + Could not load {unavailable.length === 1 ? 'this section' : 'these sections'}:{' '} + {unavailable.join(', ')}. + + + Everything else on this tab is unaffected — reload to try again. + + )} {settled ? : } @@ -158,70 +203,44 @@ function OrgPanel({ org, manifest, macro }: { org: string; manifest: Manifest; m /** Human-readable fatal error: retry button up front, raw cause tucked away. */ function FatalError({ message, onRetry }: { message: string; onRetry: () => void }) { return ( -
-

- Failed to load the dashboard data. This is usually temporary — try again in a moment. -

- -
- Error details -
{message}
-
-
+ + + Failed to load the dashboard data. + +

This is usually temporary — try again in a moment.

+ + + + + + +
{message}
+
+
+
+
); } -/** Everything that depends on a loaded manifest — tabs, org filter, panels, footer. */ +/** The active tab, for the org selected in the header. */ function Dashboard({ manifest, - macro, - setMacro, - org, - setOrg, + nav, + onToc, }: { manifest: Manifest; - macro: string; - setMacro: (value: string) => void; - org: string; - setOrg: (value: string) => void; + nav: NavModel; + onToc: (entries: TocEntry[]) => void; }) { - const orgs = Object.keys(manifest.orgs); - const derived = [ - ...new Set( - Object.values(manifest.orgs).flatMap((entry) => [ - ...(entry.sections ?? []).map((section) => section.macro), - ...(entry.chart_sections ?? []).map((section) => section.macro), - ...(entry.views ?? []).map((view) => view.macro), - ]), - ), - ]; - // The manifest's family order wins where it knows the macro; anything it - // doesn't list (older manifest, ad-hoc macro) keeps its derived position. - const declared = (manifest.macro_order ?? []).filter((name) => derived.includes(name)); - const macros = [...declared, ...derived.filter((name) => !declared.includes(name))]; - const activeMacro = macros.includes(macro) ? macro : macros[0]; - // Umbrella tabs: a macro with a parent renders as a sub-tab of that parent. - // The top bar shows one entry per umbrella (in content order); a second tab - // row appears for the active umbrella's members. The hash keeps storing the - // actual macro, so old links keep working. - const parents = manifest.macro_parents ?? {}; - const topOf = (name: string) => parents[name] ?? name; - const topTabs = [...new Set(macros.map(topOf))]; - const activeTop = topOf(activeMacro); - const subTabs = macros.filter((name) => parents[name] === activeTop); - // The org filter is global: it lists every org and the selection sticks as - // tabs change. A tab the selected org has no content for renders a short - // explanation (from the manifest) instead of a blank page, so absence reads - // as a property of the data rather than a bug. - const shownOrg = orgs.includes(org) ? org : orgs[0]; - const shownEntry = manifest.orgs[shownOrg]; - const orgHasMacro = - (shownEntry.sections ?? []).some((section) => section.macro === activeMacro) || - (shownEntry.chart_sections ?? []).some((section) => section.macro === activeMacro) || - (shownEntry.views ?? []).some((view) => view.macro === activeMacro); + const narrowHeader = useMediaQuery(NARROW_HEADER); + const { activeMacro, shownOrg, orgHasMacro } = nav; const glossary = orgHasMacro ? manifest.macro_glossaries?.[activeMacro] : undefined; + const dataAsOf = manifest.provenance.data_as_of; return ( @@ -232,39 +251,57 @@ function Dashboard({ Generated {stamp(manifest.generated_at)} UTC. Periods and filters are stated beside each table or chart.

-

- Generated {stamp(manifest.generated_at)} UTC · every table filters and sorts · click a chart - to enlarge. -

- {/* The org filter is the outermost scope — everything below it is "this - org's view" — so it sits above the content tabs. */} - {orgs.length > 1 && } - setMacro(macros.find((candidate) => topOf(candidate) === name) ?? name)} - kind="macro" - /> - {subTabs.length > 0 && ( - - )} +
+

+ + {shownOrg} + {nav.topOf(activeMacro) !== activeMacro && ( + <> + + {nav.topOf(activeMacro)} + + )} +

+

+ {activeMacro} +

+

+ {manifest.macro_summaries?.[activeMacro] ?? + 'Explore activity and insights across the Hiero ecosystem.'} +

+ {narrowHeader && dataAsOf && ( +
+ +
+ )} +
{/* Every macro ships its own explainer, listing only what that tab shows. It may be absent when a cached bundle meets an older manifest — degrade to no glossary, never a crash. */} {glossary && } + {orgHasMacro ? ( - + + + ) : ( + // Sized to read as information rather than an error. <> -

- {manifest.macro_absent_notes?.[activeMacro] ?? - `No ${activeMacro} data for ${shownOrg}.`} -

+ + + + No {activeMacro} data for {shownOrg} + + {manifest.macro_absent_notes?.[activeMacro] && ( + {manifest.macro_absent_notes[activeMacro]} + )} + + )} {/* One footer bar: WIP notice left, provenance right — same rule, same baseline. */} -
+
{manifest.wip !== false && }
@@ -300,33 +337,49 @@ export default function App() { setReloadKey((key) => key + 1); }; + const nav = manifest ? navModel(manifest, macro, org) : null; + const [toc, setToc] = useState([]); + return ( + // The header renders in every state; only the content beneath it changes shape. -
-

Hiero — analytics dashboard

- {/* The header renders in every state below; only the content beneath it - changes shape — chrome never pops in after the fact. */} - {error ? ( - - ) : !manifest ? ( - <> -

Loading…

-

- Incomplete document: dashboard data is still loading. Close print preview and wait for - the dashboard to finish loading. -

- - - ) : ( - - )} -
+ + { + writeParams({ focus: null }); + setOrg(next); + }} + toc={nav?.orgHasMacro ? toc : []} + onTab={setMacro} + dataAsOf={manifest?.provenance.data_as_of} + /> +
+ + {/* min-w-0: otherwise a wide table or nowrap stamp widens the page sideways. */} + +
+ {error ? ( + + ) : !manifest || !nav ? ( + <> +

+ Incomplete document: dashboard data is still loading. Close print preview and + wait for the dashboard to finish loading. +

+ + + ) : ( + + )} +
+
+
+
); } diff --git a/web/src/api.ts b/web/src/api.ts index e3021e275..3588e49b9 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -12,7 +12,16 @@ * compile-time error here and a test failure on the Python side. */ export type ColumnFormat = - 'hip' | 'date' | 'link' | 'evidence' | 'status' | 'flag' | 'presence' | 'number' | 'staleness'; + | 'hip' + | 'date' + | 'link' + | 'evidence' + | 'status' + | 'flag' + | 'presence' + | 'number' + | 'percent' + | 'staleness'; export interface ColumnSpec { key: string; @@ -41,13 +50,151 @@ export interface SectionRef { absorbed_by?: string; } +/** One drawn series; `color` is a hex value or a `var(--token)` reference. */ +export interface ChartSeries { + key: string; + label: string; + color: string; +} + +/** A value shown beside the series in the tooltip and table, never drawn. */ +export interface ChartDetail { + key: string; + label: string; + format: 'number' | 'decimal' | 'percent'; +} + +/** Fields every interactive chart document shares (see export/chart_data.py). */ +export interface ChartDocumentMeta { + schema_version: 1; + id: string; + org: string; + source: string; + metric: string; + unit: string; + /** Who is counted and how; always shown, independently of the chart note. */ + population: string; + /** What the rows can be sliced by; the UI offers nothing else. */ + dimensions: string[]; + note?: string; + methodology?: string[]; + generated_at?: string; + stale?: boolean; +} + +/** `trailing`: the `days` before `end`; `all`: every event; `snapshot`: the state at `end`. */ +export interface ChartWindow { + kind: 'snapshot' | 'all' | 'trailing'; + days: number | null; + end: string | null; +} + +/** Bars, lines and areas over periods or categories. */ +interface ChartDocumentBase extends ChartDocumentMeta { + /** `meter` (statuses, headline first) and `funnel` (stages in order) are single-series. */ + mark: 'bar' | 'line' | 'area' | 'meter' | 'funnel'; + stacked: boolean; + /** Draw each row as shares of its visible total; the table keeps counts. */ + normalize: boolean; + orientation: 'vertical' | 'horizontal'; + value_format: 'integer' | 'decimal'; + /** Re-rank rows by the series on show, rather than keeping the source order. */ + rank: boolean; + /** Rows charted before "Show all"; the data view always lists every row. */ + top_n: number | null; + reference: { value: number; label: string } | null; + /** A bounded scale's ceiling (e.g. 10 for a 0–10 score). */ + value_max?: number | null; + category: { key: string; label: string }; + series: ChartSeries[]; + details: ChartDetail[]; +} + +export interface TimeseriesDocument extends ChartDocumentBase { + kind: 'timeseries'; + mark: 'bar' | 'line' | 'area'; + /** `snapshot`: point-in-time measurements on the listed dates, never gap-filled. */ + frequency: 'year' | 'month' | 'week' | 'day' | 'snapshot'; + timezone: 'UTC'; + /** The last complete bucket and the one before it; null when no fair pair exists. */ + comparison: { current: string; previous: string } | null; + group: null; + window: { kind: 'calendar'; first: string | null; last: string | null }; + rows: (Row & { bucket: string; partial: boolean })[]; +} + +export interface CategoriesDocument extends ChartDocumentBase { + kind: 'categories'; + /** Rows split by a reader-selected value (e.g. a funnel cohort); never combined. */ + group: { key: string; label: string; default: string; values: string[] } | null; + window: ChartWindow; + rows: Row[]; +} + +/** A heatmap: one row per entity, one value per column, on an explicit linear scale. */ +export interface MatrixDocument extends ChartDocumentMeta { + kind: 'matrix'; + row: { key: string; label: string }; + sublabel: { key: string; label: string } | null; + /** A per-row summary (e.g. the six-month score), shown but never coloured. */ + total: { key: string; label: string } | null; + columns: { key: string; label: string }[]; + value_label: string; + /** Linear from `min` to `max`, drawn in `steps` equal shades. */ + scale: { min: number; max: number; steps: number }; + /** What a null cell means (e.g. "Not scored"); null when every cell has a value. */ + missing: string | null; + /** Row keys are GitHub logins: show avatars and profile links. */ + avatars: boolean; + top_n: number | null; + value_format: 'integer' | 'decimal'; + window: ChartWindow; + rows: Row[]; +} + +export interface NetworkNode { + id: string; + /** Members active in the repository recently: the bubble size. */ + active: number; + total: number; + category: string; + /** Precomputed layout position (y up). */ + x: number; + y: number; +} + +/** Repositories linked by shared members, with a precomputed layout. */ +export interface NetworkDocument extends ChartDocumentMeta { + kind: 'network'; + member_label: string; + categories: ChartSeries[]; + window: ChartWindow; + nodes: NetworkNode[]; + edges: { source: string; target: string; shared: number }[]; +} + +/** Individual timestamped events (releases) in a trailing window. */ +export interface EventsDocument extends ChartDocumentMeta { + kind: 'events'; + category: { key: string; label: string }; + label: { key: string; label: string }; + types: ChartSeries[]; + /** Row order for the timeline: busiest first. */ + categories: string[]; + window: ChartWindow; + /** Each has the category key, `time` (ISO UTC), the label key, and `type`. */ + rows: Row[]; +} + +export type SeriesDocument = TimeseriesDocument | CategoriesDocument; +export type ChartDocument = SeriesDocument | MatrixDocument | NetworkDocument | EventsDocument; + export interface ChartVariant { + interactive?: { kind: ChartDocument['kind']; path: string }; label: string; + /** The variant's PNG, which always exists (v1 lists no variant without one). The + * dashboard keys the variant by it and draws from `interactive` instead. */ file: string; - /** Intrinsic pixel size, when the emitter could read it — lets the browser - * reserve the image's box so loading charts don't shift the page. */ - width?: number; - height?: number; /** * This tab's own "how to read this" and derivation steps. A chart's tabs show * different populations (maintainers / committers) or different spans, so the @@ -210,6 +357,8 @@ export interface Manifest { macro_order?: string[]; /** Why a tab may be empty for an org — shown in place of a blank tab. */ macro_absent_notes?: Record; + /** Each tab's one-line purpose, shown under its title. */ + macro_summaries?: Record; /** Macro name -> ordered section-group names; each tab renders as this sequence. */ group_order?: Record; /** Display labels for rolling periods ("30d" -> "30 days"). */ @@ -267,10 +416,9 @@ export interface SectionDoc { variants?: SectionVariant[]; } -/** Deploy-relative roots: the app, the API, and the chart PNGs ship together. */ +/** Deploy-relative roots: the app and the API ship together. */ const BASE = import.meta.env.BASE_URL; export const API_ROOT = `${BASE}data/api/v1`; -export const chartUrl = (file: string): string => `${BASE}${file}`; async function getJson(url: string): Promise { const response = await fetch(url); diff --git a/web/src/app.css b/web/src/app.css index 026790cd7..65387f947 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -1,70 +1,92 @@ /* - * The dashboard stylesheet: Tailwind v4 utilities on top of the design the - * generated dashboard established. - * - * - Tailwind's preflight reset is deliberately NOT imported: the layout was - * built against browser defaults, and the reset would subtly change it. - * - The semantic palette below is the single source of colors. Dark mode is - * one token flip, not per-rule overrides. New components: compose Tailwind - * utilities from these tokens (bg-surface, text-muted, border-edge, …) and - * never introduce raw hex values. - * - The component classes further down are the pre-Tailwind vocabulary, - * kept verbatim (values unchanged) and migrated to utilities opportunistically. + * Tailwind v4 plus shadcn/ui. The semantic palette below is the single source + * of colour; dark mode is one token flip. shadcn's names are mapped onto it in + * `@theme inline` — never paste shadcn's default palette (two names collide in + * meaning). A forced theme is `data-theme` on , else the OS preference. */ -@layer theme, base, components, utilities; -@import 'tailwindcss/theme.css' layer(theme); -@import 'tailwindcss/utilities.css' layer(utilities); +@import 'tailwindcss'; +@import 'tw-animate-css'; +@import 'shadcn/tailwind.css'; +/* Fonts are self-hosted: the CSP allows fonts from 'self' only. */ +@import '@fontsource-variable/public-sans'; +/* Display face for navigation and headings; the opsz build serves both sizes. */ +@import '@fontsource-variable/bricolage-grotesque/opsz.css'; + +/* Dark = OS dark without an explicit light choice, or an explicit dark choice. + `:root` itself is matched too, so the palette's dark values can use it. + Screen only: paper always prints the light palette (see print.css). */ +@custom-variant dark { + @media screen and (prefers-color-scheme: dark) { + &:where(:root:not([data-theme='light']), :root:not([data-theme='light']) *) { + @slot; + } + } + @media screen { + &:where([data-theme='dark'], [data-theme='dark'] *) { + @slot; + } + } +} /* ---- Semantic palette ---------------------------------------------------- */ :root { color-scheme: light dark; +} +:root[data-theme='light'] { + color-scheme: light; +} +:root[data-theme='dark'] { + color-scheme: dark; +} - --page: #fafafa; /* page background */ - --ink: #1b1b1b; /* primary text */ - --muted: #666; /* secondary text */ - --soft: #888; /* tertiary text (badges, counts, labels) */ - --surface: #fff; /* cards, buttons, inputs */ - --raise: #f3f3f3; /* hovered surface */ - --edge: #e6e6e6; /* card & divider borders */ - --edge-input: #ddd; /* input & pill borders */ - --edge-strong: #ccc; /* small-button borders */ - --edge-faint: #eee; /* table wrap & image borders */ - --row-line: #f0f0f0; /* table row separators */ - --accent: #1b1b1b; /* active pill background */ - --on-accent: #fff; /* text on the active pill */ +:root { + /* Every text role is ≥ 4.5:1 (WCAG AA) on Paper and Sheet in both themes. */ + --header-h: 72px; + --brand: #a7184e; + --brand-soft: #faedf2; + --sidebar-surface: #f8f9fb; + --page: #f3f5f8; /* Paper: page ground */ + --surface: #fff; /* Sheet: cards, inputs, popovers */ + --ink: #15181c; /* Ink: primary text; also the active fill (--accent) */ + --muted: #59616c; /* Graphite: secondary text (5.9:1 on Paper) */ + --edge: #e2e5e9; /* Rule: card & divider borders */ + --link: #1d64c2; /* Signal: links, focus ring, the figure strip's rule (5.4:1) */ + + --soft: #69717c; /* tertiary text: counts, captions (4.6:1 on Paper, 4.9:1 on Sheet) */ + --raise: #eef0f3; /* hovered / secondary surface */ + --edge-input: #d5d9df; /* input & control borders */ + --edge-strong: #c3c8cf; /* emphasised borders (dashed empty cells) */ + --edge-faint: #e8ebee; /* scroll-box & chart-mat borders */ + --row-line: #eef0f3; /* table row separators */ + --accent: #15181c; /* active fill (shadcn: primary) */ + --on-accent: #fff; /* text on the active fill */ /* Semantic colors: one slot per meaning, dark values in the flip below. */ - --link: #2a78d6; /* links and link-styled cells */ - --link-ink: #255e9e; /* link-colored text that must read as text */ + --link-ink: #1b58a8; /* link-colored text that must read as text */ --ok: #0ca30c; /* positive dot/marker */ --ok-ink: #0a6b0a; /* positive text */ --warn: #fab219; /* warning dot/marker */ --warn-ink: #8a6200; /* warning text */ --neg: #b3261e; /* error text */ - /* Shape & motion: shared control geometry, one hover timing everywhere. */ - --r-card: 12px; - --r-control: 6px; - --r-frame: 8px; /* inputs, scroll boxes, and framed chart media */ - --r-pill: 999px; - --dur: 120ms; - --jump-h: 58px; /* sticky jump bar height + small scroll buffer */ - - /* Deliberately outside the light/dark flip below. - --chart-ground: chart PNGs carry a baked-in light background, so their - container must match it in either scheme or the image floats on a slab of - the wrong colour. It stops being a constant when the charts themselves - become theme-aware (see the chart legibility issue). - --lb-*: the lightbox is always dark on purpose, so its greys are scoped to - it rather than themed — three roles instead of the six one-off values - these replaced. */ - --chart-ground: #fff; - --lb-title: #fff; - --lb-text: #e2e2e2; - --lb-dim: #bbb; - --lb-bright: #eee; + /* shadcn derives its radius scale from this (lg = 8px). */ + --radius: 0.5rem; + /* Scroll offset for jump targets: the sticky header plus a buffer (plus the + group strip on phones, below). */ + --jump-h: 88px; + + --chart-general: #64748b; + --chart-triage: #b77913; + --chart-committer: #168575; + --chart-maintainer: #3975dc; + /* Fallback series colours (export/chart_data.py FALLBACK_COLORS). */ + --chart-1: #c2570c; + --chart-2: #0f7f86; + --chart-3: #6d4fc2; + --chart-4: #9a6a00; + --chart-5: #b3334d; /* HIP coverage-matrix heat ramp: more merged PRs = more ink. Mirrors HIP_EVIDENCE_RAMP (config/charts.py), which the same values reach the @@ -81,24 +103,44 @@ --heat-open-edge: #9ec5f4; --flash: #fff3d6; /* row highlight when the board jumps to a HIP */ } - -@media screen and (prefers-color-scheme: dark) { +@media (max-width: 767px) { :root { - --page: #0f0f0f; - --ink: #e6e6e6; - --muted: #999; - --soft: #888; - --surface: #1a1a1a; - --raise: #262626; - --edge: #2a2a2a; - --edge-input: #333; - --edge-strong: #333; - --edge-faint: #333; - --row-line: #222; - --accent: #e6e6e6; - --on-accent: #0f0f0f; + --jump-h: 124px; + } +} + +/* The dark flip, written once: the `dark` variant emits it for both the OS + preference and an explicit choice, so the two cannot drift apart. */ +:root { + @variant dark { + --chart-general: #94a3b8; + --chart-triage: #e9b44c; + --chart-committer: #3dc5ad; + --chart-maintainer: #79a8fa; + --chart-1: #f5945a; + --chart-2: #4cc4cc; + --chart-3: #a891f2; + --chart-4: #e0b54a; + --chart-5: #f07a92; + --brand: #f18ab4; + --brand-soft: #321d2b; + --sidebar-surface: #11151b; + --page: #0d1117; + --surface: #15181c; + --ink: #e3e6ea; + --muted: #9ba4af; /* 7.6:1 on Paper */ + --edge: #262a31; + --link: #72a8ea; /* 7.8:1 on Paper */ + + --soft: #858e99; /* 5.4:1 on Sheet */ + --raise: #1e2228; + --edge-input: #313640; + --edge-strong: #3a404a; + --edge-faint: #2a2f36; + --row-line: #1c2026; + --accent: #e3e6ea; + --on-accent: #0d0f12; - --link: #6ba3e8; --link-ink: #8ab4e8; --ok: #34b334; --ok-ink: #7fd08a; @@ -123,10 +165,53 @@ } } +/* shadcn names map onto our tokens by meaning, not name: shadcn `muted` and + `accent` are backgrounds (our --raise); `muted-foreground` is our --muted and + `primary` our --accent. In markup: text-muted-foreground, bg-primary. */ @theme inline { + --color-brand: var(--brand); + --color-brand-soft: var(--brand-soft); + --color-background: var(--page); + --color-foreground: var(--ink); + --color-card: var(--surface); + --color-card-foreground: var(--ink); + --color-popover: var(--surface); + --color-popover-foreground: var(--ink); + --color-primary: var(--accent); + --color-primary-foreground: var(--on-accent); + --color-secondary: var(--raise); + --color-secondary-foreground: var(--ink); + --color-muted: var(--raise); + --color-muted-foreground: var(--muted); + --color-accent: var(--raise); + --color-accent-foreground: var(--ink); + --color-destructive: var(--neg); + --color-border: var(--edge); + --color-input: var(--edge-input); + --color-ring: var(--link); + --color-sidebar: var(--sidebar-surface); + --color-sidebar-foreground: var(--ink); + --color-sidebar-primary: var(--accent); + --color-sidebar-primary-foreground: var(--on-accent); + --color-sidebar-accent: var(--raise); + --color-sidebar-accent-foreground: var(--ink); + --color-sidebar-border: var(--edge); + --color-sidebar-ring: var(--link); + + /* Public Sans for content; `font-display` for the sidebar and titles. */ + --font-sans: 'Public Sans Variable', ui-sans-serif, system-ui, sans-serif; + --font-display: 'Bricolage Grotesque Variable', 'Public Sans Variable', ui-sans-serif, sans-serif; + + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); + --color-page: var(--page); --color-ink: var(--ink); - --color-muted: var(--muted); --color-soft: var(--soft); --color-surface: var(--surface); --color-raise: var(--raise); @@ -135,8 +220,6 @@ --color-edge-strong: var(--edge-strong); --color-edge-faint: var(--edge-faint); --color-row-line: var(--row-line); - --color-accent: var(--accent); - --color-on-accent: var(--on-accent); --color-link: var(--link); --color-link-ink: var(--link-ink); --color-ok: var(--ok); @@ -146,75 +229,25 @@ --color-neg: var(--neg); } -/* ---- Page & element defaults --------------------------------------------- */ +/* ---- Base ---------------------------------------------------------------- */ @layer base { - body { - font-family: - system-ui, - -apple-system, - 'Segoe UI', - Roboto, - sans-serif; - max-width: 1100px; - margin: 0 auto; - padding: 24px; - color: var(--ink); - background: var(--page); - } - h1 { - font-size: 26px; - font-weight: 650; - letter-spacing: -0.01em; - margin: 0 0 4px; - } - h3 { - font-size: 14px; - font-weight: 600; - margin: 0; - } - table { - border-collapse: collapse; - width: 100%; - font-size: 13px; - } - th { - position: sticky; - top: 0; - background: var(--raise); - text-align: center; - padding: 8px 10px; + /* shadcn's base rule: preflight leaves border colour at currentColor. */ + *, + ::before, + ::after { + border-color: var(--color-border); + } + /* Preflight resets buttons to cursor: default (same as shadcn init --pointer). */ + button:not(:disabled), + [role='button']:not(:disabled) { cursor: pointer; - white-space: nowrap; - border-bottom: 1px solid var(--edge); - font-weight: 600; - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.04em; - color: var(--muted); - transition: color var(--dur); } - th:hover { + body { color: var(--ink); + background: var(--page); } - td { - padding: 6px 10px; - border-bottom: 1px solid var(--row-line); - white-space: nowrap; - text-align: center; - } - td:first-child { - font-weight: 500; - } - /* Cells centre by default (see `th`/`td`). Numeric columns additionally use - tabular figures so digits keep a constant width and columns of numbers - still line up under a centred heading. */ - td.num { - font-variant-numeric: tabular-nums; - } - tr:hover td { - background: var(--raise); - } + /* Motion is decoration, never information: honour reduced motion everywhere. */ @media (prefers-reduced-motion: reduce) { *, *::before, @@ -224,741 +257,20 @@ scroll-behavior: auto !important; } } - - /* ---- Responsive layout --------------------------------------------------- - Breakpoint convention (the first in this file — set here as the precedent - future contributors follow): - - tablet max-width: 767px tablets in portrait - phone max-width: 599px phones in portrait - narrow-phone max-width: 479px component-level re-flow only (shead - stacking, jump bar, glossary, card - padding — see @layer components below) - - Plain `@media` queries throughout — consistent with `prefers-color-scheme` - and `prefers-reduced-motion` above. No CSS custom-media (needs PostCSS) - and no Tailwind `sm:`/`md:` variants here; those belong to TSX components - that already use the Tailwind vocabulary (MetricTiles, PeriodTabs, …). - Component-class overrides live in the matching block at the end of - `@layer components` below. */ - - /* Tablet: recover breathing room without disturbing desktop layout. */ - @media (max-width: 767px) { - body { - padding: 16px; - } - } - - /* Phone: tighten further and scale the hero heading down so it leaves more - room for the subtitle and tab bar beneath it. */ - @media (max-width: 599px) { - body { - padding: 12px; - } - h1 { - font-size: 22px; - } - } } -/* ---- Component vocabulary ------------------------------------------------ */ +/* ---- Bespoke views ----------------------------------------------------------- + The two HIP views no component expresses: the coverage matrix (.hipmx*, a + heat map, not a data table) and the governance board lanes (.hipboard*). */ @layer components { - /* Shared control behaviour: one timing, one focus ring, everywhere. */ - .macro, - .tab, - .jbtn, - .dl, - .snav, - .ctab, - .chip { - transition: - background var(--dur), - border-color var(--dur), - color var(--dur); - } - - /* One bordered-surface button. The variants below differ only in size and - shape — before this they were four near-identical rule blocks that drifted - apart on hover behaviour and colour. Add a variant by setting the two - custom properties, not by restating the whole treatment. */ - .dl, - .snav, - .ctab, - .jbtn { - font: inherit; - font-size: var(--btn-size, 13px); - padding: var(--btn-pad, 5px 12px); - border: 1px solid var(--edge-strong); - border-radius: var(--r-control); - background: var(--surface); - color: var(--ink); - cursor: pointer; - text-decoration: none; - } - .dl:hover, - .snav:hover, - .ctab:hover:not(.active), - .jbtn:hover { - background: var(--raise); - } - :is(.macro, .tab, .jbtn, .dl, .snav, .ctab, .thbtn, .search, .lbclose):focus-visible { - outline: 2px solid var(--link); - outline-offset: 2px; - } - - .sub { - color: var(--muted); - font-size: 14px; - margin: 0 0 20px; - } - - /* A tab the selected org has no content for: the "why" note in place of - the panel, sized to read as information rather than an error. */ - .empty { - color: var(--muted); - font-size: 15px; - max-width: 640px; - margin: 48px auto; - text-align: center; - line-height: 1.6; - } - - /* Macro (top-level) tabs */ - .macrobar { - display: flex; - gap: 8px; - margin-bottom: 16px; - flex-wrap: wrap; - } - .macro { - padding: 9px 18px; - border: 1px solid var(--edge-input); - background: var(--surface); - border-radius: var(--r-pill); - cursor: pointer; - font-size: 14px; - font-weight: 600; - color: var(--ink); - text-decoration: none; - display: inline-block; - } - .macro.active { - background: var(--accent); - color: var(--on-accent); - border-color: var(--accent); - } - .macro:hover:not(.active) { - border-color: var(--soft); - background: var(--raise); - } - - /* Org tabs */ - .tabbar { - display: flex; - gap: 6px; - margin-bottom: 18px; - border-bottom: 1px solid var(--edge); - flex-wrap: wrap; - } - .tab { - padding: 8px 16px; - border: none; - background: none; - cursor: pointer; - font-size: 14px; - color: var(--muted); - border-bottom: 2px solid transparent; - margin-bottom: -1px; - } - .tab.active { - color: var(--ink); - border-bottom-color: var(--ink); - font-weight: 600; - } - .tab:hover { - color: var(--ink); - } - - /* Cards & section chrome */ - .card { - background: var(--surface); - border: 1px solid var(--edge); - border-radius: var(--r-card); - padding: 16px 18px; - margin-bottom: 20px; - scroll-margin-top: var(--jump-h); - } - .card.flash { - background: var(--flash); - } - .card h2 { - font-size: 16px; - font-weight: 600; - margin: 0 0 2px; - } - .shead { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px 16px; - } - .sactions { - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 6px; - flex: none; - } - .sactions .actionrow { - display: flex; - gap: 6px; - } - .dl { - --btn-size: 12px; - --btn-pad: 5px 11px; - flex: none; - display: inline-flex; - align-items: center; - gap: 6px; - } - .dl svg { - width: 13px; - height: 13px; - flex: none; - } - .desc { - color: var(--muted); - font-size: 13px; - margin: 0 0 12px; - } - .count { - color: var(--soft); - font-size: 12px; - margin: 8px 0 0; - } - - /* Glossary (arrives as pre-built HTML from the data API) */ - .glossary { - margin: 0 0 18px; - border: 1px solid var(--edge); - border-radius: var(--r-card); - background: var(--surface); - padding: 0 14px; - } - .glossary summary { - cursor: pointer; - padding: 12px 0; - font-weight: 600; - font-size: 14px; - } - .glossary dl { - margin: 0 0 10px; - font-size: 13px; - display: grid; - grid-template-columns: 150px 1fr; - gap: 5px 14px; - } - .glossary dt { - font-weight: 600; - color: var(--ink); - } - .glossary dd { - margin: 0; - color: var(--muted); - } - .gnote { - font-size: 12px; - color: var(--soft); - margin: 6px 0 12px; - } - - /* Table sections */ - .search { - width: 100%; - box-sizing: border-box; - padding: 8px 10px; - border: 1px solid var(--edge-input); - border-radius: var(--r-frame); - font-size: 14px; - margin-bottom: 12px; - background: var(--surface); - color: var(--ink); - } - /* Three-level cascade keeps the scroll box viewport-relative across the - browser matrix: 520 px for browsers without `min()` support, then - `min(520px, 60vh)` for those with `min()` but without `dvh`, and finally - `min(520px, 60dvh)` for full support. `dvh` (dynamic viewport height) - accounts for collapsible browser chrome; `60dvh` leaves at least 40 % of - the screen for content above and below the table so the page-scroll - gesture cannot be trapped. */ - .tablewrap { - overflow: auto; - max-height: 520px; /* no min() support: fixed cap */ - max-height: min(520px, 60vh); /* min() + vh, no dvh: viewport-relative */ - max-height: min(520px, 60dvh); /* full support: dvh accounts for browser chrome */ - border: 1px solid var(--edge-faint); - border-radius: var(--r-frame); - } - details.tsec { - padding-top: 14px; - padding-bottom: 14px; - } - .tsum { - display: flex; - align-items: center; - gap: 10px; - list-style: none; - cursor: pointer; - } - .tsum::-webkit-details-marker { - display: none; - } - .tsum::before { - content: '\25B8'; - color: var(--soft); - font-size: 13px; - transition: transform 0.15s ease; - } - details[open] .tsum::before { - transform: rotate(90deg); - } - .tsum h2 { - margin: 0; - font-size: 16px; - font-weight: 600; - flex: 1; - } - .sbadge { - font-size: 12px; - font-weight: 500; - font-variant-numeric: tabular-nums; - color: var(--soft); - background: var(--raise); - border-radius: var(--r-pill); - padding: 2px 10px; - white-space: nowrap; - } - .sbody { - margin-top: 14px; - } - .sbody > .dl { - float: right; - margin-bottom: 8px; - } - .sbody > .search { - clear: both; - } - - /* Freshness stamps: "data as of" (stale = older than the scheduled refresh). */ - .asof { - display: inline-block; - font-size: 0.78rem; - color: var(--soft); - margin: 0; - white-space: nowrap; - } - .asof.stale { - color: var(--warn-ink); - font-weight: 600; - } - - /* Charts */ - .gallery { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: 16px; - } - .chart { - margin: 0; - } - /* Framed chart media: the same border/radius/ground wherever a chart PNG - is shown, so a change lands once instead of in three places. */ - .chart img, - .chartscroll, - .slide img { - border: 1px solid var(--edge-faint); - border-radius: var(--r-frame); - background: var(--chart-ground); - } - .chart img { - width: 100%; - height: auto; - cursor: zoom-in; - } - .chart img:hover { - border-color: var(--edge-strong); - } - .chart figcaption { - font-size: 12px; - color: var(--muted); - margin-top: 6px; - text-align: center; - } - .chart.wide { - grid-column: 1 / -1; - } - .chartscroll { - overflow-x: auto; - overflow-y: hidden; - } - .chartscroll img { - width: auto; - max-width: none; - height: 460px; - border: 0; - border-radius: 0; - display: block; - cursor: zoom-in; - } - .chartnote { - margin: 6px 0 0; - font-size: 12px; - color: var(--muted); - line-height: 1.45; - text-align: left; - } - .charttabs { - display: flex; - gap: 6px; - margin-bottom: 8px; - } - .ctab { - --btn-size: 12px; - --btn-pad: 3px 11px; - color: var(--muted); - } - .ctab.active { - background: var(--accent); - color: var(--on-accent); - border-color: var(--accent); - font-weight: 500; - } - - /* Slideshow */ - .slidenav { - display: flex; - align-items: center; - gap: 14px; - margin-bottom: 10px; - } - .snav { - --btn-pad: 6px 14px; - } - .scount { - font-size: 13px; - color: var(--muted); - } - .slide img { - width: 100%; - height: auto; - cursor: zoom-in; - } - .slide figcaption { - font-size: 13px; - color: var(--muted); - margin-top: 8px; - text-align: center; - font-weight: 600; - } - - /* Lightbox — always dark, on purpose; no tokens */ - .lightbox { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.82); - backdrop-filter: blur(4px); - display: none; - flex-direction: column; - align-items: center; - /* Not centered via justify-content: the auto margins below centre content - that fits and top-align content that overflows, so a tall heatmap - scrolls at readable size instead of shrinking to fit. */ - overflow-y: auto; - gap: 14px; - z-index: 1000; - cursor: zoom-out; - padding: 24px; - box-sizing: border-box; - } - .lightbox img { - margin-top: auto; - max-width: 96vw; - border-radius: var(--r-frame); - box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); - background: var(--chart-ground); - } - .lightbox .hint { - position: fixed; - top: 20px; - right: 64px; - color: var(--lb-dim); - font-size: 13px; - line-height: 28px; - } - .lbclose { - position: fixed; - top: 16px; - right: 20px; - width: 32px; - height: 32px; - border-radius: var(--r-pill); - border: 1px solid rgba(255, 255, 255, 0.25); - background: rgba(255, 255, 255, 0.08); - color: var(--lb-bright); - font-size: 16px; - line-height: 1; - cursor: pointer; - transition: background var(--dur); - } - .lbclose:hover { - background: rgba(255, 255, 255, 0.2); - } - .lbtitle { - color: var(--lb-title); - font-size: 18px; - font-weight: 600; - margin: 0; - text-align: center; - } - .lbcap { - max-width: 760px; - margin: 0 0 auto; - color: var(--lb-text); - font-size: 14px; - line-height: 1.5; - text-align: center; - padding: 0 16px; - max-height: 26vh; - overflow-y: auto; - } - .lbcap:empty { - display: none; - } - .lbcap .chartnote { - color: var(--lb-text); - margin: 0; - text-align: center; - } - .lbmethod { - margin-top: 10px; - text-align: left; - } - .lbmethod summary { - cursor: pointer; - color: var(--lb-dim); - font-size: 13px; - text-align: center; - list-style-position: inside; - } - .lbmethod summary:hover { - color: var(--lb-text); - } - .lbmethod ol { - margin: 10px auto 0; - padding-left: 20px; - color: var(--lb-text); - font-size: 13px; - max-width: 680px; - } - .lbmethod li { - margin: 5px 0; - } - - /* Jump bar & groups */ - .jump { - position: sticky; - top: 0; - z-index: 50; - display: flex; - gap: 8px; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - align-items: center; - background: var(--page); - padding: 10px 0; - margin: 0 0 6px; - border-bottom: 1px solid var(--edge); - } - .group { - scroll-margin-top: var(--jump-h); - } - .jlabel { - font-size: 12px; - color: var(--soft); - font-weight: 600; - margin-right: 2px; - } - /* The pill variant: same button, rounded and with a lighter resting edge. */ - .jbtn { - border-radius: var(--r-pill); - border-color: var(--edge-input); - } - .jbtn:hover { - border-color: var(--soft); - } - .grouphdr { - font-size: 12px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--soft); - margin: 24px 0 12px; - padding-bottom: 6px; - border-bottom: 1px solid var(--edge); - } - summary.grouphdr { - cursor: pointer; - list-style: none; - } - summary.grouphdr::-webkit-details-marker { - display: none; - } - summary.grouphdr::before { - content: '\25be'; - display: inline-block; - width: 1.1em; - color: var(--soft); - transition: transform 0.15s; - } - .group:not([open]) > summary.grouphdr::before { - transform: rotate(-90deg); - } - .group:not([open]) > summary.grouphdr { - color: var(--edge-strong); - } - - /* App extras (no legacy counterpart) */ - .error { - color: var(--neg); - font-size: 14px; - } - .footrow { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 16px; - flex-wrap: wrap; - margin-top: 36px; - padding-top: 14px; - border-top: 1px solid var(--edge); - } - .provenance { - color: var(--soft); - font-size: 12px; - text-align: right; - margin: 0 0 0 auto; - } - - /* Formatted table cells (chips, HIP badges, links). */ - .chip { - display: inline-flex; - align-items: center; - gap: 5px; - font-size: 11.5px; - font-weight: 600; - border-radius: var(--r-pill); - padding: 1px 9px; - border: 1px solid var(--edge); - background: var(--surface); - } - .chip::before { - content: ''; - width: 7px; - height: 7px; - border-radius: 50%; - background: var(--dot, var(--soft)); - } - .chip-merged { - --dot: var(--ok); - color: var(--ok-ink); - } - .chip-open { - --dot: var(--warn); - color: var(--warn-ink); - } - .chip-none { - --dot: var(--edge-strong); - color: var(--soft); - } - .chip-overdue { - --dot: var(--neg); - color: var(--neg); - } - .chip-watch { - --dot: var(--warn); - color: var(--warn-ink); - } - .chip-spec { - --dot: var(--link); - color: var(--link-ink); - } - .cell-hip { - font-weight: 600; - font-variant-numeric: tabular-nums; - white-space: nowrap; - } - - /* Sortable column headers: a real button, with the sort mark's slot - reserved so toggling never shifts the column. */ - .thbtn { - all: unset; - display: inline-flex; - align-items: center; - gap: 4px; - cursor: pointer; - font: inherit; - color: inherit; - text-transform: inherit; - letter-spacing: inherit; - } - .sortmark { - display: inline-block; - width: 1em; - color: var(--soft); - text-transform: none; - } - .cell-link { - font-size: 12px; - color: var(--link); - text-decoration: none; - } - .cell-link:hover { - text-decoration: underline; - } - - /* (The former dark-mode leftovers block is gone: every rule it patched now - reads from the semantic tokens, whose dark values live in the single - token flip at the top of this file.) */ - - /* Chart PNGs bake a light ground (config/charts.py), so in dark mode the - gallery was a wall of white slabs. Inverting with a hue rotation keeps the - series hues recognisable while the ground goes dark — the pragmatic fix - for the PNG era; client-rendered charts will retire it. The lightbox is - exempt: it is always dark, and readers zoom in for a faithful look. */ - @media (prefers-color-scheme: dark) { - .chart img, - .chartscroll img, - .slide img { - filter: invert(0.9) hue-rotate(180deg); - } - } - - /* ---- HIP views (coverage matrix, governance board, evidence panel) ---- - Expressed through the semantic tokens like the rest of the stylesheet, so - the HIPs tab follows the light/dark flip instead of staying light-styled - on a dark page. The heat buckets read from --heat-*, which inverts for - dark mode (see the palette); the matrix legend renders the same classes, - so cells and legend cannot drift. */ - /* Same three-level cascade as .tablewrap above (560 px / 60vh / 60dvh). */ .hipmx-wrap { overflow: auto; max-height: 560px; /* no min() support: fixed cap */ max-height: min(560px, 60vh); /* min() + vh, no dvh: viewport-relative */ max-height: min(560px, 60dvh); /* full support: dvh accounts for browser chrome */ border: 1px solid var(--edge-faint); - border-radius: var(--r-frame); + border-radius: var(--radius); padding: 0 6px 6px 0; } .hipmx { @@ -976,6 +288,7 @@ font-size: 11px; } .hipmx th { + white-space: nowrap; border-bottom: none; cursor: default; padding: 3px 8px; @@ -987,9 +300,7 @@ } .hipmx thead .hipmx-grp th { border-bottom: 1px solid var(--edge-strong); - font-size: 10px; - letter-spacing: 0.06em; - text-transform: uppercase; + font-size: 11px; color: var(--soft); } .hipmx tbody th { @@ -1125,133 +436,6 @@ .hipmx tr.flash td.hipmx-gaps { background: var(--flash) !important; } - .hipmx-filters { - display: flex; - flex-direction: column; - gap: 8px; - margin-bottom: 12px; - } - .hipmx-filters .search { - margin-bottom: 0; - } - .hipmx-fbar { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: wrap; - font-size: 12px; - color: var(--soft); - } - .hipmx-fbtn { - padding: 3px 11px; - border: 1px solid var(--edge-input); - background: var(--surface); - border-radius: var(--r-pill); - cursor: pointer; - font-size: 12px; - color: var(--ink); - } - .hipmx-fbtn:hover { - border-color: var(--soft); - } - .hipmx-fbtn.active { - background: var(--accent); - color: var(--on-accent); - border-color: var(--accent); - } - .hipev { - margin-top: 12px; - border: 1px solid var(--edge); - border-radius: var(--r-frame); - padding: 12px 14px; - } - .hipev-head { - display: flex; - align-items: baseline; - gap: 10px; - margin-bottom: 6px; - } - .hipev-head h3 { - font-size: 13.5px; - margin: 0; - font-weight: 600; - } - .hipev-head .n { - color: var(--soft); - font-size: 12px; - } - .hipev-head button { - margin-left: auto; - } - .hipev ol { - list-style: none; - margin: 0; - padding: 0; - max-height: 300px; - overflow-y: auto; - } - .hipev li { - padding: 6px 0; - border-bottom: 1px solid var(--row-line); - font-size: 12.5px; - } - .hipev li:last-child { - border-bottom: 0; - } - .hipev .l1 { - display: flex; - gap: 10px; - align-items: baseline; - flex-wrap: wrap; - } - .hipev .l1 a { - font-weight: 600; - font-variant-numeric: tabular-nums; - color: var(--link); - text-decoration: none; - } - .hipev .l1 a:hover { - text-decoration: underline; - } - .hipev .t { - color: var(--muted); - } - .hipev .meta { - color: var(--soft); - font-size: 11.5px; - white-space: nowrap; - } - .hipev .cue { - color: var(--warn-ink); - font-size: 11.5px; - font-weight: 600; - } - .hipev .snip { - color: var(--soft); - font-size: 11px; - margin-top: 2px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - } - .hipabout { - margin: 0; - padding-left: 18px; - font-size: 13px; - color: var(--muted); - display: flex; - flex-direction: column; - gap: 8px; - max-width: 85ch; - } - .hipabout strong { - color: var(--ink); - } - .glossary .hipabout { - margin: 0 0 12px; - padding-left: 16px; - } .hipboard { display: flex; gap: 10px; @@ -1260,10 +444,10 @@ padding-bottom: 4px; } .hipboard-col { - flex: 0 0 172px; + flex: 0 0 194px; /* border-box: 172px content + padding + border */ background: var(--raise); border: 1px solid var(--edge); - border-radius: var(--r-frame); + border-radius: var(--radius); padding: 10px 10px 12px; } .hipboard-col h3 { @@ -1291,96 +475,4 @@ color: var(--soft); font-size: 12px; } - .hipchip { - padding: 2px 8px; - border: 1px solid var(--edge-input); - background: var(--surface); - border-radius: var(--r-control); - cursor: pointer; - font-size: 11.5px; - font-weight: 600; - color: var(--link-ink); - font-variant-numeric: tabular-nums; - } - .hipchip:hover { - border-color: var(--link); - } - .hipchip.active { - border-color: var(--ink); - color: var(--ink); - background: var(--raise); - } - .hipboard-info { - display: flex; - align-items: center; - gap: 10px; - flex-wrap: wrap; - margin-top: 10px; - padding: 9px 12px; - border: 1px solid var(--edge); - border-radius: var(--r-frame); - font-size: 13px; - } - .hipboard-info strong { - font-variant-numeric: tabular-nums; - } - .hipboard-info .t { - color: var(--muted); - } - .hipboard-info button { - margin-left: auto; - } - - /* ---- Responsive layout (component layer) --------------------------------- - Builds on the tablet/phone breakpoints from @layer base above, plus the - narrow-phone breakpoint (≤479 px) introduced here for component-level - re-flow. All three are documented in the convention table in @layer base. */ - - /* ---- Narrow-phone breakpoint (≤479 px) ----------------------------------- - Finer-grained adjustments that the 599 px phone breakpoint alone cannot - provide: the section header, jump bar label, glossary column, and card - padding all need more room than padding alone gives them at this width. */ - - /* Below 480 px the description + action-button row becomes too cramped. - Stack them: description first, then the action group below it. - .sactions originally has `align-items: flex-end` (right-aligns its - children within the column); override to left-align when stacked. */ - @media (max-width: 479px) { - .shead { - flex-direction: column; - } - .sactions { - align-items: flex-start; /* left-align when stacked, not right */ - } - - /* The jump bar scrolls horizontally (flex-wrap: nowrap; overflow-x: auto). - The label is visually hidden (not display:none) so it stays in the - accessibility tree: screen readers still read "Jump to" before the - button list, giving group context without consuming horizontal space. - `position: absolute` removes it from the flex layout entirely. */ - .jlabel { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - } - - /* Glossary: the fixed 150 px definition column consumes more than half a - ~280 px card body at this width. minmax(0, 120px) caps it at 120 px - and lets it shrink as needed to share space fairly with the value - column (the 0 is the floor — it can compress all the way if forced). */ - .glossary dl { - grid-template-columns: minmax(0, 120px) 1fr; - } - - /* Cards: recover 8 px on each side so the content columns have more room. */ - .card { - padding: 12px 14px; - } - } } diff --git a/web/src/chartData.ts b/web/src/chartData.ts new file mode 100644 index 000000000..e4a54403a --- /dev/null +++ b/web/src/chartData.ts @@ -0,0 +1,227 @@ +import { + API_ROOT, + type ChartDocument, + type EventsDocument, + type MatrixDocument, + type NetworkDocument, + type Row, + type SeriesDocument, +} from './api'; + +const cache = new Map>(); +const FREQUENCIES = ['year', 'month', 'week', 'day', 'snapshot']; +/** Hex or a design token: the only colours the exporter emits. */ +const COLOR = /^(#[0-9a-f]{3,8}|var\(--[\w-]+\))$/i; + +const finite = (value: unknown) => typeof value === 'number' && Number.isFinite(value); +const text = (value: unknown) => typeof value === 'string'; + +function validRows(data: SeriesDocument): boolean { + const integer = data.value_format === 'integer'; + const numeric = (row: Row) => + data.series.every(({ key }) => + integer ? Number.isSafeInteger(row[key]) && Number(row[key]) >= 0 : finite(row[key]), + ) && data.details.every(({ key }) => finite(row[key])); + if (!data.rows.every(numeric)) return false; + if (data.kind === 'timeseries') { + const buckets = new Set(data.rows.map((row) => row.bucket)); + const comparison = data.comparison ?? null; + return ( + (comparison === null || + (buckets.has(comparison.current) && buckets.has(comparison.previous))) && + FREQUENCIES.includes(data.frequency) && + data.rows.every( + (row, index) => + text(row.bucket) && + typeof row.partial === 'boolean' && + (index === 0 || data.rows[index - 1].bucket < row.bucket), + ) + ); + } + const group = data.group; + if ( + group !== null && + !( + text(group.key) && + Array.isArray(group.values) && + group.values.every(text) && + group.values.includes(group.default) + ) + ) { + return false; + } + const keys = data.rows.map((row) => { + const category = row[data.category.key]; + const cohort = group ? row[group.key] : ''; + if (!text(category) || (group && !group.values.includes(cohort as string))) return null; + return `${String(cohort)}\u0000${String(category)}`; + }); + return !keys.includes(null) && new Set(keys).size === keys.length; +} + +const unique = (values: unknown[]) => new Set(values).size === values.length; +const keyed = (value: unknown) => + !!value && text((value as { key: unknown }).key) && text((value as { label: unknown }).label); +const colored = (items: unknown) => + Array.isArray(items) && + items.every((item) => keyed(item) && COLOR.test(String((item as { color: unknown }).color))); + +function validMatrix(data: MatrixDocument): boolean { + const { scale } = data; + const integer = data.value_format === 'integer'; + const cell = (value: unknown) => + value === null || + (finite(value) && (!integer || (Number.isSafeInteger(value) && Number(value) >= 0))); + return ( + keyed(data.row) && + (data.sublabel === null || keyed(data.sublabel)) && + (data.total === null || keyed(data.total)) && + Array.isArray(data.columns) && + data.columns.length > 0 && + data.columns.every(keyed) && + unique(data.columns.map((column) => column.key)) && + text(data.value_label) && + !!scale && + finite(scale.min) && + finite(scale.max) && + scale.max > scale.min && + Number.isSafeInteger(scale.steps) && + scale.steps > 0 && + (data.missing === null || text(data.missing)) && + typeof data.avatars === 'boolean' && + (data.top_n === null || (Number.isSafeInteger(data.top_n) && data.top_n > 0)) && + Array.isArray(data.rows) && + data.rows.every( + (row) => + text(row[data.row.key]) && + data.columns.every((column) => cell(row[column.key])) && + (data.total === null || finite(row[data.total.key])), + ) && + unique(data.rows.map((row) => row[data.row.key])) + ); +} + +function validNetwork(data: NetworkDocument): boolean { + const count = (value: unknown) => Number.isSafeInteger(value) && Number(value) >= 0; + if (!(text(data.member_label) && colored(data.categories) && Array.isArray(data.nodes))) { + return false; + } + const categories = new Set(data.categories.map((category) => category.key)); + const ids = data.nodes.map((node) => node.id); + const known = new Set(ids); + return ( + data.nodes.every( + (node) => + text(node.id) && + count(node.active) && + count(node.total) && + categories.has(node.category) && + finite(node.x) && + finite(node.y), + ) && + unique(ids) && + Array.isArray(data.edges) && + data.edges.every( + (edge) => + known.has(edge.source) && + known.has(edge.target) && + edge.source !== edge.target && + count(edge.shared), + ) && + unique(data.edges.map((edge) => [edge.source, edge.target].sort().join('\u0000'))) + ); +} + +function validEvents(data: EventsDocument): boolean { + if (!(keyed(data.category) && keyed(data.label) && colored(data.types))) return false; + const types = new Set(data.types.map((type) => type.key)); + const categories = new Set(data.categories); + return ( + Array.isArray(data.categories) && + data.categories.every(text) && + unique(data.categories) && + Array.isArray(data.rows) && + data.rows.every( + (row) => + categories.has(row[data.category.key] as string) && + text(row.time) && + !Number.isNaN(Date.parse(String(row.time))) && + text(row[data.label.key]) && + types.has(row.type as string), + ) + ); +} + +/** Marks every series kind draws, plus the single-series forms only categories take. */ +const MARKS = ['bar', 'line', 'area']; +const CATEGORY_MARKS = ['meter', 'funnel']; + +function validSeries(data: SeriesDocument): boolean { + const categoryMark = CATEGORY_MARKS.includes(data.mark); + return ( + (MARKS.includes(data.mark) || + (categoryMark && data.kind === 'categories' && data.series?.length === 1)) && + ['vertical', 'horizontal'].includes(data.orientation) && + ['integer', 'decimal'].includes(data.value_format) && + typeof data.stacked === 'boolean' && + typeof data.normalize === 'boolean' && + typeof data.rank === 'boolean' && + (data.top_n === null || (Number.isSafeInteger(data.top_n) && data.top_n > 0)) && + (data.reference === null || (finite(data.reference?.value) && text(data.reference.label))) && + (data.value_max == null || (finite(data.value_max) && data.value_max > 0)) && + !!data.category && + text(data.category.key) && + text(data.category.label) && + Array.isArray(data.series) && + data.series.length > 0 && + data.series.every((s) => text(s.key) && text(s.label) && COLOR.test(String(s.color))) && + new Set(data.series.map((s) => s.key)).size === data.series.length && + Array.isArray(data.details) && + data.details.every((d) => text(d.key) && ['number', 'decimal', 'percent'].includes(d.format)) && + Array.isArray(data.rows) && + validRows(data) + ); +} + +/** Reject malformed documents instead of quietly rendering misleading charts. */ +export function validateChartDocument(value: unknown): ChartDocument { + const data = value as ChartDocument; + const meta = + !!data && + data.schema_version === 1 && + text(data.unit) && + text(data.population) && + (data.note === undefined || text(data.note)) && + Array.isArray(data.dimensions) && + !!data.window; + const valid = + meta && + (data.kind === 'matrix' + ? validMatrix(data) + : data.kind === 'network' + ? validNetwork(data) + : data.kind === 'events' + ? validEvents(data) + : (data.kind === 'timeseries' || data.kind === 'categories') && validSeries(data)); + if (!valid) { + throw new Error('The chart dataset is invalid.'); + } + return data; +} + +/** Share requests between cards; failed requests are evicted so Retry really retries. */ +export function fetchChartDocument(path: string): Promise { + const cached = cache.get(path); + if (cached) return cached; + const request = fetch(`${API_ROOT}/${path}`) + .then(async (response) => { + if (!response.ok) throw new Error(`Chart data: HTTP ${response.status}`); + return validateChartDocument(await response.json()); + }) + .catch((error: unknown) => { + cache.delete(path); + throw error; + }); + cache.set(path, request); + return request; +} diff --git a/web/src/components/AppHeader.tsx b/web/src/components/AppHeader.tsx new file mode 100644 index 000000000..826125600 --- /dev/null +++ b/web/src/components/AppHeader.tsx @@ -0,0 +1,161 @@ +/** + * Sticky header: brand, scope (the organisation, plus the page once its title + * has scrolled under the header), data freshness, search and theme. + */ +import { useState } from 'react'; +import { ChevronRightIcon } from 'lucide-react'; +import { cn } from 'cn'; +import { SidebarTrigger, useSidebar } from '@/components/ui/sidebar'; +import { stamp } from '../format'; +import { useHeaderScroll } from '../hooks/use-header-scroll'; +import { NARROW_HEADER, useMediaQuery } from '../hooks/use-media-query'; +import type { NavModel } from '../nav'; +import type { TocEntry } from '../toc'; +import { ThemeToggle } from './ThemeToggle'; +import { HieroBrand } from './HieroBrand'; +import { NavigationSearch } from './NavigationSearch'; +import { OrgAvatar, OrgSwitcher } from './OrgSwitcher'; + +function Freshness({ dataAsOf, className }: { dataAsOf: string; className?: string }) { + return ( +

+ Data as of +

+ ); +} + +/** Mirrors STALE_AFTER in export/data_api.py: the weekly refresh plus 1.5 days of slack. */ +const STALE_AFTER_HOURS = 132; +const relative = new Intl.RelativeTimeFormat('en', { numeric: 'auto' }); + +/** How old the data is, in words: the words carry the stale state, never the dot alone. */ +export function FreshnessStatus({ dataAsOf, now }: { dataAsOf: string; now?: number }) { + // Read once: the age is a summary, not a ticking clock. + const [mounted] = useState(() => Date.now()); + const hours = Math.max(0, ((now ?? mounted) - Date.parse(dataAsOf)) / 3_600_000); + const stale = hours > STALE_AFTER_HOURS; + const days = Math.round(hours / 24); + const summary = stale + ? `${days} days old, refresh overdue` + : `Updated ${hours < 36 ? relative.format(-Math.round(hours), 'hour') : relative.format(-days, 'day')}`; + return ( +
+
+ ); +} + +export function AppHeader({ + nav, + onOrg, + dataAsOf, + toc, + onTab, +}: { + nav: NavModel | null; + onOrg: (org: string) => void; + dataAsOf?: string | null; + toc: TocEntry[]; + onTab: (tab: string) => void; +}) { + const { isMobile, open } = useSidebar(); + const { scrolled, titleHidden } = useHeaderScroll(); + const narrow = useMediaQuery(NARROW_HEADER); + const parent = nav ? nav.topOf(nav.activeMacro) : null; + return ( +
+
+ {isMobile && } + + {!isMobile && ( + + )} +
+
+ {nav && ( + + )} +
+ {dataAsOf && !narrow && ( +
+ +
+ )} + + {!isMobile && ( +
+ +
+ )} +
+
+
+ ); +} diff --git a/web/src/components/AppSidebar.tsx b/web/src/components/AppSidebar.tsx new file mode 100644 index 000000000..1aa504168 --- /dev/null +++ b/web/src/components/AppSidebar.tsx @@ -0,0 +1,266 @@ +import { + UsersIcon, + ShieldCheckIcon, + GitPullRequestIcon, + LandmarkIcon, + CircleHelpIcon, + MessagesSquareIcon, + PackageIcon, + LayoutDashboardIcon, + ArrowUpRightIcon, + Code2Icon, + XIcon, +} from 'lucide-react'; +/** + * Tab navigation and the active tab's "On this page" list; on phones a Sheet + * with the tabs only (SectionGroups' strip covers the groups there). Tabs are + * buttons, not links: the URL hash is the app's state store. + */ + +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSkeleton, + SidebarMenuSub, + SidebarMenuSubButton, + SidebarMenuSubItem, + useSidebar, +} from '@/components/ui/sidebar'; +import { cn } from 'cn'; +import type { NavModel } from '../nav'; +import { useActiveSection } from '../useActiveSection'; +import { scrollToGroup, type TocEntry } from '../toc'; +import { Button } from '@/components/ui/button'; +import { HieroBrand } from './HieroBrand'; +import { ThemeToggle } from './ThemeToggle'; + +const navigationIcons: Record = { + Contributors: UsersIcon, + Governance: LandmarkIcon, + HIPs: GitPullRequestIcon, + 'Security & scorecards': ShieldCheckIcon, + 'Issues & onboarding': CircleHelpIcon, + Community: MessagesSquareIcon, + Releases: PackageIcon, +}; + +function NavigationIcon({ name }: { name: string }) { + const Icon = navigationIcons[name] ?? LayoutDashboardIcon; + return