code-to-design 0.1.4 → 0.2.0

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.
@@ -1,10 +1,10 @@
1
1
  // src/commands/scan.ts
2
- import { join as join9 } from "path";
3
- import { rm, mkdir as mkdir3 } from "fs/promises";
4
- import { existsSync as existsSync6, watch as fsWatch } from "fs";
2
+ import { join as join10 } from "path";
3
+ import { rm, mkdir as mkdir4 } from "fs/promises";
4
+ import { existsSync as existsSync7, watch as fsWatch } from "fs";
5
5
 
6
6
  // ../core/src/discovery/route-scanner.ts
7
- import { readdir, stat } from "fs/promises";
7
+ import { readdir, readFile, stat } from "fs/promises";
8
8
  import { join, extname } from "path";
9
9
  var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx"]);
10
10
  var PAGE_BASENAMES = /* @__PURE__ */ new Set(["page"]);
@@ -13,6 +13,13 @@ function isPageFile(filename) {
13
13
  const basename = filename.slice(0, -ext.length);
14
14
  return PAGE_EXTENSIONS.has(ext) && PAGE_BASENAMES.has(basename);
15
15
  }
16
+ function isPagesRouterFile(filename) {
17
+ const ext = extname(filename);
18
+ if (!PAGE_EXTENSIONS.has(ext)) return false;
19
+ const basename = filename.slice(0, -ext.length);
20
+ if (basename.startsWith("_")) return false;
21
+ return true;
22
+ }
16
23
  function shouldSkipDir(name) {
17
24
  if (name.startsWith("_")) return true;
18
25
  if (name.startsWith("@")) return true;
@@ -82,24 +89,182 @@ async function scanDir(dirPath, urlSegments, params) {
82
89
  }
83
90
  return routes;
84
91
  }
92
+ async function scanPagesDir(dirPath, urlSegments, params) {
93
+ const routes = [];
94
+ let entries;
95
+ try {
96
+ entries = await readdir(dirPath);
97
+ } catch {
98
+ return routes;
99
+ }
100
+ for (const entry of entries) {
101
+ const entryPath = join(dirPath, entry);
102
+ const entryStat = await stat(entryPath).catch(() => null);
103
+ if (!entryStat) continue;
104
+ if (entryStat.isFile() && isPagesRouterFile(entry)) {
105
+ const ext = extname(entry);
106
+ const basename = entry.slice(0, -ext.length);
107
+ let fileUrlSegments;
108
+ let fileParams;
109
+ if (basename === "index") {
110
+ fileUrlSegments = urlSegments;
111
+ fileParams = params;
112
+ } else {
113
+ const param = parseDynamicSegment(basename);
114
+ const urlPart = segmentToUrlPart(basename);
115
+ fileUrlSegments = [...urlSegments, urlPart];
116
+ fileParams = param ? [...params, param] : params;
117
+ }
118
+ const urlPath = "/" + fileUrlSegments.join("/");
119
+ routes.push({
120
+ urlPath: urlPath || "/",
121
+ filePath: entryPath,
122
+ params: [...fileParams],
123
+ isDynamic: fileParams.length > 0
124
+ });
125
+ }
126
+ if (entryStat.isDirectory()) {
127
+ if (entry === "api") continue;
128
+ if (entry.startsWith("_")) continue;
129
+ if (entry === "node_modules" || entry === ".next") continue;
130
+ const param = parseDynamicSegment(entry);
131
+ const urlPart = segmentToUrlPart(entry);
132
+ const newParams = param ? [...params, param] : params;
133
+ const nested = await scanPagesDir(entryPath, [...urlSegments, urlPart], newParams);
134
+ routes.push(...nested);
135
+ }
136
+ }
137
+ return routes;
138
+ }
139
+ function parseReactRouterParam(segment) {
140
+ if (!segment.startsWith(":")) return null;
141
+ return { name: segment.slice(1), isCatchAll: false, isOptional: false };
142
+ }
143
+ async function findFiles(dir, extensions) {
144
+ const results = [];
145
+ let entries;
146
+ try {
147
+ entries = await readdir(dir);
148
+ } catch {
149
+ return results;
150
+ }
151
+ for (const entry of entries) {
152
+ const full = join(dir, entry);
153
+ const s = await stat(full).catch(() => null);
154
+ if (!s) continue;
155
+ if (s.isDirectory()) {
156
+ if (entry === "node_modules" || entry === ".git" || entry === "dist" || entry === "build") continue;
157
+ results.push(...await findFiles(full, extensions));
158
+ } else if (extensions.has(extname(entry))) {
159
+ results.push(full);
160
+ }
161
+ }
162
+ return results;
163
+ }
164
+ async function hasReactRouter(projectRoot) {
165
+ try {
166
+ const pkg = JSON.parse(await readFile(join(projectRoot, "package.json"), "utf-8"));
167
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
168
+ return "react-router-dom" in deps || "react-router" in deps;
169
+ } catch {
170
+ return false;
171
+ }
172
+ }
173
+ function extractRoutePathsFromSource(source) {
174
+ const paths = [];
175
+ const seen = /* @__PURE__ */ new Set();
176
+ const jsxPattern = /<Route\s[^>]*?path\s*=\s*["']([^"']+)["']/g;
177
+ let match;
178
+ while ((match = jsxPattern.exec(source)) !== null) {
179
+ const p = match[1];
180
+ if (p !== "*" && !seen.has(p)) {
181
+ seen.add(p);
182
+ paths.push(p);
183
+ }
184
+ }
185
+ const objPattern = /path\s*:\s*["']([^"']+)["']/g;
186
+ while ((match = objPattern.exec(source)) !== null) {
187
+ const p = match[1];
188
+ if (p !== "*" && !seen.has(p)) {
189
+ seen.add(p);
190
+ paths.push(p);
191
+ }
192
+ }
193
+ return paths;
194
+ }
195
+ async function scanReactRouter(projectRoot) {
196
+ const routes = [];
197
+ const seen = /* @__PURE__ */ new Set();
198
+ const srcDir = join(projectRoot, "src");
199
+ const scanRoot = await stat(srcDir).then(() => srcDir).catch(() => projectRoot);
200
+ const files = await findFiles(scanRoot, PAGE_EXTENSIONS);
201
+ for (const filePath of files) {
202
+ let source;
203
+ try {
204
+ source = await readFile(filePath, "utf-8");
205
+ } catch {
206
+ continue;
207
+ }
208
+ if (!source.includes("react-router-dom") && !source.includes("react-router")) {
209
+ continue;
210
+ }
211
+ if (!source.includes("Route") && !source.includes("createBrowserRouter") && !source.includes("createRoutesFromElements") && !source.match(/path\s*:\s*["']/)) {
212
+ continue;
213
+ }
214
+ const extractedPaths = extractRoutePathsFromSource(source);
215
+ for (const routePath of extractedPaths) {
216
+ if (seen.has(routePath)) continue;
217
+ seen.add(routePath);
218
+ const segments = routePath.split("/").filter(Boolean);
219
+ const params = [];
220
+ for (const seg of segments) {
221
+ const param = parseReactRouterParam(seg);
222
+ if (param) params.push(param);
223
+ }
224
+ const urlPath = routePath.startsWith("/") ? routePath : "/" + routePath;
225
+ routes.push({
226
+ urlPath,
227
+ filePath,
228
+ params,
229
+ isDynamic: params.length > 0
230
+ });
231
+ }
232
+ }
233
+ return routes;
234
+ }
85
235
  async function scanRoutes(options) {
86
- const { appDir } = options;
236
+ const { appDir, routerType = "auto" } = options;
237
+ if (routerType === "react-router") {
238
+ const routes2 = await scanReactRouter(appDir);
239
+ routes2.sort((a, b) => a.urlPath.localeCompare(b.urlPath));
240
+ return routes2;
241
+ }
242
+ if (routerType === "auto") {
243
+ const isReactRouter = await hasReactRouter(appDir);
244
+ if (isReactRouter) {
245
+ const routes2 = await scanReactRouter(appDir);
246
+ routes2.sort((a, b) => a.urlPath.localeCompare(b.urlPath));
247
+ return routes2;
248
+ }
249
+ }
87
250
  const dirStat = await stat(appDir).catch(() => null);
88
251
  if (!dirStat || !dirStat.isDirectory()) {
89
252
  throw new Error(`App directory not found: ${appDir}`);
90
253
  }
91
- const routes = await scanDir(appDir, [], []);
254
+ const dirName = appDir.replace(/\/$/, "").split("/").pop();
255
+ const isPagesRouter = routerType === "pages-router" || routerType === "auto" && dirName === "pages";
256
+ const routes = isPagesRouter ? await scanPagesDir(appDir, [], []) : await scanDir(appDir, [], []);
92
257
  routes.sort((a, b) => a.urlPath.localeCompare(b.urlPath));
93
258
  return routes;
94
259
  }
95
260
 
96
261
  // ../core/src/analysis/code-analyzer.ts
97
- import { readFile as readFile2 } from "fs/promises";
262
+ import { readFile as readFile3 } from "fs/promises";
98
263
  import { join as join3, dirname, resolve } from "path";
99
264
  import { existsSync as existsSync2 } from "fs";
100
265
 
101
266
  // ../core/src/analysis/auth-detector.ts
102
- import { readFile } from "fs/promises";
267
+ import { readFile as readFile2 } from "fs/promises";
103
268
  import { join as join2 } from "path";
104
269
  import { existsSync } from "fs";
105
270
  var MIDDLEWARE_FILES = ["middleware.ts", "middleware.js", "middleware.tsx", "middleware.jsx"];
@@ -147,7 +312,7 @@ async function detectAuth(projectRoot, allSources) {
147
312
  const middlewarePath = join2(projectRoot, filename);
148
313
  if (existsSync(middlewarePath)) {
149
314
  try {
150
- const source = await readFile(middlewarePath, "utf-8");
315
+ const source = await readFile2(middlewarePath, "utf-8");
151
316
  const cookies = extractCookieNames(source);
152
317
  if (cookies.length > 0) {
153
318
  config.hasAuth = true;
@@ -198,7 +363,7 @@ async function readPathAliases(projectRoot) {
198
363
  const configPath = join3(projectRoot, filename);
199
364
  if (!existsSync2(configPath)) continue;
200
365
  try {
201
- const content = await readFile2(configPath, "utf-8");
366
+ const content = await readFile3(configPath, "utf-8");
202
367
  const stripped = content.replace(
203
368
  /"(?:[^"\\]|\\.)*"|\/\/.*$|\/\*[\s\S]*?\*\//gm,
204
369
  (match) => match.startsWith('"') ? match : ""
@@ -242,7 +407,7 @@ async function traceImports(filePath, projectRoot, aliases, maxDepth, visited =
242
407
  visited.add(filePath);
243
408
  let source;
244
409
  try {
245
- source = await readFile2(filePath, "utf-8");
410
+ source = await readFile3(filePath, "utf-8");
246
411
  } catch {
247
412
  return results;
248
413
  }
@@ -529,8 +694,8 @@ async function generateMocks(analysis, options) {
529
694
 
530
695
  // ../core/src/render/pre-renderer.ts
531
696
  import { chromium } from "playwright";
532
- import { mkdir, writeFile } from "fs/promises";
533
- import { join as join5 } from "path";
697
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
698
+ import { join as join6 } from "path";
534
699
 
535
700
  // ../core/src/render/dev-server.ts
536
701
  import { spawn } from "child_process";
@@ -633,6 +798,173 @@ ${stderr.slice(-500)}`));
633
798
  };
634
799
  }
635
800
 
801
+ // ../core/src/render/style-inliner.ts
802
+ async function inlineStylesAndCleanup(page) {
803
+ await page.evaluate(`(() => {
804
+ const links = document.querySelectorAll('link[rel="stylesheet"]');
805
+ links.forEach(link => {
806
+ try {
807
+ const href = link.getAttribute('href');
808
+ if (!href) return;
809
+ for (const sheet of document.styleSheets) {
810
+ if (sheet.href && sheet.href.includes(href.replace(/^\\//, ''))) {
811
+ const rules = Array.from(sheet.cssRules).map(r => r.cssText).join('\\n');
812
+ const style = document.createElement('style');
813
+ style.textContent = rules;
814
+ link.parentNode.replaceChild(style, link);
815
+ break;
816
+ }
817
+ }
818
+ } catch (e) {}
819
+ });
820
+ document.querySelectorAll('script').forEach(s => s.remove());
821
+ })()`);
822
+ }
823
+
824
+ // ../core/src/render/interaction-capturer.ts
825
+ import { mkdir, writeFile } from "fs/promises";
826
+ import { join as join5 } from "path";
827
+ function slugifyRoute(urlPath) {
828
+ if (urlPath === "/") return "index";
829
+ return urlPath.replace(/^\//, "").replace(/\//g, "-").replace(/:/g, "_").replace(/[^a-zA-Z0-9_-]/g, "_");
830
+ }
831
+ async function findClickableElements(page, maxElements) {
832
+ return page.evaluate(`((max) => {
833
+ const selectors = [
834
+ '[role="tab"]:not([aria-selected="true"]):not([aria-disabled="true"])',
835
+ '[role="button"]:not([aria-disabled="true"]):not([disabled])',
836
+ 'button:not([disabled]):not([type="submit"])',
837
+ '[data-tab]:not(.active):not(.selected)',
838
+ '.tab:not(.active):not(.selected)',
839
+ 'a[href="#"]:not(.active)',
840
+ 'a[href^="#"]:not([href="#"]):not(.active)',
841
+ ];
842
+
843
+ const seen = new Set();
844
+ const results = [];
845
+
846
+ for (const sel of selectors) {
847
+ if (results.length >= max) break;
848
+ const elements = document.querySelectorAll(sel);
849
+
850
+ for (const el of elements) {
851
+ if (results.length >= max) break;
852
+ if (seen.has(el)) continue;
853
+ seen.add(el);
854
+
855
+ const rect = el.getBoundingClientRect();
856
+ if (rect.width === 0 || rect.height === 0) continue;
857
+ const style = window.getComputedStyle(el);
858
+ if (style.display === 'none' || style.visibility === 'hidden') continue;
859
+
860
+ if (el.tagName === 'A') {
861
+ const href = el.getAttribute('href') || '';
862
+ if (href.startsWith('http') || href.startsWith('//')) continue;
863
+ }
864
+
865
+ if (el.tagName === 'BUTTON' && el.type === 'submit') continue;
866
+
867
+ const text = (el.textContent || '').trim().slice(0, 50);
868
+ const ariaLabel = el.getAttribute('aria-label');
869
+ const role = el.getAttribute('role');
870
+ const tag = el.tagName.toLowerCase();
871
+
872
+ let desc = '';
873
+ if (role === 'tab') desc = 'Tab: ' + (ariaLabel || text || 'unnamed');
874
+ else if (role === 'button') desc = 'Button: ' + (ariaLabel || text || 'unnamed');
875
+ else if (tag === 'button') desc = 'Button: ' + (ariaLabel || text || 'unnamed');
876
+ else desc = 'Clickable: ' + (ariaLabel || text || 'unnamed');
877
+
878
+ let uniqueSelector = '';
879
+ const id = el.getAttribute('id');
880
+ if (id) {
881
+ uniqueSelector = '#' + CSS.escape(id);
882
+ } else {
883
+ const dataTestId = el.getAttribute('data-testid');
884
+ if (dataTestId) {
885
+ uniqueSelector = '[data-testid="' + CSS.escape(dataTestId) + '"]';
886
+ } else {
887
+ const allMatching = document.querySelectorAll(sel);
888
+ const idx = Array.from(allMatching).indexOf(el);
889
+ uniqueSelector = '__INDEX__' + sel + '__' + idx;
890
+ }
891
+ }
892
+
893
+ results.push({ selector: uniqueSelector, description: desc });
894
+ }
895
+ }
896
+
897
+ return results;
898
+ })(${maxElements})`);
899
+ }
900
+ async function captureInteractions(page, pageUrl, route, stateName, outputDir, options) {
901
+ const maxInteractions = options?.maxInteractions ?? 5;
902
+ const settleTime = options?.settleTime ?? 500;
903
+ const routeSlug = slugifyRoute(route.urlPath);
904
+ const stateDir = join5(outputDir, "renders", routeSlug);
905
+ await mkdir(stateDir, { recursive: true });
906
+ const clickables = await findClickableElements(page, maxInteractions);
907
+ if (clickables.length === 0) return [];
908
+ const results = [];
909
+ for (let i = 0; i < clickables.length; i++) {
910
+ const clickable = clickables[i];
911
+ const htmlRelPath = join5("renders", routeSlug, `${stateName}_interaction_${i}.html`);
912
+ const htmlAbsPath = join5(outputDir, htmlRelPath);
913
+ try {
914
+ let clicked = false;
915
+ if (clickable.selector.startsWith("__INDEX__")) {
916
+ const parts = clickable.selector.slice("__INDEX__".length);
917
+ const lastUnderscoreIdx = parts.lastIndexOf("__");
918
+ const sel = parts.slice(0, lastUnderscoreIdx);
919
+ const idx = parseInt(parts.slice(lastUnderscoreIdx + 2), 10);
920
+ const elements = await page.$$(sel);
921
+ if (elements[idx]) {
922
+ await elements[idx].click();
923
+ clicked = true;
924
+ }
925
+ } else {
926
+ const element = await page.$(clickable.selector);
927
+ if (element) {
928
+ await element.click();
929
+ clicked = true;
930
+ }
931
+ }
932
+ if (!clicked) {
933
+ results.push({
934
+ elementDescription: clickable.description,
935
+ htmlPath: htmlRelPath,
936
+ success: false,
937
+ error: "Element not found on re-query"
938
+ });
939
+ continue;
940
+ }
941
+ await page.waitForTimeout(settleTime);
942
+ await inlineStylesAndCleanup(page);
943
+ const html = await page.content();
944
+ await writeFile(htmlAbsPath, html, "utf-8");
945
+ results.push({
946
+ elementDescription: clickable.description,
947
+ htmlPath: htmlRelPath,
948
+ success: true
949
+ });
950
+ } catch (err) {
951
+ results.push({
952
+ elementDescription: clickable.description,
953
+ htmlPath: htmlRelPath,
954
+ success: false,
955
+ error: err instanceof Error ? err.message : String(err)
956
+ });
957
+ }
958
+ try {
959
+ await page.goto(pageUrl, { waitUntil: "networkidle", timeout: 1e4 });
960
+ await page.waitForTimeout(settleTime);
961
+ } catch {
962
+ break;
963
+ }
964
+ }
965
+ return results;
966
+ }
967
+
636
968
  // ../core/src/render/pre-renderer.ts
637
969
  var DEFAULT_CONCURRENCY = 3;
638
970
  var DEFAULT_PAGE_TIMEOUT = 15e3;
@@ -650,7 +982,7 @@ function buildUrlPath(route, mockConfig) {
650
982
  }
651
983
  return path;
652
984
  }
653
- function slugifyRoute(urlPath) {
985
+ function slugifyRoute2(urlPath) {
654
986
  if (urlPath === "/") return "index";
655
987
  return urlPath.replace(/^\//, "").replace(/\//g, "-").replace(/:/g, "_").replace(/[^a-zA-Z0-9_-]/g, "_");
656
988
  }
@@ -707,14 +1039,14 @@ async function setupMockInterception(page, devServerUrl, mockConfig, authConfig)
707
1039
  }
708
1040
  async function renderPage(context, devServerUrl, task, outputDir, options, viewport) {
709
1041
  const { route, mockConfig, authConfig } = task;
710
- const routeSlug = slugifyRoute(route.urlPath);
711
- const stateDir = join5(outputDir, "renders", routeSlug);
712
- await mkdir(stateDir, { recursive: true });
1042
+ const routeSlug = slugifyRoute2(route.urlPath);
1043
+ const stateDir = join6(outputDir, "renders", routeSlug);
1044
+ await mkdir2(stateDir, { recursive: true });
713
1045
  const filePrefix = viewport ? `${mockConfig.stateName}_${viewport.name}` : mockConfig.stateName;
714
- const htmlRelPath = join5("renders", routeSlug, `${filePrefix}.html`);
715
- const pngRelPath = join5("renders", routeSlug, `${filePrefix}.png`);
716
- const htmlAbsPath = join5(outputDir, htmlRelPath);
717
- const pngAbsPath = join5(outputDir, pngRelPath);
1046
+ const htmlRelPath = join6("renders", routeSlug, `${filePrefix}.html`);
1047
+ const pngRelPath = join6("renders", routeSlug, `${filePrefix}.png`);
1048
+ const htmlAbsPath = join6(outputDir, htmlRelPath);
1049
+ const pngAbsPath = join6(outputDir, pngRelPath);
718
1050
  const page = await context.newPage();
719
1051
  if (viewport) {
720
1052
  await page.setViewportSize({ width: viewport.width, height: viewport.height });
@@ -728,42 +1060,47 @@ async function renderPage(context, devServerUrl, task, outputDir, options, viewp
728
1060
  timeout: options.pageTimeout
729
1061
  });
730
1062
  await page.waitForTimeout(options.settleTime);
731
- await page.evaluate(`(() => {
732
- const links = document.querySelectorAll('link[rel="stylesheet"]');
733
- links.forEach(link => {
734
- try {
735
- const href = link.getAttribute('href');
736
- if (!href) return;
737
- for (const sheet of document.styleSheets) {
738
- if (sheet.href && sheet.href.includes(href.replace(/^\\//, ''))) {
739
- const rules = Array.from(sheet.cssRules).map(r => r.cssText).join('\\n');
740
- const style = document.createElement('style');
741
- style.textContent = rules;
742
- link.parentNode.replaceChild(style, link);
743
- break;
744
- }
745
- }
746
- } catch (e) {}
747
- });
748
- document.querySelectorAll('script').forEach(s => s.remove());
749
- })()`);
1063
+ await inlineStylesAndCleanup(page);
750
1064
  const html = await page.content();
751
- await writeFile(htmlAbsPath, html, "utf-8");
1065
+ await writeFile2(htmlAbsPath, html, "utf-8");
752
1066
  await page.screenshot({ path: pngAbsPath, fullPage: true });
1067
+ const interactionStates = ["success", "empty"];
1068
+ const shouldCapture = options.captureInteractionStates !== false && interactionStates.includes(mockConfig.stateName);
1069
+ let interactions;
1070
+ if (shouldCapture) {
1071
+ await page.goto(fullUrl, { waitUntil: "networkidle", timeout: options.pageTimeout });
1072
+ await page.waitForTimeout(options.settleTime);
1073
+ const interactionResults = await captureInteractions(
1074
+ page,
1075
+ fullUrl,
1076
+ route,
1077
+ mockConfig.stateName,
1078
+ outputDir,
1079
+ { maxInteractions: options.maxInteractions, settleTime: 500 }
1080
+ );
1081
+ const successful = interactionResults.filter((r) => r.success);
1082
+ if (successful.length > 0) {
1083
+ interactions = successful.map((r) => ({
1084
+ description: r.elementDescription,
1085
+ htmlPath: r.htmlPath
1086
+ }));
1087
+ }
1088
+ }
753
1089
  return {
754
1090
  route,
755
1091
  stateName: mockConfig.stateName,
756
1092
  htmlPath: htmlRelPath,
757
1093
  screenshotPath: pngRelPath,
758
1094
  success: true,
759
- viewportName: viewport?.name
1095
+ viewportName: viewport?.name,
1096
+ interactions
760
1097
  };
761
1098
  } catch (err) {
762
1099
  const errorMsg = err instanceof Error ? err.message : String(err);
763
1100
  const errorHtml = `<!DOCTYPE html><html><body style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif;color:#666;">
764
1101
  <div style="text-align:center"><h2>Render Failed</h2><p>${route.urlPath} [${mockConfig.stateName}]</p><pre style="color:#c00">${errorMsg}</pre></div>
765
1102
  </body></html>`;
766
- await writeFile(htmlAbsPath, errorHtml, "utf-8").catch(() => {
1103
+ await writeFile2(htmlAbsPath, errorHtml, "utf-8").catch(() => {
767
1104
  });
768
1105
  return {
769
1106
  route,
@@ -820,7 +1157,8 @@ function buildManifest(results, projectName, viewports) {
820
1157
  status: result.success ? "ok" : "error",
821
1158
  error: result.error,
822
1159
  viewport: result.viewportName,
823
- viewportWidth: result.viewportName ? viewportWidthMap.get(result.viewportName) : void 0
1160
+ viewportWidth: result.viewportName ? viewportWidthMap.get(result.viewportName) : void 0,
1161
+ interactions: result.interactions
824
1162
  });
825
1163
  }
826
1164
  return {
@@ -832,16 +1170,18 @@ function buildManifest(results, projectName, viewports) {
832
1170
  async function preRenderPages(tasks, options) {
833
1171
  const {
834
1172
  projectRoot,
835
- outputDir = join5(projectRoot, ".c2d"),
1173
+ outputDir = join6(projectRoot, ".c2d"),
836
1174
  concurrency = DEFAULT_CONCURRENCY,
837
1175
  pageTimeout = DEFAULT_PAGE_TIMEOUT,
838
1176
  settleTime = DEFAULT_SETTLE_TIME,
839
1177
  viewportWidth = DEFAULT_VIEWPORT.width,
840
- viewportHeight = DEFAULT_VIEWPORT.height
1178
+ viewportHeight = DEFAULT_VIEWPORT.height,
1179
+ captureInteractions: captureInteractionStates = true,
1180
+ maxInteractions
841
1181
  } = options;
842
1182
  const viewports = options.viewports;
843
1183
  const useMultiViewport = viewports && viewports.length > 0;
844
- await mkdir(outputDir, { recursive: true });
1184
+ await mkdir2(outputDir, { recursive: true });
845
1185
  let devServer = null;
846
1186
  try {
847
1187
  devServer = await startDevServer(projectRoot, {
@@ -864,20 +1204,20 @@ async function preRenderPages(tasks, options) {
864
1204
  results = await processWithConcurrency(
865
1205
  expandedItems,
866
1206
  concurrency,
867
- ({ task, viewport: vp }) => renderPage(context, devServer.url, task, outputDir, { pageTimeout, settleTime }, vp),
1207
+ ({ task, viewport: vp }) => renderPage(context, devServer.url, task, outputDir, { pageTimeout, settleTime, captureInteractionStates, maxInteractions }, vp),
868
1208
  options.onProgress
869
1209
  );
870
1210
  } else {
871
1211
  results = await processWithConcurrency(
872
1212
  tasks,
873
1213
  concurrency,
874
- (task) => renderPage(context, devServer.url, task, outputDir, { pageTimeout, settleTime }),
1214
+ (task) => renderPage(context, devServer.url, task, outputDir, { pageTimeout, settleTime, captureInteractionStates, maxInteractions }),
875
1215
  options.onProgress
876
1216
  );
877
1217
  }
878
1218
  let projectName = "unknown";
879
1219
  try {
880
- const pkg = await import(join5(projectRoot, "package.json"), { with: { type: "json" } });
1220
+ const pkg = await import(join6(projectRoot, "package.json"), { with: { type: "json" } });
881
1221
  projectName = pkg.default?.name || "unknown";
882
1222
  } catch {
883
1223
  }
@@ -886,8 +1226,8 @@ async function preRenderPages(tasks, options) {
886
1226
  projectName,
887
1227
  useMultiViewport ? viewports : void 0
888
1228
  );
889
- await writeFile(
890
- join5(outputDir, "manifest.json"),
1229
+ await writeFile2(
1230
+ join6(outputDir, "manifest.json"),
891
1231
  JSON.stringify(manifest, null, 2),
892
1232
  "utf-8"
893
1233
  );
@@ -905,13 +1245,14 @@ async function preRenderPages(tasks, options) {
905
1245
 
906
1246
  // src/server/canvas-server.ts
907
1247
  import { createServer as createServer2 } from "http";
908
- import { join as join7 } from "path";
1248
+ import { join as join8 } from "path";
1249
+ import { existsSync as existsSync5 } from "fs";
909
1250
  import sirv from "sirv";
910
1251
 
911
1252
  // src/server/api-routes.ts
912
- import { readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1253
+ import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
913
1254
  import { existsSync as existsSync4 } from "fs";
914
- import { join as join6, dirname as dirname2 } from "path";
1255
+ import { join as join7, dirname as dirname2 } from "path";
915
1256
  import { randomUUID } from "crypto";
916
1257
  async function handleApiRequest(req, res, c2dDir) {
917
1258
  const url = req.url ?? "/";
@@ -944,12 +1285,12 @@ async function handleApiRequest(req, res, c2dDir) {
944
1285
  return false;
945
1286
  }
946
1287
  async function handleGetManifest(res, c2dDir) {
947
- const manifestPath = join6(c2dDir, "manifest.json");
1288
+ const manifestPath = join7(c2dDir, "manifest.json");
948
1289
  if (!existsSync4(manifestPath)) {
949
1290
  sendJson(res, 404, { error: "manifest.json not found" });
950
1291
  return;
951
1292
  }
952
- const data = await readFile3(manifestPath, "utf-8");
1293
+ const data = await readFile4(manifestPath, "utf-8");
953
1294
  sendRawJson(res, 200, data);
954
1295
  }
955
1296
  async function handleGetComments(res, c2dDir) {
@@ -1014,11 +1355,11 @@ async function handlePostDrawings(req, res, c2dDir) {
1014
1355
  sendJson(res, 200, { ok: true });
1015
1356
  }
1016
1357
  async function loadDrawings(c2dDir) {
1017
- const drawingsPath = join6(c2dDir, "drawings.json");
1358
+ const drawingsPath = join7(c2dDir, "drawings.json");
1018
1359
  if (!existsSync4(drawingsPath)) {
1019
1360
  return [];
1020
1361
  }
1021
- const data = await readFile3(drawingsPath, "utf-8");
1362
+ const data = await readFile4(drawingsPath, "utf-8");
1022
1363
  try {
1023
1364
  const parsed = JSON.parse(data);
1024
1365
  return Array.isArray(parsed) ? parsed : [];
@@ -1027,12 +1368,12 @@ async function loadDrawings(c2dDir) {
1027
1368
  }
1028
1369
  }
1029
1370
  async function saveDrawings(c2dDir, drawings) {
1030
- const drawingsPath = join6(c2dDir, "drawings.json");
1371
+ const drawingsPath = join7(c2dDir, "drawings.json");
1031
1372
  const dir = dirname2(drawingsPath);
1032
1373
  if (!existsSync4(dir)) {
1033
- await mkdir2(dir, { recursive: true });
1374
+ await mkdir3(dir, { recursive: true });
1034
1375
  }
1035
- await writeFile2(drawingsPath, JSON.stringify(drawings, null, 2), "utf-8");
1376
+ await writeFile3(drawingsPath, JSON.stringify(drawings, null, 2), "utf-8");
1036
1377
  }
1037
1378
  function isValidCommentInput(value) {
1038
1379
  if (typeof value !== "object" || value === null) return false;
@@ -1040,11 +1381,11 @@ function isValidCommentInput(value) {
1040
1381
  return typeof obj.x === "number" && typeof obj.y === "number" && typeof obj.text === "string" && typeof obj.author === "string";
1041
1382
  }
1042
1383
  async function loadComments(c2dDir) {
1043
- const commentsPath = join6(c2dDir, "comments.json");
1384
+ const commentsPath = join7(c2dDir, "comments.json");
1044
1385
  if (!existsSync4(commentsPath)) {
1045
1386
  return [];
1046
1387
  }
1047
- const data = await readFile3(commentsPath, "utf-8");
1388
+ const data = await readFile4(commentsPath, "utf-8");
1048
1389
  try {
1049
1390
  const parsed = JSON.parse(data);
1050
1391
  return Array.isArray(parsed) ? parsed : [];
@@ -1053,12 +1394,12 @@ async function loadComments(c2dDir) {
1053
1394
  }
1054
1395
  }
1055
1396
  async function saveComments(c2dDir, comments) {
1056
- const commentsPath = join6(c2dDir, "comments.json");
1397
+ const commentsPath = join7(c2dDir, "comments.json");
1057
1398
  const dir = dirname2(commentsPath);
1058
1399
  if (!existsSync4(dir)) {
1059
- await mkdir2(dir, { recursive: true });
1400
+ await mkdir3(dir, { recursive: true });
1060
1401
  }
1061
- await writeFile2(commentsPath, JSON.stringify(comments, null, 2), "utf-8");
1402
+ await writeFile3(commentsPath, JSON.stringify(comments, null, 2), "utf-8");
1062
1403
  }
1063
1404
  function readRequestBody(req) {
1064
1405
  return new Promise((resolve2, reject) => {
@@ -1106,10 +1447,12 @@ function tryListen(requestHandler, port) {
1106
1447
  });
1107
1448
  }
1108
1449
  async function startCanvasServer(options) {
1109
- const { port = 4800, canvasDir, c2dDir } = options;
1450
+ const { port = 4800, canvasDir, c2dDir, projectRoot } = options;
1110
1451
  const canvasHandler = sirv(canvasDir, { single: true, dev: true });
1111
- const rendersDir = join7(c2dDir, "renders");
1452
+ const rendersDir = join8(c2dDir, "renders");
1112
1453
  const rendersHandler = sirv(rendersDir, { dev: true });
1454
+ const publicDir = projectRoot ? join8(projectRoot, "public") : null;
1455
+ const publicHandler = publicDir && existsSync5(publicDir) ? sirv(publicDir, { dev: true }) : null;
1113
1456
  const requestHandler = async (req, res) => {
1114
1457
  const url = req.url ?? "/";
1115
1458
  const method = req.method ?? "GET";
@@ -1143,6 +1486,15 @@ async function startCanvasServer(options) {
1143
1486
  });
1144
1487
  return;
1145
1488
  }
1489
+ if (publicHandler) {
1490
+ publicHandler(req, res, () => {
1491
+ canvasHandler(req, res, () => {
1492
+ res.writeHead(404);
1493
+ res.end("Not found");
1494
+ });
1495
+ });
1496
+ return;
1497
+ }
1146
1498
  canvasHandler(req, res, () => {
1147
1499
  res.writeHead(404);
1148
1500
  res.end("Not found");
@@ -1163,14 +1515,14 @@ async function startCanvasServer(options) {
1163
1515
  }
1164
1516
 
1165
1517
  // src/config.ts
1166
- import { existsSync as existsSync5 } from "fs";
1167
- import { readFile as readFile4 } from "fs/promises";
1168
- import { join as join8 } from "path";
1518
+ import { existsSync as existsSync6 } from "fs";
1519
+ import { readFile as readFile5 } from "fs/promises";
1520
+ import { join as join9 } from "path";
1169
1521
  var DEFAULT_PORT = 4800;
1170
1522
  async function loadConfig(projectRoot) {
1171
1523
  let fileConfig = {};
1172
- const configPath = join8(projectRoot, "c2d.config.js");
1173
- if (existsSync5(configPath)) {
1524
+ const configPath = join9(projectRoot, "c2d.config.js");
1525
+ if (existsSync6(configPath)) {
1174
1526
  try {
1175
1527
  const mod = await import(configPath);
1176
1528
  fileConfig = mod.default || mod;
@@ -1186,24 +1538,51 @@ async function loadConfig(projectRoot) {
1186
1538
  devServerCommand: fileConfig.devServerCommand
1187
1539
  };
1188
1540
  }
1189
- async function detectNextJsProject(projectRoot) {
1190
- const hasNextConfig = existsSync5(join8(projectRoot, "next.config.ts")) || existsSync5(join8(projectRoot, "next.config.js")) || existsSync5(join8(projectRoot, "next.config.mjs"));
1191
- let appDir = join8(projectRoot, "app");
1192
- let hasAppDir = existsSync5(appDir);
1193
- if (!hasAppDir) {
1194
- appDir = join8(projectRoot, "src", "app");
1195
- hasAppDir = existsSync5(appDir);
1196
- }
1541
+ async function detectProject(projectRoot) {
1197
1542
  let projectName = "unknown";
1198
1543
  try {
1199
- const pkg = JSON.parse(await readFile4(join8(projectRoot, "package.json"), "utf-8"));
1544
+ const pkg = JSON.parse(await readFile5(join9(projectRoot, "package.json"), "utf-8"));
1200
1545
  projectName = pkg.name || "unknown";
1201
1546
  } catch {
1202
1547
  }
1548
+ const hasNextConfig = existsSync6(join9(projectRoot, "next.config.ts")) || existsSync6(join9(projectRoot, "next.config.js")) || existsSync6(join9(projectRoot, "next.config.mjs"));
1549
+ if (hasNextConfig) {
1550
+ let appDir = join9(projectRoot, "app");
1551
+ let hasAppDir = existsSync6(appDir);
1552
+ if (!hasAppDir) {
1553
+ appDir = join9(projectRoot, "src", "app");
1554
+ hasAppDir = existsSync6(appDir);
1555
+ }
1556
+ if (hasAppDir) {
1557
+ return { isSupported: true, projectType: "nextjs-app", appDir, projectRoot, projectName };
1558
+ }
1559
+ appDir = join9(projectRoot, "pages");
1560
+ hasAppDir = existsSync6(appDir);
1561
+ if (!hasAppDir) {
1562
+ appDir = join9(projectRoot, "src", "pages");
1563
+ hasAppDir = existsSync6(appDir);
1564
+ }
1565
+ if (hasAppDir) {
1566
+ return { isSupported: true, projectType: "nextjs-pages", appDir, projectRoot, projectName };
1567
+ }
1568
+ return { isSupported: false, projectType: "unknown", appDir: null, projectRoot, projectName };
1569
+ }
1570
+ try {
1571
+ const pkg = JSON.parse(await readFile5(join9(projectRoot, "package.json"), "utf-8"));
1572
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1573
+ if ("react-router-dom" in deps || "react-router" in deps) {
1574
+ return { isSupported: true, projectType: "react-router", appDir: null, projectRoot, projectName };
1575
+ }
1576
+ } catch {
1577
+ }
1578
+ return { isSupported: false, projectType: "unknown", appDir: null, projectRoot, projectName };
1579
+ }
1580
+ async function detectNextJsProject(projectRoot) {
1581
+ const result = await detectProject(projectRoot);
1203
1582
  return {
1204
- isNextJs: hasNextConfig,
1205
- appDir: hasAppDir ? appDir : null,
1206
- projectName
1583
+ isNextJs: result.projectType === "nextjs-app" || result.projectType === "nextjs-pages",
1584
+ appDir: result.appDir,
1585
+ projectName: result.projectName
1207
1586
  };
1208
1587
  }
1209
1588
 
@@ -1250,30 +1629,34 @@ async function runScan(options) {
1250
1629
  banner();
1251
1630
  const config = await loadConfig(projectRoot);
1252
1631
  header("Detecting project...");
1253
- const project = await detectNextJsProject(projectRoot);
1254
- if (!project.isNextJs) {
1255
- error("Not a Next.js project (no next.config.ts/js/mjs found).");
1256
- log("Code to Design currently supports Next.js App Router projects only.");
1632
+ const project = await detectProject(projectRoot);
1633
+ if (!project.isSupported) {
1634
+ error("Unsupported project type.");
1635
+ log("Code to Design supports Next.js (App Router / Pages Router) and React Router (Vite) projects.");
1257
1636
  process.exit(1);
1258
1637
  }
1259
- if (!project.appDir) {
1260
- error("No app/ directory found. Code to Design requires Next.js App Router.");
1638
+ if (project.projectType !== "react-router" && !project.appDir) {
1639
+ error("No app/ or pages/ directory found.");
1261
1640
  process.exit(1);
1262
1641
  }
1263
- success(`Project: ${project.projectName}`);
1264
- success(`App directory: ${project.appDir}`);
1265
- const c2dDir = join9(projectRoot, ".c2d");
1642
+ success(`Project: ${project.projectName} (${project.projectType})`);
1643
+ if (project.appDir) {
1644
+ success(`App directory: ${project.appDir}`);
1645
+ }
1646
+ const c2dDir = join10(projectRoot, ".c2d");
1266
1647
  if (skipRender) {
1267
- if (!existsSync6(join9(c2dDir, "manifest.json"))) {
1648
+ if (!existsSync7(join10(c2dDir, "manifest.json"))) {
1268
1649
  error("No previous renders found. Run without --skip-render first.");
1269
1650
  process.exit(1);
1270
1651
  }
1271
1652
  log("Skipping render, using existing canvas data...");
1272
- await startServer(c2dDir, config.port, open);
1653
+ await startServer(c2dDir, config.port, open, projectRoot);
1273
1654
  return;
1274
1655
  }
1275
1656
  header("Discovering routes...");
1276
- const routes = await scanRoutes({ appDir: project.appDir });
1657
+ const scanDir2 = project.projectType === "react-router" ? project.projectRoot : project.appDir;
1658
+ const routerType = project.projectType === "react-router" ? "react-router" : project.projectType === "nextjs-pages" ? "pages-router" : "app-router";
1659
+ const routes = await scanRoutes({ appDir: scanDir2, routerType });
1277
1660
  if (routes.length === 0) {
1278
1661
  error("No routes found in app/ directory.");
1279
1662
  process.exit(1);
@@ -1316,10 +1699,10 @@ async function runScan(options) {
1316
1699
  success("Using fallback mocks (no API key or no API dependencies)");
1317
1700
  }
1318
1701
  header(`Pre-rendering ${renderTasks.length} page states...`);
1319
- if (existsSync6(join9(c2dDir, "renders"))) {
1320
- await rm(join9(c2dDir, "renders"), { recursive: true });
1702
+ if (existsSync7(join10(c2dDir, "renders"))) {
1703
+ await rm(join10(c2dDir, "renders"), { recursive: true });
1321
1704
  }
1322
- await mkdir3(c2dDir, { recursive: true });
1705
+ await mkdir4(c2dDir, { recursive: true });
1323
1706
  const { results, manifest } = await preRenderPages(renderTasks, {
1324
1707
  projectRoot,
1325
1708
  outputDir: c2dDir,
@@ -1342,8 +1725,9 @@ async function runScan(options) {
1342
1725
  }
1343
1726
  }
1344
1727
  if (watch) {
1345
- const server = await startServerNonBlocking(c2dDir, config.port, open);
1346
- watchAndRerender(projectRoot, project.appDir, c2dDir, config);
1728
+ const server = await startServerNonBlocking(c2dDir, config.port, open, projectRoot);
1729
+ const watchDir = project.projectType === "react-router" ? join10(projectRoot, "src") : project.appDir;
1730
+ watchAndRerender(projectRoot, watchDir, c2dDir, config, routerType);
1347
1731
  await new Promise((resolve2) => {
1348
1732
  const shutdown = async () => {
1349
1733
  log("\nShutting down...");
@@ -1354,10 +1738,10 @@ async function runScan(options) {
1354
1738
  process.on("SIGTERM", shutdown);
1355
1739
  });
1356
1740
  } else {
1357
- await startServer(c2dDir, config.port, open);
1741
+ await startServer(c2dDir, config.port, open, projectRoot);
1358
1742
  }
1359
1743
  }
1360
- async function startServer(c2dDir, port, open) {
1744
+ async function startServer(c2dDir, port, open, projectRoot) {
1361
1745
  header("Starting canvas server...");
1362
1746
  const canvasDir = await resolveCanvasDir(c2dDir);
1363
1747
  if (!canvasDir) {
@@ -1366,7 +1750,8 @@ async function startServer(c2dDir, port, open) {
1366
1750
  const server = await startCanvasServer({
1367
1751
  port,
1368
1752
  canvasDir,
1369
- c2dDir
1753
+ c2dDir,
1754
+ projectRoot
1370
1755
  });
1371
1756
  success(`Canvas server running at ${server.url}`);
1372
1757
  log("Share this URL with your team for collaborative review");
@@ -1388,7 +1773,7 @@ async function startServer(c2dDir, port, open) {
1388
1773
  process.on("SIGTERM", shutdown);
1389
1774
  });
1390
1775
  }
1391
- async function startServerNonBlocking(c2dDir, port, open) {
1776
+ async function startServerNonBlocking(c2dDir, port, open, projectRoot) {
1392
1777
  header("Starting canvas server...");
1393
1778
  const canvasDir = await resolveCanvasDir(c2dDir);
1394
1779
  if (!canvasDir) {
@@ -1397,7 +1782,8 @@ async function startServerNonBlocking(c2dDir, port, open) {
1397
1782
  const server = await startCanvasServer({
1398
1783
  port,
1399
1784
  canvasDir,
1400
- c2dDir
1785
+ c2dDir,
1786
+ projectRoot
1401
1787
  });
1402
1788
  success(`Canvas server running at ${server.url}`);
1403
1789
  log("Share this URL with your team for collaborative review");
@@ -1417,20 +1803,20 @@ async function resolveCanvasDir(c2dDir) {
1417
1803
  const __filename = fileURLToPath(import.meta.url);
1418
1804
  let dir = dirname3(__filename);
1419
1805
  for (let i = 0; i < 5; i++) {
1420
- const candidate = join9(dir, "canvas-dist");
1421
- if (existsSync6(candidate) && existsSync6(join9(candidate, "index.html"))) {
1806
+ const candidate = join10(dir, "canvas-dist");
1807
+ if (existsSync7(candidate) && existsSync7(join10(candidate, "index.html"))) {
1422
1808
  return candidate;
1423
1809
  }
1424
1810
  dir = dirname3(dir);
1425
1811
  }
1426
- const monorepoDev = join9(dirname3(__filename), "..", "..", "..", "..", "apps", "canvas", "dist");
1427
- if (existsSync6(monorepoDev) && existsSync6(join9(monorepoDev, "index.html"))) {
1812
+ const monorepoDev = join10(dirname3(__filename), "..", "..", "..", "..", "apps", "canvas", "dist");
1813
+ if (existsSync7(monorepoDev) && existsSync7(join10(monorepoDev, "index.html"))) {
1428
1814
  return monorepoDev;
1429
1815
  }
1430
- const placeholder = join9(c2dDir, "_canvas");
1431
- await mkdir3(placeholder, { recursive: true });
1432
- const { writeFile: writeFile3 } = await import("fs/promises");
1433
- await writeFile3(join9(placeholder, "index.html"), `<!DOCTYPE html><html><body>
1816
+ const placeholder = join10(c2dDir, "_canvas");
1817
+ await mkdir4(placeholder, { recursive: true });
1818
+ const { writeFile: writeFile4 } = await import("fs/promises");
1819
+ await writeFile4(join10(placeholder, "index.html"), `<!DOCTYPE html><html><body>
1434
1820
  <h1>Code to Design</h1>
1435
1821
  <p>Canvas app not built. Run: <code>cd apps/canvas && npx vite build</code></p>
1436
1822
  <p><a href="/api/manifest">View Manifest</a></p>
@@ -1445,7 +1831,7 @@ function shouldIgnoreFile(filename) {
1445
1831
  const ext = filename.slice(filename.lastIndexOf("."));
1446
1832
  return !WATCH_EXTENSIONS.has(ext);
1447
1833
  }
1448
- function watchAndRerender(projectRoot, appDir, c2dDir, config) {
1834
+ function watchAndRerender(projectRoot, appDir, c2dDir, config, routerType) {
1449
1835
  let debounceTimer;
1450
1836
  let isRendering = false;
1451
1837
  log(`Watching ${appDir} for changes...`);
@@ -1458,7 +1844,7 @@ function watchAndRerender(projectRoot, appDir, c2dDir, config) {
1458
1844
  log(`
1459
1845
  File changed: ${filename}. Re-rendering...`);
1460
1846
  try {
1461
- const routes = await scanRoutes({ appDir });
1847
+ const routes = await scanRoutes({ appDir, routerType });
1462
1848
  const filteredRoutes = routes.filter(
1463
1849
  (r) => !config.excludeRoutes.some((pattern) => r.urlPath.includes(pattern))
1464
1850
  );
@@ -1475,10 +1861,10 @@ File changed: ${filename}. Re-rendering...`);
1475
1861
  });
1476
1862
  }
1477
1863
  }
1478
- if (existsSync6(join9(c2dDir, "renders"))) {
1479
- await rm(join9(c2dDir, "renders"), { recursive: true });
1864
+ if (existsSync7(join10(c2dDir, "renders"))) {
1865
+ await rm(join10(c2dDir, "renders"), { recursive: true });
1480
1866
  }
1481
- await mkdir3(c2dDir, { recursive: true });
1867
+ await mkdir4(c2dDir, { recursive: true });
1482
1868
  const { results } = await preRenderPages(renderTasks, {
1483
1869
  projectRoot,
1484
1870
  outputDir: c2dDir,
@@ -1498,7 +1884,8 @@ File changed: ${filename}. Re-rendering...`);
1498
1884
  export {
1499
1885
  startCanvasServer,
1500
1886
  loadConfig,
1887
+ detectProject,
1501
1888
  detectNextJsProject,
1502
1889
  runScan
1503
1890
  };
1504
- //# sourceMappingURL=chunk-BHEMO5RW.js.map
1891
+ //# sourceMappingURL=chunk-HYFKUTC3.js.map