@spectrabrain/eds 0.1.0 → 0.1.1

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.
Files changed (55) hide show
  1. package/AI.md +13 -63
  2. package/AI_CONTEXT.json +26 -95
  3. package/CHANGELOG.md +66 -4
  4. package/CONSUMER_AGENT.md +86 -164
  5. package/FRONTEND_CONVENTIONS.md +252 -0
  6. package/QUICKSTART.md +24 -29
  7. package/README.md +24 -73
  8. package/TAILWIND_V4.md +49 -184
  9. package/TOKEN_CONTEXT.md +63 -36
  10. package/consumer/align/templates/components.json +21 -0
  11. package/consumer/align/templates/env.mock +1 -0
  12. package/consumer/align/templates/eslint.config.js +35 -0
  13. package/consumer/align/templates/package.scripts.json +10 -0
  14. package/consumer/align/templates/prettierignore +2 -0
  15. package/consumer/align/templates/prettierrc.json +7 -0
  16. package/consumer/align/templates/src/app/App.tsx +16 -0
  17. package/consumer/align/templates/src/app/main.tsx +27 -0
  18. package/consumer/align/templates/src/app/providers/ErrorBoundary.tsx +58 -0
  19. package/consumer/align/templates/src/app/providers/ErrorHandler.tsx +29 -0
  20. package/consumer/align/templates/src/app/router/index.tsx +23 -0
  21. package/consumer/align/templates/src/app/styles/index.css +2 -0
  22. package/consumer/align/templates/src/mocks/browser.ts +5 -0
  23. package/consumer/align/templates/src/mocks/handlers.ts +4 -0
  24. package/consumer/align/templates/src/shared/api/index.ts +23 -0
  25. package/consumer/align/templates/src/shared/api/queryClient.ts +24 -0
  26. package/consumer/align/templates/src/shared/lib/eventBus.ts +44 -0
  27. package/consumer/align/templates/src/shared/lib/utils/index.ts +6 -0
  28. package/consumer/align/templates/src/vite-env.d.ts +2 -0
  29. package/consumer/align/templates/tsconfig.json +22 -0
  30. package/consumer/align/templates/vite.config.babel.ts +36 -0
  31. package/consumer/align/templates/vite.config.rolldown.ts +35 -0
  32. package/consumer/align-analysis.mjs +1159 -0
  33. package/consumer/align-plan.mjs +377 -0
  34. package/consumer/align-rules.mjs +473 -0
  35. package/consumer/align.mjs +401 -0
  36. package/consumer/cli.mjs +90 -40
  37. package/consumer/css-root.mjs +24 -4
  38. package/consumer/doctor.mjs +47 -21
  39. package/consumer/init.mjs +39 -25
  40. package/consumer/managed-block.mjs +11 -5
  41. package/consumer/paths.mjs +69 -14
  42. package/consumer/register-scripts.mjs +17 -12
  43. package/consumer/scan-classes.mjs +4 -3
  44. package/consumer/sync.mjs +35 -13
  45. package/consumer/update-agent-context.mjs +63 -7
  46. package/consumer-postinstall.mjs +8 -7
  47. package/dist/components/Button/README.md +66 -95
  48. package/dist/components/Button/spec.json +19 -138
  49. package/dist/components.css +102 -366
  50. package/dist/index.d.mts +20 -18
  51. package/dist/index.d.ts +20 -18
  52. package/dist/tokens.d.mts +3 -0
  53. package/dist/tokens.d.ts +3 -0
  54. package/llms.txt +20 -14
  55. package/package.json +3 -1
@@ -0,0 +1,29 @@
1
+ import { useEffect, type ReactNode } from "react";
2
+ import { isAxiosError } from "axios";
3
+ import { toast } from "sonner";
4
+ import eventBus from "@/shared/lib/eventBus";
5
+
6
+ interface ErrorHandlerProviderProps {
7
+ children: ReactNode;
8
+ }
9
+
10
+ // Shows the 401 and 403 errors shared/api emits on the event bus as toasts. Session handling
11
+ // (for example clearing the auth store on 401) belongs here, not in shared/api.
12
+ export function ErrorHandlerProvider({ children }: ErrorHandlerProviderProps) {
13
+ useEffect(() => {
14
+ const handleError = (error: unknown) => {
15
+ const status = isAxiosError(error) ? error.response?.status : undefined;
16
+ if (status === 401) {
17
+ toast.error("로그인이 필요합니다.");
18
+ } else if (status === 403) {
19
+ toast.error("접근 권한이 없습니다.");
20
+ }
21
+ };
22
+ eventBus.subscribe("error", handleError);
23
+ return () => {
24
+ eventBus.unsubscribe("error", handleError);
25
+ };
26
+ }, []);
27
+
28
+ return children;
29
+ }
@@ -0,0 +1,23 @@
1
+ import { Suspense, lazy } from "react";
2
+ import { Navigate, Route, Routes } from "react-router-dom";
3
+ import { ErrorBoundary } from "@/app/providers/ErrorBoundary";
4
+
5
+ // One lazy chunk per page. Replace this example with the project's routes and paths. Screens of a backend
6
+ // resource use pages/<domain>/<action>/page.tsx; screens outside one (home, login, not-found) use pages/<name>/page.tsx.
7
+ const HomePage = lazy(() => import("@/pages/home/page"));
8
+
9
+ function Router() {
10
+ return (
11
+ <ErrorBoundary>
12
+ {/* Use a skeleton from widgets/layout as the fallback when the pages have one. */}
13
+ <Suspense fallback={null}>
14
+ <Routes>
15
+ <Route path="/" element={<HomePage />} />
16
+ <Route path="*" element={<Navigate to="/" replace />} />
17
+ </Routes>
18
+ </Suspense>
19
+ </ErrorBoundary>
20
+ );
21
+ }
22
+
23
+ export default Router;
@@ -0,0 +1,2 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
@@ -0,0 +1,5 @@
1
+ import { setupWorker } from "msw/browser";
2
+ import { handlers } from "./handlers";
3
+
4
+ // The Service Worker that answers API requests in npm run dev:mock.
5
+ export const worker = setupWorker(...handlers);
@@ -0,0 +1,4 @@
1
+ import type { RequestHandler } from "msw";
2
+
3
+ // Spread each domain's handlers from mocks/<domain>/index.ts, for example ...accountHandlers.
4
+ export const handlers: RequestHandler[] = [];
@@ -0,0 +1,23 @@
1
+ import axios from "axios";
2
+ import eventBus from "@/shared/lib/eventBus";
3
+
4
+ // The one axios instance for backend calls. Keep the base URL, headers and credentials the
5
+ // project already sends: the backend contract does not change.
6
+ const axiosInstance = axios.create({
7
+ baseURL: window.location.origin, // Replace with the base URL the existing requests use.
8
+ // withCredentials: true, // Enable only when the existing requests send cookies.
9
+ });
10
+
11
+ axiosInstance.interceptors.response.use(
12
+ (response) => response,
13
+ (error) => {
14
+ const status = error.response?.status;
15
+ // shared/ never imports entities: app/providers/ErrorHandler reacts to these events.
16
+ if (status === 401 || status === 403) {
17
+ eventBus.emit("error", error);
18
+ }
19
+ return Promise.reject(error);
20
+ },
21
+ );
22
+
23
+ export default axiosInstance;
@@ -0,0 +1,24 @@
1
+ import { QueryClient } from "@tanstack/react-query";
2
+ import { isAxiosError } from "axios";
3
+
4
+ export const queryClient = new QueryClient({
5
+ defaultOptions: {
6
+ queries: {
7
+ // Fresh for a short window so quick remounts and navigation do not refetch;
8
+ // mutations invalidate the keys they change.
9
+ staleTime: 1000 * 30,
10
+ gcTime: 1000 * 60 * 5,
11
+ retry: (failureCount, error) => {
12
+ const status = isAxiosError(error) ? error.response?.status : undefined;
13
+ // Client errors (4xx) do not succeed on retry.
14
+ if (status && status >= 400 && status < 500) {
15
+ return false;
16
+ }
17
+ return failureCount < 2;
18
+ },
19
+ },
20
+ mutations: {
21
+ retry: false,
22
+ },
23
+ },
24
+ });
@@ -0,0 +1,44 @@
1
+ type EventCallback<T> = (data: T) => void;
2
+
3
+ // App-wide events (for example "error") that lower layers emit and app/providers handle.
4
+ class EventBus {
5
+ private static instance: EventBus;
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ private eventListeners: Map<string, Set<EventCallback<any>>> = new Map();
8
+
9
+ private constructor() {}
10
+
11
+ public static getInstance(): EventBus {
12
+ if (!EventBus.instance) {
13
+ EventBus.instance = new EventBus();
14
+ }
15
+ return EventBus.instance;
16
+ }
17
+
18
+ public subscribe<T>(event: string, callback: EventCallback<T>): void {
19
+ if (!this.eventListeners.has(event)) {
20
+ this.eventListeners.set(event, new Set());
21
+ }
22
+ this.eventListeners.get(event)?.add(callback);
23
+ }
24
+
25
+ public unsubscribe<T>(event: string, callback: EventCallback<T>): void {
26
+ const callbacks = this.eventListeners.get(event);
27
+ if (callbacks) {
28
+ callbacks.delete(callback);
29
+ if (callbacks.size === 0) {
30
+ this.eventListeners.delete(event);
31
+ }
32
+ }
33
+ }
34
+
35
+ public emit<T>(event: string, data: T): void {
36
+ const callbacks = this.eventListeners.get(event);
37
+ if (callbacks) {
38
+ callbacks.forEach((callback) => callback(data));
39
+ }
40
+ }
41
+ }
42
+
43
+ export const eventBus = EventBus.getInstance();
44
+ export default eventBus;
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference types="vite/client" />
2
+ /// <reference types="vite-plugin-svgr/client" />
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "paths": {
9
+ "@/*": ["./{{eds.srcDir}}/*"]
10
+ },
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "verbatimModuleSyntax": true,
16
+ "moduleDetection": "force",
17
+ "noEmit": true,
18
+ "jsx": "react-jsx",
19
+ "strict": {{eds.strict}}
20
+ },
21
+ "include": ["{{eds.srcDir}}"]
22
+ }
@@ -0,0 +1,36 @@
1
+ import { fileURLToPath, URL } from "node:url";
2
+ import { defineConfig } from "vite";
3
+ import react from "@vitejs/plugin-react";
4
+ import svgr from "vite-plugin-svgr";
5
+ import tailwindcss from "@tailwindcss/vite";
6
+
7
+ // Vite 5–7 with @vitejs/plugin-react 4 or 5. Merge rules: PLAN.md step 3.
8
+ export default defineConfig({
9
+ plugins: [
10
+ react({
11
+ babel: {
12
+ plugins: [["babel-plugin-react-compiler", {}]],
13
+ },
14
+ }),
15
+ svgr(),
16
+ // The Vite plugin rebases the url() of imported package CSS, so the EDS font is emitted.
17
+ tailwindcss(),
18
+ ],
19
+ resolve: {
20
+ alias: {
21
+ "@": fileURLToPath(new URL("./{{eds.srcDir}}", import.meta.url)),
22
+ },
23
+ // One React instance for the app and the pre-bundled dependencies.
24
+ dedupe: ["react", "react-dom"],
25
+ },
26
+ build: {
27
+ // Production bundles drop console.log/info/debug and debugger; console.error and warn stay.
28
+ minify: "terser",
29
+ terserOptions: {
30
+ compress: {
31
+ drop_console: ["log", "info", "debug"],
32
+ drop_debugger: true,
33
+ },
34
+ },
35
+ },
36
+ });
@@ -0,0 +1,35 @@
1
+ import { fileURLToPath, URL } from "node:url";
2
+ import { defineConfig } from "vite";
3
+ import react, { reactCompilerPreset } from "@vitejs/plugin-react";
4
+ import babel from "@rolldown/plugin-babel";
5
+ import svgr from "vite-plugin-svgr";
6
+ import tailwindcss from "@tailwindcss/vite";
7
+
8
+ // Vite 8+ with @vitejs/plugin-react 6, which runs the React Compiler through @rolldown/plugin-babel.
9
+ // Merge rules: PLAN.md step 3.
10
+ export default defineConfig({
11
+ plugins: [
12
+ react(),
13
+ babel({ presets: [reactCompilerPreset()] }),
14
+ svgr(),
15
+ // The Vite plugin rebases the url() of imported package CSS, so the EDS font is emitted.
16
+ tailwindcss(),
17
+ ],
18
+ resolve: {
19
+ alias: {
20
+ "@": fileURLToPath(new URL("./{{eds.srcDir}}", import.meta.url)),
21
+ },
22
+ // One React instance for the app and the pre-bundled dependencies.
23
+ dedupe: ["react", "react-dom"],
24
+ },
25
+ build: {
26
+ // Production bundles drop console.log/info/debug and debugger; console.error and warn stay.
27
+ minify: "terser",
28
+ terserOptions: {
29
+ compress: {
30
+ drop_console: ["log", "info", "debug"],
31
+ drop_debugger: true,
32
+ },
33
+ },
34
+ },
35
+ });