@texturehq/edges 0.0.15 → 0.0.19

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/dist/theme.css CHANGED
@@ -8,7 +8,61 @@
8
8
  }
9
9
  }
10
10
 
11
- @theme {
11
+ @keyframes slide-in-from-right {
12
+ from {
13
+ transform: translateX(100%);
14
+ }
15
+ to {
16
+ transform: translateX(0);
17
+ }
18
+ }
19
+
20
+ @keyframes slide-in-from-left {
21
+ from {
22
+ transform: translateX(-100%);
23
+ }
24
+ to {
25
+ transform: translateX(0);
26
+ }
27
+ }
28
+
29
+ @keyframes slide-out-to-right {
30
+ from {
31
+ transform: translateX(0);
32
+ }
33
+ to {
34
+ transform: translateX(100%);
35
+ }
36
+ }
37
+
38
+ @keyframes slide-out-to-left {
39
+ from {
40
+ transform: translateX(0);
41
+ }
42
+ to {
43
+ transform: translateX(-100%);
44
+ }
45
+ }
46
+
47
+ @keyframes fade-in {
48
+ from {
49
+ opacity: 0;
50
+ }
51
+ to {
52
+ opacity: 1;
53
+ }
54
+ }
55
+
56
+ @keyframes fade-out {
57
+ from {
58
+ opacity: 1;
59
+ }
60
+ to {
61
+ opacity: 0;
62
+ }
63
+ }
64
+
65
+ @theme inline {
12
66
  /* Base Font Families */
13
67
  --font-sans: "Inter", system-ui, sans-serif;
14
68
  --font-brand: "TT Firs Neue", "Helvetica Neue", "Arial", system-ui, sans-serif;
@@ -280,8 +334,8 @@
280
334
  --color-stone-900: var(--stone-900);
281
335
  --color-stone-950: var(--stone-950);
282
336
 
283
- --color-black: var(--black);
284
- --color-white: var(--white);
337
+ --color-black: var(--color-neutral-black);
338
+ --color-white: var(--color-neutral-white);
285
339
 
286
340
  --spacing: 0.25rem;
287
341
 
@@ -407,6 +461,12 @@
407
461
  --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
408
462
  --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
409
463
  --animate-bounce: bounce 1s infinite;
464
+ --animate-slide-in-from-right: slide-in-from-right 0.3s ease-out;
465
+ --animate-slide-in-from-left: slide-in-from-left 0.3s ease-out;
466
+ --animate-slide-out-to-right: slide-out-to-right 0.3s ease-in;
467
+ --animate-slide-out-to-left: slide-out-to-left 0.3s ease-in;
468
+ --animate-fade-in: fade-in 0.2s ease-out;
469
+ --animate-fade-out: fade-out 0.15s ease-in;
410
470
 
411
471
  @keyframes spin {
412
472
  to {
@@ -1015,3 +1075,28 @@
1015
1075
  --skeleton-highlight: #374151;
1016
1076
  --skeleton-wave: rgba(255, 255, 255, 0.1);
1017
1077
  }
1078
+
1079
+ /* Define the slide animation utilities */
1080
+ .slide-in-from-right {
1081
+ animation: slide-in-from-right 0.3s ease-out;
1082
+ }
1083
+
1084
+ .slide-in-from-left {
1085
+ animation: slide-in-from-left 0.3s ease-out;
1086
+ }
1087
+
1088
+ .slide-out-to-right {
1089
+ animation: slide-out-to-right 0.15s ease-in;
1090
+ }
1091
+
1092
+ .slide-out-to-left {
1093
+ animation: slide-out-to-left 0.15s ease-in;
1094
+ }
1095
+
1096
+ .fade-in {
1097
+ animation: fade-in 0.2s ease-out;
1098
+ }
1099
+
1100
+ .fade-out {
1101
+ animation: fade-out 0.15s ease-in;
1102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@texturehq/edges",
3
- "version": "0.0.15",
3
+ "version": "0.0.19",
4
4
  "author": "Nicholas Brown <nick@texturehq.com>",
5
5
  "description": "A shared component library for Texture",
6
6
  "type": "module",
@@ -9,7 +9,9 @@
9
9
  "types": "./dist/index.d.ts",
10
10
  "sideEffects": false,
11
11
  "files": [
12
- "dist/**"
12
+ "dist/**",
13
+ "templates/**",
14
+ "scripts/**"
13
15
  ],
14
16
  "exports": {
15
17
  ".": {
@@ -18,12 +20,17 @@
18
20
  "default": "./dist/index.js"
19
21
  },
20
22
  "./styles.css": "./dist/styles.css",
21
- "./theme.css": "./dist/theme.css"
23
+ "./dist/styles.css": "./dist/styles.css",
24
+ "./theme.css": "./dist/theme.css",
25
+ "./dist/theme.css": "./dist/theme.css"
22
26
  },
23
27
  "scripts": {
24
28
  "dev": "yarn watch",
25
29
  "watch": "tsup --watch",
26
30
  "build": "tsup",
31
+ "build:yalc": "tsup && npx yalc publish --sig && npx yalc push",
32
+ "dev:yalc": "tsup && npx yalc publish --sig && npx yalc push && echo 'Package updated! Run in target project: yarn install && yarn dev'",
33
+ "dev:yalc:force": "tsup && npx yalc publish --sig && npx yalc push && echo 'Package updated! Run in target project: rm -rf .vite && yarn cache clean && yarn dev --force'",
27
34
  "clean": "rm -rf dist",
28
35
  "lint": "eslint --ext .ts,.tsx src/",
29
36
  "lint:quiet": "eslint --ext .ts,.tsx src/ --quiet",
@@ -33,7 +40,8 @@
33
40
  "test:coverage": "vitest run --coverage",
34
41
  "test:ui": "vitest --ui",
35
42
  "storybook": "storybook dev -p 6010 --no-open",
36
- "build-storybook": "storybook build"
43
+ "build-storybook": "storybook build",
44
+ "postinstall": "node scripts/setup-cursor-rules.js || echo \"! setup-cursor-rules: non-fatal error\""
37
45
  },
38
46
  "peerDependencies": {
39
47
  "react": "^18.2.0",
@@ -47,6 +55,7 @@
47
55
  "next-intl": "^4.0.2",
48
56
  "react-ace": "^14.0.1",
49
57
  "react-aria-components": "^1.7.1",
58
+ "react-stately": "^3.35.0",
50
59
  "tailwind-merge": "^3.2.0"
51
60
  },
52
61
  "devDependencies": {
@@ -84,6 +93,7 @@
84
93
  "typescript": "~5.4.2",
85
94
  "vite": "^5.1.6",
86
95
  "vite-plugin-svgr": "^4.3.0",
87
- "vitest": "^3.1.1"
96
+ "vitest": "^3.1.1",
97
+ "yalc": "^1.0.0-pre.53"
88
98
  }
89
99
  }
@@ -0,0 +1,154 @@
1
+ // generate-components-manifest.js
2
+ // Build-time script that scans exported components and creates dist/components.manifest.json
3
+
4
+ import fs from "fs";
5
+ import path from "path";
6
+
7
+ const ROOT = process.cwd();
8
+ const SRC_DIR = path.join(ROOT, "src");
9
+ const DIST_DIR = path.join(ROOT, "dist");
10
+
11
+ function read(file) {
12
+ return fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
13
+ }
14
+
15
+ function ensureDir(dir) {
16
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir);
17
+ }
18
+
19
+ function extractExportsFromIndex(indexContent) {
20
+ // Matches: export { Button } from "./components/Button";
21
+ const re = /export\s+\{\s*([A-Za-z0-9_,\s]+)\s*\}\s+from\s+"\.\/components\/([A-Za-z0-9_/.-]+)"/g;
22
+ const results = [];
23
+ let m;
24
+ while ((m = re.exec(indexContent))) {
25
+ const exported = m[1]
26
+ .split(",")
27
+ .map((s) => s.trim())
28
+ .filter(Boolean);
29
+ const rel = m[2];
30
+ exported.forEach((name) => {
31
+ // Only keep PascalCase (components); ignore sub-exports like Tab, TabList if not directory-named
32
+ results.push({ name, relPath: rel });
33
+ });
34
+ }
35
+ return results;
36
+ }
37
+
38
+ function findComponentFile(relPath) {
39
+ // Prefer ComponentName/ComponentName.tsx
40
+ const base = path.join(SRC_DIR, relPath);
41
+ const dir = path.dirname(base);
42
+ const compName = path.basename(base);
43
+ const candidates = [
44
+ path.join(dir, `${compName}.tsx`),
45
+ path.join(dir, `${compName}.ts`),
46
+ path.join(base, `${compName}.tsx`),
47
+ path.join(base, `index.tsx`),
48
+ path.join(base, `index.ts`),
49
+ ];
50
+ for (const f of candidates) {
51
+ if (fs.existsSync(f)) return f;
52
+ }
53
+ return null;
54
+ }
55
+
56
+ function getJsdocAbove(content, idx) {
57
+ // Find the last JSDoc block before idx
58
+ const upTo = content.slice(0, idx);
59
+ const start = upTo.lastIndexOf("/**");
60
+ const end = upTo.lastIndexOf("*/");
61
+ if (start !== -1 && end !== -1 && end > start) {
62
+ const block = upTo.slice(start + 3, end).trim();
63
+ return block
64
+ .split("\n")
65
+ .map((l) => l.replace(/^\s*\*\s?/, "").trim())
66
+ .join(" ")
67
+ .trim();
68
+ }
69
+ return "";
70
+ }
71
+
72
+ function extractProps(content, componentName) {
73
+ // Try `export interface <Name>Props` first
74
+ const ifaceRe = new RegExp(`export\\s+interface\\s+${componentName}Props\\s*(?:extends\\s+[^{]+)?\\{([\\s\\S]*?)\\}`, "m");
75
+ let m = ifaceRe.exec(content);
76
+ if (!m) {
77
+ // Try generic name: any exported *Props
78
+ const anyIfaceRe = /export\s+interface\s+([A-Za-z0-9_]+Props)\s*(?:extends\s+[^{]+)?\{([\s\S]*?)\}/m;
79
+ m = anyIfaceRe.exec(content);
80
+ }
81
+ if (!m) {
82
+ // Try type alias
83
+ const typeRe = new RegExp(`export\\s+type\\s+${componentName}Props\\s*=\\s*(?:[^{&]+&\s*)?([\\s\\S]*?)\\}`, "m");
84
+ m = typeRe.exec(content);
85
+ }
86
+ const props = [];
87
+ if (m) {
88
+ const body = m[2] || m[1] || "";
89
+ const lines = body
90
+ .split("\n")
91
+ .map((l) => l.trim())
92
+ .filter((l) => !!l && !l.startsWith("//"));
93
+ for (const line of lines) {
94
+ // Match: name?: Type; or name: Type;
95
+ const pm = /^(readonly\s+)?([A-Za-z0-9_]+)\??:\s*([^;]+);?/.exec(line);
96
+ if (pm) {
97
+ const name = pm[2];
98
+ const type = pm[3].trim();
99
+ props.push({ name, type });
100
+ }
101
+ }
102
+ }
103
+ return props;
104
+ }
105
+
106
+ function run() {
107
+ try {
108
+ const indexPath = path.join(SRC_DIR, "index.ts");
109
+ const indexContent = read(indexPath);
110
+ const exports = extractExportsFromIndex(indexContent);
111
+
112
+ const components = [];
113
+ for (const { name, relPath } of exports) {
114
+ // Only treat PascalCase names as components
115
+ if (!/^[A-Z]/.test(name)) continue;
116
+
117
+ const componentFile = findComponentFile(relPath);
118
+ if (!componentFile) continue;
119
+ const content = read(componentFile);
120
+
121
+ // Description: JSDoc above `export function Name` or above `export interface NameProps`
122
+ let idx = content.indexOf(`export function ${name}`);
123
+ if (idx === -1) idx = content.search(new RegExp(`export\\s+(const|class)\\s+${name}\b`));
124
+ if (idx === -1) idx = content.search(new RegExp(`export\\s+(interface|type)\\s+${name}Props\b`));
125
+ const description = idx !== -1 ? getJsdocAbove(content, idx) : "";
126
+
127
+ const props = extractProps(content, name);
128
+
129
+ components.push({
130
+ name,
131
+ importRoot: "@texturehq/edges",
132
+ importPath: `@texturehq/edges/components/${name}`,
133
+ description,
134
+ props,
135
+ });
136
+ }
137
+
138
+ ensureDir(DIST_DIR);
139
+ const manifest = {
140
+ version: process.env.npm_package_version || "unknown",
141
+ generatedAt: new Date().toISOString(),
142
+ components: components.sort((a, b) => a.name.localeCompare(b.name)),
143
+ };
144
+ fs.writeFileSync(path.join(DIST_DIR, "components.manifest.json"), JSON.stringify(manifest, null, 2));
145
+ console.log(`Generated ${path.join("dist", "components.manifest.json")} with ${components.length} components.`);
146
+ } catch (err) {
147
+ console.error("Failed to generate components manifest:", err);
148
+ process.exitCode = 1;
149
+ }
150
+ }
151
+
152
+ run();
153
+
154
+
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ // setup-cursor-rules-manual.js (manual installer for Cursor rules)
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const setupCursorRules = () => {
7
+ const cwd = process.cwd();
8
+
9
+ // Don't run inside the edges package itself
10
+ try {
11
+ const pkgJsonPath = path.join(cwd, "package.json");
12
+ if (fs.existsSync(pkgJsonPath)) {
13
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
14
+ if (pkg.name === "@texturehq/edges") return;
15
+ }
16
+ } catch {
17
+ // ignore
18
+ }
19
+
20
+ // Try multiple possible locations for the templates directory
21
+ const possibleTemplatePaths = [
22
+ // Yalc path (the actual files)
23
+ path.join(cwd, 'node_modules/.yalc/@texturehq/edges/templates/cursor-rules'),
24
+ // Regular npm install path
25
+ path.join(__dirname, '../templates/cursor-rules'),
26
+ // Alternative paths
27
+ path.join(__dirname, '../../templates/cursor-rules'),
28
+ path.join(__dirname, '../../../templates/cursor-rules'),
29
+ // Direct path from node_modules
30
+ path.join(cwd, 'node_modules/@texturehq/edges/templates/cursor-rules'),
31
+ ];
32
+
33
+ let templatesDir = null;
34
+ for (const templatePath of possibleTemplatePaths) {
35
+ if (fs.existsSync(templatePath)) {
36
+ templatesDir = templatePath;
37
+ break;
38
+ }
39
+ }
40
+
41
+ if (!templatesDir) {
42
+ console.log('⚠️ Could not find cursor rules templates. Tried paths:');
43
+ possibleTemplatePaths.forEach(path => console.log(` - ${path}`));
44
+ return;
45
+ }
46
+
47
+ console.log(`✅ Found templates at: ${templatesDir}`);
48
+
49
+ // Try to read package version from multiple locations
50
+ const possiblePackageJsonPaths = [
51
+ // Yalc path (the actual files)
52
+ path.join(cwd, 'node_modules/.yalc/@texturehq/edges/package.json'),
53
+ // Regular npm install path
54
+ path.join(__dirname, '../package.json'),
55
+ path.join(__dirname, '../../package.json'),
56
+ path.join(__dirname, '../../../package.json'),
57
+ path.join(cwd, 'node_modules/@texturehq/edges/package.json'),
58
+ ];
59
+
60
+ let packageVersion = 'unknown';
61
+ for (const packageJsonPath of possiblePackageJsonPaths) {
62
+ if (fs.existsSync(packageJsonPath)) {
63
+ try {
64
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
65
+ packageVersion = packageJson.version;
66
+ console.log(`📦 Found package version: ${packageVersion}`);
67
+ break;
68
+ } catch (error) {
69
+ console.log(`⚠️ Could not read package.json at: ${packageJsonPath}`);
70
+ }
71
+ }
72
+ }
73
+
74
+ const templateFiles = fs.readdirSync(templatesDir).filter(file => file.endsWith('.mdc'));
75
+
76
+ if (templateFiles.length === 0) {
77
+ console.log('⚠️ No .mdc files found in templates directory');
78
+ return;
79
+ }
80
+
81
+ // Always use package-level rules for version-specific behavior
82
+ const cursorDir = path.join(cwd, '.cursor');
83
+ const rulesDir = path.join(cursorDir, 'rules');
84
+
85
+ // Create directories recursively
86
+ fs.mkdirSync(cursorDir, { recursive: true });
87
+ fs.mkdirSync(rulesDir, { recursive: true });
88
+
89
+ let copiedCount = 0;
90
+
91
+ templateFiles.forEach(file => {
92
+ const templatePath = path.join(templatesDir, file);
93
+
94
+ // Read template content and inject version info
95
+ let templateContent = fs.readFileSync(templatePath, 'utf8');
96
+
97
+ // Add version information to the rule
98
+ const versionHeader = `\n\n<!-- @texturehq/edges version: ${packageVersion} -->\n`;
99
+ templateContent = templateContent + versionHeader;
100
+
101
+ const targetPath = path.join(rulesDir, file);
102
+
103
+ // Backup existing rule before overwriting
104
+ if (fs.existsSync(targetPath)) {
105
+ const backupsDir = path.join(rulesDir, "_backups");
106
+ fs.mkdirSync(backupsDir, { recursive: true });
107
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
108
+ fs.copyFileSync(targetPath, path.join(backupsDir, `${file}.${ts}.bak`));
109
+ }
110
+ // Always overwrite to ensure version-specific rules
111
+ fs.writeFileSync(targetPath, templateContent, "utf8");
112
+ copiedCount++;
113
+ console.log(`✅ Updated .cursor/rules/${file} for @texturehq/edges v${packageVersion}`);
114
+ });
115
+
116
+ // Additionally, try to render a components list rule from the manifest if available
117
+ const possibleManifestPaths = [
118
+ path.join(cwd, 'node_modules/.yalc/@texturehq/edges/dist/components.manifest.json'),
119
+ path.join(cwd, 'node_modules/@texturehq/edges/dist/components.manifest.json')
120
+ ];
121
+ let manifestPath = null;
122
+ for (const p of possibleManifestPaths) {
123
+ if (fs.existsSync(p)) { manifestPath = p; break; }
124
+ }
125
+ if (manifestPath) {
126
+ try {
127
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
128
+ const lines = [
129
+ '---',
130
+ 'alwaysApply: true',
131
+ '---',
132
+ '',
133
+ `## @texturehq/edges Components (v${manifest.version || 'unknown'})`,
134
+ ''
135
+ ];
136
+ for (const c of manifest.components || []) {
137
+ lines.push(`- ${c.name}`);
138
+ if (c.importRoot) lines.push(` - Import: \`import { ${c.name} } from "${c.importRoot}"\``);
139
+ if (c.importPath) lines.push(` - Subpath: \`import { ${c.name} } from "${c.importPath}"\``);
140
+ if (c.props && c.props.length) {
141
+ const propNames = c.props.slice(0, 8).map(p => p.name).join(', ');
142
+ lines.push(` - Props: ${propNames}${c.props.length > 8 ? ', …' : ''}`);
143
+ }
144
+ }
145
+ const outPath = path.join(rulesDir, 'edges-components.mdc');
146
+ // Backup existing components manifest before overwriting
147
+ if (fs.existsSync(outPath)) {
148
+ const backupsDir = path.join(rulesDir, "_backups");
149
+ fs.mkdirSync(backupsDir, { recursive: true });
150
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
151
+ fs.copyFileSync(outPath, path.join(backupsDir, `edges-components.mdc.${ts}.bak`));
152
+ }
153
+ fs.writeFileSync(outPath, lines.join('\n'), "utf8");
154
+ console.log(`✅ Wrote .cursor/rules/edges-components.mdc from manifest (${manifest.components?.length || 0} components)`);
155
+ } catch (err) {
156
+ console.log('⚠️ Failed to read components.manifest.json:', err.message);
157
+ }
158
+ } else {
159
+ console.log('ℹ️ No components.manifest.json found; skipping edges-components.mdc generation');
160
+ }
161
+
162
+ if (copiedCount > 0) {
163
+ console.log(`🎨 Updated ${copiedCount} cursor rule(s) for @texturehq/edges v${packageVersion}`);
164
+ }
165
+ };
166
+
167
+ setupCursorRules();
@@ -0,0 +1,113 @@
1
+ // scripts/setup-cursor-rules.js
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ const setupCursorRules = () => {
6
+ const cwd = process.env.INIT_CWD || process.cwd();
7
+
8
+ // Don't run inside the edges package itself
9
+ try {
10
+ const pkgJsonPath = path.join(cwd, "package.json");
11
+ if (fs.existsSync(pkgJsonPath)) {
12
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
13
+ if (pkg.name === "@texturehq/edges") return;
14
+ }
15
+ } catch {
16
+ // ignore
17
+ }
18
+
19
+ const templatesDir = path.join(path.dirname(new URL(import.meta.url).pathname), '../templates/cursor-rules');
20
+ const cursorDir = path.join(cwd, '.cursor');
21
+ const rulesDir = path.join(cursorDir, 'rules');
22
+
23
+ // Create directories recursively
24
+ fs.mkdirSync(cursorDir, { recursive: true });
25
+ fs.mkdirSync(rulesDir, { recursive: true });
26
+
27
+ // Check if templates directory exists
28
+ if (!fs.existsSync(templatesDir)) {
29
+ console.log('⚠️ No cursor rules templates found');
30
+ return;
31
+ }
32
+
33
+ // Copy all .mdc files from templates to the project's cursor rules
34
+ const templateFiles = fs.readdirSync(templatesDir).filter(file => file.endsWith('.mdc'));
35
+
36
+ if (templateFiles.length === 0) {
37
+ console.log('⚠️ No .mdc files found in templates directory');
38
+ return;
39
+ }
40
+
41
+ let copiedCount = 0;
42
+
43
+ templateFiles.forEach(file => {
44
+ const templatePath = path.join(templatesDir, file);
45
+ const targetPath = path.join(rulesDir, file);
46
+
47
+ // Backup existing rule before overwriting
48
+ if (fs.existsSync(targetPath)) {
49
+ const backupsDir = path.join(rulesDir, "_backups");
50
+ fs.mkdirSync(backupsDir, { recursive: true });
51
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
52
+ fs.copyFileSync(targetPath, path.join(backupsDir, `${file}.${ts}.bak`));
53
+ }
54
+ // Always copy to ensure latest version
55
+ fs.copyFileSync(templatePath, targetPath);
56
+ copiedCount++;
57
+ console.log(`✅ Updated .cursor/rules/${file}`);
58
+ });
59
+
60
+ if (copiedCount > 0) {
61
+ console.log(`🎨 Added ${copiedCount} cursor rule(s) for @texturehq/edges design system`);
62
+ } else {
63
+ console.log('ℹ️ All cursor rules already exist');
64
+ }
65
+
66
+ // Also render edges-components.mdc from manifest when present
67
+ const manifestPath = [
68
+ path.join(cwd, 'node_modules/@texturehq/edges/dist/components.manifest.json')
69
+ ].find(p => fs.existsSync(p));
70
+
71
+ if (manifestPath) {
72
+ try {
73
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
74
+ const lines = [
75
+ '---',
76
+ 'alwaysApply: true',
77
+ '---',
78
+ '',
79
+ `## @texturehq/edges Components (v${manifest.version || 'unknown'})`,
80
+ ''
81
+ ];
82
+ for (const c of manifest.components || []) {
83
+ lines.push(`- ${c.name}`);
84
+ if (c.importRoot) lines.push(` - Import: \`import { ${c.name} } from "${c.importRoot}"\``);
85
+ if (c.importPath) lines.push(` - Subpath: \`import { ${c.name} } from "${c.importPath}"\``);
86
+ if (c.props && c.props.length) {
87
+ const propNames = c.props.slice(0, 8).map(p => p.name).join(', ');
88
+ lines.push(` - Props: ${propNames}${c.props.length > 8 ? ', …' : ''}`);
89
+ }
90
+ }
91
+ const outPath = path.join(rulesDir, 'edges-components.mdc');
92
+ // Backup existing components manifest before overwriting
93
+ if (fs.existsSync(outPath)) {
94
+ const backupsDir = path.join(rulesDir, "_backups");
95
+ fs.mkdirSync(backupsDir, { recursive: true });
96
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
97
+ fs.copyFileSync(outPath, path.join(backupsDir, `edges-components.mdc.${ts}.bak`));
98
+ }
99
+ fs.writeFileSync(outPath, lines.join('\n'), "utf8");
100
+ console.log(`✅ Wrote .cursor/rules/edges-components.mdc from manifest (${manifest.components?.length || 0} components)`);
101
+ } catch (err) {
102
+ console.log('⚠️ Failed to read components.manifest.json:', err.message);
103
+ }
104
+ }
105
+ };
106
+
107
+ try {
108
+ setupCursorRules();
109
+ } catch (err) {
110
+ console.warn("setup-cursor-rules error:", err);
111
+ }
112
+
113
+