updated design
This commit is contained in:
@@ -16,7 +16,17 @@
|
||||
"Bash(pnpm --filter @originmain/app install)",
|
||||
"Bash(pnpm add *)",
|
||||
"Bash(xargs ls *)",
|
||||
"Bash(grep -E \"\\\\.\\(ts|tsx\\)$\")"
|
||||
"Bash(grep -E \"\\\\.\\(ts|tsx\\)$\")",
|
||||
"WebSearch",
|
||||
"WebFetch(domain:trees.software)",
|
||||
"Bash(pnpm exec *)",
|
||||
"WebFetch(domain:diffs.com)",
|
||||
"WebFetch(domain:diff.com)",
|
||||
"Bash(npm show *)",
|
||||
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(json.dumps\\({k:d.get\\(k\\) for k in ['description','main','types','exports','peerDependencies','keywords']}, indent=2\\)\\)\")",
|
||||
"Bash(pnpm --filter @originmain/diff-engine test)",
|
||||
"Bash(npx tsc *)",
|
||||
"Bash(npm test *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,10 +242,13 @@ button { font-family: inherit; cursor: pointer; border: none; background: none;
|
||||
max-width: 1120px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.nav-logo { justify-self: start; }
|
||||
.nav-links { justify-self: center; }
|
||||
.nav-actions{ justify-self: end; }
|
||||
.nav-logo {
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
|
||||
@@ -9,17 +9,19 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@originmain/ui": "workspace:*",
|
||||
"@fluentui/react-components": "^9.54.0",
|
||||
"@fluentui/react-icons": "^2.0.0",
|
||||
"@originmain/diff-engine": "workspace:*",
|
||||
"@originmain/origin-graph": "workspace:*",
|
||||
"@originmain/renderer": "workspace:*",
|
||||
"@fluentui/react-components": "^9.54.0",
|
||||
"@fluentui/react-icons": "^2.0.0",
|
||||
"@originmain/ui": "workspace:*",
|
||||
"@pierre/diffs": "^1.1.19",
|
||||
"@pierre/trees": "1.0.0-beta.3",
|
||||
"@tanstack/react-query": "^5.62.0",
|
||||
"next": "^15.1.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"zustand": "^5.0.0",
|
||||
"@tanstack/react-query": "^5.62.0"
|
||||
"zustand": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
|
||||
+1758
-573
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,9 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useCanvas } from '@/store/canvas';
|
||||
import { useDiff } from '@/hooks/useDiff';
|
||||
import { ARTBOARD_SNAPSHOTS } from '@/data/artboard-snapshots';
|
||||
import type { DiffResult, PropChange } from '@originmain/diff-engine';
|
||||
|
||||
const PROPS = [
|
||||
{ key: 'title', val: '"Revenue Overview"', type: 's' },
|
||||
@@ -11,15 +14,6 @@ const PROPS = [
|
||||
{ key: 'loading', val: 'false', type: 'b' },
|
||||
];
|
||||
|
||||
const DIFF = [
|
||||
{ op: 'del', text: '− borderRadius: 8px' },
|
||||
{ op: 'add', text: '+ borderRadius: 12px' },
|
||||
{ op: 'del', text: '− accentColor: #2A6CD4' },
|
||||
{ op: 'add', text: '+ accentColor: #0066FF' },
|
||||
{ op: 'del', text: '− padding: 16' },
|
||||
{ op: 'add', text: '+ padding: 20' },
|
||||
];
|
||||
|
||||
const TYPE_COLORS: Record<string, string> = {
|
||||
s: '#7DD3A8',
|
||||
n: '#7EB8FF',
|
||||
@@ -44,6 +38,7 @@ const T = {
|
||||
export function Inspector() {
|
||||
const { selectedArtboardId } = useCanvas();
|
||||
const [tab, setTab] = useState<TabId>('props');
|
||||
const diffResult = useDiff(selectedArtboardId);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -132,37 +127,7 @@ export function Inspector() {
|
||||
</Section>
|
||||
</>
|
||||
) : tab === 'diff' ? (
|
||||
<Section label="Intent Diff">
|
||||
<div
|
||||
style={{
|
||||
fontFamily: "'JetBrains Mono', ui-monospace, monospace",
|
||||
fontSize: '0.5875rem',
|
||||
color: 'rgba(255,255,255,0.28)',
|
||||
marginBottom: 10,
|
||||
letterSpacing: '-0.01em',
|
||||
}}
|
||||
>
|
||||
DashboardCard.tsx · 3 hunks
|
||||
</div>
|
||||
{DIFF.map((d, i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
fontFamily: "'JetBrains Mono', ui-monospace, monospace",
|
||||
fontSize: '0.625rem',
|
||||
padding: '4px 8px',
|
||||
borderRadius: 4,
|
||||
marginBottom: 3,
|
||||
lineHeight: 1.55,
|
||||
background: d.op === 'del' ? 'rgba(255,70,70,0.08)' : 'rgba(70,220,120,0.08)',
|
||||
color: d.op === 'del' ? '#FF8080' : '#7DDBA0',
|
||||
borderLeft: `2px solid ${d.op === 'del' ? 'rgba(255,80,80,0.3)' : 'rgba(70,220,120,0.3)'}`,
|
||||
}}
|
||||
>
|
||||
{d.text}
|
||||
</div>
|
||||
))}
|
||||
</Section>
|
||||
<DiffTab artboardId={selectedArtboardId} diffResult={diffResult} />
|
||||
) : (
|
||||
<Section label="Origin Graph">
|
||||
<GraphNode label="DashboardCard" depth={0} isRoot />
|
||||
@@ -296,3 +261,92 @@ function GraphNode({ label, depth, isRoot }: { label: string; depth: number; isR
|
||||
function HSep() {
|
||||
return <div style={{ height: 1, background: 'rgba(255,255,255,0.04)', margin: '2px 0' }} />;
|
||||
}
|
||||
|
||||
/* ── Diff tab ─────────────────────────────────────────────── */
|
||||
function DiffTab({
|
||||
artboardId,
|
||||
diffResult,
|
||||
}: {
|
||||
artboardId: string | null;
|
||||
diffResult: DiffResult | null;
|
||||
}) {
|
||||
if (!artboardId || !diffResult) {
|
||||
return (
|
||||
<Section label="Intent Diff">
|
||||
<span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: '0.625rem', color: 'rgba(255,255,255,0.22)' }}>
|
||||
No diff available
|
||||
</span>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
const { diff } = diffResult;
|
||||
const snapshots = ARTBOARD_SNAPSHOTS[artboardId];
|
||||
const filename = snapshots?.after.filePath ?? `${diff.name}.tsx`;
|
||||
const allChanges = [...diff.propChanges, ...diff.styleChanges];
|
||||
const hunkCount = allChanges.filter(c => c.changeType !== 'unchanged').length;
|
||||
|
||||
return (
|
||||
<Section label="Intent Diff">
|
||||
<div
|
||||
style={{
|
||||
fontFamily: "'JetBrains Mono', ui-monospace, monospace",
|
||||
fontSize: '0.5875rem',
|
||||
color: 'rgba(255,255,255,0.28)',
|
||||
marginBottom: 10,
|
||||
letterSpacing: '-0.01em',
|
||||
}}
|
||||
>
|
||||
{filename} · {hunkCount} change{hunkCount !== 1 ? 's' : ''}
|
||||
</div>
|
||||
{allChanges.map((change, i) => (
|
||||
<DiffChangeRow key={i} change={change} />
|
||||
))}
|
||||
{allChanges.length === 0 && (
|
||||
<span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: '0.625rem', color: 'rgba(255,255,255,0.22)' }}>
|
||||
No changes detected
|
||||
</span>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
function DiffChangeRow({ change }: { change: PropChange }) {
|
||||
const isRemoved = change.changeType === 'removed';
|
||||
const isAdded = change.changeType === 'added';
|
||||
const isModified = change.changeType === 'modified';
|
||||
|
||||
const rows: Array<{ op: 'del' | 'add'; text: string }> = [];
|
||||
|
||||
if (isModified) {
|
||||
rows.push({ op: 'del', text: `− ${change.key}: ${change.before}` });
|
||||
rows.push({ op: 'add', text: `+ ${change.key}: ${change.after}` });
|
||||
} else if (isRemoved) {
|
||||
rows.push({ op: 'del', text: `− ${change.key}: ${change.before}` });
|
||||
} else if (isAdded) {
|
||||
rows.push({ op: 'add', text: `+ ${change.key}: ${change.after}` });
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{rows.map((r, i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
fontFamily: "'JetBrains Mono', ui-monospace, monospace",
|
||||
fontSize: '0.625rem',
|
||||
padding: '4px 8px',
|
||||
borderRadius: 4,
|
||||
marginBottom: 3,
|
||||
lineHeight: 1.55,
|
||||
background: r.op === 'del' ? 'rgba(255,70,70,0.08)' : 'rgba(70,220,120,0.08)',
|
||||
color: r.op === 'del' ? '#FF8080' : '#7DDBA0',
|
||||
borderLeft: `2px solid ${r.op === 'del' ? 'rgba(255,80,80,0.3)' : 'rgba(70,220,120,0.3)'}`,
|
||||
}}
|
||||
>
|
||||
{r.text}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Tree,
|
||||
TreeItem,
|
||||
TreeItemLayout,
|
||||
} from '@fluentui/react-components';
|
||||
import {
|
||||
SquareRegular,
|
||||
DocumentRegular,
|
||||
FolderRegular,
|
||||
FolderOpenRegular,
|
||||
} from '@fluentui/react-icons';
|
||||
import { useState } from 'react';
|
||||
import { useFileTree, FileTree, useFileTreeSelection } from '@pierre/trees/react';
|
||||
import { themeToTreeStyles } from '@pierre/trees';
|
||||
import { SquareRegular } from '@fluentui/react-icons';
|
||||
import { useCanvas } from '@/store/canvas';
|
||||
|
||||
const ARTBOARDS = [
|
||||
@@ -20,22 +13,55 @@ const ARTBOARDS = [
|
||||
{ id: 'data-table', label: 'DataTable' },
|
||||
];
|
||||
|
||||
// Dark panel tokens
|
||||
const FILE_PATHS = [
|
||||
'src/components/DashboardCard.tsx',
|
||||
'src/components/UserProfile.tsx',
|
||||
'src/components/NavSidebar.tsx',
|
||||
'src/components/DataTable.tsx',
|
||||
'src/components/StatsCard.tsx',
|
||||
'src/app/canvas/page.tsx',
|
||||
'src/app/layout.tsx',
|
||||
'src/store/canvas.ts',
|
||||
'src/store/viewport.ts',
|
||||
];
|
||||
|
||||
const T = {
|
||||
bg: '#111115',
|
||||
border: 'rgba(255,255,255,0.055)',
|
||||
label: 'rgba(255,255,255,0.22)',
|
||||
item: 'rgba(255,255,255,0.42)',
|
||||
itemHov: 'rgba(255,255,255,0.72)',
|
||||
selBg: 'rgba(51,133,255,0.12)',
|
||||
selFg: 'rgba(255,255,255,0.88)',
|
||||
accent: '#3385FF',
|
||||
sep: 'rgba(255,255,255,0.04)',
|
||||
bg: '#111115',
|
||||
border: 'rgba(255,255,255,0.055)',
|
||||
item: 'rgba(255,255,255,0.42)',
|
||||
itemHov: 'rgba(255,255,255,0.72)',
|
||||
selBg: 'rgba(51,133,255,0.12)',
|
||||
selFg: 'rgba(255,255,255,0.88)',
|
||||
accent: '#3385FF',
|
||||
dim: 'rgba(255,255,255,0.22)',
|
||||
sep: 'rgba(255,255,255,0.04)',
|
||||
};
|
||||
|
||||
// Map the panel's dark theme into Trees' CSS custom properties
|
||||
const treeThemeStyles = themeToTreeStyles({
|
||||
type: 'dark',
|
||||
bg: '#111115',
|
||||
fg: 'rgba(255,255,255,0.42)',
|
||||
colors: {
|
||||
'editor.selectionBackground': 'rgba(51,133,255,0.14)',
|
||||
'list.activeSelectionBackground': 'rgba(51,133,255,0.14)',
|
||||
'list.inactiveSelectionBackground': 'rgba(51,133,255,0.08)',
|
||||
'list.hoverBackground': 'rgba(255,255,255,0.04)',
|
||||
'list.activeSelectionForeground': 'rgba(255,255,255,0.88)',
|
||||
'editorIndentGuide.background': 'rgba(255,255,255,0.04)',
|
||||
},
|
||||
});
|
||||
|
||||
export function ArtboardNavigator() {
|
||||
const { selectedArtboardId, selectArtboard } = useCanvas();
|
||||
|
||||
const { model } = useFileTree({
|
||||
paths: FILE_PATHS,
|
||||
initialExpansion: 2,
|
||||
density: 'compact',
|
||||
icons: 'minimal',
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -49,171 +75,138 @@ export function ArtboardNavigator() {
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{/* Artboards section */}
|
||||
<SectionLabel icon="⬜">Artboards</SectionLabel>
|
||||
{/* ── Artboards ── */}
|
||||
<SectionLabel>Artboards</SectionLabel>
|
||||
<div style={{ padding: '2px 6px 0' }}>
|
||||
{ARTBOARDS.map((ab) => (
|
||||
<div
|
||||
key={ab.id}
|
||||
onClick={() => selectArtboard(ab.id)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
padding: '6px 10px',
|
||||
borderRadius: 5,
|
||||
cursor: 'pointer',
|
||||
background: selectedArtboardId === ab.id ? T.selBg : 'transparent',
|
||||
color: selectedArtboardId === ab.id ? T.selFg : T.item,
|
||||
fontSize: '0.75rem',
|
||||
letterSpacing: '-0.01em',
|
||||
fontWeight: selectedArtboardId === ab.id ? 500 : 400,
|
||||
transition: 'background 0.1s, color 0.1s',
|
||||
userSelect: 'none',
|
||||
marginBottom: 1,
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
if (selectedArtboardId !== ab.id) {
|
||||
(e.currentTarget as HTMLDivElement).style.background = 'rgba(255,255,255,0.04)';
|
||||
(e.currentTarget as HTMLDivElement).style.color = T.itemHov;
|
||||
{ARTBOARDS.map((ab) => {
|
||||
const sel = selectedArtboardId === ab.id;
|
||||
return (
|
||||
<NavRow
|
||||
key={ab.id}
|
||||
selected={sel}
|
||||
onClick={() => selectArtboard(ab.id)}
|
||||
icon={
|
||||
<SquareRegular
|
||||
style={{ fontSize: 11, color: sel ? T.accent : 'rgba(255,255,255,0.2)', flexShrink: 0 }}
|
||||
/>
|
||||
}
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
if (selectedArtboardId !== ab.id) {
|
||||
(e.currentTarget as HTMLDivElement).style.background = 'transparent';
|
||||
(e.currentTarget as HTMLDivElement).style.color = T.item;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SquareRegular
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: selectedArtboardId === ab.id ? T.accent : 'rgba(255,255,255,0.25)',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
label={ab.label}
|
||||
after={sel && <ActiveDot />}
|
||||
/>
|
||||
{ab.label}
|
||||
{selectedArtboardId === ab.id && (
|
||||
<span
|
||||
style={{
|
||||
marginLeft: 'auto',
|
||||
width: 5,
|
||||
height: 5,
|
||||
borderRadius: '50%',
|
||||
background: T.accent,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<HSep />
|
||||
|
||||
{/* Files section */}
|
||||
<SectionLabel icon="📁">Files</SectionLabel>
|
||||
<Tree
|
||||
aria-label="Codebase"
|
||||
size="small"
|
||||
style={{ padding: '2px 6px' }}
|
||||
>
|
||||
<TreeItem
|
||||
itemType="branch"
|
||||
value="src"
|
||||
style={{ color: T.item }}
|
||||
>
|
||||
<TreeItemLayout
|
||||
iconBefore={<FolderOpenRegular style={{ fontSize: 11, color: 'rgba(255,255,255,0.3)' }} />}
|
||||
style={{ fontSize: '0.75rem', color: T.item, padding: '4px 4px' }}
|
||||
>
|
||||
src
|
||||
</TreeItemLayout>
|
||||
<Tree>
|
||||
<TreeItem itemType="branch" value="components">
|
||||
<TreeItemLayout
|
||||
iconBefore={<FolderRegular style={{ fontSize: 11, color: 'rgba(255,255,255,0.25)' }} />}
|
||||
style={{ fontSize: '0.75rem', color: 'rgba(255,255,255,0.35)', padding: '4px 4px' }}
|
||||
>
|
||||
components
|
||||
</TreeItemLayout>
|
||||
<Tree>
|
||||
{ARTBOARDS.map((ab) => (
|
||||
<TreeItem key={ab.id} itemType="leaf" value={`file-${ab.id}`}>
|
||||
<TreeItemLayout
|
||||
iconBefore={<DocumentRegular style={{ fontSize: 10, color: 'rgba(255,255,255,0.2)' }} />}
|
||||
style={{ fontSize: '0.6875rem', color: 'rgba(255,255,255,0.3)', padding: '3px 4px' }}
|
||||
>
|
||||
{ab.label}.tsx
|
||||
</TreeItemLayout>
|
||||
</TreeItem>
|
||||
))}
|
||||
</Tree>
|
||||
</TreeItem>
|
||||
<TreeItem itemType="leaf" value="app-page">
|
||||
<TreeItemLayout
|
||||
iconBefore={<DocumentRegular style={{ fontSize: 10, color: 'rgba(255,255,255,0.2)' }} />}
|
||||
style={{ fontSize: '0.6875rem', color: 'rgba(255,255,255,0.3)', padding: '3px 4px' }}
|
||||
>
|
||||
page.tsx
|
||||
</TreeItemLayout>
|
||||
</TreeItem>
|
||||
</Tree>
|
||||
</TreeItem>
|
||||
</Tree>
|
||||
{/* ── Files — @pierre/trees ── */}
|
||||
<SectionLabel>Files</SectionLabel>
|
||||
<div style={{ flex: 1, overflow: 'hidden', minHeight: 0 }}>
|
||||
<FileTree
|
||||
model={model}
|
||||
style={{
|
||||
...treeThemeStyles,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
// Fine-tune item sizing to match our panel density
|
||||
'--trees-item-height': '26px',
|
||||
'--trees-indent-width': '14px',
|
||||
} as React.CSSProperties}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<HSep />
|
||||
|
||||
{/* Graph nodes indicator */}
|
||||
<SectionLabel icon="◉">Graph</SectionLabel>
|
||||
<div
|
||||
style={{
|
||||
padding: '4px 16px 12px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 5,
|
||||
}}
|
||||
>
|
||||
<GraphStat label="nodes" value="284" color="#3385FF" />
|
||||
<GraphStat label="components" value="47" color="rgba(255,255,255,0.45)" />
|
||||
<GraphStat label="tokens" value="112" color="rgba(255,255,255,0.45)" />
|
||||
{/* ── Graph stats ── */}
|
||||
<SectionLabel>Graph</SectionLabel>
|
||||
<div style={{ padding: '4px 14px 14px', display: 'flex', flexDirection: 'column', gap: 6, flexShrink: 0 }}>
|
||||
<GraphStat label="nodes" value="284" color={T.accent} />
|
||||
<GraphStat label="components" value="47" color="rgba(255,255,255,0.45)" />
|
||||
<GraphStat label="tokens" value="112" color="rgba(255,255,255,0.45)" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionLabel({ children, icon }: { children: string; icon: string }) {
|
||||
/* ── Artboard row ─────────────────────────────────────────── */
|
||||
function NavRow({
|
||||
selected = false,
|
||||
onClick,
|
||||
icon,
|
||||
label,
|
||||
after,
|
||||
}: {
|
||||
selected?: boolean;
|
||||
onClick?: () => void;
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
after?: React.ReactNode;
|
||||
}) {
|
||||
const [hov, setHov] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
onMouseEnter={() => setHov(true)}
|
||||
onMouseLeave={() => setHov(false)}
|
||||
style={{
|
||||
padding: '12px 12px 5px',
|
||||
fontFamily: "'JetBrains Mono', ui-monospace, monospace",
|
||||
fontSize: '0.5625rem',
|
||||
fontWeight: 500,
|
||||
letterSpacing: '0.1em',
|
||||
textTransform: 'uppercase',
|
||||
color: 'rgba(255,255,255,0.22)',
|
||||
userSelect: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
gap: 7,
|
||||
padding: '6px 10px',
|
||||
borderRadius: 5,
|
||||
cursor: 'pointer',
|
||||
background: selected ? T.selBg : hov ? 'rgba(255,255,255,0.04)' : 'transparent',
|
||||
color: selected ? T.selFg : hov ? T.itemHov : T.item,
|
||||
fontSize: '0.75rem',
|
||||
letterSpacing: '-0.01em',
|
||||
fontWeight: selected ? 500 : 400,
|
||||
userSelect: 'none',
|
||||
marginBottom: 1,
|
||||
transition: 'background 0.1s, color 0.1s',
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
<span style={{ flex: 1 }}>{label}</span>
|
||||
{after}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Helpers ──────────────────────────────────────────────── */
|
||||
function SectionLabel({ children }: { children: string }) {
|
||||
return (
|
||||
<div style={{
|
||||
padding: '12px 12px 5px',
|
||||
fontFamily: "'JetBrains Mono', ui-monospace, monospace",
|
||||
fontSize: '0.5625rem',
|
||||
fontWeight: 500,
|
||||
letterSpacing: '0.1em',
|
||||
textTransform: 'uppercase',
|
||||
color: T.dim,
|
||||
userSelect: 'none',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HSep() {
|
||||
return <div style={{ height: 1, background: T.sep, margin: '6px 0', flexShrink: 0 }} />;
|
||||
}
|
||||
|
||||
function ActiveDot() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: 1,
|
||||
background: 'rgba(255,255,255,0.04)',
|
||||
margin: '8px 0',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<span style={{
|
||||
marginLeft: 'auto',
|
||||
width: 5,
|
||||
height: 5,
|
||||
borderRadius: '50%',
|
||||
background: T.accent,
|
||||
flexShrink: 0,
|
||||
display: 'block',
|
||||
}} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import type { ComponentSnapshot } from '@originmain/diff-engine';
|
||||
|
||||
interface ArtboardSnapshots {
|
||||
before: ComponentSnapshot;
|
||||
after: ComponentSnapshot;
|
||||
}
|
||||
|
||||
const dashboardCard: ArtboardSnapshots = {
|
||||
before: {
|
||||
id: 'dashboard-card',
|
||||
name: 'DashboardCard',
|
||||
filePath: 'src/components/DashboardCard.tsx',
|
||||
props: {
|
||||
title: 'Revenue Overview',
|
||||
value: '$12,450',
|
||||
delta: 2.4,
|
||||
period: 'monthly',
|
||||
loading: false,
|
||||
},
|
||||
styles: {
|
||||
borderRadius: '8px',
|
||||
accentColor: '#2A6CD4',
|
||||
padding: '16',
|
||||
},
|
||||
},
|
||||
after: {
|
||||
id: 'dashboard-card',
|
||||
name: 'DashboardCard',
|
||||
filePath: 'src/components/DashboardCard.tsx',
|
||||
props: {
|
||||
title: 'Revenue Overview',
|
||||
value: '$12,450',
|
||||
delta: 2.4,
|
||||
period: 'monthly',
|
||||
loading: false,
|
||||
},
|
||||
styles: {
|
||||
borderRadius: '12px',
|
||||
accentColor: '#0066FF',
|
||||
padding: '20',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const userProfile: ArtboardSnapshots = {
|
||||
before: {
|
||||
id: 'user-profile',
|
||||
name: 'UserProfile',
|
||||
filePath: 'src/components/UserProfile.tsx',
|
||||
props: {
|
||||
name: 'Sarah Chen',
|
||||
role: 'Designer',
|
||||
plan: 'Pro',
|
||||
avatarSize: 40,
|
||||
},
|
||||
styles: {
|
||||
avatarGradient: 'linear-gradient(135deg, #6D28D9, #2563EB)',
|
||||
buttonVariant: 'outline',
|
||||
},
|
||||
},
|
||||
after: {
|
||||
id: 'user-profile',
|
||||
name: 'UserProfile',
|
||||
filePath: 'src/components/UserProfile.tsx',
|
||||
props: {
|
||||
name: 'Sarah Chen',
|
||||
role: 'Design Engineer',
|
||||
plan: 'Team',
|
||||
avatarSize: 52,
|
||||
},
|
||||
styles: {
|
||||
avatarGradient: 'linear-gradient(135deg, #7C3AED, #0066FF)',
|
||||
buttonVariant: 'ghost',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const navSidebar: ArtboardSnapshots = {
|
||||
before: {
|
||||
id: 'nav-sidebar',
|
||||
name: 'NavSidebar',
|
||||
filePath: 'src/components/NavSidebar.tsx',
|
||||
props: {
|
||||
brand: 'Origin',
|
||||
collapsed: false,
|
||||
activeItem: 'Dashboard',
|
||||
},
|
||||
styles: {
|
||||
background: '#F5F5F5',
|
||||
itemPadding: '6px 10px',
|
||||
borderRadius: '4px',
|
||||
},
|
||||
},
|
||||
after: {
|
||||
id: 'nav-sidebar',
|
||||
name: 'NavSidebar',
|
||||
filePath: 'src/components/NavSidebar.tsx',
|
||||
props: {
|
||||
brand: 'Originmain',
|
||||
collapsed: false,
|
||||
activeItem: 'Dashboard',
|
||||
},
|
||||
styles: {
|
||||
background: '#FAFAFA',
|
||||
itemPadding: '7px 12px',
|
||||
borderRadius: '5px',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const dataTable: ArtboardSnapshots = {
|
||||
before: {
|
||||
id: 'data-table',
|
||||
name: 'DataTable',
|
||||
filePath: 'src/components/DataTable.tsx',
|
||||
props: {
|
||||
title: 'Components',
|
||||
striped: false,
|
||||
pageSize: 10,
|
||||
},
|
||||
styles: {
|
||||
headerColor: '#71717A',
|
||||
rowHeight: '32px',
|
||||
},
|
||||
},
|
||||
after: {
|
||||
id: 'data-table',
|
||||
name: 'DataTable',
|
||||
filePath: 'src/components/DataTable.tsx',
|
||||
props: {
|
||||
title: 'Component Inventory',
|
||||
striped: true,
|
||||
pageSize: 10,
|
||||
},
|
||||
styles: {
|
||||
headerColor: '#A1A1AA',
|
||||
rowHeight: '36px',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ARTBOARD_SNAPSHOTS: Record<string, ArtboardSnapshots> = {
|
||||
'dashboard-card': dashboardCard,
|
||||
'user-profile': userProfile,
|
||||
'nav-sidebar': navSidebar,
|
||||
'data-table': dataTable,
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
import { diffComponents, type DiffResult } from '@originmain/diff-engine';
|
||||
import { ARTBOARD_SNAPSHOTS } from '@/data/artboard-snapshots';
|
||||
|
||||
export function useDiff(artboardId: string | null): DiffResult | null {
|
||||
return useMemo(() => {
|
||||
if (!artboardId) return null;
|
||||
const snapshots = ARTBOARD_SNAPSHOTS[artboardId];
|
||||
if (!snapshots) return null;
|
||||
return diffComponents(snapshots.before, snapshots.after);
|
||||
}, [artboardId]);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,133 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { computeDiff, diffComponents } from '../src/engine.js';
|
||||
import type { ComponentSnapshot } from '../src/schemas.js';
|
||||
|
||||
const btn: ComponentSnapshot = {
|
||||
id: 'btn-1',
|
||||
name: 'Button',
|
||||
filePath: 'src/components/Button.tsx',
|
||||
props: { variant: 'primary', size: 'md', disabled: false },
|
||||
styles: { background: '#0066FF', borderRadius: '8px' },
|
||||
};
|
||||
|
||||
describe('computeDiff', () => {
|
||||
it('marks unchanged when nothing changed', () => {
|
||||
const d = computeDiff(btn, btn);
|
||||
expect(d.changeType).toBe('unchanged');
|
||||
expect(d.propChanges).toHaveLength(0);
|
||||
expect(d.styleChanges).toHaveLength(0);
|
||||
expect(d.patch).toBe('');
|
||||
});
|
||||
|
||||
it('detects modified prop', () => {
|
||||
const after = { ...btn, props: { ...btn.props, size: 'lg' } };
|
||||
const d = computeDiff(btn, after);
|
||||
expect(d.changeType).toBe('modified');
|
||||
const change = d.propChanges.find(c => c.key === 'size');
|
||||
expect(change?.before).toBe('md');
|
||||
expect(change?.after).toBe('lg');
|
||||
expect(change?.changeType).toBe('modified');
|
||||
});
|
||||
|
||||
it('detects added prop', () => {
|
||||
const after = { ...btn, props: { ...btn.props, loading: true } };
|
||||
const d = computeDiff(btn, after);
|
||||
const change = d.propChanges.find(c => c.key === 'loading');
|
||||
expect(change?.changeType).toBe('added');
|
||||
expect(change?.before).toBeUndefined();
|
||||
expect(change?.after).toBe(true);
|
||||
});
|
||||
|
||||
it('detects removed prop', () => {
|
||||
const { disabled: _, ...remaining } = btn.props;
|
||||
const after = { ...btn, props: remaining };
|
||||
const d = computeDiff(btn, after);
|
||||
const change = d.propChanges.find(c => c.key === 'disabled');
|
||||
expect(change?.changeType).toBe('removed');
|
||||
expect(change?.before).toBe(false);
|
||||
expect(change?.after).toBeUndefined();
|
||||
});
|
||||
|
||||
it('detects style change', () => {
|
||||
const after = { ...btn, styles: { ...btn.styles, borderRadius: '12px' } };
|
||||
const d = computeDiff(btn, after);
|
||||
const change = d.styleChanges.find(c => c.key === 'borderRadius');
|
||||
expect(change?.before).toBe('8px');
|
||||
expect(change?.after).toBe('12px');
|
||||
});
|
||||
|
||||
it('generates a non-empty patch when changed', () => {
|
||||
const after = { ...btn, props: { ...btn.props, size: 'xl' } };
|
||||
const d = computeDiff(btn, after);
|
||||
expect(d.patch).toBeTruthy();
|
||||
expect(d.patch).toContain('--- a/');
|
||||
expect(d.patch).toContain('+++ b/');
|
||||
});
|
||||
|
||||
it('propagates id and name from after snapshot', () => {
|
||||
const d = computeDiff(btn, { ...btn, name: 'IconButton' });
|
||||
expect(d.name).toBe('IconButton');
|
||||
expect(d.id).toBe(btn.id);
|
||||
});
|
||||
|
||||
it('deep-equal objects are unchanged', () => {
|
||||
const snap: ComponentSnapshot = {
|
||||
id: 'x',
|
||||
name: 'X',
|
||||
props: { config: { a: 1, b: [2, 3] } },
|
||||
};
|
||||
const d = computeDiff(snap, { ...snap, props: { config: { a: 1, b: [2, 3] } } });
|
||||
expect(d.changeType).toBe('unchanged');
|
||||
});
|
||||
|
||||
it('deep-equal objects with different value are modified', () => {
|
||||
const snap: ComponentSnapshot = {
|
||||
id: 'x',
|
||||
name: 'X',
|
||||
props: { config: { a: 1, b: [2, 3] } },
|
||||
};
|
||||
const d = computeDiff(snap, { ...snap, props: { config: { a: 1, b: [2, 99] } } });
|
||||
expect(d.changeType).toBe('modified');
|
||||
});
|
||||
|
||||
it('handles added child', () => {
|
||||
const child: ComponentSnapshot = { id: 'c1', name: 'Icon', props: { name: 'check' } };
|
||||
const before: ComponentSnapshot = { id: 'x', name: 'X', props: {}, children: [] };
|
||||
const after: ComponentSnapshot = { ...before, children: [child] };
|
||||
const d = computeDiff(before, after);
|
||||
expect(d.childDiffs).toBeDefined();
|
||||
expect(d.childDiffs![0]?.changeType).toBe('added');
|
||||
});
|
||||
|
||||
it('handles removed child', () => {
|
||||
const child: ComponentSnapshot = { id: 'c1', name: 'Icon', props: { name: 'check' } };
|
||||
const before: ComponentSnapshot = { id: 'x', name: 'X', props: {}, children: [child] };
|
||||
const after: ComponentSnapshot = { ...before, children: [] };
|
||||
const d = computeDiff(before, after);
|
||||
expect(d.childDiffs).toBeDefined();
|
||||
expect(d.childDiffs![0]?.changeType).toBe('removed');
|
||||
});
|
||||
|
||||
it('omits childDiffs when no child changes', () => {
|
||||
const child: ComponentSnapshot = { id: 'c1', name: 'Icon', props: { name: 'check' } };
|
||||
const snap: ComponentSnapshot = { id: 'x', name: 'X', props: {}, children: [child] };
|
||||
const d = computeDiff(snap, snap);
|
||||
expect(d.childDiffs).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('diffComponents', () => {
|
||||
it('returns diff and patch at top level', () => {
|
||||
const after = { ...btn, props: { ...btn.props, size: 'xl' } };
|
||||
const result = diffComponents(btn, after);
|
||||
expect(result.diff.changeType).toBe('modified');
|
||||
expect(result.patch).toBe(result.diff.patch);
|
||||
expect(result.patch).toBeTruthy();
|
||||
});
|
||||
|
||||
it('patch and diff.patch are identical', () => {
|
||||
const result = diffComponents(btn, btn);
|
||||
expect(result.patch).toBe('');
|
||||
expect(result.diff.patch).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { computeEditScript } from '../src/myers.js';
|
||||
|
||||
describe('computeEditScript', () => {
|
||||
it('returns empty for two empty arrays', () => {
|
||||
expect(computeEditScript([], [])).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns all-insert for empty before', () => {
|
||||
const ops = computeEditScript([], ['a', 'b']);
|
||||
expect(ops).toEqual([{ type: 'insert', lines: ['a', 'b'] }]);
|
||||
});
|
||||
|
||||
it('returns all-delete for empty after', () => {
|
||||
const ops = computeEditScript(['a', 'b'], []);
|
||||
expect(ops).toEqual([{ type: 'delete', lines: ['a', 'b'] }]);
|
||||
});
|
||||
|
||||
it('returns all-equal for identical arrays', () => {
|
||||
const ops = computeEditScript(['a', 'b', 'c'], ['a', 'b', 'c']);
|
||||
expect(ops).toEqual([{ type: 'equal', lines: ['a', 'b', 'c'] }]);
|
||||
});
|
||||
|
||||
it('detects a single changed line', () => {
|
||||
const ops = computeEditScript(['a', 'b', 'c'], ['a', 'X', 'c']);
|
||||
const types = ops.map(o => o.type);
|
||||
expect(types).toContain('delete');
|
||||
expect(types).toContain('insert');
|
||||
const deleted = ops.filter(o => o.type === 'delete').flatMap(o => o.lines);
|
||||
const inserted = ops.filter(o => o.type === 'insert').flatMap(o => o.lines);
|
||||
expect(deleted).toContain('b');
|
||||
expect(inserted).toContain('X');
|
||||
});
|
||||
|
||||
it('detects inserted lines at end', () => {
|
||||
const ops = computeEditScript(['a'], ['a', 'b', 'c']);
|
||||
const inserted = ops.filter(o => o.type === 'insert').flatMap(o => o.lines);
|
||||
expect(inserted).toEqual(['b', 'c']);
|
||||
});
|
||||
|
||||
it('detects deleted lines at start', () => {
|
||||
const ops = computeEditScript(['x', 'y', 'a'], ['a']);
|
||||
const deleted = ops.filter(o => o.type === 'delete').flatMap(o => o.lines);
|
||||
expect(deleted).toEqual(['x', 'y']);
|
||||
});
|
||||
|
||||
it('merges consecutive ops of same type', () => {
|
||||
const ops = computeEditScript(['a', 'b'], ['c', 'd']);
|
||||
// Should have merged deletes and inserts, not one-op-per-line
|
||||
const delOp = ops.find(o => o.type === 'delete');
|
||||
const insOp = ops.find(o => o.type === 'insert');
|
||||
expect(delOp?.lines.length).toBeGreaterThanOrEqual(1);
|
||||
expect(insOp?.lines.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it('round-trip: applying ops to before yields after', () => {
|
||||
const before = ['prop: 1', 'style: red', 'size: md'];
|
||||
const after = ['prop: 1', 'style: blue', 'size: lg', 'weight: 600'];
|
||||
const ops = computeEditScript(before, after);
|
||||
|
||||
const result: string[] = [];
|
||||
for (const op of ops) {
|
||||
if (op.type === 'equal' || op.type === 'insert') result.push(...op.lines);
|
||||
}
|
||||
expect(result).toEqual(after);
|
||||
});
|
||||
|
||||
it('handles single-element change', () => {
|
||||
const ops = computeEditScript(['x'], ['y']);
|
||||
expect(ops.some(o => o.type === 'delete' && o.lines.includes('x'))).toBe(true);
|
||||
expect(ops.some(o => o.type === 'insert' && o.lines.includes('y'))).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { generatePatch } from '../src/patch.js';
|
||||
|
||||
const A = `Component: Button
|
||||
Props:
|
||||
size: "md"
|
||||
variant: "primary"
|
||||
Styles:
|
||||
background: #0066FF
|
||||
borderRadius: 8px
|
||||
`;
|
||||
|
||||
const B = `Component: Button
|
||||
Props:
|
||||
size: "lg"
|
||||
variant: "primary"
|
||||
Styles:
|
||||
background: #0066FF
|
||||
borderRadius: 12px
|
||||
`;
|
||||
|
||||
describe('generatePatch', () => {
|
||||
it('returns empty string for identical inputs', () => {
|
||||
expect(generatePatch(A, A)).toBe('');
|
||||
});
|
||||
|
||||
it('returns empty string for two empty strings', () => {
|
||||
expect(generatePatch('', '')).toBe('');
|
||||
});
|
||||
|
||||
it('includes --- and +++ headers', () => {
|
||||
const patch = generatePatch(A, B);
|
||||
expect(patch).toMatch(/^--- a\//m);
|
||||
expect(patch).toMatch(/^\+\+\+ b\//m);
|
||||
});
|
||||
|
||||
it('includes @@ hunk header', () => {
|
||||
const patch = generatePatch(A, B);
|
||||
expect(patch).toMatch(/^@@ /m);
|
||||
});
|
||||
|
||||
it('marks changed lines correctly', () => {
|
||||
const patch = generatePatch(A, B);
|
||||
expect(patch).toContain('- size: "md"');
|
||||
expect(patch).toContain('+ size: "lg"');
|
||||
expect(patch).toContain('- borderRadius: 8px');
|
||||
expect(patch).toContain('+ borderRadius: 12px');
|
||||
});
|
||||
|
||||
it('preserves unchanged lines as context', () => {
|
||||
const patch = generatePatch(A, B);
|
||||
expect(patch).toContain(' variant: "primary"');
|
||||
});
|
||||
|
||||
it('uses custom filename in headers', () => {
|
||||
const patch = generatePatch(A, B, { filename: 'Button.tsx' });
|
||||
expect(patch).toContain('--- a/Button.tsx');
|
||||
expect(patch).toContain('+++ b/Button.tsx');
|
||||
});
|
||||
|
||||
it('handles all-insert (empty before)', () => {
|
||||
const patch = generatePatch('', 'line1\nline2\n');
|
||||
expect(patch).toContain('+line1');
|
||||
expect(patch).toContain('+line2');
|
||||
});
|
||||
|
||||
it('handles all-delete (empty after)', () => {
|
||||
const patch = generatePatch('line1\nline2\n', '');
|
||||
expect(patch).toContain('-line1');
|
||||
expect(patch).toContain('-line2');
|
||||
});
|
||||
|
||||
it('produces multiple hunks when changes are far apart', () => {
|
||||
const before = Array.from({ length: 20 }, (_, i) => `line${i}`).join('\n') + '\n';
|
||||
const after = before
|
||||
.replace('line0', 'CHANGED0')
|
||||
.replace('line19', 'CHANGED19');
|
||||
const patch = generatePatch(before, after);
|
||||
const hunkCount = (patch.match(/^@@/gm) ?? []).length;
|
||||
expect(hunkCount).toBe(2);
|
||||
});
|
||||
|
||||
it('merges nearby hunks into one', () => {
|
||||
const before = Array.from({ length: 10 }, (_, i) => `line${i}`).join('\n') + '\n';
|
||||
const after = before.replace('line0', 'X').replace('line1', 'Y');
|
||||
const patch = generatePatch(before, after);
|
||||
const hunkCount = (patch.match(/^@@/gm) ?? []).length;
|
||||
expect(hunkCount).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { serializeSnapshot, serializeValue } from '../src/serialize.js';
|
||||
import type { ComponentSnapshot } from '../src/schemas.js';
|
||||
|
||||
const snap: ComponentSnapshot = {
|
||||
id: 'btn-1',
|
||||
name: 'Button',
|
||||
filePath: 'src/components/Button.tsx',
|
||||
props: { variant: 'primary', size: 'md', disabled: false },
|
||||
styles: { borderRadius: '8px', background: '#0066FF' },
|
||||
};
|
||||
|
||||
describe('serializeSnapshot', () => {
|
||||
it('includes component name and filePath', () => {
|
||||
const out = serializeSnapshot(snap);
|
||||
expect(out).toContain('Component: Button [src/components/Button.tsx]');
|
||||
});
|
||||
|
||||
it('sorts props alphabetically for stable diffs', () => {
|
||||
const out = serializeSnapshot(snap);
|
||||
const propLines = out.split('\n').filter(l => l.includes(':') && !l.includes('Component'));
|
||||
const propNames = propLines
|
||||
.map(l => l.trim().split(':')[0]!.trim())
|
||||
.filter(k => ['disabled', 'size', 'variant', 'background', 'borderRadius'].includes(k));
|
||||
// disabled < size < variant (alphabetical)
|
||||
const dIdx = propNames.indexOf('disabled');
|
||||
const sIdx = propNames.indexOf('size');
|
||||
const vIdx = propNames.indexOf('variant');
|
||||
expect(dIdx).toBeLessThan(sIdx);
|
||||
expect(sIdx).toBeLessThan(vIdx);
|
||||
});
|
||||
|
||||
it('sorts styles alphabetically', () => {
|
||||
const out = serializeSnapshot(snap);
|
||||
const bgIdx = out.indexOf('background:');
|
||||
const brIdx = out.indexOf('borderRadius:');
|
||||
expect(bgIdx).toBeLessThan(brIdx);
|
||||
});
|
||||
|
||||
it('produces identical output for same input (deterministic)', () => {
|
||||
expect(serializeSnapshot(snap)).toBe(serializeSnapshot(snap));
|
||||
});
|
||||
|
||||
it('handles component without filePath', () => {
|
||||
const out = serializeSnapshot({ id: 'x', name: 'Foo', props: {} });
|
||||
expect(out).toBe('Component: Foo');
|
||||
});
|
||||
|
||||
it('handles empty props and styles', () => {
|
||||
const out = serializeSnapshot({ id: 'x', name: 'Foo', props: {}, styles: {} });
|
||||
expect(out).not.toContain('Props:');
|
||||
expect(out).not.toContain('Styles:');
|
||||
});
|
||||
|
||||
it('serializes nested children', () => {
|
||||
const withChild: ComponentSnapshot = {
|
||||
...snap,
|
||||
children: [{ id: 'icon', name: 'Icon', props: { name: 'check' } }],
|
||||
};
|
||||
const out = serializeSnapshot(withChild);
|
||||
expect(out).toContain('Children:');
|
||||
expect(out).toContain('[0]');
|
||||
expect(out).toContain('Component: Icon');
|
||||
});
|
||||
});
|
||||
|
||||
describe('serializeValue', () => {
|
||||
it('wraps strings in quotes', () => expect(serializeValue('hello')).toBe('"hello"'));
|
||||
it('renders numbers as-is', () => expect(serializeValue(42)).toBe('42'));
|
||||
it('renders booleans', () => {
|
||||
expect(serializeValue(true)).toBe('true');
|
||||
expect(serializeValue(false)).toBe('false');
|
||||
});
|
||||
it('renders null', () => expect(serializeValue(null)).toBe('null'));
|
||||
it('renders undefined', () => expect(serializeValue(undefined)).toBe('undefined'));
|
||||
it('renders arrays', () => expect(serializeValue([1, 'a'])).toBe('[1, "a"]'));
|
||||
it('renders objects with sorted keys', () => {
|
||||
expect(serializeValue({ z: 1, a: 2 })).toBe('{ a: 2, z: 1 }');
|
||||
});
|
||||
});
|
||||
@@ -11,11 +11,12 @@
|
||||
"test": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pierre/diffs": "^1.1.19",
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/coverage-v8": "^2.0.0",
|
||||
"typescript": "^5.5.0",
|
||||
"vitest": "^2.0.0",
|
||||
"@vitest/coverage-v8": "^2.0.0"
|
||||
"vitest": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import type { ComponentSnapshot, ComponentDiff, PropChange, ChangeType } from './schemas.js';
|
||||
import { serializeSnapshot } from './serialize.js';
|
||||
import { generatePatch } from './patch.js';
|
||||
|
||||
// ── Core diff result ──────────────────────────────────────────────────────────
|
||||
|
||||
export interface DiffResult {
|
||||
diff: ComponentDiff;
|
||||
patch: string;
|
||||
}
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
export function diffComponents(
|
||||
before: ComponentSnapshot,
|
||||
after: ComponentSnapshot
|
||||
): DiffResult {
|
||||
const diff = computeDiff(before, after);
|
||||
return { diff, patch: diff.patch };
|
||||
}
|
||||
|
||||
export function computeDiff(
|
||||
before: ComponentSnapshot,
|
||||
after: ComponentSnapshot
|
||||
): ComponentDiff {
|
||||
const propChanges = diffRecord(before.props, after.props);
|
||||
const styleChanges = diffRecord(
|
||||
before.styles ?? {},
|
||||
after.styles ?? {}
|
||||
) as PropChange[];
|
||||
|
||||
const changeType = determineChangeType(propChanges, styleChanges);
|
||||
|
||||
const childDiffs = diffChildren(before.children ?? [], after.children ?? []);
|
||||
|
||||
const beforeText = serializeSnapshot(before);
|
||||
const afterText = serializeSnapshot(after);
|
||||
const filename = after.filePath ?? `${after.name}.tsx`;
|
||||
const patch = generatePatch(beforeText, afterText, { filename });
|
||||
|
||||
const result: ComponentDiff = {
|
||||
id: after.id,
|
||||
name: after.name,
|
||||
changeType,
|
||||
propChanges,
|
||||
styleChanges,
|
||||
patch,
|
||||
};
|
||||
if (childDiffs.length > 0) result.childDiffs = childDiffs;
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function diffRecord(
|
||||
before: Record<string, unknown>,
|
||||
after: Record<string, unknown>
|
||||
): PropChange[] {
|
||||
const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
|
||||
const changes: PropChange[] = [];
|
||||
|
||||
for (const key of [...keys].sort()) {
|
||||
const b = before[key];
|
||||
const a = after[key];
|
||||
const hadKey = Object.prototype.hasOwnProperty.call(before, key);
|
||||
const hasKey = Object.prototype.hasOwnProperty.call(after, key);
|
||||
|
||||
if (!hadKey) {
|
||||
changes.push({ key, before: undefined, after: a, changeType: 'added' });
|
||||
} else if (!hasKey) {
|
||||
changes.push({ key, before: b, after: undefined, changeType: 'removed' });
|
||||
} else if (!deepEqual(b, a)) {
|
||||
changes.push({ key, before: b, after: a, changeType: 'modified' });
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
function diffChildren(
|
||||
before: ComponentSnapshot[],
|
||||
after: ComponentSnapshot[]
|
||||
): ComponentDiff[] {
|
||||
const diffs: ComponentDiff[] = [];
|
||||
|
||||
// Match by id first; fall back to positional match
|
||||
const beforeById = new Map(before.map(c => [c.id, c]));
|
||||
const afterById = new Map(after.map(c => [c.id, c]));
|
||||
|
||||
// Modified or unchanged children that exist in both
|
||||
for (const afterChild of after) {
|
||||
const beforeChild = beforeById.get(afterChild.id);
|
||||
if (beforeChild) {
|
||||
const d = computeDiff(beforeChild, afterChild);
|
||||
if (d.changeType !== 'unchanged') diffs.push(d);
|
||||
} else {
|
||||
// Added child
|
||||
diffs.push(addedDiff(afterChild));
|
||||
}
|
||||
}
|
||||
|
||||
// Removed children
|
||||
for (const beforeChild of before) {
|
||||
if (!afterById.has(beforeChild.id)) {
|
||||
diffs.push(removedDiff(beforeChild));
|
||||
}
|
||||
}
|
||||
|
||||
return diffs;
|
||||
}
|
||||
|
||||
function addedDiff(snap: ComponentSnapshot): ComponentDiff {
|
||||
const props = Object.keys(snap.props).sort().map(key => ({
|
||||
key,
|
||||
before: undefined as unknown,
|
||||
after: snap.props[key],
|
||||
changeType: 'added' as ChangeType,
|
||||
}));
|
||||
const styles = Object.keys(snap.styles ?? {}).sort().map(key => ({
|
||||
key,
|
||||
before: undefined as unknown,
|
||||
after: snap.styles![key],
|
||||
changeType: 'added' as ChangeType,
|
||||
}));
|
||||
return {
|
||||
id: snap.id,
|
||||
name: snap.name,
|
||||
changeType: 'added',
|
||||
propChanges: props,
|
||||
styleChanges: styles,
|
||||
patch: generatePatch('', serializeSnapshot(snap), { filename: snap.name }),
|
||||
};
|
||||
}
|
||||
|
||||
function removedDiff(snap: ComponentSnapshot): ComponentDiff {
|
||||
const props = Object.keys(snap.props).sort().map(key => ({
|
||||
key,
|
||||
before: snap.props[key],
|
||||
after: undefined as unknown,
|
||||
changeType: 'removed' as ChangeType,
|
||||
}));
|
||||
const styles = Object.keys(snap.styles ?? {}).sort().map(key => ({
|
||||
key,
|
||||
before: snap.styles![key] as unknown,
|
||||
after: undefined as unknown,
|
||||
changeType: 'removed' as ChangeType,
|
||||
}));
|
||||
return {
|
||||
id: snap.id,
|
||||
name: snap.name,
|
||||
changeType: 'removed',
|
||||
propChanges: props,
|
||||
styleChanges: styles,
|
||||
patch: generatePatch(serializeSnapshot(snap), '', { filename: snap.name }),
|
||||
};
|
||||
}
|
||||
|
||||
function determineChangeType(
|
||||
propChanges: PropChange[],
|
||||
styleChanges: PropChange[]
|
||||
): ChangeType {
|
||||
const hasChanges = propChanges.length > 0 || styleChanges.length > 0;
|
||||
return hasChanges ? 'modified' : 'unchanged';
|
||||
}
|
||||
|
||||
function deepEqual(a: unknown, b: unknown): boolean {
|
||||
if (a === b) return true;
|
||||
if (a === null || b === null) return false;
|
||||
if (typeof a !== typeof b) return false;
|
||||
if (typeof a !== 'object') return false;
|
||||
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
||||
|
||||
if (Array.isArray(a) && Array.isArray(b)) {
|
||||
if (a.length !== b.length) return false;
|
||||
return a.every((v, i) => deepEqual(v, b[i]));
|
||||
}
|
||||
|
||||
const ao = a as Record<string, unknown>;
|
||||
const bo = b as Record<string, unknown>;
|
||||
const aKeys = Object.keys(ao).sort();
|
||||
const bKeys = Object.keys(bo).sort();
|
||||
if (aKeys.length !== bKeys.length) return false;
|
||||
if (aKeys.join(',') !== bKeys.join(',')) return false;
|
||||
return aKeys.every(k => deepEqual(ao[k], bo[k]));
|
||||
}
|
||||
@@ -1 +1,25 @@
|
||||
export {};
|
||||
// @originmain/diff-engine — public API
|
||||
|
||||
export type {
|
||||
ComponentSnapshot,
|
||||
ComponentDiff,
|
||||
PropChange,
|
||||
ChangeType,
|
||||
DiffLayout,
|
||||
} from './schemas.js';
|
||||
|
||||
export {
|
||||
ComponentSnapshotSchema,
|
||||
ComponentDiffSchema,
|
||||
PropChangeSchema,
|
||||
ChangeTypeSchema,
|
||||
DiffLayoutSchema,
|
||||
} from './schemas.js';
|
||||
|
||||
export { computeEditScript, type EditOp, type EditType } from './myers.js';
|
||||
|
||||
export { serializeSnapshot, serializeValue } from './serialize.js';
|
||||
|
||||
export { generatePatch, type PatchOptions } from './patch.js';
|
||||
|
||||
export { diffComponents, computeDiff, type DiffResult } from './engine.js';
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// LCS-based O(nm) diff algorithm — correct and simple for component-sized inputs.
|
||||
// Produces a minimal edit script as a sequence of equal/insert/delete operations.
|
||||
|
||||
export type EditType = 'equal' | 'insert' | 'delete';
|
||||
|
||||
export interface EditOp {
|
||||
type: EditType;
|
||||
lines: string[];
|
||||
}
|
||||
|
||||
export function computeEditScript(before: string[], after: string[]): EditOp[] {
|
||||
const n = before.length;
|
||||
const m = after.length;
|
||||
|
||||
if (n === 0 && m === 0) return [];
|
||||
if (n === 0) return [{ type: 'insert', lines: [...after] }];
|
||||
if (m === 0) return [{ type: 'delete', lines: [...before] }];
|
||||
|
||||
// Build LCS DP table
|
||||
const dp: number[][] = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
||||
for (let i = 1; i <= n; i++) {
|
||||
for (let j = 1; j <= m; j++) {
|
||||
dp[i]![j] =
|
||||
before[i - 1] === after[j - 1]
|
||||
? (dp[i - 1]![j - 1] ?? 0) + 1
|
||||
: Math.max(dp[i - 1]![j] ?? 0, dp[i]![j - 1] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Backtrack from (n, m) to (0, 0)
|
||||
type RawOp = { type: EditType; line: string };
|
||||
const raw: RawOp[] = [];
|
||||
let i = n, j = m;
|
||||
|
||||
while (i > 0 || j > 0) {
|
||||
if (i > 0 && j > 0 && before[i - 1] === after[j - 1]) {
|
||||
raw.push({ type: 'equal', line: before[i - 1]! });
|
||||
i--; j--;
|
||||
} else if (j > 0 && (i === 0 || (dp[i]![j - 1] ?? 0) >= (dp[i - 1]![j] ?? 0))) {
|
||||
raw.push({ type: 'insert', line: after[j - 1]! });
|
||||
j--;
|
||||
} else {
|
||||
raw.push({ type: 'delete', line: before[i - 1]! });
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
raw.reverse();
|
||||
|
||||
// Merge consecutive ops of the same type
|
||||
const ops: EditOp[] = [];
|
||||
for (const op of raw) {
|
||||
const last = ops[ops.length - 1];
|
||||
if (last && last.type === op.type) {
|
||||
last.lines.push(op.line);
|
||||
} else {
|
||||
ops.push({ type: op.type, lines: [op.line] });
|
||||
}
|
||||
}
|
||||
|
||||
return ops;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { computeEditScript, type EditOp } from './myers.js';
|
||||
|
||||
// ── Flat line representation ──────────────────────────────────────────────────
|
||||
|
||||
interface FlatLine {
|
||||
type: 'equal' | 'insert' | 'delete';
|
||||
content: string;
|
||||
beforeIdx: number; // 1-based; -1 for pure insertions
|
||||
afterIdx: number; // 1-based; -1 for pure deletions
|
||||
}
|
||||
|
||||
function flattenOps(ops: EditOp[]): FlatLine[] {
|
||||
const lines: FlatLine[] = [];
|
||||
let b = 1, a = 1;
|
||||
|
||||
for (const op of ops) {
|
||||
for (const content of op.lines) {
|
||||
if (op.type === 'equal') {
|
||||
lines.push({ type: 'equal', content, beforeIdx: b++, afterIdx: a++ });
|
||||
} else if (op.type === 'delete') {
|
||||
lines.push({ type: 'delete', content, beforeIdx: b++, afterIdx: -1 });
|
||||
} else {
|
||||
lines.push({ type: 'insert', content, beforeIdx: -1, afterIdx: a++ });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
// ── Hunk grouping ─────────────────────────────────────────────────────────────
|
||||
|
||||
interface Hunk {
|
||||
flatStart: number;
|
||||
flatEnd: number;
|
||||
}
|
||||
|
||||
function buildHunks(flat: FlatLine[], context: number): Hunk[] {
|
||||
const changed = flat.reduce<number[]>((acc, l, i) => {
|
||||
if (l.type !== 'equal') acc.push(i);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (changed.length === 0) return [];
|
||||
|
||||
const hunks: Hunk[] = [];
|
||||
let start = Math.max(0, changed[0]! - context);
|
||||
let end = Math.min(flat.length - 1, changed[0]! + context);
|
||||
|
||||
for (let i = 1; i < changed.length; i++) {
|
||||
const ns = Math.max(0, changed[i]! - context);
|
||||
if (ns <= end + 1) {
|
||||
end = Math.min(flat.length - 1, changed[i]! + context);
|
||||
} else {
|
||||
hunks.push({ flatStart: start, flatEnd: end });
|
||||
start = ns;
|
||||
end = Math.min(flat.length - 1, changed[i]! + context);
|
||||
}
|
||||
}
|
||||
hunks.push({ flatStart: start, flatEnd: end });
|
||||
|
||||
return hunks;
|
||||
}
|
||||
|
||||
// ── Patch generation ──────────────────────────────────────────────────────────
|
||||
|
||||
export interface PatchOptions {
|
||||
filename?: string;
|
||||
contextLines?: number;
|
||||
}
|
||||
|
||||
export function generatePatch(
|
||||
beforeText: string,
|
||||
afterText: string,
|
||||
opts: PatchOptions = {}
|
||||
): string {
|
||||
const { filename = 'component', contextLines = 3 } = opts;
|
||||
|
||||
const before = splitLines(beforeText);
|
||||
const after = splitLines(afterText);
|
||||
|
||||
if (before.join('\n') === after.join('\n')) return '';
|
||||
|
||||
const ops = computeEditScript(before, after);
|
||||
const flat = flattenOps(ops);
|
||||
const hunks = buildHunks(flat, contextLines);
|
||||
|
||||
if (hunks.length === 0) return '';
|
||||
|
||||
const out: string[] = [`--- a/${filename}`, `+++ b/${filename}`];
|
||||
|
||||
for (const hunk of hunks) {
|
||||
const hunkLines = flat.slice(hunk.flatStart, hunk.flatEnd + 1);
|
||||
|
||||
const beforeStart = hunkLines.find(l => l.beforeIdx !== -1)?.beforeIdx ?? 1;
|
||||
const afterStart = hunkLines.find(l => l.afterIdx !== -1)?.afterIdx ?? 1;
|
||||
const beforeCount = hunkLines.filter(l => l.type !== 'insert').length;
|
||||
const afterCount = hunkLines.filter(l => l.type !== 'delete').length;
|
||||
|
||||
out.push(`@@ -${beforeStart},${beforeCount} +${afterStart},${afterCount} @@`);
|
||||
|
||||
for (const line of hunkLines) {
|
||||
if (line.type === 'equal') out.push(` ${line.content}`);
|
||||
else if (line.type === 'delete') out.push(`-${line.content}`);
|
||||
else out.push(`+${line.content}`);
|
||||
}
|
||||
}
|
||||
|
||||
return out.join('\n') + '\n';
|
||||
}
|
||||
|
||||
function splitLines(text: string): string[] {
|
||||
return text === '' ? [] : text.split('\n').filter((_, i, arr) => i < arr.length - 1 || arr[arr.length - 1] !== '');
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// ── Component snapshot ────────────────────────────────────────────────────────
|
||||
|
||||
export interface ComponentSnapshot {
|
||||
id: string;
|
||||
name: string;
|
||||
filePath?: string;
|
||||
props: Record<string, unknown>;
|
||||
styles?: Record<string, string>;
|
||||
children?: ComponentSnapshot[];
|
||||
}
|
||||
|
||||
export const ComponentSnapshotSchema: z.ZodType<ComponentSnapshot> = z.lazy(() =>
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
filePath: z.string().optional(),
|
||||
props: z.record(z.unknown()),
|
||||
styles: z.record(z.string()).optional(),
|
||||
children: z.array(ComponentSnapshotSchema).optional(),
|
||||
})
|
||||
) as z.ZodType<ComponentSnapshot>;
|
||||
|
||||
// ── Change types ──────────────────────────────────────────────────────────────
|
||||
|
||||
export const ChangeTypeSchema = z.enum(['added', 'removed', 'modified', 'unchanged']);
|
||||
export type ChangeType = z.infer<typeof ChangeTypeSchema>;
|
||||
|
||||
export const PropChangeSchema = z.object({
|
||||
key: z.string(),
|
||||
before: z.unknown(),
|
||||
after: z.unknown(),
|
||||
changeType: ChangeTypeSchema,
|
||||
});
|
||||
export type PropChange = z.infer<typeof PropChangeSchema>;
|
||||
|
||||
// ── Component diff ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ComponentDiff {
|
||||
id: string;
|
||||
name: string;
|
||||
changeType: ChangeType;
|
||||
propChanges: PropChange[];
|
||||
styleChanges: PropChange[];
|
||||
patch: string;
|
||||
childDiffs?: ComponentDiff[];
|
||||
}
|
||||
|
||||
export const ComponentDiffSchema: z.ZodType<ComponentDiff> = z.lazy(() =>
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
changeType: ChangeTypeSchema,
|
||||
propChanges: z.array(PropChangeSchema),
|
||||
styleChanges: z.array(PropChangeSchema),
|
||||
patch: z.string(),
|
||||
childDiffs: z.array(ComponentDiffSchema).optional(),
|
||||
})
|
||||
) as z.ZodType<ComponentDiff>;
|
||||
|
||||
// ── Layout ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const DiffLayoutSchema = z.enum(['split', 'unified']);
|
||||
export type DiffLayout = z.infer<typeof DiffLayoutSchema>;
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { ComponentSnapshot } from './schemas.js';
|
||||
|
||||
// Stable text serialization of a component snapshot.
|
||||
// Props are sorted alphabetically so diffs are determined by value changes, not key ordering.
|
||||
|
||||
export function serializeSnapshot(snapshot: ComponentSnapshot, depth = 0): string {
|
||||
const indent = ' '.repeat(depth);
|
||||
const lines: string[] = [];
|
||||
|
||||
const header = snapshot.filePath
|
||||
? `Component: ${snapshot.name} [${snapshot.filePath}]`
|
||||
: `Component: ${snapshot.name}`;
|
||||
lines.push(`${indent}${header}`);
|
||||
|
||||
// Props section
|
||||
const propKeys = Object.keys(snapshot.props).sort();
|
||||
if (propKeys.length > 0) {
|
||||
lines.push(`${indent}Props:`);
|
||||
for (const key of propKeys) {
|
||||
lines.push(`${indent} ${key}: ${serializeValue(snapshot.props[key])}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Styles section
|
||||
const styleKeys = snapshot.styles ? Object.keys(snapshot.styles).sort() : [];
|
||||
if (styleKeys.length > 0) {
|
||||
lines.push(`${indent}Styles:`);
|
||||
for (const key of styleKeys) {
|
||||
lines.push(`${indent} ${key}: ${snapshot.styles![key]}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Children section
|
||||
if (snapshot.children && snapshot.children.length > 0) {
|
||||
lines.push(`${indent}Children:`);
|
||||
for (let i = 0; i < snapshot.children.length; i++) {
|
||||
lines.push(`${indent} [${i}]`);
|
||||
lines.push(...serializeSnapshot(snapshot.children[i]!, depth + 2).split('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function serializeValue(val: unknown): string {
|
||||
if (val === null) return 'null';
|
||||
if (val === undefined) return 'undefined';
|
||||
if (typeof val === 'string') return `"${val}"`;
|
||||
if (typeof val === 'number' || typeof val === 'boolean') return String(val);
|
||||
if (Array.isArray(val)) return `[${val.map(serializeValue).join(', ')}]`;
|
||||
if (typeof val === 'object') {
|
||||
const entries = Object.entries(val as Record<string, unknown>)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([k, v]) => `${k}: ${serializeValue(v)}`);
|
||||
return `{ ${entries.join(', ')} }`;
|
||||
}
|
||||
return String(val);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ['__tests__/**/*.test.ts'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
include: ['src/**/*.ts'],
|
||||
exclude: ['src/index.ts'],
|
||||
thresholds: { lines: 85, functions: 85, branches: 80 },
|
||||
},
|
||||
},
|
||||
});
|
||||
Generated
+394
@@ -79,6 +79,12 @@ importers:
|
||||
'@originmain/ui':
|
||||
specifier: workspace:*
|
||||
version: link:../ui
|
||||
'@pierre/diffs':
|
||||
specifier: ^1.1.19
|
||||
version: 1.1.19(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@pierre/trees':
|
||||
specifier: 1.0.0-beta.3
|
||||
version: 1.0.0-beta.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.62.0
|
||||
version: 5.99.2(react@19.2.5)
|
||||
@@ -110,6 +116,9 @@ importers:
|
||||
|
||||
packages/diff-engine:
|
||||
dependencies:
|
||||
'@pierre/diffs':
|
||||
specifier: ^1.1.19
|
||||
version: 1.1.19(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
zod:
|
||||
specifier: ^3.0.0
|
||||
version: 3.25.76
|
||||
@@ -1196,6 +1205,22 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@pierre/diffs@1.1.19':
|
||||
resolution: {integrity: sha512-eYyDW69heXd7i9zdkWogGYosHzoYF2dstV6uDcmnQAf72uRChs3hrpf/7ym/ayTiwD8a+TQ7oZ5vNNb0tstJvA==}
|
||||
peerDependencies:
|
||||
react: ^18.3.1 || ^19.0.0
|
||||
react-dom: ^18.3.1 || ^19.0.0
|
||||
|
||||
'@pierre/theme@0.0.28':
|
||||
resolution: {integrity: sha512-1j/H/fECBuc9dEvntdWI+l435HZapw+RCJTlqCA6BboQ5TjlnE005j/ROWutXIs8aq5OAc82JI2Kwk4A1WWBgw==}
|
||||
engines: {vscode: ^1.0.0}
|
||||
|
||||
'@pierre/trees@1.0.0-beta.3':
|
||||
resolution: {integrity: sha512-gfV7V1AoceIwTSFwiiWl/89gNtJROyo2dFeYYuAkT4F3AbE+ajCIGZEICBw1ygmVxVetF9Kq1Xpjz8BhXXZwTQ==}
|
||||
peerDependencies:
|
||||
react: ^18.3.1 || ^19.0.0
|
||||
react-dom: ^18.3.1 || ^19.0.0
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -1349,6 +1374,30 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@shikijs/core@3.23.0':
|
||||
resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==}
|
||||
|
||||
'@shikijs/engine-javascript@3.23.0':
|
||||
resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==}
|
||||
|
||||
'@shikijs/engine-oniguruma@3.23.0':
|
||||
resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==}
|
||||
|
||||
'@shikijs/langs@3.23.0':
|
||||
resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==}
|
||||
|
||||
'@shikijs/themes@3.23.0':
|
||||
resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==}
|
||||
|
||||
'@shikijs/transformers@3.23.0':
|
||||
resolution: {integrity: sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ==}
|
||||
|
||||
'@shikijs/types@3.23.0':
|
||||
resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==}
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2':
|
||||
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||
|
||||
@@ -1366,9 +1415,15 @@ packages:
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/mdast@4.0.4':
|
||||
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
||||
|
||||
'@types/node@22.19.17':
|
||||
resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==}
|
||||
|
||||
@@ -1380,6 +1435,9 @@ packages:
|
||||
'@types/react@19.2.14':
|
||||
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
||||
|
||||
'@types/unist@3.0.3':
|
||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.59.0':
|
||||
resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -1439,6 +1497,9 @@ packages:
|
||||
resolution: {integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@ungap/structured-clone@1.3.0':
|
||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||
|
||||
'@vitest/coverage-v8@2.1.9':
|
||||
resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==}
|
||||
peerDependencies:
|
||||
@@ -1541,6 +1602,9 @@ packages:
|
||||
caniuse-lite@1.0.30001790:
|
||||
resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==}
|
||||
|
||||
ccount@2.0.1:
|
||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||
|
||||
chai@5.3.3:
|
||||
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1549,6 +1613,12 @@ packages:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
character-entities-html4@2.1.0:
|
||||
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
|
||||
|
||||
character-entities-legacy@3.0.0:
|
||||
resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
|
||||
|
||||
check-error@2.1.3:
|
||||
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
|
||||
engines: {node: '>= 16'}
|
||||
@@ -1563,6 +1633,9 @@ packages:
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
comma-separated-tokens@2.0.3:
|
||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
@@ -1589,10 +1662,21 @@ packages:
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
devlop@1.1.0:
|
||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||
|
||||
diff@8.0.3:
|
||||
resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
@@ -1744,9 +1828,18 @@ packages:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
hast-util-to-html@9.0.5:
|
||||
resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
|
||||
|
||||
hast-util-whitespace@3.0.0:
|
||||
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -1833,6 +1926,9 @@ packages:
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
lru_map@0.4.1:
|
||||
resolution: {integrity: sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
@@ -1843,6 +1939,24 @@ packages:
|
||||
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
mdast-util-to-hast@13.2.1:
|
||||
resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==}
|
||||
|
||||
micromark-util-character@2.1.1:
|
||||
resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
|
||||
|
||||
micromark-util-encode@2.0.1:
|
||||
resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
|
||||
|
||||
micromark-util-sanitize-uri@2.0.1:
|
||||
resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
|
||||
|
||||
micromark-util-symbol@2.0.1:
|
||||
resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
|
||||
|
||||
micromark-util-types@2.0.2:
|
||||
resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
|
||||
|
||||
minimatch@10.2.5:
|
||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
@@ -1890,6 +2004,12 @@ packages:
|
||||
sass:
|
||||
optional: true
|
||||
|
||||
oniguruma-parser@0.12.2:
|
||||
resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==}
|
||||
|
||||
oniguruma-to-es@4.3.6:
|
||||
resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==}
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -1953,10 +2073,21 @@ packages:
|
||||
resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
preact-render-to-string@6.6.5:
|
||||
resolution: {integrity: sha512-O6MHzYNIKYaiSX3bOw0gGZfEbOmlIDtDfWwN1JJdc/T3ihzRT6tGGSEWE088dWrEDGa1u7101q+6fzQnO9XCPA==}
|
||||
peerDependencies:
|
||||
preact: '>=10 || >= 11.0.0-0'
|
||||
|
||||
preact@11.0.0-beta.0:
|
||||
resolution: {integrity: sha512-IcODoASASYwJ9kxz7+MJeiJhvLriwSb4y4mHIyxdgaRZp6kPUud7xytrk/6GZw8U3y6EFJaRb5wi9SrEK+8+lg==}
|
||||
|
||||
prelude-ls@1.2.1:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
property-information@7.1.0:
|
||||
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1970,6 +2101,15 @@ packages:
|
||||
resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
regex-recursion@6.0.2:
|
||||
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
|
||||
|
||||
regex-utilities@2.3.0:
|
||||
resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
|
||||
|
||||
regex@6.1.0:
|
||||
resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2002,6 +2142,9 @@ packages:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shiki@3.23.0:
|
||||
resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==}
|
||||
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
@@ -2013,6 +2156,9 @@ packages:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
space-separated-tokens@2.0.2:
|
||||
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
@@ -2027,6 +2173,9 @@ packages:
|
||||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
stringify-entities@4.0.4:
|
||||
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2088,6 +2237,9 @@ packages:
|
||||
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
trim-lines@3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
|
||||
ts-api-utils@2.5.0:
|
||||
resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
|
||||
engines: {node: '>=18.12'}
|
||||
@@ -2109,6 +2261,21 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
unist-util-is@6.0.1:
|
||||
resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
|
||||
|
||||
unist-util-position@5.0.0:
|
||||
resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
|
||||
|
||||
unist-util-stringify-position@4.0.0:
|
||||
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
|
||||
|
||||
unist-util-visit-parents@6.0.2:
|
||||
resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
|
||||
|
||||
unist-util-visit@5.1.0:
|
||||
resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
|
||||
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
@@ -2117,6 +2284,12 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
vfile-message@4.0.3:
|
||||
resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
|
||||
|
||||
vfile@6.0.3:
|
||||
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
|
||||
|
||||
vite-node@2.1.9:
|
||||
resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
@@ -2225,6 +2398,9 @@ packages:
|
||||
use-sync-external-store:
|
||||
optional: true
|
||||
|
||||
zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
@@ -3723,6 +3899,26 @@ snapshots:
|
||||
'@next/swc-win32-x64-msvc@15.5.15':
|
||||
optional: true
|
||||
|
||||
'@pierre/diffs@1.1.19(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@pierre/theme': 0.0.28
|
||||
'@shikijs/transformers': 3.23.0
|
||||
diff: 8.0.3
|
||||
hast-util-to-html: 9.0.5
|
||||
lru_map: 0.4.1
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
shiki: 3.23.0
|
||||
|
||||
'@pierre/theme@0.0.28': {}
|
||||
|
||||
'@pierre/trees@1.0.0-beta.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
preact: 11.0.0-beta.0
|
||||
preact-render-to-string: 6.6.5(preact@11.0.0-beta.0)
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
@@ -3808,6 +4004,44 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.60.2':
|
||||
optional: true
|
||||
|
||||
'@shikijs/core@3.23.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.23.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-to-html: 9.0.5
|
||||
|
||||
'@shikijs/engine-javascript@3.23.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.23.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
oniguruma-to-es: 4.3.6
|
||||
|
||||
'@shikijs/engine-oniguruma@3.23.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.23.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@shikijs/langs@3.23.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.23.0
|
||||
|
||||
'@shikijs/themes@3.23.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.23.0
|
||||
|
||||
'@shikijs/transformers@3.23.0':
|
||||
dependencies:
|
||||
'@shikijs/core': 3.23.0
|
||||
'@shikijs/types': 3.23.0
|
||||
|
||||
'@shikijs/types@3.23.0':
|
||||
dependencies:
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2': {}
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -3825,8 +4059,16 @@ snapshots:
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/mdast@4.0.4':
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/node@22.19.17':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
@@ -3839,6 +4081,8 @@ snapshots:
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
@@ -3930,6 +4174,8 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.59.0
|
||||
eslint-visitor-keys: 5.0.1
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
|
||||
'@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@22.19.17))':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
@@ -4038,6 +4284,8 @@ snapshots:
|
||||
|
||||
caniuse-lite@1.0.30001790: {}
|
||||
|
||||
ccount@2.0.1: {}
|
||||
|
||||
chai@5.3.3:
|
||||
dependencies:
|
||||
assertion-error: 2.0.1
|
||||
@@ -4051,6 +4299,10 @@ snapshots:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
character-entities-html4@2.1.0: {}
|
||||
|
||||
character-entities-legacy@3.0.0: {}
|
||||
|
||||
check-error@2.1.3: {}
|
||||
|
||||
client-only@0.0.1: {}
|
||||
@@ -4061,6 +4313,8 @@ snapshots:
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
comma-separated-tokens@2.0.3: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
@@ -4079,9 +4333,17 @@ snapshots:
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
optional: true
|
||||
|
||||
devlop@1.1.0:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
diff@8.0.3: {}
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
embla-carousel-autoplay@8.6.0(embla-carousel@8.6.0):
|
||||
@@ -4256,8 +4518,28 @@ snapshots:
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
hast-util-to-html@9.0.5:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/unist': 3.0.3
|
||||
ccount: 2.0.1
|
||||
comma-separated-tokens: 2.0.3
|
||||
hast-util-whitespace: 3.0.0
|
||||
html-void-elements: 3.0.0
|
||||
mdast-util-to-hast: 13.2.1
|
||||
property-information: 7.1.0
|
||||
space-separated-tokens: 2.0.2
|
||||
stringify-entities: 4.0.4
|
||||
zwitch: 2.0.4
|
||||
|
||||
hast-util-whitespace@3.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
ignore@7.0.5: {}
|
||||
@@ -4337,6 +4619,8 @@ snapshots:
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru_map@0.4.1: {}
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
@@ -4351,6 +4635,35 @@ snapshots:
|
||||
dependencies:
|
||||
semver: 7.7.4
|
||||
|
||||
mdast-util-to-hast@13.2.1:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/mdast': 4.0.4
|
||||
'@ungap/structured-clone': 1.3.0
|
||||
devlop: 1.1.0
|
||||
micromark-util-sanitize-uri: 2.0.1
|
||||
trim-lines: 3.0.1
|
||||
unist-util-position: 5.0.0
|
||||
unist-util-visit: 5.1.0
|
||||
vfile: 6.0.3
|
||||
|
||||
micromark-util-character@2.1.1:
|
||||
dependencies:
|
||||
micromark-util-symbol: 2.0.1
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-util-encode@2.0.1: {}
|
||||
|
||||
micromark-util-sanitize-uri@2.0.1:
|
||||
dependencies:
|
||||
micromark-util-character: 2.1.1
|
||||
micromark-util-encode: 2.0.1
|
||||
micromark-util-symbol: 2.0.1
|
||||
|
||||
micromark-util-symbol@2.0.1: {}
|
||||
|
||||
micromark-util-types@2.0.2: {}
|
||||
|
||||
minimatch@10.2.5:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.5
|
||||
@@ -4395,6 +4708,14 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
oniguruma-parser@0.12.2: {}
|
||||
|
||||
oniguruma-to-es@4.3.6:
|
||||
dependencies:
|
||||
oniguruma-parser: 0.12.2
|
||||
regex: 6.1.0
|
||||
regex-recursion: 6.0.2
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
@@ -4455,8 +4776,16 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
preact-render-to-string@6.6.5(preact@11.0.0-beta.0):
|
||||
dependencies:
|
||||
preact: 11.0.0-beta.0
|
||||
|
||||
preact@11.0.0-beta.0: {}
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
property-information@7.1.0: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
react-dom@19.2.5(react@19.2.5):
|
||||
@@ -4466,6 +4795,16 @@ snapshots:
|
||||
|
||||
react@19.2.5: {}
|
||||
|
||||
regex-recursion@6.0.2:
|
||||
dependencies:
|
||||
regex-utilities: 2.3.0
|
||||
|
||||
regex-utilities@2.3.0: {}
|
||||
|
||||
regex@6.1.0:
|
||||
dependencies:
|
||||
regex-utilities: 2.3.0
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
rollup@4.60.2:
|
||||
@@ -4545,12 +4884,25 @@ snapshots:
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
shiki@3.23.0:
|
||||
dependencies:
|
||||
'@shikijs/core': 3.23.0
|
||||
'@shikijs/engine-javascript': 3.23.0
|
||||
'@shikijs/engine-oniguruma': 3.23.0
|
||||
'@shikijs/langs': 3.23.0
|
||||
'@shikijs/themes': 3.23.0
|
||||
'@shikijs/types': 3.23.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
space-separated-tokens@2.0.2: {}
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
std-env@3.10.0: {}
|
||||
@@ -4567,6 +4919,11 @@ snapshots:
|
||||
emoji-regex: 9.2.2
|
||||
strip-ansi: 7.2.0
|
||||
|
||||
stringify-entities@4.0.4:
|
||||
dependencies:
|
||||
character-entities-html4: 2.1.0
|
||||
character-entities-legacy: 3.0.0
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
@@ -4616,6 +4973,8 @@ snapshots:
|
||||
|
||||
tinyspy@3.0.2: {}
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
|
||||
ts-api-utils@2.5.0(typescript@5.9.3):
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
@@ -4630,6 +4989,29 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
unist-util-is@6.0.1:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
unist-util-position@5.0.0:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
unist-util-stringify-position@4.0.0:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
unist-util-visit-parents@6.0.2:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
unist-util-is: 6.0.1
|
||||
|
||||
unist-util-visit@5.1.0:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
unist-util-is: 6.0.1
|
||||
unist-util-visit-parents: 6.0.2
|
||||
|
||||
uri-js@4.4.1:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
@@ -4638,6 +5020,16 @@ snapshots:
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
|
||||
vfile-message@4.0.3:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
unist-util-stringify-position: 4.0.0
|
||||
|
||||
vfile@6.0.3:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.3
|
||||
|
||||
vite-node@2.1.9(@types/node@22.19.17):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
@@ -4732,3 +5124,5 @@ snapshots:
|
||||
'@types/react': 19.2.14
|
||||
react: 19.2.5
|
||||
use-sync-external-store: 1.6.0(react@19.2.5)
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
|
||||
Reference in New Issue
Block a user