create-lumen 2.0.0-alpha.0 → 2.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -12
- package/bin/cli.js +1 -0
- package/package.json +3 -2
- package/schema/fixtures/v2/invalid/architecture-preset-type-mismatch.json +35 -0
- package/schema/fixtures/v2/invalid/harness-empty-commands.json +37 -0
- package/schema/fixtures/v2/invalid/harness-missing-command.json +41 -0
- package/schema/fixtures/v2/invalid/invalid-validation-mode.json +34 -0
- package/schema/fixtures/v2/invalid/shadcn-without-tailwind.json +1 -1
- package/schema/fixtures/v2/valid/harness-full.json +44 -0
- package/schema/fixtures/v2/valid/react-vite-type-based.json +1 -1
- package/schema/fixtures/v2/valid/validation-none.json +41 -0
- package/schema/fixtures/v2/valid/validation-relaxed.json +41 -0
- package/schema/lumen.config.v2.json +54 -2
- package/skills/create-lumen/SKILL.md +110 -0
- package/skills/create-lumen/references/manifest-v2.md +9 -0
- package/src/agent-context.js +114 -0
- package/src/cli-args.js +65 -3
- package/src/configure.js +4 -0
- package/src/css.js +14 -30
- package/src/dependencies.js +28 -18
- package/src/engine/capabilities.js +174 -4
- package/src/engine/composition.js +644 -0
- package/src/engine/index.js +1 -0
- package/src/injector.js +294 -446
- package/src/main.js +20 -7
- package/src/manifest/emit.js +49 -3
- package/src/manifest/schema.js +65 -7
- package/src/prompts.js +459 -247
- package/src/readme.js +23 -2
- package/src/scaffold.js +17 -0
- package/templates/architectures/hybrid/main.jsx +10 -0
- package/templates/architectures/hybrid/main.tsx +10 -0
- package/templates/architectures/hybrid/src/App.jsx +20 -0
- package/templates/architectures/hybrid/src/App.tsx +20 -0
- package/templates/architectures/hybrid/src/shared/components/ui/Button.jsx +31 -0
- package/templates/architectures/hybrid/src/shared/components/ui/Button.tsx +36 -0
- package/templates/architectures/hybrid/src/shared/components/ui/index.js +1 -0
- package/templates/architectures/hybrid/src/shared/components/ui/index.ts +1 -0
- package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.jsx +28 -0
- package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.ts +28 -0
- package/templates/architectures/hybrid/src/shared/layouts/.gitkeep +0 -0
- package/templates/architectures/hybrid/src/shared/stores/.gitkeep +0 -0
- package/templates/architectures/hybrid/src/shared/styles/.gitkeep +0 -0
- package/templates/architectures/hybrid/src/shared/types/index.js +20 -0
- package/templates/architectures/hybrid/src/shared/types/index.ts +16 -0
- package/templates/architectures/hybrid/src/shared/utils/cn.jsx +3 -0
- package/templates/architectures/hybrid/src/shared/utils/cn.ts +5 -0
- package/templates/architectures/none/main.jsx +10 -0
- package/templates/architectures/none/main.tsx +10 -0
- package/templates/architectures/none/src/App.jsx +15 -0
- package/templates/architectures/none/src/App.tsx +15 -0
- package/templates/architectures/none/src/styles/.gitkeep +1 -0
- package/templates/architectures/none/src/types/index.js +20 -0
- package/templates/architectures/none/src/types/index.ts +16 -0
- package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.jsx +11 -0
- package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.tsx +11 -0
- package/templates/conditional/testing/jest/none/src/__tests__/App.test.jsx +11 -0
- package/templates/conditional/testing/jest/none/src/__tests__/App.test.tsx +11 -0
- package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.jsx +10 -0
- package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.tsx +10 -0
- package/templates/conditional/testing/vitest/none/src/__tests__/App.test.jsx +10 -0
- package/templates/conditional/testing/vitest/none/src/__tests__/App.test.tsx +10 -0
- package/templates/css/tailwind/src/globals.css +10 -0
- package/templates/css/tailwind/src/themes.css +86 -4
- package/templates/ui/shadcn/lib/utils.js +6 -0
- package/templates/ui/shadcn/lib/utils.ts +6 -0
- package/templates/ui/shadcn/primitives/Button.jsx +48 -0
- package/templates/ui/shadcn/primitives/Button.tsx +59 -0
- package/templates/ui/shadcn/primitives/Card.jsx +55 -0
- package/templates/ui/shadcn/primitives/Card.tsx +73 -0
- package/templates/ui/shadcn/primitives/index.js +9 -0
- package/templates/ui/shadcn/primitives/index.ts +9 -0
- package/templates/css/bootstrap/src/themes.css +0 -14
- /package/templates/{css/bootstrap/src/globals.css → architectures/hybrid/src/features/.gitkeep} +0 -0
package/src/readme.js
CHANGED
|
@@ -10,8 +10,6 @@ function buildBuiltWith(responses) {
|
|
|
10
10
|
|
|
11
11
|
if (responses.cssFramework === "tailwind") {
|
|
12
12
|
items.push("[Tailwind CSS](https://tailwindcss.com/)");
|
|
13
|
-
} else if (responses.cssFramework === "bootstrap") {
|
|
14
|
-
items.push("[Bootstrap](https://getbootstrap.com/)");
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
if (responses.router) {
|
|
@@ -119,6 +117,29 @@ ${nestedTree(appChildren)}
|
|
|
119
117
|
${nestedTree(shared)}`;
|
|
120
118
|
}
|
|
121
119
|
|
|
120
|
+
if (responses.architecture === "hybrid") {
|
|
121
|
+
const shared = [
|
|
122
|
+
"components/ # Shared UI components",
|
|
123
|
+
"hooks/ # Shared custom hooks",
|
|
124
|
+
"layouts/ # High-level layout wrappers",
|
|
125
|
+
"stores/ # Global state stores",
|
|
126
|
+
`styles/ # ${responses.cssFramework === "none" ? "main.css (reset) + themes.css (CSS variables)" : "globals.css (framework directives) + themes.css"}`,
|
|
127
|
+
"types/ # Global type definitions",
|
|
128
|
+
"utils/ # Pure utility functions",
|
|
129
|
+
];
|
|
130
|
+
return `src/
|
|
131
|
+
├── features/ # Feature modules (domain boundaries)
|
|
132
|
+
└── shared/ # Reusable, business-agnostic resources
|
|
133
|
+
${nestedTree(shared)}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (responses.architecture === "none") {
|
|
137
|
+
return `src/
|
|
138
|
+
├── App.${responses.language === "ts" ? "tsx" : "jsx"} # Root application component
|
|
139
|
+
├── main.${responses.language === "ts" ? "tsx" : "jsx"} # Application entry point
|
|
140
|
+
└── styles/ # Stylesheets`;
|
|
141
|
+
}
|
|
142
|
+
|
|
122
143
|
const rows = ["components/ # Reusable UI components (common, form)"];
|
|
123
144
|
if (responses.apiClient !== "none") {
|
|
124
145
|
rows.push("config/ # API configuration (axios/fetch)");
|
package/src/scaffold.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { promises as fsp } from "fs";
|
|
1
2
|
import path from "path";
|
|
2
3
|
import { execa } from "execa";
|
|
4
|
+
import { copyDirRecursive } from "@/utils/fs.js";
|
|
3
5
|
import { isYarnV1 } from "@/utils/yarn-v1.js";
|
|
4
6
|
|
|
5
7
|
export async function runViteCreate(projectPath, projectName, pkg, language) {
|
|
@@ -27,3 +29,18 @@ export async function runBaseInstall(projectPath, pkg) {
|
|
|
27
29
|
export async function runGitInit(projectPath) {
|
|
28
30
|
await execa("git", ["init"], { stdio: "pipe", cwd: projectPath });
|
|
29
31
|
}
|
|
32
|
+
|
|
33
|
+
export async function scaffoldBase(projectPath, provider, language, templatesDir) {
|
|
34
|
+
if (provider && typeof provider.getFilePlan === "function") {
|
|
35
|
+
const filePlan = provider.getFilePlan({ lang: language });
|
|
36
|
+
const baseDir = path.join(templatesDir, "..", filePlan);
|
|
37
|
+
try {
|
|
38
|
+
await fsp.access(baseDir);
|
|
39
|
+
await copyDirRecursive(baseDir, projectPath);
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
// Base directory does not exist yet in templates/bases
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import App from "./App";
|
|
4
|
+
import "./shared/styles/globals.css";
|
|
5
|
+
|
|
6
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
7
|
+
<React.StrictMode>
|
|
8
|
+
<App />
|
|
9
|
+
</React.StrictMode>
|
|
10
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Button } from "@/shared/components/ui/Button";
|
|
2
|
+
|
|
3
|
+
export default function App() {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
style={{
|
|
7
|
+
minHeight: "100vh",
|
|
8
|
+
display: "flex",
|
|
9
|
+
flexDirection: "column",
|
|
10
|
+
alignItems: "center",
|
|
11
|
+
justifyContent: "center",
|
|
12
|
+
backgroundColor: "var(--color-bg, #ffffff)",
|
|
13
|
+
}}
|
|
14
|
+
>
|
|
15
|
+
<h1>Welcome to Lumen</h1>
|
|
16
|
+
<p>Hybrid architecture (src/features + src/shared).</p>
|
|
17
|
+
<Button>Click me</Button>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Button } from "@/shared/components/ui/Button";
|
|
2
|
+
|
|
3
|
+
export default function App() {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
style={{
|
|
7
|
+
minHeight: "100vh",
|
|
8
|
+
display: "flex",
|
|
9
|
+
flexDirection: "column",
|
|
10
|
+
alignItems: "center",
|
|
11
|
+
justifyContent: "center",
|
|
12
|
+
backgroundColor: "var(--color-bg, #ffffff)",
|
|
13
|
+
}}
|
|
14
|
+
>
|
|
15
|
+
<h1>Welcome to Lumen</h1>
|
|
16
|
+
<p>Hybrid architecture (src/features + src/shared).</p>
|
|
17
|
+
<Button>Click me</Button>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function Button({
|
|
2
|
+
variant = "primary",
|
|
3
|
+
children,
|
|
4
|
+
className,
|
|
5
|
+
...props
|
|
6
|
+
}) {
|
|
7
|
+
const styles = {
|
|
8
|
+
primary: { backgroundColor: "var(--color-primary)", color: "white" },
|
|
9
|
+
secondary: {
|
|
10
|
+
backgroundColor: "var(--color-border)",
|
|
11
|
+
color: "var(--color-fg)",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<button
|
|
17
|
+
style={{
|
|
18
|
+
padding: "8px 16px",
|
|
19
|
+
borderRadius: "8px",
|
|
20
|
+
fontWeight: 500,
|
|
21
|
+
border: "none",
|
|
22
|
+
cursor: "pointer",
|
|
23
|
+
...styles[variant],
|
|
24
|
+
}}
|
|
25
|
+
className={className}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</button>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
2
|
+
variant?: "primary" | "secondary";
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function Button({
|
|
7
|
+
variant = "primary",
|
|
8
|
+
children,
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: ButtonProps) {
|
|
12
|
+
const styles = {
|
|
13
|
+
primary: { backgroundColor: "var(--color-primary)", color: "white" },
|
|
14
|
+
secondary: {
|
|
15
|
+
backgroundColor: "var(--color-border)",
|
|
16
|
+
color: "var(--color-fg)",
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<button
|
|
22
|
+
style={{
|
|
23
|
+
padding: "8px 16px",
|
|
24
|
+
borderRadius: "8px",
|
|
25
|
+
fontWeight: 500,
|
|
26
|
+
border: "none",
|
|
27
|
+
cursor: "pointer",
|
|
28
|
+
...styles[variant],
|
|
29
|
+
}}
|
|
30
|
+
className={className}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</button>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Button } from "./Button";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Button } from "./Button";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
export function useLocalStorage(key, initialValue) {
|
|
4
|
+
const [storedValue, setStoredValue] = useState(() => {
|
|
5
|
+
try {
|
|
6
|
+
const item = window.localStorage.getItem(key);
|
|
7
|
+
return item ? JSON.parse(item) : initialValue;
|
|
8
|
+
} catch {
|
|
9
|
+
return initialValue;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const setValue = useCallback(
|
|
14
|
+
(value) => {
|
|
15
|
+
try {
|
|
16
|
+
const valueToStore =
|
|
17
|
+
value instanceof Function ? value(storedValue) : value;
|
|
18
|
+
setStoredValue(valueToStore);
|
|
19
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error(error);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
[key, storedValue]
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return [storedValue, setValue];
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
4
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
5
|
+
try {
|
|
6
|
+
const item = window.localStorage.getItem(key);
|
|
7
|
+
return item ? (JSON.parse(item) as T) : initialValue;
|
|
8
|
+
} catch {
|
|
9
|
+
return initialValue;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const setValue = useCallback(
|
|
14
|
+
(value: T | ((prev: T) => T)) => {
|
|
15
|
+
try {
|
|
16
|
+
const valueToStore =
|
|
17
|
+
value instanceof Function ? value(storedValue) : value;
|
|
18
|
+
setStoredValue(valueToStore);
|
|
19
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error(error);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
[key, storedValue]
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return [storedValue, setValue] as const;
|
|
28
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} User
|
|
3
|
+
* @property {string} id
|
|
4
|
+
* @property {string} email
|
|
5
|
+
* @property {string} name
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} ApiResponse
|
|
10
|
+
* @property {*} data
|
|
11
|
+
* @property {string} message
|
|
12
|
+
* @property {boolean} success
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} CreateUserPayload
|
|
17
|
+
* @property {string} email
|
|
18
|
+
* @property {string} name
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface User {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ApiResponse<T> {
|
|
8
|
+
data: T;
|
|
9
|
+
message: string;
|
|
10
|
+
success: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CreateUserPayload {
|
|
14
|
+
email: string;
|
|
15
|
+
name: string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default function App() {
|
|
2
|
+
return (
|
|
3
|
+
<div
|
|
4
|
+
style={{
|
|
5
|
+
minHeight: "100vh",
|
|
6
|
+
display: "flex",
|
|
7
|
+
alignItems: "center",
|
|
8
|
+
justifyContent: "center",
|
|
9
|
+
backgroundColor: "var(--color-bg, #ffffff)",
|
|
10
|
+
}}
|
|
11
|
+
>
|
|
12
|
+
<h1>Welcome to Lumen</h1>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default function App() {
|
|
2
|
+
return (
|
|
3
|
+
<div
|
|
4
|
+
style={{
|
|
5
|
+
minHeight: "100vh",
|
|
6
|
+
display: "flex",
|
|
7
|
+
alignItems: "center",
|
|
8
|
+
justifyContent: "center",
|
|
9
|
+
backgroundColor: "var(--color-bg, #ffffff)",
|
|
10
|
+
}}
|
|
11
|
+
>
|
|
12
|
+
<h1>Welcome to Lumen</h1>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} User
|
|
3
|
+
* @property {string} id
|
|
4
|
+
* @property {string} email
|
|
5
|
+
* @property {string} name
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} ApiResponse
|
|
10
|
+
* @property {*} data
|
|
11
|
+
* @property {string} message
|
|
12
|
+
* @property {boolean} success
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} CreateUserPayload
|
|
17
|
+
* @property {string} email
|
|
18
|
+
* @property {string} name
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface User {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ApiResponse<T> {
|
|
8
|
+
data: T;
|
|
9
|
+
message: string;
|
|
10
|
+
success: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CreateUserPayload {
|
|
14
|
+
email: string;
|
|
15
|
+
name: string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, it, expect } from "@jest/globals";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom/jest-globals";
|
|
4
|
+
import App from "@/App";
|
|
5
|
+
|
|
6
|
+
describe("App", () => {
|
|
7
|
+
it("renders without crashing", () => {
|
|
8
|
+
render(<App />);
|
|
9
|
+
expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, it, expect } from "@jest/globals";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom/jest-globals";
|
|
4
|
+
import App from "@/App";
|
|
5
|
+
|
|
6
|
+
describe("App", () => {
|
|
7
|
+
it("renders without crashing", () => {
|
|
8
|
+
render(<App />);
|
|
9
|
+
expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, it, expect } from "@jest/globals";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom/jest-globals";
|
|
4
|
+
import App from "@/App";
|
|
5
|
+
|
|
6
|
+
describe("App", () => {
|
|
7
|
+
it("renders without crashing", () => {
|
|
8
|
+
render(<App />);
|
|
9
|
+
expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, it, expect } from "@jest/globals";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom/jest-globals";
|
|
4
|
+
import App from "@/App";
|
|
5
|
+
|
|
6
|
+
describe("App", () => {
|
|
7
|
+
it("renders without crashing", () => {
|
|
8
|
+
render(<App />);
|
|
9
|
+
expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import App from "@/App";
|
|
4
|
+
|
|
5
|
+
describe("App", () => {
|
|
6
|
+
it("renders without crashing", () => {
|
|
7
|
+
render(<App />);
|
|
8
|
+
expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import App from "@/App";
|
|
4
|
+
|
|
5
|
+
describe("App", () => {
|
|
6
|
+
it("renders without crashing", () => {
|
|
7
|
+
render(<App />);
|
|
8
|
+
expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import App from "@/App";
|
|
4
|
+
|
|
5
|
+
describe("App", () => {
|
|
6
|
+
it("renders without crashing", () => {
|
|
7
|
+
render(<App />);
|
|
8
|
+
expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import App from "@/App";
|
|
4
|
+
|
|
5
|
+
describe("App", () => {
|
|
6
|
+
it("renders without crashing", () => {
|
|
7
|
+
render(<App />);
|
|
8
|
+
expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -1,9 +1,91 @@
|
|
|
1
1
|
/* themes.css — Tailwind v4 theme tokens
|
|
2
2
|
* Synced with ThemeProvider / AppProvider via
|
|
3
|
-
* document.documentElement.dataset.theme.
|
|
3
|
+
* document.documentElement.dataset.theme and .dark class.
|
|
4
4
|
*/
|
|
5
|
-
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
|
|
5
|
+
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *, .dark, .dark *));
|
|
6
6
|
|
|
7
|
-
@theme {
|
|
8
|
-
--color-
|
|
7
|
+
@theme inline {
|
|
8
|
+
--color-background: var(--background);
|
|
9
|
+
--color-foreground: var(--foreground);
|
|
10
|
+
--color-card: var(--card);
|
|
11
|
+
--color-card-foreground: var(--card-foreground);
|
|
12
|
+
--color-popover: var(--popover);
|
|
13
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
14
|
+
--color-primary: var(--primary);
|
|
15
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
16
|
+
--color-secondary: var(--secondary);
|
|
17
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
18
|
+
--color-muted: var(--muted);
|
|
19
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
20
|
+
--color-accent: var(--accent);
|
|
21
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
22
|
+
--color-destructive: var(--destructive);
|
|
23
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
24
|
+
--color-border: var(--border);
|
|
25
|
+
--color-input: var(--input);
|
|
26
|
+
--color-ring: var(--ring);
|
|
27
|
+
--color-chart-1: var(--chart-1);
|
|
28
|
+
--color-chart-2: var(--chart-2);
|
|
29
|
+
--color-chart-3: var(--chart-3);
|
|
30
|
+
--color-chart-4: var(--chart-4);
|
|
31
|
+
--color-chart-5: var(--chart-5);
|
|
32
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
33
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
34
|
+
--radius-lg: var(--radius);
|
|
35
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:root {
|
|
39
|
+
--background: oklch(1 0 0);
|
|
40
|
+
--foreground: oklch(0.145 0 0);
|
|
41
|
+
--card: oklch(1 0 0);
|
|
42
|
+
--card-foreground: oklch(0.145 0 0);
|
|
43
|
+
--popover: oklch(1 0 0);
|
|
44
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
45
|
+
--primary: oklch(0.205 0 0);
|
|
46
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
47
|
+
--secondary: oklch(0.97 0 0);
|
|
48
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
49
|
+
--muted: oklch(0.97 0 0);
|
|
50
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
51
|
+
--accent: oklch(0.97 0 0);
|
|
52
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
53
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
54
|
+
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
55
|
+
--border: oklch(0.922 0 0);
|
|
56
|
+
--input: oklch(0.922 0 0);
|
|
57
|
+
--ring: oklch(0.708 0 0);
|
|
58
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
59
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
60
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
61
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
62
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
63
|
+
--radius: 0.625rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
[data-theme="dark"], .dark {
|
|
67
|
+
--background: oklch(0.145 0 0);
|
|
68
|
+
--foreground: oklch(0.985 0 0);
|
|
69
|
+
--card: oklch(0.145 0 0);
|
|
70
|
+
--card-foreground: oklch(0.985 0 0);
|
|
71
|
+
--popover: oklch(0.145 0 0);
|
|
72
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
73
|
+
--primary: oklch(0.985 0 0);
|
|
74
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
75
|
+
--secondary: oklch(0.269 0 0);
|
|
76
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
77
|
+
--muted: oklch(0.269 0 0);
|
|
78
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
79
|
+
--accent: oklch(0.269 0 0);
|
|
80
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
81
|
+
--destructive: oklch(0.396 0.141 25.723);
|
|
82
|
+
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
83
|
+
--border: oklch(0.269 0 0);
|
|
84
|
+
--input: oklch(0.269 0 0);
|
|
85
|
+
--ring: oklch(0.439 0 0);
|
|
86
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
87
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
88
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
89
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
90
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
9
91
|
}
|