Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"recommendations": ["repowise-dev.repowise"]
"recommendations": [
"repowise-dev.repowise"
]
}
64 changes: 34 additions & 30 deletions AGENTS.md

Large diffs are not rendered by default.

49 changes: 48 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 5 additions & 20 deletions e2e/next-app/scripts/assert-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const APP_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const REPO_ROOT = resolve(APP_ROOT, '..', '..');
const NEXT_DIR = resolve(APP_ROOT, '.next');
const STATIC_JS = resolve(NEXT_DIR, 'static');

Expand All @@ -44,26 +43,12 @@ function emitLaneReceipt(): void {
);
}

// Default engine is still MEASURED from the workspace plugin source so a
// future default change is reflected without touching this script. Symbol:
// `getSharedEngine()` fallback `... || 'v2'` in
// packages/next-plugin/src/singleton.ts.
const pluginSrc = readFileSync(
resolve(REPO_ROOT, 'packages', 'next-plugin', 'src', 'singleton.ts'),
'utf8'
);
const defaultMatch = pluginSrc.match(/\|\|\s*['"](v[12])['"]/);
if (!defaultMatch) {
throw new AssertionError(
'Cannot determine default engine from packages/next-plugin/src/singleton.ts — update the receipt probe'
);
}
const engineDefault = defaultMatch[1] as 'v2';

// v1 is retired: no override path remains, so the loaded engine is always the
// workspace default and no ANIMUS_ENGINE override is possible.
// v1 is retired (openspec: retire-extract-v1): v2 is the only engine, so the
// receipt records v2 as both default and loaded, with no override. Engine
// identity is never inferred from plugin/config source (guardrail G3).
const engineDefault = 'v2' as const;
const engineLoaded = 'v2' as const;
const engineOverride = false;
const engineLoaded: 'v2' = engineDefault;

// hostVersion from the fixture's installed host, not the manifest range.
const hostVersion = (
Expand Down
1 change: 1 addition & 0 deletions e2e/next16-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.animus/
57 changes: 57 additions & 0 deletions e2e/next16-app/app/client/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use client';

import { useState } from 'react';

import { Badge, Button, Card, Stack } from '../../src/components';

const sizes = ['small', 'medium', 'large'] as const;
const intents = ['primary', 'secondary', 'danger', 'ghost'] as const;

export default function ClientPage() {
const [sizeIndex, setSizeIndex] = useState(0);
const [intentIndex, setIntentIndex] = useState(0);
const currentSize = sizes[sizeIndex % sizes.length];
const currentIntent = intents[intentIndex % intents.length];

return (
<Stack p={32} gap={24}>
<Stack gap={4}>
<h2>Interactive Client Page</h2>
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.875rem' }}>
Variant toggling via useState — proves extracted CSS covers all states
</p>
</Stack>

<Stack direction="row" gap={8} alignItems="center">
<Button
size={currentSize}
intent={currentIntent}
onClick={() => setSizeIndex((i) => i + 1)}
>
Size: {currentSize}
</Button>
<Button
size="medium"
intent="ghost"
onClick={() => setIntentIndex((i) => i + 1)}
>
Intent: {currentIntent}
</Button>
</Stack>

<Card elevation="raised" sizing="100%">
<Stack gap={8}>
<Badge
size={currentSize === 'large' ? 'large' : 'small'}
intent="info"
>
Dynamic badge
</Badge>
<p style={{ fontSize: '0.875rem', color: 'var(--color-text-muted)' }}>
Card with dynamic badge size tied to button state
</p>
</Stack>
</Card>
</Stack>
);
}
15 changes: 15 additions & 0 deletions e2e/next16-app/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const metadata = {
title: 'Animus Next.js Test App',
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
96 changes: 96 additions & 0 deletions e2e/next16-app/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {
Badge,
Box,
Button,
Card,
Fade,
Family,
Pulse,
Stack,
} from '../src/components';
import { ds } from '../src/ds';

const Heading = ds
.styles({
fontSize: 48,
fontWeight: 700,
})
.asElement('h1');

const Subheading = ds
.styles({
color: 'text.muted',
})
.asElement('p');

export default function Home() {
return (
<Stack p={32} gap={24}>
<Stack gap={8}>
<Heading>Next.js Test App</Heading>
<Subheading>Extraction proof for webpack plugin</Subheading>
</Stack>

<Stack direction="row" gap={8} flexWrap="wrap">
<Button size="small" intent="primary">
Small Primary
</Button>
<Button size="medium" intent="secondary">
Medium Secondary
</Button>
<Button size="large" intent="danger">
Large Danger
</Button>
<Button size="medium" intent="ghost">
Ghost
</Button>
</Stack>

<Stack direction="row" gap={16}>
<Card elevation="flat" sizing="200px">
<span>Flat</span>
</Card>
<Card elevation="raised" sizing="200px">
<span>Raised</span>
</Card>
<Card elevation="floating" sizing="200px">
<strong>Floating</strong>
</Card>
</Stack>

<Box gap={8} flexWrap="wrap">
<Badge size="small" intent="info">
Info
</Badge>
<Badge size="large" intent="info">
Large Info
</Badge>
<Badge size="small" intent="danger">
Compound (small+danger=bold)
</Badge>
<Badge size="large" intent="danger">
Large Danger
</Badge>
</Box>

<Family.Root density="comfortable">
<Box p={4}>
<Family.Child intent="secondary">
<Button intent="danger">Large Danger</Button>
</Family.Child>
</Box>
</Family.Root>

<Box p={16} gap={8}>
<Badge size="large" intent="info">
RSC Page — no compose() here
</Badge>
</Box>

<Stack direction="row" gap={8}>
<Pulse>Pulse</Pulse>
<Fade>Fade In</Fade>
</Stack>
</Stack>
);
}
6 changes: 6 additions & 0 deletions e2e/next16-app/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
6 changes: 6 additions & 0 deletions e2e/next16-app/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { withAnimus } from '@animus-ui/next-plugin';

export default withAnimus({
verbose: true,
system: './src/ds.ts',
})({});
25 changes: 25 additions & 0 deletions e2e/next16-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@animus-ui/next16-app",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"verify:build": "bash ../../scripts/verify/build-consumer.sh",
"verify:assert": "bash ../../scripts/verify/assert-consumer.sh e2e/next16-app/.next e2e/next16-app/scripts/assert-build.ts",
"verify": "vp run @animus-ui/next16-app#verify:build && vp run @animus-ui/next16-app#verify:assert",
"start": "next start"
},
"dependencies": {
"@animus-ui/next-plugin": "workspace:*",
"@animus-ui/system": "workspace:*",
"@animus-ui/test-ds": "workspace:*",
"next": "^16.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@animus-ui/assertions": "workspace:*",
"typescript": "^5.9.2"
}
}
Loading
Loading