From 6a6dc7e665c5a019213737fe03f7dd48beff5e45 Mon Sep 17 00:00:00 2001
From: SakshiKekre
Date: Fri, 31 Oct 2025 10:11:29 -0400
Subject: [PATCH 1/8] migrate: Add initial version of Learn:API page
---
app/src/Router.tsx | 16 ++
.../shared/static/APIMetadataCard.tsx | 116 +++++++++++
.../shared/static/APIPlayground.tsx | 23 +++
.../components/shared/static/CodeBlock.tsx | 64 +++++++
.../shared/static/ContentSection.tsx | 3 +
.../static/VariableParameterExplorer.tsx | 181 ++++++++++++++++++
app/src/constants/apiExamples.ts | 59 ++++++
app/src/pages/API.page.tsx | 152 +++++++++++++++
8 files changed, 614 insertions(+)
create mode 100644 app/src/components/shared/static/APIMetadataCard.tsx
create mode 100644 app/src/components/shared/static/APIPlayground.tsx
create mode 100644 app/src/components/shared/static/CodeBlock.tsx
create mode 100644 app/src/components/shared/static/VariableParameterExplorer.tsx
create mode 100644 app/src/constants/apiExamples.ts
create mode 100644 app/src/pages/API.page.tsx
diff --git a/app/src/Router.tsx b/app/src/Router.tsx
index 881d7088b..b48c1b827 100644
--- a/app/src/Router.tsx
+++ b/app/src/Router.tsx
@@ -7,6 +7,7 @@ import { PopulationCreationFlow } from './flows/populationCreationFlow';
import { ReportCreationFlow } from './flows/reportCreationFlow';
import { SimulationCreationFlow } from './flows/simulationCreationFlow';
import DashboardPage from './pages/Dashboard.page';
+import APIPage from './pages/API.page';
import DonatePage from './pages/Donate.page';
import HomePage from './pages/Home.page';
import PoliciesPage from './pages/Policies.page';
@@ -101,6 +102,21 @@ const router = createBrowserRouter(
},
],
},
+ // Static pages that need metadata - use MetadataLazyLoader + StaticLayout
+ {
+ element: ,
+ children: [
+ {
+ element: ,
+ children: [
+ {
+ path: 'api',
+ element: ,
+ },
+ ],
+ },
+ ],
+ },
// Routes that don't need metadata at all (no guard)
{
element: ,
diff --git a/app/src/components/shared/static/APIMetadataCard.tsx b/app/src/components/shared/static/APIMetadataCard.tsx
new file mode 100644
index 000000000..284c5f6a4
--- /dev/null
+++ b/app/src/components/shared/static/APIMetadataCard.tsx
@@ -0,0 +1,116 @@
+import { Badge, Card, Stack, Text } from '@mantine/core';
+import { colors, spacing, typography } from '@/designTokens';
+
+interface ParameterMetadata {
+ type: 'parameter';
+ parameter: string;
+ label?: string;
+ description?: string;
+ unit?: string;
+ period?: string | null;
+ economy?: boolean;
+ household?: boolean;
+ values?: Record;
+}
+
+interface VariableMetadata {
+ name: string;
+ label?: string;
+ description?: string;
+ entity?: string;
+ definitionPeriod?: string;
+ unit?: string;
+ category?: string;
+ defaultValue?: number;
+ isInputVariable?: boolean;
+ valueType?: string;
+}
+
+interface APIMetadataCardProps {
+ metadata: ParameterMetadata | VariableMetadata;
+ onClick?: () => void;
+}
+
+export default function APIMetadataCard({ metadata, onClick }: APIMetadataCardProps) {
+ const isParameter = 'type' in metadata && metadata.type === 'parameter';
+ const paramData = isParameter ? (metadata as ParameterMetadata) : null;
+ const varData = !isParameter ? (metadata as VariableMetadata) : null;
+
+ const displayLabel = paramData?.label || varData?.label || varData?.name || '';
+ const description = metadata.description || '';
+ const pythonName = paramData?.parameter || varData?.name || '';
+
+ return (
+
+
+
+ {isParameter ? 'Parameter' : 'Variable'}
+
+
+
+ {displayLabel}
+
+
+ {description && (
+
+ {description}
+
+ )}
+
+ {varData?.entity && (
+
+ Entity: {varData.entity}
+
+ )}
+
+ {(paramData?.period || varData?.definitionPeriod) && (
+
+ Period: {paramData?.period || varData?.definitionPeriod}
+
+ )}
+
+ {metadata.unit && (
+
+ Unit: {metadata.unit}
+
+ )}
+
+
+ Python name: {pythonName}
+
+
+
+ );
+}
diff --git a/app/src/components/shared/static/APIPlayground.tsx b/app/src/components/shared/static/APIPlayground.tsx
new file mode 100644
index 000000000..30940b6d0
--- /dev/null
+++ b/app/src/components/shared/static/APIPlayground.tsx
@@ -0,0 +1,23 @@
+import { Box } from '@mantine/core';
+
+interface APIPlaygroundProps {
+ countryId: string;
+}
+
+export default function APIPlayground({ countryId }: APIPlaygroundProps) {
+ const streamlitUrl = `https://policyengine-api-light.streamlit.app/?embedded=true&country_id=${countryId}`;
+
+ return (
+
+ );
+}
diff --git a/app/src/components/shared/static/CodeBlock.tsx b/app/src/components/shared/static/CodeBlock.tsx
new file mode 100644
index 000000000..622f9b27d
--- /dev/null
+++ b/app/src/components/shared/static/CodeBlock.tsx
@@ -0,0 +1,64 @@
+import { IconCheck, IconCopy } from '@tabler/icons-react';
+import { ActionIcon, Box, Code, CopyButton, Stack, Text } from '@mantine/core';
+import { colors, spacing, typography } from '@/designTokens';
+
+interface CodeBlockProps {
+ code: string;
+ language?: string;
+ title?: string;
+}
+
+export default function CodeBlock({ code, language: _language, title }: CodeBlockProps) {
+ return (
+
+ {title && (
+
+ {title}
+
+ )}
+
+
+ {code}
+
+
+
+ {({ copied, copy }) => (
+
+ {copied ? : }
+
+ )}
+
+
+
+
+ );
+}
diff --git a/app/src/components/shared/static/ContentSection.tsx b/app/src/components/shared/static/ContentSection.tsx
index ece115a77..bc49d48c1 100644
--- a/app/src/components/shared/static/ContentSection.tsx
+++ b/app/src/components/shared/static/ContentSection.tsx
@@ -7,6 +7,7 @@ export interface ContentSectionProps {
variant?: 'primary' | 'secondary' | 'accent';
children: ReactNode;
centerTitle?: boolean;
+ id?: string;
}
export default function ContentSection({
@@ -14,6 +15,7 @@ export default function ContentSection({
variant = 'primary',
children,
centerTitle = false,
+ id,
}: ContentSectionProps) {
const backgrounds = {
primary: colors.white,
@@ -29,6 +31,7 @@ export default function ContentSection({
return (
;
+}
+
+interface VariableMetadata {
+ name: string;
+ label?: string;
+ description?: string;
+ entity?: string;
+ definitionPeriod?: string;
+ unit?: string;
+ category?: string;
+ defaultValue?: number;
+ isInputVariable?: boolean;
+ valueType?: string;
+}
+
+interface Metadata {
+ variables: Record;
+ parameters: Record;
+}
+
+interface VariableParameterExplorerProps {
+ metadata: Metadata;
+}
+
+export default function VariableParameterExplorer({ metadata }: VariableParameterExplorerProps) {
+ const [query, setQuery] = useState('');
+ const [showAbolitions, setShowAbolitions] = useState(false);
+ const [page, setPage] = useState(0);
+
+ // Convert metadata to card array
+ const variableCards: VariableMetadata[] = Object.values(metadata.variables || {});
+ const parameterCards: ParameterMetadata[] = Object.values(metadata.parameters || {});
+
+ // Filter and sort
+ const filterByQuery = (item: ParameterMetadata | VariableMetadata) => {
+ const label = item.label || '';
+ const pythonName = 'type' in item ? item.parameter : item.name;
+
+ // Hide abolitions unless checkbox checked
+ if (!showAbolitions && pythonName?.startsWith('gov.abolitions')) {
+ return false;
+ }
+
+ // Filter by search query
+ if (query) {
+ const normalizedQuery = query.replaceAll(' ', '').toLowerCase();
+ const normalizedLabel = label.replaceAll(' ', '').toLowerCase();
+ return (
+ normalizedLabel.includes(normalizedQuery) ||
+ pythonName?.toLowerCase().includes(query.toLowerCase())
+ );
+ }
+
+ return true;
+ };
+
+ const allCards = [...parameterCards, ...variableCards].filter(filterByQuery).sort((a, b) => {
+ const labelA = (a.label || ('name' in a ? a.name : '')).toLowerCase();
+ const labelB = (b.label || ('name' in b ? b.name : '')).toLowerCase();
+ return labelA.localeCompare(labelB);
+ });
+
+ const totalPages = Math.ceil(allCards.length / CARDS_PER_PAGE);
+ const startIdx = page * CARDS_PER_PAGE;
+ const endIdx = startIdx + CARDS_PER_PAGE;
+ const currentCards = allCards.slice(startIdx, endIdx);
+
+ const handleQueryChange = (value: string) => {
+ setQuery(value);
+ setPage(0); // Reset to first page on new search
+ };
+
+ const handlePageInputChange = (e: React.ChangeEvent) => {
+ const value = parseInt(e.target.value, 10);
+ if (!isNaN(value) && value >= 1 && value <= totalPages) {
+ setPage(value - 1); // Convert from 1-indexed to 0-indexed
+ }
+ };
+
+ return (
+
+ {/* Search and Filter Controls */}
+
+ handleQueryChange(e.currentTarget.value)}
+ leftSection={ }
+ style={{ flex: 1 }}
+ />
+
+ {
+ setShowAbolitions(e.currentTarget.checked);
+ setPage(0);
+ }}
+ />
+
+
+
+ {/* Cards Grid */}
+
+ {currentCards.map((card, idx) => (
+
+
+
+ ))}
+
+
+ {/* Pagination Controls */}
+ {totalPages > 1 && (
+
+ }
+ onClick={() => setPage(page - 1)}
+ disabled={page === 0}
+ >
+ Previous
+
+
+
+ Page
+
+ of {totalPages}
+
+
+ }
+ onClick={() => setPage(page + 1)}
+ disabled={page >= totalPages - 1}
+ >
+ Next
+
+
+ )}
+
+ {/* Results Count */}
+
+ Showing {currentCards.length} of {allCards.length} results
+
+
+ );
+}
diff --git a/app/src/constants/apiExamples.ts b/app/src/constants/apiExamples.ts
new file mode 100644
index 000000000..896f362aa
--- /dev/null
+++ b/app/src/constants/apiExamples.ts
@@ -0,0 +1,59 @@
+export const TOKEN_FETCH_CODE = `import requests
+import json
+
+CLIENT_ID = "YOUR_CLIENT_ID"
+CLIENT_SECRET = "YOUR_CLIENT_SECRET"
+
+payload = {
+ "client_id": CLIENT_ID,
+ "client_secret": CLIENT_SECRET,
+ "audience": "https://household.api.policyengine.org",
+ "grant_type": "client_credentials"
+}
+
+headers = { "content-type": "application/json" }
+
+auth_response = requests.post(
+ "https://policyengine.uk.auth0.com/oauth/token",
+ headers=headers,
+ json=payload
+)
+
+result = auth_response.json()
+print(result["access_token"])`;
+
+export const TOKEN_RESPONSE_CODE = `{
+ "access_token": "YOUR_ACCESS_TOKEN",
+ "token_type": "Bearer"
+}`;
+
+export const getCalculateRequestCode = (countryId: string) => `import requests
+
+url = "https://household.api.policyengine.org/${countryId}/calculate"
+
+headers = {
+ "Authorization": "Bearer YOUR_TOKEN_HERE",
+ "Content-Type": "application/json",
+}
+
+household_data = {
+ "household": {
+ "people": {
+ "parent": {
+ "age": { "2023": 30 },
+ "employment_income": { "2023": 20000 }
+ },
+ "child": {
+ "age": { "2023": 5 }
+ }
+ },
+ "households": {
+ "household": {
+ "members": ["parent", "child"]
+ }
+ }
+ }
+}
+
+response = requests.post(url, headers=headers, json=household_data)
+print(response.json())`;
diff --git a/app/src/pages/API.page.tsx b/app/src/pages/API.page.tsx
new file mode 100644
index 000000000..7fe637502
--- /dev/null
+++ b/app/src/pages/API.page.tsx
@@ -0,0 +1,152 @@
+import { useSelector } from 'react-redux';
+import { Stack } from '@mantine/core';
+import APIPlayground from '@/components/shared/static/APIPlayground';
+import CodeBlock from '@/components/shared/static/CodeBlock';
+import ContentSection from '@/components/shared/static/ContentSection';
+import HeroSection from '@/components/shared/static/HeroSection';
+import RichTextBlock from '@/components/shared/static/RichTextBlock';
+import StaticPageLayout from '@/components/shared/static/StaticPageLayout';
+import VariableParameterExplorer from '@/components/shared/static/VariableParameterExplorer';
+import {
+ getCalculateRequestCode,
+ TOKEN_FETCH_CODE,
+ TOKEN_RESPONSE_CODE,
+} from '@/constants/apiExamples';
+import { spacing } from '@/designTokens';
+import { useCurrentCountry } from '@/hooks/useCurrentCountry';
+import type { RootState } from '@/store';
+
+export default function APIPage() {
+ const countryId = useCurrentCountry();
+ const metadata = useSelector((state: RootState) => state.metadata);
+
+ // Sample API response for demonstration
+ const sampleResponse = {
+ status: 'ok',
+ result: {
+ households: {
+ household: {
+ household_net_income: {
+ '2023': 18500,
+ },
+ },
+ },
+ people: {
+ parent: {
+ employment_income: {
+ '2023': 20000,
+ },
+ income_tax: {
+ '2023': 1500,
+ },
+ },
+ child: {
+ age: {
+ '2023': 5,
+ },
+ },
+ },
+ },
+ };
+
+ return (
+
+
+
+
+
+
+ PolicyEngine's REST API (
+
+ https://household.api.policyengine.org
+
+ ) simulates tax-benefit policy outcomes and reform impacts for households. Access
+ requires a Client ID and Client Secret provided by PolicyEngine. For
+ access, contact us at hello@policyengine.org
+ .
+
+ On this page:
+
+
+
+
+
+
+
+ Execute a credentials exchange using your client ID and client secret to obtain an
+ authentication token. Include this token in the authorization header of every request as
+ "Bearer YOUR_TOKEN". Tokens expire monthly for security.
+
+
+
+
+
+
+
+
+
+
+
+ Returns household-level policy outcomes. Pass in a household object defining people,
+ groups and any variable values. Use null values for requested variables - these will be
+ computed and returned.
+
+
+ POST /{countryId}/calculate
+
+
+
+
+
+
+
+
+
+
+
+ Access information about all available variables and parameters in the PolicyEngine API.
+
+
+ GET /{countryId}/metadata
+
+
+ {metadata && }
+
+
+
+
+ Try out the API in this interactive demo.
+
+
+
+
+ );
+}
From 3b51ca09991c73365f1b870f6ec053ab7bb35a4c Mon Sep 17 00:00:00 2001
From: SakshiKekre
Date: Sun, 2 Nov 2025 02:28:07 -0500
Subject: [PATCH 2/8] feat: Change styles for components, fix related tests
---
app/src/Router.tsx | 2 +-
.../shared/static/APIMetadataCard.tsx | 4 +-
.../shared/static/APIPlayground.tsx | 5 +-
.../components/shared/static/ActionButton.tsx | 6 +-
.../components/shared/static/CodeBlock.tsx | 96 +++++++++----
.../static/VariableParameterExplorer.tsx | 4 +-
app/src/pages/API.page.tsx | 136 ++++++++++--------
app/src/styles/stylesheets/RichTextBlock.css | 42 ------
.../shared/static/ActionButton.test.tsx | 12 +-
.../shared/static/ContentSection.test.tsx | 16 +++
10 files changed, 183 insertions(+), 140 deletions(-)
delete mode 100644 app/src/styles/stylesheets/RichTextBlock.css
diff --git a/app/src/Router.tsx b/app/src/Router.tsx
index b48c1b827..fe1cbdd87 100644
--- a/app/src/Router.tsx
+++ b/app/src/Router.tsx
@@ -6,8 +6,8 @@ import { PolicyCreationFlow } from './flows/policyCreationFlow';
import { PopulationCreationFlow } from './flows/populationCreationFlow';
import { ReportCreationFlow } from './flows/reportCreationFlow';
import { SimulationCreationFlow } from './flows/simulationCreationFlow';
-import DashboardPage from './pages/Dashboard.page';
import APIPage from './pages/API.page';
+import DashboardPage from './pages/Dashboard.page';
import DonatePage from './pages/Donate.page';
import HomePage from './pages/Home.page';
import PoliciesPage from './pages/Policies.page';
diff --git a/app/src/components/shared/static/APIMetadataCard.tsx b/app/src/components/shared/static/APIMetadataCard.tsx
index 284c5f6a4..955b979a3 100644
--- a/app/src/components/shared/static/APIMetadataCard.tsx
+++ b/app/src/components/shared/static/APIMetadataCard.tsx
@@ -62,7 +62,7 @@ export default function APIMetadataCard({ metadata, onClick }: APIMetadataCardPr
}}
>
-
+
{isParameter ? 'Parameter' : 'Variable'}
@@ -105,7 +105,7 @@ export default function APIMetadataCard({ metadata, onClick }: APIMetadataCardPr
fontSize: typography.fontSize.xs,
color: colors.text.tertiary,
wordBreak: 'break-all',
- fontFamily: 'monospace',
+ fontFamily: typography.fontFamily.mono,
}}
>
Python name: {pythonName}
diff --git a/app/src/components/shared/static/APIPlayground.tsx b/app/src/components/shared/static/APIPlayground.tsx
index 30940b6d0..992188134 100644
--- a/app/src/components/shared/static/APIPlayground.tsx
+++ b/app/src/components/shared/static/APIPlayground.tsx
@@ -1,11 +1,12 @@
import { Box } from '@mantine/core';
+import { spacing } from '@/designTokens';
interface APIPlaygroundProps {
countryId: string;
}
export default function APIPlayground({ countryId }: APIPlaygroundProps) {
- const streamlitUrl = `https://policyengine-api-light.streamlit.app/?embedded=true&country_id=${countryId}`;
+ const streamlitUrl = `https://policyengine-policyengine-api-demo-app-xy5rgn.streamlit.app/?embed=true&embed_options=light_theme&embed_options=hide_footer&mode=${countryId}`;
return (
diff --git a/app/src/components/shared/static/ActionButton.tsx b/app/src/components/shared/static/ActionButton.tsx
index 465cf0010..05b1b17a6 100644
--- a/app/src/components/shared/static/ActionButton.tsx
+++ b/app/src/components/shared/static/ActionButton.tsx
@@ -8,6 +8,7 @@ export interface ActionButtonProps {
variant?: 'primary' | 'secondary' | 'inverted';
multiline?: boolean;
caption?: string;
+ target?: '_blank' | '_self';
}
export default function ActionButton({
@@ -16,6 +17,7 @@ export default function ActionButton({
variant = 'primary',
multiline = false,
caption,
+ target = '_blank',
}: ActionButtonProps) {
const buttonRef = useRef(null);
const [buttonWidth, setButtonWidth] = useState(null);
@@ -64,8 +66,8 @@ export default function ActionButton({
ref={buttonRef}
component="a"
href={href}
- target="_blank"
- rel="noopener noreferrer"
+ target={target}
+ rel={target === '_blank' ? 'noopener noreferrer' : undefined}
size="lg"
px={spacing.xl}
py={spacing.lg}
diff --git a/app/src/components/shared/static/CodeBlock.tsx b/app/src/components/shared/static/CodeBlock.tsx
index 622f9b27d..3675af94e 100644
--- a/app/src/components/shared/static/CodeBlock.tsx
+++ b/app/src/components/shared/static/CodeBlock.tsx
@@ -1,19 +1,24 @@
-import { IconCheck, IconCopy } from '@tabler/icons-react';
-import { ActionIcon, Box, Code, CopyButton, Stack, Text } from '@mantine/core';
+import { useState } from 'react';
+import { IconCheck, IconChevronDown, IconChevronUp, IconCopy } from '@tabler/icons-react';
+import { Box, Button, Code, CopyButton, Flex, Group, Stack, Text } from '@mantine/core';
import { colors, spacing, typography } from '@/designTokens';
interface CodeBlockProps {
code: string;
language?: string;
title?: string;
+ showExpand?: boolean;
}
-export default function CodeBlock({ code, language: _language, title }: CodeBlockProps) {
+export default function CodeBlock({ code, language, title, showExpand = true }: CodeBlockProps) {
+ const [isExpanded, setIsExpanded] = useState(false);
+
return (
-
+
{title && (
)}
-
+
+ {/* Header bar */}
+
+
+ {language || 'code'}
+
+
+ {showExpand && (
+ :
+ }
+ onClick={() => setIsExpanded(!isExpanded)}
+ >
+ {isExpanded ? 'Shrink' : 'Expand'}
+
+ )}
+
+ {({ copied, copy }) => (
+ : }
+ onClick={copy}
+ >
+ {copied ? 'Copied' : 'Copy'}
+
+ )}
+
+
+
+
+ {/* Code content */}
{code}
-
-
- {({ copied, copy }) => (
-
- {copied ? : }
-
- )}
-
-
);
diff --git a/app/src/components/shared/static/VariableParameterExplorer.tsx b/app/src/components/shared/static/VariableParameterExplorer.tsx
index de3440db8..bc3eaa495 100644
--- a/app/src/components/shared/static/VariableParameterExplorer.tsx
+++ b/app/src/components/shared/static/VariableParameterExplorer.tsx
@@ -152,9 +152,9 @@ export default function VariableParameterExplorer({ metadata }: VariableParamete
style={{
width: '50px',
textAlign: 'center',
- padding: '4px 8px',
+ padding: `${spacing.xs} ${spacing.sm}`,
border: `1px solid ${colors.gray[300]}`,
- borderRadius: '4px',
+ borderRadius: spacing.radius.sm,
fontSize: typography.fontSize.sm,
}}
/>
diff --git a/app/src/pages/API.page.tsx b/app/src/pages/API.page.tsx
index 7fe637502..06d6eae1b 100644
--- a/app/src/pages/API.page.tsx
+++ b/app/src/pages/API.page.tsx
@@ -1,10 +1,10 @@
import { useSelector } from 'react-redux';
-import { Stack } from '@mantine/core';
+import { Anchor, Code, List, Stack, Text, Title } from '@mantine/core';
import APIPlayground from '@/components/shared/static/APIPlayground';
import CodeBlock from '@/components/shared/static/CodeBlock';
import ContentSection from '@/components/shared/static/ContentSection';
+import CTASection from '@/components/shared/static/CTASection';
import HeroSection from '@/components/shared/static/HeroSection';
-import RichTextBlock from '@/components/shared/static/RichTextBlock';
import StaticPageLayout from '@/components/shared/static/StaticPageLayout';
import VariableParameterExplorer from '@/components/shared/static/VariableParameterExplorer';
import {
@@ -56,49 +56,65 @@ export default function APIPage() {
description="Build tax and benefit policy analysis into your applications with our comprehensive API"
/>
-
-
-
- PolicyEngine's REST API (
-
- https://household.api.policyengine.org
-
- ) simulates tax-benefit policy outcomes and reform impacts for households. Access
- requires a Client ID and Client Secret provided by PolicyEngine. For
- access, contact us at hello@policyengine.org
- .
-
- On this page:
-
-
-
+
+
+ PolicyEngine's REST API (
+
+ https://household.api.policyengine.org
+
+ ) simulates tax-benefit policy outcomes and reform impacts for households. Access
+ requires a{' '}
+
+ Client ID
+ {' '}
+ and{' '}
+
+ Client Secret
+ {' '}
+ provided by PolicyEngine. For access, contact us at{' '}
+ hello@policyengine.org .
+
+
+ On this page:
+
+
+
+ Fetch an authentication token
+
+
+ Calculate household-level policy outcomes
+
+
+ Variable and parameter metadata
+
+
+ API playground
+
+
+
+ }
+ cta={{
+ text: 'Try API Playground',
+ href: '#playground',
+ target: '_self',
+ }}
+ />
-
-
- Execute a credentials exchange using your client ID and client secret to obtain an
- authentication token. Include this token in the authorization header of every request as
- "Bearer YOUR_TOKEN". Tokens expire monthly for security.
-
-
-
+
+ Execute a credentials exchange using your client ID and client secret to obtain an
+ authentication token. Include this token in the authorization header of every request as
+ "Bearer YOUR_TOKEN". Tokens expire monthly for security.
+
+
@@ -109,16 +125,16 @@ export default function APIPage() {
title="Calculate household-level policy outcomes"
variant="primary"
>
-
-
+
+
Returns household-level policy outcomes. Pass in a household object defining people,
groups and any variable values. Use null values for requested variables - these will be
computed and returned.
-
-
- POST /{countryId}/calculate
-
-
+
+
+ POST /{countryId}/calculate
+
+
-
-
+
+
Access information about all available variables and parameters in the PolicyEngine API.
-
-
- GET /{countryId}/metadata
-
-
+
+
+ GET /{countryId}/metadata
+
+
{metadata && }
-
- Try out the API in this interactive demo.
-
+
+ Try out the API in this interactive demo.
+
diff --git a/app/src/styles/stylesheets/RichTextBlock.css b/app/src/styles/stylesheets/RichTextBlock.css
deleted file mode 100644
index 6b8516c21..000000000
--- a/app/src/styles/stylesheets/RichTextBlock.css
+++ /dev/null
@@ -1,42 +0,0 @@
-/* Note: For most components, we don't condone separate CSS stylesheets, but Mantine
- provides no way to provide styling to child components natively */
-
-.rich-text-block p {
- font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
- font-size: 16px;
- line-height: 1.625;
- margin-bottom: 16px;
- margin-top: 0;
-}
-
-.rich-text-block p:last-child {
- margin-bottom: 0;
-}
-
-.rich-text-block a {
- color: var(--link-color);
- text-decoration: underline;
- transition: opacity 0.2s ease;
-}
-
-.rich-text-block a:hover {
- opacity: 0.8;
-}
-
-.rich-text-block strong {
- font-weight: 700;
-}
-
-.rich-text-block em {
- font-style: italic;
-}
-
-.rich-text-block ul,
-.rich-text-block ol {
- margin-bottom: 16px;
- padding-left: 32px;
-}
-
-.rich-text-block li {
- margin-bottom: 4px;
-}
diff --git a/app/src/tests/unit/components/shared/static/ActionButton.test.tsx b/app/src/tests/unit/components/shared/static/ActionButton.test.tsx
index 054f733cf..73f492946 100644
--- a/app/src/tests/unit/components/shared/static/ActionButton.test.tsx
+++ b/app/src/tests/unit/components/shared/static/ActionButton.test.tsx
@@ -21,7 +21,7 @@ describe('ActionButton', () => {
expect(link).toHaveAttribute('href', TEST_BUTTON_HREF);
});
- test('given href then link opens in new tab', () => {
+ test('given href then link opens in new tab by default', () => {
// Given / When
render( );
@@ -31,6 +31,16 @@ describe('ActionButton', () => {
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
});
+ test('given target prop _self then link opens in same tab', () => {
+ // Given / When
+ render( );
+
+ // Then
+ const link = screen.getByRole('link', { name: TEST_BUTTON_TEXT });
+ expect(link).toHaveAttribute('target', '_self');
+ expect(link).not.toHaveAttribute('rel');
+ });
+
test('given caption then caption text is rendered', () => {
// Given / When
render( );
diff --git a/app/src/tests/unit/components/shared/static/ContentSection.test.tsx b/app/src/tests/unit/components/shared/static/ContentSection.test.tsx
index a836198b3..ca49354f6 100644
--- a/app/src/tests/unit/components/shared/static/ContentSection.test.tsx
+++ b/app/src/tests/unit/components/shared/static/ContentSection.test.tsx
@@ -71,4 +71,20 @@ describe('ContentSection', () => {
// Then
expect(container.firstChild).toBeInTheDocument();
});
+
+ test('given id prop then element has id attribute', () => {
+ // Given
+ const testId = 'test-section';
+
+ // When
+ render(
+
+ Content
+
+ );
+
+ // Then
+ const section = document.getElementById(testId);
+ expect(section).toBeInTheDocument();
+ });
});
From f1d2f54aaa93864767c13581417e135a192957f6 Mon Sep 17 00:00:00 2001
From: SakshiKekre
Date: Sun, 2 Nov 2025 02:31:14 -0500
Subject: [PATCH 3/8] fix: Remove unused import
---
app/src/components/shared/static/RichTextBlock.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/app/src/components/shared/static/RichTextBlock.tsx b/app/src/components/shared/static/RichTextBlock.tsx
index 055f07835..58c58de72 100644
--- a/app/src/components/shared/static/RichTextBlock.tsx
+++ b/app/src/components/shared/static/RichTextBlock.tsx
@@ -1,8 +1,6 @@
import { ReactNode } from 'react';
import { colors } from '@/designTokens';
-import '@/styles/stylesheets/RichTextBlock.css';
-
export interface RichTextBlockProps {
children: ReactNode;
variant?: 'default' | 'inverted';
From 553d433e55e2dc140390fa88a0b5ff2970b320d3 Mon Sep 17 00:00:00 2001
From: SakshiKekre
Date: Mon, 3 Nov 2025 00:07:24 -0500
Subject: [PATCH 4/8] feat: Add api page link to homepage header
---
.../home-header/DesktopNavigation.tsx | 27 +++++++++++++++
.../components/home-header/HeaderContent.tsx | 10 +++++-
app/src/components/home-header/MobileMenu.tsx | 33 +++++++++++++++++++
app/src/components/shared/HomeHeader.tsx | 3 ++
.../components/home-header/HeaderMocks.ts | 3 ++
.../fixtures/components/home/HomeMocks.ts | 2 ++
.../home-header/DesktopNavigation.test.tsx | 11 ++++++-
.../home-header/HeaderContent.test.tsx | 4 +++
.../home-header/MobileMenu.test.tsx | 7 ++++
9 files changed, 98 insertions(+), 2 deletions(-)
diff --git a/app/src/components/home-header/DesktopNavigation.tsx b/app/src/components/home-header/DesktopNavigation.tsx
index 669705732..144496791 100644
--- a/app/src/components/home-header/DesktopNavigation.tsx
+++ b/app/src/components/home-header/DesktopNavigation.tsx
@@ -11,12 +11,14 @@ interface NavLink {
interface DesktopNavigationProps {
navLinks: NavLink[];
aboutLinks: NavLink[];
+ learnLinks: NavLink[];
onNavClick: (path?: string) => void;
}
export default function DesktopNavigation({
navLinks,
aboutLinks,
+ learnLinks,
onNavClick,
}: DesktopNavigationProps) {
const countryId = useCurrentCountry();
@@ -74,6 +76,31 @@ export default function DesktopNavigation({
{link.label}
))}
+
+
+
+
+
+
+ Learn
+
+
+
+
+
+
+ {learnLinks.map((link) => (
+ onNavClick(link.path)}>
+ {link.label}
+
+ ))}
+
+
);
}
diff --git a/app/src/components/home-header/HeaderContent.tsx b/app/src/components/home-header/HeaderContent.tsx
index d38ccc6c6..bdde603ac 100644
--- a/app/src/components/home-header/HeaderContent.tsx
+++ b/app/src/components/home-header/HeaderContent.tsx
@@ -15,6 +15,7 @@ interface HeaderContentProps {
onClose: () => void;
navLinks: NavLink[];
aboutLinks: NavLink[];
+ learnLinks: NavLink[];
onNavClick: (path?: string) => void;
}
@@ -24,6 +25,7 @@ export default function HeaderContent({
onClose,
navLinks,
aboutLinks,
+ learnLinks,
onNavClick,
}: HeaderContentProps) {
return (
@@ -41,7 +43,12 @@ export default function HeaderContent({
-
+
@@ -54,6 +61,7 @@ export default function HeaderContent({
onClose={onClose}
navLinks={navLinks}
aboutLinks={aboutLinks}
+ learnLinks={learnLinks}
onNavClick={onNavClick}
/>
diff --git a/app/src/components/home-header/MobileMenu.tsx b/app/src/components/home-header/MobileMenu.tsx
index cbf804b82..3aa30cdb2 100644
--- a/app/src/components/home-header/MobileMenu.tsx
+++ b/app/src/components/home-header/MobileMenu.tsx
@@ -13,6 +13,7 @@ interface MobileMenuProps {
onClose: () => void;
navLinks: NavLink[];
aboutLinks: NavLink[];
+ learnLinks: NavLink[];
onNavClick: (path?: string) => void;
}
@@ -22,6 +23,7 @@ export default function MobileMenu({
onClose,
navLinks,
aboutLinks,
+ learnLinks,
onNavClick,
}: MobileMenuProps) {
return (
@@ -95,6 +97,37 @@ export default function MobileMenu({
))}
+
+
+
+ {/* Learn Section */}
+
+
+ Learn
+
+
+ {learnLinks.map((link) => (
+ onNavClick(link.path)}
+ style={{ fontFamily: typography.fontFamily.primary }}
+ >
+ {link.label}
+
+ ))}
+
+
>
diff --git a/app/src/components/shared/HomeHeader.tsx b/app/src/components/shared/HomeHeader.tsx
index 6aae3088b..1650667a5 100644
--- a/app/src/components/shared/HomeHeader.tsx
+++ b/app/src/components/shared/HomeHeader.tsx
@@ -19,6 +19,8 @@ export default function HeaderNavigation() {
{ label: 'Supporters', path: `/${countryId}/supporters` },
];
+ const learnLinks: NavLink[] = [{ label: 'API', path: `/${countryId}/api` }];
+
const navLinks: NavLink[] = [{ label: 'Donate', path: `/${countryId}/donate` }];
const handleNavClick = (path?: string) => {
@@ -61,6 +63,7 @@ export default function HeaderNavigation() {
onClose={close}
navLinks={navLinks}
aboutLinks={aboutLinks}
+ learnLinks={learnLinks}
onNavClick={handleNavClick}
/>
diff --git a/app/src/tests/fixtures/components/home-header/HeaderMocks.ts b/app/src/tests/fixtures/components/home-header/HeaderMocks.ts
index e971dd398..03b2eb7e4 100644
--- a/app/src/tests/fixtures/components/home-header/HeaderMocks.ts
+++ b/app/src/tests/fixtures/components/home-header/HeaderMocks.ts
@@ -5,12 +5,15 @@ export const MOCK_ABOUT_LINKS = [
{ label: 'Supporters', path: '/us/supporters' },
];
+export const MOCK_LEARN_LINKS = [{ label: 'API', path: '/us/api' }];
+
export const MOCK_HEADER_PROPS = {
opened: false,
onOpen: () => {},
onClose: () => {},
navLinks: MOCK_NAV_LINKS,
aboutLinks: MOCK_ABOUT_LINKS,
+ learnLinks: MOCK_LEARN_LINKS,
onNavClick: () => {},
} as const;
diff --git a/app/src/tests/fixtures/components/home/HomeMocks.ts b/app/src/tests/fixtures/components/home/HomeMocks.ts
index 9800d502f..7121f3c3b 100644
--- a/app/src/tests/fixtures/components/home/HomeMocks.ts
+++ b/app/src/tests/fixtures/components/home/HomeMocks.ts
@@ -5,6 +5,8 @@ export const MOCK_ABOUT_LINKS = [
{ label: 'Supporters', path: '/us/supporters' },
];
+export const MOCK_LEARN_LINKS = [{ label: 'API', path: '/us/api' }];
+
export const EXPECTED_TEXT = {
US: {
HERO_TITLE: 'Computing public policy for everyone',
diff --git a/app/src/tests/unit/components/home-header/DesktopNavigation.test.tsx b/app/src/tests/unit/components/home-header/DesktopNavigation.test.tsx
index 474c78ff9..11908aad8 100644
--- a/app/src/tests/unit/components/home-header/DesktopNavigation.test.tsx
+++ b/app/src/tests/unit/components/home-header/DesktopNavigation.test.tsx
@@ -1,7 +1,11 @@
import { renderWithCountry, screen } from '@test-utils';
import { describe, expect, test, vi } from 'vitest';
import DesktopNavigation from '@/components/home-header/DesktopNavigation';
-import { MOCK_ABOUT_LINKS, MOCK_NAV_LINKS } from '@/tests/fixtures/components/home/HomeMocks';
+import {
+ MOCK_ABOUT_LINKS,
+ MOCK_LEARN_LINKS,
+ MOCK_NAV_LINKS,
+} from '@/tests/fixtures/components/home/HomeMocks';
describe('DesktopNavigation', () => {
test('given nav links then displays nav items', () => {
@@ -13,6 +17,7 @@ describe('DesktopNavigation', () => {
,
'us'
@@ -33,6 +38,7 @@ describe('DesktopNavigation', () => {
,
'us'
@@ -55,6 +61,7 @@ describe('DesktopNavigation', () => {
,
'us'
@@ -76,6 +83,7 @@ describe('DesktopNavigation', () => {
,
'us'
@@ -98,6 +106,7 @@ describe('DesktopNavigation', () => {
,
'us'
diff --git a/app/src/tests/unit/components/home-header/HeaderContent.test.tsx b/app/src/tests/unit/components/home-header/HeaderContent.test.tsx
index 77b95b928..551b95677 100644
--- a/app/src/tests/unit/components/home-header/HeaderContent.test.tsx
+++ b/app/src/tests/unit/components/home-header/HeaderContent.test.tsx
@@ -3,6 +3,7 @@ import { describe, expect, test, vi } from 'vitest';
import HeaderContent from '@/components/home-header/HeaderContent';
import {
MOCK_ABOUT_LINKS,
+ MOCK_LEARN_LINKS,
MOCK_NAV_LINKS,
} from '@/tests/fixtures/components/home-header/HeaderMocks';
@@ -21,6 +22,7 @@ describe('HeaderContent', () => {
onClose={onClose}
navLinks={MOCK_NAV_LINKS}
aboutLinks={MOCK_ABOUT_LINKS}
+ learnLinks={MOCK_LEARN_LINKS}
onNavClick={onNavClick}
/>,
'us'
@@ -45,6 +47,7 @@ describe('HeaderContent', () => {
onClose={onClose}
navLinks={MOCK_NAV_LINKS}
aboutLinks={MOCK_ABOUT_LINKS}
+ learnLinks={MOCK_LEARN_LINKS}
onNavClick={onNavClick}
/>,
'us'
@@ -70,6 +73,7 @@ describe('HeaderContent', () => {
onClose={onClose}
navLinks={MOCK_NAV_LINKS}
aboutLinks={MOCK_ABOUT_LINKS}
+ learnLinks={MOCK_LEARN_LINKS}
onNavClick={onNavClick}
/>,
'us'
diff --git a/app/src/tests/unit/components/home-header/MobileMenu.test.tsx b/app/src/tests/unit/components/home-header/MobileMenu.test.tsx
index bff4e8e59..89eabc65a 100644
--- a/app/src/tests/unit/components/home-header/MobileMenu.test.tsx
+++ b/app/src/tests/unit/components/home-header/MobileMenu.test.tsx
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
import MobileMenu from '@/components/home-header/MobileMenu';
import {
MOCK_ABOUT_LINKS,
+ MOCK_LEARN_LINKS,
MOCK_NAV_LINKS,
} from '@/tests/fixtures/components/home-header/HeaderMocks';
@@ -25,6 +26,7 @@ describe('MobileMenu', () => {
onClose={onClose}
navLinks={MOCK_NAV_LINKS}
aboutLinks={MOCK_ABOUT_LINKS}
+ learnLinks={MOCK_LEARN_LINKS}
onNavClick={onNavClick}
/>,
'us'
@@ -52,6 +54,7 @@ describe('MobileMenu', () => {
onClose={onClose}
navLinks={MOCK_NAV_LINKS}
aboutLinks={MOCK_ABOUT_LINKS}
+ learnLinks={MOCK_LEARN_LINKS}
onNavClick={onNavClick}
/>,
'us'
@@ -79,6 +82,7 @@ describe('MobileMenu', () => {
onClose={onClose}
navLinks={MOCK_NAV_LINKS}
aboutLinks={MOCK_ABOUT_LINKS}
+ learnLinks={MOCK_LEARN_LINKS}
onNavClick={onNavClick}
/>,
'us'
@@ -104,6 +108,7 @@ describe('MobileMenu', () => {
onClose={onClose}
navLinks={MOCK_NAV_LINKS}
aboutLinks={MOCK_ABOUT_LINKS}
+ learnLinks={MOCK_LEARN_LINKS}
onNavClick={onNavClick}
/>,
'us'
@@ -130,6 +135,7 @@ describe('MobileMenu', () => {
onClose={onClose}
navLinks={MOCK_NAV_LINKS}
aboutLinks={MOCK_ABOUT_LINKS}
+ learnLinks={MOCK_LEARN_LINKS}
onNavClick={onNavClick}
/>,
'us'
@@ -157,6 +163,7 @@ describe('MobileMenu', () => {
onClose={onClose}
navLinks={MOCK_NAV_LINKS}
aboutLinks={MOCK_ABOUT_LINKS}
+ learnLinks={MOCK_LEARN_LINKS}
onNavClick={onNavClick}
/>,
'us'
From 86e498df03fd53d47d382b5ea0ec31b186c14de6 Mon Sep 17 00:00:00 2001
From: SakshiKekre
Date: Mon, 3 Nov 2025 00:36:41 -0500
Subject: [PATCH 5/8] fix: Restore richtext stylesheet
---
app/src/pages/API.page.tsx | 110 +++++++++----------
app/src/styles/stylesheets/RichTextBlock.css | 42 +++++++
2 files changed, 95 insertions(+), 57 deletions(-)
create mode 100644 app/src/styles/stylesheets/RichTextBlock.css
diff --git a/app/src/pages/API.page.tsx b/app/src/pages/API.page.tsx
index 06d6eae1b..dfb89e9d0 100644
--- a/app/src/pages/API.page.tsx
+++ b/app/src/pages/API.page.tsx
@@ -1,10 +1,11 @@
import { useSelector } from 'react-redux';
-import { Anchor, Code, List, Stack, Text, Title } from '@mantine/core';
+import { Code, List, Stack, Text, Title } from '@mantine/core';
import APIPlayground from '@/components/shared/static/APIPlayground';
import CodeBlock from '@/components/shared/static/CodeBlock';
import ContentSection from '@/components/shared/static/ContentSection';
import CTASection from '@/components/shared/static/CTASection';
import HeroSection from '@/components/shared/static/HeroSection';
+import RichTextBlock from '@/components/shared/static/RichTextBlock';
import StaticPageLayout from '@/components/shared/static/StaticPageLayout';
import VariableParameterExplorer from '@/components/shared/static/VariableParameterExplorer';
import {
@@ -60,46 +61,39 @@ export default function APIPage() {
title="Getting Started"
variant="primary"
content={
-
-
+
+
PolicyEngine's REST API (
-
https://household.api.policyengine.org
-
+
) simulates tax-benefit policy outcomes and reform impacts for households. Access
- requires a{' '}
-
- Client ID
- {' '}
- and{' '}
-
- Client Secret
- {' '}
- provided by PolicyEngine. For access, contact us at{' '}
- hello@policyengine.org .
-
-
- On this page:
-
-
-
- Fetch an authentication token
-
-
- Calculate household-level policy outcomes
-
-
- Variable and parameter metadata
-
-
- API playground
-
-
-
+ requires a Client ID and Client Secret provided by
+ PolicyEngine. For access, contact us at{' '}
+ hello@policyengine.org .
+
+
+ On this page:
+
+
+
}
cta={{
text: 'Try API Playground',
@@ -109,12 +103,14 @@ export default function APIPage() {
/>
-
- Execute a credentials exchange using your client ID and client secret to obtain an
- authentication token. Include this token in the authorization header of every request as
- "Bearer YOUR_TOKEN". Tokens expire monthly for security.
-
-
+
+
+ Execute a credentials exchange using your client ID and client secret to obtain an
+ authentication token. Include this token in the authorization header of every request as
+ "Bearer YOUR_TOKEN". Tokens expire monthly for security.
+
+
+
@@ -125,16 +121,16 @@ export default function APIPage() {
title="Calculate household-level policy outcomes"
variant="primary"
>
-
-
+
+
Returns household-level policy outcomes. Pass in a household object defining people,
groups and any variable values. Use null values for requested variables - these will be
computed and returned.
-
-
- POST /{countryId}/calculate
-
-
+
+
+
+ POST /{countryId}/calculate
+
-
-
+
+
Access information about all available variables and parameters in the PolicyEngine API.
-
-
- GET /{countryId}/metadata
-
-
+
+
+
+ GET /{countryId}/metadata
+
{metadata && }
-
- Try out the API in this interactive demo.
-
+
+ Try out the API in this interactive demo.
+
diff --git a/app/src/styles/stylesheets/RichTextBlock.css b/app/src/styles/stylesheets/RichTextBlock.css
new file mode 100644
index 000000000..6b8516c21
--- /dev/null
+++ b/app/src/styles/stylesheets/RichTextBlock.css
@@ -0,0 +1,42 @@
+/* Note: For most components, we don't condone separate CSS stylesheets, but Mantine
+ provides no way to provide styling to child components natively */
+
+.rich-text-block p {
+ font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
+ font-size: 16px;
+ line-height: 1.625;
+ margin-bottom: 16px;
+ margin-top: 0;
+}
+
+.rich-text-block p:last-child {
+ margin-bottom: 0;
+}
+
+.rich-text-block a {
+ color: var(--link-color);
+ text-decoration: underline;
+ transition: opacity 0.2s ease;
+}
+
+.rich-text-block a:hover {
+ opacity: 0.8;
+}
+
+.rich-text-block strong {
+ font-weight: 700;
+}
+
+.rich-text-block em {
+ font-style: italic;
+}
+
+.rich-text-block ul,
+.rich-text-block ol {
+ margin-bottom: 16px;
+ padding-left: 32px;
+}
+
+.rich-text-block li {
+ margin-bottom: 4px;
+}
From 654908f35ce5f850191f8c56796d8f476a24e5d3 Mon Sep 17 00:00:00 2001
From: SakshiKekre
Date: Mon, 3 Nov 2025 00:41:13 -0500
Subject: [PATCH 6/8] fix: Restore RichTextBlock
---
app/src/components/shared/static/RichTextBlock.tsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/src/components/shared/static/RichTextBlock.tsx b/app/src/components/shared/static/RichTextBlock.tsx
index 58c58de72..055f07835 100644
--- a/app/src/components/shared/static/RichTextBlock.tsx
+++ b/app/src/components/shared/static/RichTextBlock.tsx
@@ -1,6 +1,8 @@
import { ReactNode } from 'react';
import { colors } from '@/designTokens';
+import '@/styles/stylesheets/RichTextBlock.css';
+
export interface RichTextBlockProps {
children: ReactNode;
variant?: 'default' | 'inverted';
From daa992a5a8e9efee5cf86fe45da0aeb1602b32ec Mon Sep 17 00:00:00 2001
From: SakshiKekre
Date: Mon, 3 Nov 2025 01:15:40 -0500
Subject: [PATCH 7/8] feat: Add codemirror library for code blocks on API page
---
.../components/shared/static/CodeBlock.tsx | 39 +++++++++++--------
app/src/pages/API.page.tsx | 10 ++---
2 files changed, 27 insertions(+), 22 deletions(-)
diff --git a/app/src/components/shared/static/CodeBlock.tsx b/app/src/components/shared/static/CodeBlock.tsx
index 3675af94e..b18e23378 100644
--- a/app/src/components/shared/static/CodeBlock.tsx
+++ b/app/src/components/shared/static/CodeBlock.tsx
@@ -1,6 +1,9 @@
import { useState } from 'react';
import { IconCheck, IconChevronDown, IconChevronUp, IconCopy } from '@tabler/icons-react';
-import { Box, Button, Code, CopyButton, Flex, Group, Stack, Text } from '@mantine/core';
+import { langs } from '@uiw/codemirror-extensions-langs';
+import CodeMirror, { EditorView } from '@uiw/react-codemirror';
+import { espresso } from 'thememirror';
+import { Box, Button, CopyButton, Flex, Group, Stack, Text } from '@mantine/core';
import { colors, spacing, typography } from '@/designTokens';
interface CodeBlockProps {
@@ -13,6 +16,16 @@ interface CodeBlockProps {
export default function CodeBlock({ code, language, title, showExpand = true }: CodeBlockProps) {
const [isExpanded, setIsExpanded] = useState(false);
+ // Map common language names to CodeMirror language extensions
+ const getLanguageExtension = () => {
+ if (!language) {
+ return langs.json();
+ }
+
+ const langKey = language.toLowerCase() as keyof typeof langs;
+ return langKey in langs ? langs[langKey]() : langs.json();
+ };
+
return (
{title && (
@@ -82,22 +95,14 @@ export default function CodeBlock({ code, language, title, showExpand = true }:
- {/* Code content */}
-
- {code}
-
+ {/* Code content with syntax highlighting */}
+
);
diff --git a/app/src/pages/API.page.tsx b/app/src/pages/API.page.tsx
index dfb89e9d0..423ff2a1e 100644
--- a/app/src/pages/API.page.tsx
+++ b/app/src/pages/API.page.tsx
@@ -1,5 +1,5 @@
import { useSelector } from 'react-redux';
-import { Code, List, Stack, Text, Title } from '@mantine/core';
+import { Code, SimpleGrid, Title } from '@mantine/core';
import APIPlayground from '@/components/shared/static/APIPlayground';
import CodeBlock from '@/components/shared/static/CodeBlock';
import ContentSection from '@/components/shared/static/ContentSection';
@@ -110,10 +110,10 @@ export default function APIPage() {
"Bearer YOUR_TOKEN". Tokens expire monthly for security.
-
+
-
+
POST /{countryId}/calculate
-
+
-
+
From d2804fa7bf53c19453df26ad7b72861c1fc7d724 Mon Sep 17 00:00:00 2001
From: SakshiKekre
Date: Mon, 3 Nov 2025 01:21:10 -0500
Subject: [PATCH 8/8] fix: Install dependency
---
app/package-lock.json | 803 ++++++++++++++++++++++++++++++++++++++++++
app/package.json | 6 +-
2 files changed, 808 insertions(+), 1 deletion(-)
diff --git a/app/package-lock.json b/app/package-lock.json
index 22ce382a7..aa7af56ab 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -16,6 +16,9 @@
"@tabler/icons-react": "^3.34.0",
"@tanstack/react-query": "^5.84.1",
"@tanstack/react-query-devtools": "^5.83.0",
+ "@uiw/codemirror-extensions-langs": "^4.25.2",
+ "@uiw/codemirror-theme-github": "^4.25.2",
+ "@uiw/react-codemirror": "^4.25.2",
"dayjs": "^1.11.13",
"framer-motion": "^12.23.24",
"react": "^19.1.0",
@@ -23,6 +26,7 @@
"react-plotly.js": "^2.6.0",
"react-redux": "^9.2.0",
"react-router-dom": "^7.9.3",
+ "thememirror": "^2.0.1",
"wordwrapjs": "^5.1.1"
},
"devDependencies": {
@@ -428,6 +432,416 @@
"findup": "bin/findup.js"
}
},
+ "node_modules/@codemirror/autocomplete": {
+ "version": "6.19.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.1.tgz",
+ "integrity": "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.17.0",
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/commands": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz",
+ "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.4.0",
+ "@codemirror/view": "^6.27.0",
+ "@lezer/common": "^1.1.0"
+ }
+ },
+ "node_modules/@codemirror/lang-angular": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.4.tgz",
+ "integrity": "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/lang-html": "^6.0.0",
+ "@codemirror/lang-javascript": "^6.1.2",
+ "@codemirror/language": "^6.0.0",
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.3.3"
+ }
+ },
+ "node_modules/@codemirror/lang-cpp": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz",
+ "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@lezer/cpp": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-css": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
+ "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@lezer/common": "^1.0.2",
+ "@lezer/css": "^1.1.7"
+ }
+ },
+ "node_modules/@codemirror/lang-go": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz",
+ "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/language": "^6.6.0",
+ "@codemirror/state": "^6.0.0",
+ "@lezer/common": "^1.0.0",
+ "@lezer/go": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-html": {
+ "version": "6.4.11",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
+ "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/lang-css": "^6.0.0",
+ "@codemirror/lang-javascript": "^6.0.0",
+ "@codemirror/language": "^6.4.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.17.0",
+ "@lezer/common": "^1.0.0",
+ "@lezer/css": "^1.1.0",
+ "@lezer/html": "^1.3.12"
+ }
+ },
+ "node_modules/@codemirror/lang-java": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz",
+ "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@lezer/java": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-javascript": {
+ "version": "6.2.4",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
+ "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/language": "^6.6.0",
+ "@codemirror/lint": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.17.0",
+ "@lezer/common": "^1.0.0",
+ "@lezer/javascript": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-jinja": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-jinja/-/lang-jinja-6.0.0.tgz",
+ "integrity": "sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/lang-html": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.2.0",
+ "@lezer/lr": "^1.4.0"
+ }
+ },
+ "node_modules/@codemirror/lang-json": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
+ "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@lezer/json": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-less": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz",
+ "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/lang-css": "^6.2.0",
+ "@codemirror/language": "^6.0.0",
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-liquid": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.3.0.tgz",
+ "integrity": "sha512-fY1YsUExcieXRTsCiwX/bQ9+PbCTA/Fumv7C7mTUZHoFkibfESnaXwpr2aKH6zZVwysEunsHHkaIpM/pl3xETQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/lang-html": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/common": "^1.0.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.3.1"
+ }
+ },
+ "node_modules/@codemirror/lang-markdown": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz",
+ "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.7.1",
+ "@codemirror/lang-html": "^6.0.0",
+ "@codemirror/language": "^6.3.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/common": "^1.2.1",
+ "@lezer/markdown": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-php": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz",
+ "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/lang-html": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@lezer/common": "^1.0.0",
+ "@lezer/php": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-python": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz",
+ "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.3.2",
+ "@codemirror/language": "^6.8.0",
+ "@codemirror/state": "^6.0.0",
+ "@lezer/common": "^1.2.1",
+ "@lezer/python": "^1.1.4"
+ }
+ },
+ "node_modules/@codemirror/lang-rust": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz",
+ "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@lezer/rust": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-sass": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz",
+ "integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/lang-css": "^6.2.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@lezer/common": "^1.0.2",
+ "@lezer/sass": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-sql": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz",
+ "integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-vue": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz",
+ "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/lang-html": "^6.0.0",
+ "@codemirror/lang-javascript": "^6.1.2",
+ "@codemirror/language": "^6.0.0",
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.3.1"
+ }
+ },
+ "node_modules/@codemirror/lang-wast": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz",
+ "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-xml": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
+ "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/language": "^6.4.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/common": "^1.0.0",
+ "@lezer/xml": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/lang-yaml": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
+ "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.2.0",
+ "@lezer/lr": "^1.0.0",
+ "@lezer/yaml": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/language": {
+ "version": "6.11.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
+ "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.23.0",
+ "@lezer/common": "^1.1.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0",
+ "style-mod": "^4.0.0"
+ }
+ },
+ "node_modules/@codemirror/language-data": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.2.tgz",
+ "integrity": "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/lang-angular": "^0.1.0",
+ "@codemirror/lang-cpp": "^6.0.0",
+ "@codemirror/lang-css": "^6.0.0",
+ "@codemirror/lang-go": "^6.0.0",
+ "@codemirror/lang-html": "^6.0.0",
+ "@codemirror/lang-java": "^6.0.0",
+ "@codemirror/lang-javascript": "^6.0.0",
+ "@codemirror/lang-jinja": "^6.0.0",
+ "@codemirror/lang-json": "^6.0.0",
+ "@codemirror/lang-less": "^6.0.0",
+ "@codemirror/lang-liquid": "^6.0.0",
+ "@codemirror/lang-markdown": "^6.0.0",
+ "@codemirror/lang-php": "^6.0.0",
+ "@codemirror/lang-python": "^6.0.0",
+ "@codemirror/lang-rust": "^6.0.0",
+ "@codemirror/lang-sass": "^6.0.0",
+ "@codemirror/lang-sql": "^6.0.0",
+ "@codemirror/lang-vue": "^0.1.1",
+ "@codemirror/lang-wast": "^6.0.0",
+ "@codemirror/lang-xml": "^6.0.0",
+ "@codemirror/lang-yaml": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/legacy-modes": "^6.4.0"
+ }
+ },
+ "node_modules/@codemirror/legacy-modes": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz",
+ "integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0"
+ }
+ },
+ "node_modules/@codemirror/lint": {
+ "version": "6.9.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.1.tgz",
+ "integrity": "sha512-te7To1EQHePBQQzasDKWmK2xKINIXpk+xAiSYr9ZN+VB4KaT+/Hi2PEkeErTk5BV3PTz1TLyQL4MtJfPkKZ9sw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.35.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/search": {
+ "version": "6.5.11",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
+ "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/state": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
+ "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
+ "license": "MIT",
+ "dependencies": {
+ "@marijn/find-cluster-break": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/theme-one-dark": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
+ "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/highlight": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/view": {
+ "version": "6.38.6",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
+ "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/state": "^6.5.0",
+ "crelt": "^1.0.6",
+ "style-mod": "^4.1.0",
+ "w3c-keyname": "^2.2.4"
+ }
+ },
"node_modules/@csstools/color-helpers": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
@@ -1423,6 +1837,183 @@
"buffer": "^6.0.3"
}
},
+ "node_modules/@lezer/common": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.3.0.tgz",
+ "integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==",
+ "license": "MIT"
+ },
+ "node_modules/@lezer/cpp": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.3.tgz",
+ "integrity": "sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/css": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
+ "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.3.0"
+ }
+ },
+ "node_modules/@lezer/go": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz",
+ "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.3.0"
+ }
+ },
+ "node_modules/@lezer/highlight": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
+ "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.3.0"
+ }
+ },
+ "node_modules/@lezer/html": {
+ "version": "1.3.12",
+ "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz",
+ "integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/java": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz",
+ "integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/javascript": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
+ "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.1.3",
+ "@lezer/lr": "^1.3.0"
+ }
+ },
+ "node_modules/@lezer/json": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
+ "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/lr": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
+ "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/markdown": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.5.1.tgz",
+ "integrity": "sha512-F3ZFnIfNAOy/jPSk6Q0e3bs7e9grfK/n5zerkKoc5COH6Guy3Zb0vrJwXzdck79K16goBhYBRAvhf+ksqe0cMg==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.0.0",
+ "@lezer/highlight": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/php": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz",
+ "integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.1.0"
+ }
+ },
+ "node_modules/@lezer/python": {
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz",
+ "integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/rust": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz",
+ "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/sass": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.1.0.tgz",
+ "integrity": "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/xml": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz",
+ "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/yaml": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz",
+ "integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.4.0"
+ }
+ },
"node_modules/@mantine/core": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.1.2.tgz",
@@ -1583,6 +2174,12 @@
"license": "ISC",
"peer": true
},
+ "node_modules/@marijn/find-cluster-break": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
+ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
+ "license": "MIT"
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1777,6 +2374,52 @@
}
}
},
+ "node_modules/@replit/codemirror-lang-nix": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@replit/codemirror-lang-nix/-/codemirror-lang-nix-6.0.1.tgz",
+ "integrity": "sha512-lvzjoYn9nfJzBD5qdm3Ut6G3+Or2wEacYIDJ49h9+19WSChVnxv4ojf+rNmQ78ncuxIt/bfbMvDLMeMP0xze6g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/common": "^1.0.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@replit/codemirror-lang-solidity": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@replit/codemirror-lang-solidity/-/codemirror-lang-solidity-6.0.2.tgz",
+ "integrity": "sha512-/dpTVH338KFV6SaDYYSadkB4bI/0B0QRF/bkt1XS3t3QtyR49mn6+2k0OUQhvt2ZSO7kt10J+OPilRAtgbmX0w==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/highlight": "^1.2.0"
+ },
+ "peerDependencies": {
+ "@codemirror/language": "^6.0.0"
+ }
+ },
+ "node_modules/@replit/codemirror-lang-svelte": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/@replit/codemirror-lang-svelte/-/codemirror-lang-svelte-6.0.0.tgz",
+ "integrity": "sha512-U2OqqgMM6jKelL0GNWbAmqlu1S078zZNoBqlJBW+retTc5M4Mha6/Y2cf4SVg6ddgloJvmcSpt4hHrVoM4ePRA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/lang-css": "^6.0.1",
+ "@codemirror/lang-html": "^6.2.0",
+ "@codemirror/lang-javascript": "^6.1.1",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/common": "^1.0.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/javascript": "^1.2.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.19",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz",
@@ -3122,6 +3765,111 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@uiw/codemirror-extensions-basic-setup": {
+ "version": "4.25.2",
+ "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.2.tgz",
+ "integrity": "sha512-s2fbpdXrSMWEc86moll/d007ZFhu6jzwNu5cWv/2o7egymvLeZO52LWkewgbr+BUCGWGPsoJVWeaejbsb/hLcw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/commands": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/lint": "^6.0.0",
+ "@codemirror/search": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://jaywcjlove.github.io/#/sponsor"
+ },
+ "peerDependencies": {
+ "@codemirror/autocomplete": ">=6.0.0",
+ "@codemirror/commands": ">=6.0.0",
+ "@codemirror/language": ">=6.0.0",
+ "@codemirror/lint": ">=6.0.0",
+ "@codemirror/search": ">=6.0.0",
+ "@codemirror/state": ">=6.0.0",
+ "@codemirror/view": ">=6.0.0"
+ }
+ },
+ "node_modules/@uiw/codemirror-extensions-langs": {
+ "version": "4.25.2",
+ "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-langs/-/codemirror-extensions-langs-4.25.2.tgz",
+ "integrity": "sha512-fWS9fP52QJAFgXbsUl6vKMBqQ2PIT8z5TvX8BKBqPz/I+ayh6Fk+HzoeUtslUGPTu+UTPgB5m0qt3rTIDXWvng==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/language-data": "^6.5.1",
+ "@replit/codemirror-lang-nix": "^6.0.1",
+ "@replit/codemirror-lang-solidity": "^6.0.1",
+ "@replit/codemirror-lang-svelte": "^6.0.0",
+ "codemirror-lang-mermaid": "^0.5.0"
+ },
+ "funding": {
+ "url": "https://jaywcjlove.github.io/#/sponsor"
+ },
+ "peerDependencies": {
+ "@codemirror/language": ">=6.0.0",
+ "@codemirror/language-data": ">=6.0.0"
+ }
+ },
+ "node_modules/@uiw/codemirror-theme-github": {
+ "version": "4.25.2",
+ "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-github/-/codemirror-theme-github-4.25.2.tgz",
+ "integrity": "sha512-9g3ujmYCNU2VQCp0+XzI1NS5hSZGgXRtH+5yWli5faiPvHGYZUVke+5Pnzdn/1tkgW6NpTQ7U/JHsyQkgbnZ/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@uiw/codemirror-themes": "4.25.2"
+ },
+ "funding": {
+ "url": "https://jaywcjlove.github.io/#/sponsor"
+ }
+ },
+ "node_modules/@uiw/codemirror-themes": {
+ "version": "4.25.2",
+ "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.25.2.tgz",
+ "integrity": "sha512-WFYxW3OlCkMomXQBlQdGj1JZ011UNCa7xYdmgYqywVc4E8f5VgIzRwCZSBNVjpWGGDBOjc+Z6F65l7gttP16pg==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://jaywcjlove.github.io/#/sponsor"
+ },
+ "peerDependencies": {
+ "@codemirror/language": ">=6.0.0",
+ "@codemirror/state": ">=6.0.0",
+ "@codemirror/view": ">=6.0.0"
+ }
+ },
+ "node_modules/@uiw/react-codemirror": {
+ "version": "4.25.2",
+ "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.25.2.tgz",
+ "integrity": "sha512-XP3R1xyE0CP6Q0iR0xf3ed+cJzJnfmbLelgJR6osVVtMStGGZP3pGQjjwDRYptmjGHfEELUyyBLdY25h0BQg7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.6",
+ "@codemirror/commands": "^6.1.0",
+ "@codemirror/state": "^6.1.1",
+ "@codemirror/theme-one-dark": "^6.0.0",
+ "@uiw/codemirror-extensions-basic-setup": "4.25.2",
+ "codemirror": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://jaywcjlove.github.io/#/sponsor"
+ },
+ "peerDependencies": {
+ "@babel/runtime": ">=7.11.0",
+ "@codemirror/state": ">=6.0.0",
+ "@codemirror/theme-one-dark": ">=6.0.0",
+ "@codemirror/view": ">=6.0.0",
+ "codemirror": ">=6.0.0",
+ "react": ">=17.0.0",
+ "react-dom": ">=17.0.0"
+ }
+ },
"node_modules/@vitejs/plugin-react": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.6.0.tgz",
@@ -4247,6 +4995,32 @@
"node": ">=6"
}
},
+ "node_modules/codemirror": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
+ "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/commands": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/lint": "^6.0.0",
+ "@codemirror/search": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ }
+ },
+ "node_modules/codemirror-lang-mermaid": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/codemirror-lang-mermaid/-/codemirror-lang-mermaid-0.5.0.tgz",
+ "integrity": "sha512-Taw/2gPCyNArQJCxIP/HSUif+3zrvD+6Ugt7KJZ2dUKou/8r3ZhcfG8krNTZfV2iu8AuGnymKuo7bLPFyqsh/A==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.9.0",
+ "@lezer/highlight": "^1.1.6",
+ "@lezer/lr": "^1.3.10"
+ }
+ },
"node_modules/color-alpha": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/color-alpha/-/color-alpha-1.0.4.tgz",
@@ -4450,6 +5224,12 @@
"license": "MIT",
"peer": true
},
+ "node_modules/crelt": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
+ "license": "MIT"
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -11210,6 +11990,12 @@
"webpack": "^5.27.0"
}
},
+ "node_modules/style-mod": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
+ "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
+ "license": "MIT"
+ },
"node_modules/stylelint": {
"version": "16.21.1",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.21.1.tgz",
@@ -11827,6 +12613,17 @@
}
}
},
+ "node_modules/thememirror": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/thememirror/-/thememirror-2.0.1.tgz",
+ "integrity": "sha512-d5i6FVvWWPkwrm4cHLI3t9AT1OrkAt7Ig8dtdYSofgF7C/eiyNuq6zQzSTusWTde3jpW9WLvA9J/fzNKMUsd0w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ }
+ },
"node_modules/through2": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
@@ -12654,6 +13451,12 @@
"pbf": "^3.2.1"
}
},
+ "node_modules/w3c-keyname": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
+ "license": "MIT"
+ },
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
diff --git a/app/package.json b/app/package.json
index 7eafef8b2..9435d153b 100644
--- a/app/package.json
+++ b/app/package.json
@@ -31,6 +31,9 @@
"@tabler/icons-react": "^3.34.0",
"@tanstack/react-query": "^5.84.1",
"@tanstack/react-query-devtools": "^5.83.0",
+ "@uiw/codemirror-extensions-langs": "^4.25.2",
+ "@uiw/codemirror-theme-github": "^4.25.2",
+ "@uiw/react-codemirror": "^4.25.2",
"dayjs": "^1.11.13",
"framer-motion": "^12.23.24",
"react": "^19.1.0",
@@ -38,6 +41,7 @@
"react-plotly.js": "^2.6.0",
"react-redux": "^9.2.0",
"react-router-dom": "^7.9.3",
+ "thememirror": "^2.0.1",
"wordwrapjs": "^5.1.1"
},
"devDependencies": {
@@ -76,4 +80,4 @@
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.3"
}
-}
\ No newline at end of file
+}