@wtdlee/repomap 0.3.0 → 0.3.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 (71) hide show
  1. package/dist/analyzers/index.d.ts +69 -5
  2. package/dist/analyzers/index.js +1 -5
  3. package/dist/chunk-3PWXDB7B.js +153 -0
  4. package/dist/{generators/page-map-generator.js → chunk-3YFXZAP7.js} +322 -358
  5. package/dist/chunk-6F4PWJZI.js +1 -0
  6. package/dist/{generators/rails-map-generator.js → chunk-E4WRODSI.js} +86 -94
  7. package/dist/chunk-GNBMJMET.js +2519 -0
  8. package/dist/{server/doc-server.js → chunk-M6YNU536.js} +702 -303
  9. package/dist/chunk-OWM6WNLE.js +2610 -0
  10. package/dist/chunk-SSU6QFTX.js +1058 -0
  11. package/dist/cli.d.ts +0 -1
  12. package/dist/cli.js +348 -452
  13. package/dist/dataflow-analyzer-BfAiqVUp.d.ts +180 -0
  14. package/dist/env-detector-EEMVUEIA.js +1 -0
  15. package/dist/generators/index.d.ts +431 -3
  16. package/dist/generators/index.js +2 -3
  17. package/dist/index.d.ts +53 -10
  18. package/dist/index.js +8 -11
  19. package/dist/page-map-generator-6MJGPBVA.js +1 -0
  20. package/dist/rails-UWSDRS33.js +1 -0
  21. package/dist/rails-map-generator-D2URLMVJ.js +2 -0
  22. package/dist/server/index.d.ts +33 -1
  23. package/dist/server/index.js +7 -1
  24. package/dist/types.d.ts +39 -37
  25. package/dist/types.js +1 -5
  26. package/package.json +4 -2
  27. package/dist/analyzers/base-analyzer.d.ts +0 -45
  28. package/dist/analyzers/base-analyzer.js +0 -47
  29. package/dist/analyzers/dataflow-analyzer.d.ts +0 -29
  30. package/dist/analyzers/dataflow-analyzer.js +0 -425
  31. package/dist/analyzers/graphql-analyzer.d.ts +0 -22
  32. package/dist/analyzers/graphql-analyzer.js +0 -386
  33. package/dist/analyzers/pages-analyzer.d.ts +0 -84
  34. package/dist/analyzers/pages-analyzer.js +0 -1695
  35. package/dist/analyzers/rails/index.d.ts +0 -46
  36. package/dist/analyzers/rails/index.js +0 -145
  37. package/dist/analyzers/rails/rails-controller-analyzer.d.ts +0 -82
  38. package/dist/analyzers/rails/rails-controller-analyzer.js +0 -478
  39. package/dist/analyzers/rails/rails-grpc-analyzer.d.ts +0 -44
  40. package/dist/analyzers/rails/rails-grpc-analyzer.js +0 -262
  41. package/dist/analyzers/rails/rails-model-analyzer.d.ts +0 -88
  42. package/dist/analyzers/rails/rails-model-analyzer.js +0 -493
  43. package/dist/analyzers/rails/rails-react-analyzer.d.ts +0 -41
  44. package/dist/analyzers/rails/rails-react-analyzer.js +0 -529
  45. package/dist/analyzers/rails/rails-routes-analyzer.d.ts +0 -62
  46. package/dist/analyzers/rails/rails-routes-analyzer.js +0 -540
  47. package/dist/analyzers/rails/rails-view-analyzer.d.ts +0 -49
  48. package/dist/analyzers/rails/rails-view-analyzer.js +0 -386
  49. package/dist/analyzers/rails/ruby-parser.d.ts +0 -63
  50. package/dist/analyzers/rails/ruby-parser.js +0 -212
  51. package/dist/analyzers/rest-api-analyzer.d.ts +0 -65
  52. package/dist/analyzers/rest-api-analyzer.js +0 -479
  53. package/dist/core/cache.d.ts +0 -47
  54. package/dist/core/cache.js +0 -151
  55. package/dist/core/engine.d.ts +0 -46
  56. package/dist/core/engine.js +0 -319
  57. package/dist/core/index.d.ts +0 -2
  58. package/dist/core/index.js +0 -2
  59. package/dist/generators/markdown-generator.d.ts +0 -25
  60. package/dist/generators/markdown-generator.js +0 -782
  61. package/dist/generators/mermaid-generator.d.ts +0 -35
  62. package/dist/generators/mermaid-generator.js +0 -364
  63. package/dist/generators/page-map-generator.d.ts +0 -22
  64. package/dist/generators/rails-map-generator.d.ts +0 -21
  65. package/dist/server/doc-server.d.ts +0 -30
  66. package/dist/utils/env-detector.d.ts +0 -31
  67. package/dist/utils/env-detector.js +0 -188
  68. package/dist/utils/parallel.d.ts +0 -23
  69. package/dist/utils/parallel.js +0 -70
  70. package/dist/utils/port.d.ts +0 -15
  71. package/dist/utils/port.js +0 -41
@@ -1,142 +1,128 @@
1
- /**
2
- * Interactive page map generator
3
- */
4
- export class PageMapGenerator {
5
- graphqlOps = [];
6
- apiCalls = [];
7
- components = [];
8
- generatePageMapHtml(report, options) {
9
- const allPages = [];
10
- const envResult = options?.envResult;
11
- const railsAnalysis = options?.railsAnalysis;
12
- const activeTab = options?.activeTab || 'pages';
13
- // Get repository name for display
14
- const repoName = report.repositories[0]?.displayName || report.repositories[0]?.name || 'Repository';
15
- for (const repoResult of report.repositories) {
16
- this.graphqlOps.push(...(repoResult.analysis?.graphqlOperations || []));
17
- this.apiCalls.push(...(repoResult.analysis?.apiCalls || []));
18
- // Collect component information
19
- const comps = repoResult.analysis?.components || [];
20
- for (const comp of comps) {
21
- this.components.push({
22
- name: comp.name,
23
- filePath: comp.filePath,
24
- type: comp.type,
25
- dependencies: comp.dependencies || [],
26
- });
27
- }
28
- }
29
- for (const repoResult of report.repositories) {
30
- const pages = repoResult.analysis?.pages || [];
31
- for (const page of pages) {
32
- allPages.push({
33
- ...page,
34
- repo: repoResult.name,
35
- children: [],
36
- parent: null,
37
- depth: 0,
38
- });
39
- }
40
- }
41
- const { rootPages, relations } = this.buildHierarchy(allPages);
42
- return this.renderPageMapHtml(allPages, rootPages, relations, repoName, {
43
- envResult,
44
- railsAnalysis,
45
- activeTab,
1
+ // src/generators/page-map-generator.ts
2
+ var PageMapGenerator = class {
3
+ graphqlOps = [];
4
+ apiCalls = [];
5
+ components = [];
6
+ generatePageMapHtml(report, options) {
7
+ const allPages = [];
8
+ const envResult = options?.envResult;
9
+ const railsAnalysis = options?.railsAnalysis;
10
+ const activeTab = options?.activeTab || "pages";
11
+ const repoName = report.repositories[0]?.displayName || report.repositories[0]?.name || "Repository";
12
+ for (const repoResult of report.repositories) {
13
+ this.graphqlOps.push(...repoResult.analysis?.graphqlOperations || []);
14
+ this.apiCalls.push(...repoResult.analysis?.apiCalls || []);
15
+ const comps = repoResult.analysis?.components || [];
16
+ for (const comp of comps) {
17
+ this.components.push({
18
+ name: comp.name,
19
+ filePath: comp.filePath,
20
+ type: comp.type,
21
+ dependencies: comp.dependencies || []
22
+ });
23
+ }
24
+ }
25
+ for (const repoResult of report.repositories) {
26
+ const pages = repoResult.analysis?.pages || [];
27
+ for (const page of pages) {
28
+ allPages.push({
29
+ ...page,
30
+ repo: repoResult.name,
31
+ children: [],
32
+ parent: null,
33
+ depth: 0
46
34
  });
35
+ }
47
36
  }
48
- buildHierarchy(pages) {
49
- const pathMap = new Map();
50
- const relations = [];
51
- for (const page of pages) {
52
- pathMap.set(page.path, page);
53
- }
54
- for (const page of pages) {
55
- const segments = page.path.split('/').filter(Boolean);
56
- for (let i = segments.length - 1; i >= 1; i--) {
57
- const parentPath = '/' + segments.slice(0, i).join('/');
58
- const parent = pathMap.get(parentPath);
59
- if (parent) {
60
- page.parent = parentPath;
61
- page.depth = parent.depth + 1;
62
- if (!parent.children.includes(page.path)) {
63
- parent.children.push(page.path);
64
- }
65
- relations.push({
66
- from: parentPath,
67
- to: page.path,
68
- type: 'parent-child',
69
- description: `Sub-page of ${parentPath}`,
70
- });
71
- break;
72
- }
73
- }
74
- if (!page.parent) {
75
- // Use segment count for depth when no parent page exists
76
- // This ensures proper indentation based on URL structure
77
- page.depth = Math.max(0, segments.length - 1);
78
- }
79
- if (page.layout) {
80
- for (const other of pages) {
81
- if (other.path !== page.path && other.layout === page.layout) {
82
- const existing = relations.find((r) => r.type === 'same-layout' &&
83
- ((r.from === page.path && r.to === other.path) ||
84
- (r.from === other.path && r.to === page.path)));
85
- if (!existing) {
86
- relations.push({
87
- from: page.path,
88
- to: other.path,
89
- type: 'same-layout',
90
- description: `Both use ${page.layout}`,
91
- });
92
- }
93
- }
94
- }
37
+ const { rootPages, relations } = this.buildHierarchy(allPages);
38
+ return this.renderPageMapHtml(allPages, rootPages, relations, repoName, {
39
+ envResult,
40
+ railsAnalysis,
41
+ activeTab
42
+ });
43
+ }
44
+ buildHierarchy(pages) {
45
+ const pathMap = /* @__PURE__ */ new Map();
46
+ const relations = [];
47
+ for (const page of pages) {
48
+ pathMap.set(page.path, page);
49
+ }
50
+ for (const page of pages) {
51
+ const segments = page.path.split("/").filter(Boolean);
52
+ for (let i = segments.length - 1; i >= 1; i--) {
53
+ const parentPath = "/" + segments.slice(0, i).join("/");
54
+ const parent = pathMap.get(parentPath);
55
+ if (parent) {
56
+ page.parent = parentPath;
57
+ page.depth = parent.depth + 1;
58
+ if (!parent.children.includes(page.path)) {
59
+ parent.children.push(page.path);
60
+ }
61
+ relations.push({
62
+ from: parentPath,
63
+ to: page.path,
64
+ type: "parent-child",
65
+ description: `Sub-page of ${parentPath}`
66
+ });
67
+ break;
68
+ }
69
+ }
70
+ if (!page.parent) {
71
+ page.depth = Math.max(0, segments.length - 1);
72
+ }
73
+ if (page.layout) {
74
+ for (const other of pages) {
75
+ if (other.path !== page.path && other.layout === page.layout) {
76
+ const existing = relations.find(
77
+ (r) => r.type === "same-layout" && (r.from === page.path && r.to === other.path || r.from === other.path && r.to === page.path)
78
+ );
79
+ if (!existing) {
80
+ relations.push({
81
+ from: page.path,
82
+ to: other.path,
83
+ type: "same-layout",
84
+ description: `Both use ${page.layout}`
85
+ });
95
86
  }
87
+ }
96
88
  }
97
- const rootPages = pages.filter((p) => !p.parent).sort((a, b) => a.path.localeCompare(b.path));
98
- return { rootPages, relations };
89
+ }
90
+ }
91
+ const rootPages = pages.filter((p) => !p.parent).sort((a, b) => a.path.localeCompare(b.path));
92
+ return { rootPages, relations };
93
+ }
94
+ renderPageMapHtml(allPages, rootPages, relations, repoName, options) {
95
+ const envResult = options?.envResult;
96
+ const railsAnalysis = options?.railsAnalysis;
97
+ const activeTab = options?.activeTab || "pages";
98
+ const graphqlOpsJson = JSON.stringify(
99
+ this.graphqlOps.map((op) => ({
100
+ name: op.name,
101
+ type: op.type,
102
+ variables: op.variables,
103
+ fields: op.fields,
104
+ returnType: op.returnType,
105
+ usedIn: op.usedIn
106
+ }))
107
+ );
108
+ const componentsJson = JSON.stringify(this.components);
109
+ const railsRoutesJson = railsAnalysis ? JSON.stringify(railsAnalysis.routes.routes) : "[]";
110
+ const railsControllersJson = railsAnalysis ? JSON.stringify(railsAnalysis.controllers.controllers) : "[]";
111
+ const railsModelsJson = railsAnalysis ? JSON.stringify(railsAnalysis.models.models) : "[]";
112
+ const railsViewsJson = railsAnalysis ? JSON.stringify(railsAnalysis.views) : '{ "views": [], "pages": [], "summary": {} }';
113
+ const railsReactJson = railsAnalysis ? JSON.stringify(railsAnalysis.react) : '{ "components": [], "entryPoints": [], "summary": {} }';
114
+ const railsGrpcJson = railsAnalysis ? JSON.stringify(railsAnalysis.grpc) : '{ "services": [] }';
115
+ const railsSummaryJson = railsAnalysis ? JSON.stringify(railsAnalysis.summary) : "null";
116
+ const hasRails = envResult?.hasRails || false;
117
+ const hasNextjs = envResult?.hasNextjs || false;
118
+ const hasReact = envResult?.hasReact || false;
119
+ const groups = /* @__PURE__ */ new Map();
120
+ for (const page of allPages) {
121
+ const seg = page.path.split("/").filter(Boolean)[0] || "root";
122
+ if (!groups.has(seg)) groups.set(seg, []);
123
+ groups.get(seg)?.push(page);
99
124
  }
100
- renderPageMapHtml(allPages, rootPages, relations, repoName, options) {
101
- const envResult = options?.envResult;
102
- const railsAnalysis = options?.railsAnalysis;
103
- const activeTab = options?.activeTab || 'pages';
104
- const graphqlOpsJson = JSON.stringify(this.graphqlOps.map((op) => ({
105
- name: op.name,
106
- type: op.type,
107
- variables: op.variables,
108
- fields: op.fields,
109
- returnType: op.returnType,
110
- usedIn: op.usedIn,
111
- })));
112
- const componentsJson = JSON.stringify(this.components);
113
- // Rails data for integrated view
114
- const railsRoutesJson = railsAnalysis ? JSON.stringify(railsAnalysis.routes.routes) : '[]';
115
- const railsControllersJson = railsAnalysis
116
- ? JSON.stringify(railsAnalysis.controllers.controllers)
117
- : '[]';
118
- const railsModelsJson = railsAnalysis ? JSON.stringify(railsAnalysis.models.models) : '[]';
119
- const railsViewsJson = railsAnalysis
120
- ? JSON.stringify(railsAnalysis.views)
121
- : '{ "views": [], "pages": [], "summary": {} }';
122
- const railsReactJson = railsAnalysis
123
- ? JSON.stringify(railsAnalysis.react)
124
- : '{ "components": [], "entryPoints": [], "summary": {} }';
125
- const railsGrpcJson = railsAnalysis ? JSON.stringify(railsAnalysis.grpc) : '{ "services": [] }';
126
- const railsSummaryJson = railsAnalysis ? JSON.stringify(railsAnalysis.summary) : 'null';
127
- // Environment info
128
- const hasRails = envResult?.hasRails || false;
129
- const hasNextjs = envResult?.hasNextjs || false;
130
- const hasReact = envResult?.hasReact || false;
131
- // Group by first path segment
132
- const groups = new Map();
133
- for (const page of allPages) {
134
- const seg = page.path.split('/').filter(Boolean)[0] || 'root';
135
- if (!groups.has(seg))
136
- groups.set(seg, []);
137
- groups.get(seg)?.push(page);
138
- }
139
- return `<!DOCTYPE html>
125
+ return `<!DOCTYPE html>
140
126
  <html lang="en">
141
127
  <head>
142
128
  <meta charset="UTF-8">
@@ -147,23 +133,21 @@ export class PageMapGenerator {
147
133
  <body>
148
134
  <header class="header">
149
135
  <div style="display:flex;align-items:center;gap:24px">
150
- <h1 style="cursor:pointer" onclick="location.href='/'">📊 ${repoName}</h1>
136
+ <h1 style="cursor:pointer" onclick="location.href='/'">\u{1F4CA} ${repoName}</h1>
151
137
  <nav style="display:flex;gap:4px">
152
- <a href="/page-map" class="nav-link ${activeTab === 'pages' ? 'active' : ''}">Page Map</a>
153
- ${hasRails ? `<a href="/rails-map" class="nav-link ${activeTab === 'rails' ? 'active' : ''}">Rails Map</a>` : ''}
138
+ <a href="/page-map" class="nav-link ${activeTab === "pages" ? "active" : ""}">Page Map</a>
139
+ ${hasRails ? `<a href="/rails-map" class="nav-link ${activeTab === "rails" ? "active" : ""}">Rails Map</a>` : ""}
154
140
  <a href="/docs" class="nav-link">Docs</a>
155
141
  <a href="/api/report" class="nav-link" target="_blank">API</a>
156
142
  </nav>
157
143
  </div>
158
144
  <div style="display:flex;gap:12px;align-items:center">
159
145
  <!-- Environment filter badges -->
160
- ${hasRails && hasNextjs
161
- ? `<div class="env-filters" style="display:flex;gap:4px;margin-right:8px">
146
+ ${hasRails && hasNextjs ? `<div class="env-filters" style="display:flex;gap:4px;margin-right:8px">
162
147
  <button class="env-badge env-badge-active" data-env="all" onclick="filterByEnv('all')">All</button>
163
- <button class="env-badge" data-env="nextjs" onclick="filterByEnv('nextjs')">⚛️ Next.js</button>
164
- <button class="env-badge" data-env="rails" onclick="filterByEnv('rails')">🛤️ Rails</button>
165
- </div>`
166
- : ''}
148
+ <button class="env-badge" data-env="nextjs" onclick="filterByEnv('nextjs')">\u269B\uFE0F Next.js</button>
149
+ <button class="env-badge" data-env="rails" onclick="filterByEnv('rails')">\u{1F6E4}\uFE0F Rails</button>
150
+ </div>` : ""}
167
151
  <input class="search" type="text" placeholder="Search pages, queries..." oninput="filter(this.value)">
168
152
  <div class="tabs">
169
153
  <button class="tab active" onclick="setView('tree')">List</button>
@@ -199,13 +183,12 @@ export class PageMapGenerator {
199
183
  <h3 style="margin-top:16px;font-size:10px;text-transform:uppercase;color:var(--text2);letter-spacing:1px">Frontend</h3>
200
184
  <div class="stats" id="stats-container">
201
185
  <div class="stat" data-filter="pages"><div class="stat-val">${allPages.length}</div><div class="stat-label">Pages</div></div>
202
- <div class="stat" data-filter="hierarchies"><div class="stat-val">${relations.filter((r) => r.type === 'parent-child').length}</div><div class="stat-label">Hierarchies</div></div>
186
+ <div class="stat" data-filter="hierarchies"><div class="stat-val">${relations.filter((r) => r.type === "parent-child").length}</div><div class="stat-label">Hierarchies</div></div>
203
187
  <div class="stat" data-filter="graphql"><div class="stat-val">${this.graphqlOps.length}</div><div class="stat-label">GraphQL</div></div>
204
188
  <div class="stat" data-filter="restapi"><div class="stat-val">${this.apiCalls.length}</div><div class="stat-label">REST API</div></div>
205
189
  </div>
206
190
 
207
- ${hasRails && railsAnalysis
208
- ? `
191
+ ${hasRails && railsAnalysis ? `
209
192
  <!-- Rails Stats -->
210
193
  <h3 style="margin-top:16px;font-size:10px;text-transform:uppercase;color:var(--text2);letter-spacing:1px;cursor:pointer" onclick="switchToRailsTab()">Rails Backend</h3>
211
194
  <div class="stats" id="rails-stats">
@@ -213,25 +196,24 @@ export class PageMapGenerator {
213
196
  <div class="stat" data-filter="rails-controllers" onclick="showRailsControllers(); this.blur();"><div class="stat-val">${railsAnalysis.summary.totalControllers}</div><div class="stat-label">Controllers</div></div>
214
197
  <div class="stat" data-filter="rails-models" onclick="showRailsModels(); this.blur();"><div class="stat-val">${railsAnalysis.summary.totalModels}</div><div class="stat-label">Models</div></div>
215
198
  <div class="stat" data-filter="rails-grpc" onclick="showRailsGrpc(); this.blur();"><div class="stat-val">${railsAnalysis.summary.totalGrpcServices}</div><div class="stat-label">gRPC</div></div>
216
- <div class="stat" data-filter="rails-react" onclick="showReactComponents(); this.blur();"><div class="stat-val">${railsAnalysis.summary.totalReactComponents}</div><div class="stat-label">⚛ React</div></div>
199
+ <div class="stat" data-filter="rails-react" onclick="showReactComponents(); this.blur();"><div class="stat-val">${railsAnalysis.summary.totalReactComponents}</div><div class="stat-label">\u269B React</div></div>
217
200
  </div>
218
- `
219
- : ''}
201
+ ` : ""}
220
202
  </aside>
221
203
 
222
204
  <div class="content">
223
205
  <!-- Pages Tree View (for all screens - Next.js/React/Rails) -->
224
- <div class="tree-view ${activeTab === 'pages' ? 'active' : ''}" id="tree-view" data-tab="pages">
225
- ${allPages.length > 0 ? this.buildTreeHtml(groups, allPages) : ''}
226
- <div id="page-map-react-components-section" style="${hasRails ? 'margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px' : ''}">
206
+ <div class="tree-view ${activeTab === "pages" ? "active" : ""}" id="tree-view" data-tab="pages">
207
+ ${allPages.length > 0 ? this.buildTreeHtml(groups, allPages) : ""}
208
+ <div id="page-map-react-components-section" style="${hasRails ? "margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px" : ""}">
227
209
  </div>
228
- <div id="page-map-rails-section" style="${allPages.length > 0 && hasRails ? 'margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px' : ''}">
229
- ${hasRails && allPages.length === 0 ? '<div style="padding:20px;color:var(--text2)">Loading screens...</div>' : ''}
210
+ <div id="page-map-rails-section" style="${allPages.length > 0 && hasRails ? "margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px" : ""}">
211
+ ${hasRails && allPages.length === 0 ? '<div style="padding:20px;color:var(--text2)">Loading screens...</div>' : ""}
230
212
  </div>
231
213
  </div>
232
214
 
233
215
  <!-- Rails Routes View (dedicated) -->
234
- <div class="tree-view ${activeTab === 'rails' ? 'active' : ''}" id="rails-tree-view" data-tab="rails">
216
+ <div class="tree-view ${activeTab === "rails" ? "active" : ""}" id="rails-tree-view" data-tab="rails">
235
217
  <div id="rails-routes-container">
236
218
  ${hasRails ? '<div style="padding:20px;color:var(--text2)">Loading Rails routes...</div>' : '<div style="padding:40px;text-align:center;color:var(--text2)">No Rails environment detected</div>'}
237
219
  </div>
@@ -254,7 +236,7 @@ export class PageMapGenerator {
254
236
  <div class="detail" id="detail">
255
237
  <div class="detail-header">
256
238
  <div class="detail-title" id="detail-title"></div>
257
- <button class="detail-close" onclick="closeDetail()">×</button>
239
+ <button class="detail-close" onclick="closeDetail()">\xD7</button>
258
240
  </div>
259
241
  <div class="detail-body" id="detail-body"></div>
260
242
  </div>
@@ -263,10 +245,10 @@ export class PageMapGenerator {
263
245
  <div class="modal-box">
264
246
  <div class="modal-head">
265
247
  <div style="display:flex;align-items:center;gap:8px">
266
- <button id="modal-back" class="modal-back" onclick="modalBack()" style="display:none">←</button>
248
+ <button id="modal-back" class="modal-back" onclick="modalBack()" style="display:none">\u2190</button>
267
249
  <h3 id="modal-title"></h3>
268
250
  </div>
269
- <button class="modal-close" onclick="closeModal()">×</button>
251
+ <button class="modal-close" onclick="closeModal()">\xD7</button>
270
252
  </div>
271
253
  <div class="modal-body" id="modal-body"></div>
272
254
  </div>
@@ -398,7 +380,7 @@ export class PageMapGenerator {
398
380
  let html = '<div style="max-height:60vh;overflow-y:auto">';
399
381
  for (const [ns, routes] of routesByNamespace) {
400
382
  html += '<div style="margin-bottom:16px">';
401
- html += '<div style="font-weight:600;margin-bottom:8px;color:var(--accent)">📂 ' + ns + ' (' + routes.length + ')</div>';
383
+ html += '<div style="font-weight:600;margin-bottom:8px;color:var(--accent)">\u{1F4C2} ' + ns + ' (' + routes.length + ')</div>';
402
384
  html += '<table style="width:100%;border-collapse:collapse;font-size:12px">';
403
385
  html += '<tr style="background:var(--bg3)"><th style="padding:6px;text-align:left">Method</th><th style="padding:6px;text-align:left">Path</th><th style="padding:6px;text-align:left">Controller#Action</th></tr>';
404
386
  routes.slice(0, 20).forEach(r => {
@@ -416,7 +398,7 @@ export class PageMapGenerator {
416
398
  }
417
399
  html += '</div>';
418
400
 
419
- showModal('🛤️ Rails Routes (' + railsRoutes.length + ')', html);
401
+ showModal('\u{1F6E4}\uFE0F Rails Routes (' + railsRoutes.length + ')', html);
420
402
  }
421
403
 
422
404
  function showRailsControllers() {
@@ -443,7 +425,7 @@ export class PageMapGenerator {
443
425
  });
444
426
  html += '</div>';
445
427
 
446
- showModal('🎮 Rails Controllers (' + railsControllers.length + ')', html);
428
+ showModal('\u{1F3AE} Rails Controllers (' + railsControllers.length + ')', html);
447
429
  }
448
430
 
449
431
  function showRailsModels() {
@@ -455,10 +437,10 @@ export class PageMapGenerator {
455
437
  let html = '<div style="max-height:60vh;overflow-y:auto">';
456
438
  railsModels.forEach(model => {
457
439
  html += '<div style="background:var(--bg3);padding:12px;border-radius:6px;margin-bottom:8px">';
458
- html += '<div style="font-weight:600;margin-bottom:4px">📦 ' + model.className + '</div>';
440
+ html += '<div style="font-weight:600;margin-bottom:4px">\u{1F4E6} ' + model.className + '</div>';
459
441
  html += '<div style="display:flex;gap:16px;font-size:11px;color:var(--text2);margin-bottom:8px">';
460
- html += '<span>📎 ' + (model.associations?.length || 0) + ' associations</span>';
461
- html += '<span>✓ ' + (model.validations?.length || 0) + ' validations</span>';
442
+ html += '<span>\u{1F4CE} ' + (model.associations?.length || 0) + ' associations</span>';
443
+ html += '<span>\u2713 ' + (model.validations?.length || 0) + ' validations</span>';
462
444
  html += '</div>';
463
445
  if (model.associations && model.associations.length > 0) {
464
446
  html += '<div style="display:flex;flex-wrap:wrap;gap:4px">';
@@ -473,7 +455,7 @@ export class PageMapGenerator {
473
455
  });
474
456
  html += '</div>';
475
457
 
476
- showModal('📦 Rails Models (' + railsModels.length + ')', html);
458
+ showModal('\u{1F4E6} Rails Models (' + railsModels.length + ')', html);
477
459
  }
478
460
 
479
461
  function showReactComponents() {
@@ -503,18 +485,18 @@ export class PageMapGenerator {
503
485
 
504
486
  html += '<div style="background:var(--bg3);padding:12px;border-radius:6px;margin-bottom:8px;cursor:pointer" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
505
487
  html += '<div style="display:flex;align-items:center;justify-content:space-between">';
506
- html += '<div style="font-weight:600;display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px">⚛</span>' + comp.name + ssrBadge + '</div>';
488
+ html += '<div style="font-weight:600;display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px">\u269B</span>' + comp.name + ssrBadge + '</div>';
507
489
  html += '<span style="font-size:11px;color:var(--text2)">' + usageCount + ' usage' + (usageCount !== 1 ? 's' : '') + '</span>';
508
490
  html += '</div>';
509
491
 
510
492
  // Entry point info
511
493
  if (comp.entryFile) {
512
- html += '<div style="font-size:10px;color:var(--text2);margin-top:4px;font-family:monospace">📥 entries/' + comp.entryFile + '</div>';
494
+ html += '<div style="font-size:10px;color:var(--text2);margin-top:4px;font-family:monospace">\u{1F4E5} entries/' + comp.entryFile + '</div>';
513
495
  }
514
496
 
515
497
  // Source file info
516
498
  if (comp.sourceFile) {
517
- html += '<div style="font-size:10px;color:var(--accent);margin-top:2px;font-family:monospace">📄 ' + comp.sourceFile + '</div>';
499
+ html += '<div style="font-size:10px;color:var(--accent);margin-top:2px;font-family:monospace">\u{1F4C4} ' + comp.sourceFile + '</div>';
518
500
  }
519
501
 
520
502
  // Usage preview
@@ -534,7 +516,7 @@ export class PageMapGenerator {
534
516
  });
535
517
 
536
518
  html += '</div>';
537
- showModal(' React Components (' + railsReact.components.length + ')', html);
519
+ showModal('\u269B React Components (' + railsReact.components.length + ')', html);
538
520
  }
539
521
 
540
522
  function showReactComponentDetail(encodedData) {
@@ -544,7 +526,7 @@ export class PageMapGenerator {
544
526
 
545
527
  // Component Info
546
528
  html += '<div class="detail-section">';
547
- html += '<div class="detail-label">⚛ Component Name</div>';
529
+ html += '<div class="detail-label">\u269B Component Name</div>';
548
530
  html += '<div style="display:flex;align-items:center;gap:8px">';
549
531
  html += '<span style="font-family:monospace;font-size:16px;font-weight:600">' + comp.name + '</span>';
550
532
  if (comp.ssr) {
@@ -557,7 +539,7 @@ export class PageMapGenerator {
557
539
  // Entry Point
558
540
  if (comp.entryFile) {
559
541
  html += '<div class="detail-section">';
560
- html += '<div class="detail-label">📥 Entry Point</div>';
542
+ html += '<div class="detail-label">\u{1F4E5} Entry Point</div>';
561
543
  html += '<div class="code-path">';
562
544
  html += comp.entryFile;
563
545
  html += '</div></div>';
@@ -566,7 +548,7 @@ export class PageMapGenerator {
566
548
  // Source File
567
549
  if (comp.sourceFile || comp.importPath) {
568
550
  html += '<div class="detail-section">';
569
- html += '<div class="detail-label">📄 Source File</div>';
551
+ html += '<div class="detail-label">\u{1F4C4} Source File</div>';
570
552
  html += '<div class="code-path" style="color:var(--accent)">';
571
553
  html += comp.sourceFile || comp.importPath;
572
554
  html += '</div></div>';
@@ -575,7 +557,7 @@ export class PageMapGenerator {
575
557
  // Usage in Views
576
558
  if (comp.usedIn && comp.usedIn.length > 0) {
577
559
  html += '<div class="detail-section">';
578
- html += '<div class="detail-label">📍 Used in Views (' + comp.usedIn.length + ')</div>';
560
+ html += '<div class="detail-label">\u{1F4CD} Used in Views (' + comp.usedIn.length + ')</div>';
579
561
  html += '<div class="detail-items">';
580
562
 
581
563
  comp.usedIn.forEach(usage => {
@@ -600,7 +582,7 @@ export class PageMapGenerator {
600
582
  html += '</div></div>';
601
583
  }
602
584
 
603
- showModal(' ' + comp.name, html, true);
585
+ showModal('\u269B ' + comp.name, html, true);
604
586
  }
605
587
 
606
588
  function showRailsGrpc() {
@@ -612,7 +594,7 @@ export class PageMapGenerator {
612
594
  let html = '<div style="max-height:60vh;overflow-y:auto">';
613
595
  railsGrpc.services.forEach(svc => {
614
596
  html += '<div style="background:var(--bg3);padding:12px;border-radius:6px;margin-bottom:8px">';
615
- html += '<div style="font-weight:600;margin-bottom:4px">🔌 ' + svc.className + '</div>';
597
+ html += '<div style="font-weight:600;margin-bottom:4px">\u{1F50C} ' + svc.className + '</div>';
616
598
  if (svc.namespace) {
617
599
  html += '<div style="font-size:11px;color:var(--text2);margin-bottom:8px">namespace: ' + svc.namespace + '</div>';
618
600
  }
@@ -628,7 +610,7 @@ export class PageMapGenerator {
628
610
  });
629
611
  html += '</div>';
630
612
 
631
- showModal('🔌 gRPC Services (' + railsGrpc.services.length + ')', html);
613
+ showModal('\u{1F50C} gRPC Services (' + railsGrpc.services.length + ')', html);
632
614
  }
633
615
 
634
616
  // Render Rails routes in tree view
@@ -674,7 +656,7 @@ export class PageMapGenerator {
674
656
  controllerInfo = railsControllers.find(c => {
675
657
  // Strategy 1: Match by filePath (most accurate)
676
658
  // filePath: "api/v1/users_controller.rb" or "users_controller.rb"
677
- const filePathNormalized = c.filePath.replace(/_controller\.rb$/, '').replace(/_/g, '');
659
+ const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
678
660
  if (filePathNormalized === routeCtrl.replace(/_/g, '')) return true;
679
661
 
680
662
  // Strategy 2: Match by controller name (without namespace)
@@ -771,7 +753,7 @@ export class PageMapGenerator {
771
753
  const coverageTooltip = 'Percentage of routes successfully matched with controller actions to extract details (JSON/HTML rendering, redirects, etc). This is a tool analysis metric, not a code quality indicator.';
772
754
  const coverageClass = coverage > 70 ? 'coverage-high' : coverage > 40 ? 'coverage-mid' : 'coverage-low';
773
755
  html += '<div class="coverage-info">';
774
- html += '<div class="coverage-text" title="' + coverageTooltip + '">Action Details Coverage: <span class="' + coverageClass + '">' + coverage + '%</span> (' + totalWithActionInfo + '/' + combinedData.length + ' routes analyzed) ℹ️</div>';
756
+ html += '<div class="coverage-text" title="' + coverageTooltip + '">Action Details Coverage: <span class="' + coverageClass + '">' + coverage + '%</span> (' + totalWithActionInfo + '/' + combinedData.length + ' routes analyzed) \u2139\uFE0F</div>';
775
757
  html += '</div>';
776
758
  }
777
759
  html += '</div>';
@@ -782,8 +764,8 @@ export class PageMapGenerator {
782
764
 
783
765
  html += '<div class="group">';
784
766
  html += '<div class="group-header" onclick="toggleGroup(this)" style="border-left-color:' + color + '">';
785
- html += '<span class="group-toggle">▼</span>';
786
- html += '<span class="group-name">📂 ' + ns + '</span>';
767
+ html += '<span class="group-toggle">\u25BC</span>';
768
+ html += '<span class="group-name">\u{1F4C2} ' + ns + '</span>';
787
769
  html += '<span class="group-count">' + routes.length + '</span>';
788
770
  html += '</div>';
789
771
  const routeListId = 'routes-' + ns.replace(/[^a-zA-Z0-9]/g, '-');
@@ -803,7 +785,7 @@ export class PageMapGenerator {
803
785
  if (displayPath.includes('=>') || displayPath.includes('redirect')) {
804
786
  // Extract just the route pattern before any redirect logic
805
787
  const match = displayPath.match(/^([^"]+)"/);
806
- displayPath = match ? match[1].trim() + ' redirect' : displayPath.slice(0, 60) + '...';
788
+ displayPath = match ? match[1].trim() + ' \u2192 redirect' : displayPath.slice(0, 60) + '...';
807
789
  }
808
790
  if (displayPath.length > 80) {
809
791
  displayPath = displayPath.slice(0, 77) + '...';
@@ -817,7 +799,7 @@ export class PageMapGenerator {
817
799
  if (action) {
818
800
  if (action.rendersJson) indicators += '<span class="route-tag route-tag-json" title="Returns JSON">JSON</span>';
819
801
  if (action.rendersHtml && !action.rendersJson) indicators += '<span class="route-tag route-tag-html" title="Returns HTML">HTML</span>';
820
- if (action.redirectsTo) indicators += '<span class="route-tag route-tag-redirect" title="Redirects">→</span>';
802
+ if (action.redirectsTo) indicators += '<span class="route-tag route-tag-redirect" title="Redirects">\u2192</span>';
821
803
  }
822
804
  if (route.hasView) indicators += '<span class="route-tag route-tag-view" title="Has View Template">View</span>';
823
805
  if (route.services.length > 0) indicators += '<span class="route-tag route-tag-svc" title="Uses Services: ' + route.services.join(', ') + '">Svc</span>';
@@ -846,7 +828,7 @@ export class PageMapGenerator {
846
828
  html += '</div>';
847
829
 
848
830
  if (hasMoreRoutes) {
849
- html += '<div id="' + routeListId + '-more" style="padding:8px 12px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + routeListId + '\\', ' + routes.length + ')">▼ Show ' + (routes.length - routeLimit) + ' more routes</div>';
831
+ html += '<div id="' + routeListId + '-more" style="padding:8px 12px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + routeListId + '\\', ' + routes.length + ')">\u25BC Show ' + (routes.length - routeLimit) + ' more routes</div>';
850
832
  }
851
833
 
852
834
  html += '</div>';
@@ -935,7 +917,7 @@ export class PageMapGenerator {
935
917
  // Response Type - NEW
936
918
  if (action) {
937
919
  html += '<div class="detail-section">';
938
- html += '<div class="detail-label">📡 Response Type</div>';
920
+ html += '<div class="detail-label">\u{1F4E1} Response Type</div>';
939
921
  html += '<div class="detail-value">';
940
922
  const responseTypes = [];
941
923
  if (action.rendersJson) responseTypes.push('<span style="background:#3b82f6;color:white;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px">JSON</span>');
@@ -952,7 +934,7 @@ export class PageMapGenerator {
952
934
  // Redirect destination if exists
953
935
  if (action.redirectsTo) {
954
936
  html += '<div class="detail-section">';
955
- html += '<div class="detail-label">↪️ Redirects To</div>';
937
+ html += '<div class="detail-label">\u21AA\uFE0F Redirects To</div>';
956
938
  html += '<div class="detail-value" style="font-family:monospace;font-size:12px;background:var(--bg3);padding:8px;border-radius:4px">' + action.redirectsTo + '</div>';
957
939
  html += '</div>';
958
940
  }
@@ -961,7 +943,7 @@ export class PageMapGenerator {
961
943
  // View info
962
944
  if (route.hasView && route.view) {
963
945
  html += '<div class="detail-section">';
964
- html += '<div class="detail-label">📄 View Template</div>';
946
+ html += '<div class="detail-label">\u{1F4C4} View Template</div>';
965
947
  html += '<div class="detail-value" style="font-family:monospace;font-size:12px">app/views/' + route.view.path + '</div>';
966
948
  if (route.view.partials && route.view.partials.length > 0) {
967
949
  html += '<div style="margin-top:6px;font-size:11px;color:var(--text2)">Partials: ' + route.view.partials.slice(0, 5).join(', ') + (route.view.partials.length > 5 ? '...' : '') + '</div>';
@@ -975,7 +957,7 @@ export class PageMapGenerator {
975
957
  // Before/After Filters - NEW
976
958
  if (ctrl && (ctrl.beforeActions.length > 0 || ctrl.afterActions.length > 0)) {
977
959
  html += '<div class="detail-section">';
978
- html += '<div class="detail-label">🔒 Filters Applied to This Action</div>';
960
+ html += '<div class="detail-label">\u{1F512} Filters Applied to This Action</div>';
979
961
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
980
962
 
981
963
  // Filter before_actions that apply to this action
@@ -1023,7 +1005,7 @@ export class PageMapGenerator {
1023
1005
  const services = route.services && route.services.length > 0 ? route.services : (action?.servicesCalled || []);
1024
1006
  if (services.length > 0) {
1025
1007
  html += '<div class="detail-section">';
1026
- html += '<div class="detail-label">⚙️ Services Called</div>';
1008
+ html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
1027
1009
  html += '<div class="detail-items">';
1028
1010
  services.forEach(s => {
1029
1011
  html += '<div class="detail-item"><span class="tag" style="background:#8b5cf6">Service</span><span class="name" style="font-family:monospace">' + s + '</span></div>';
@@ -1034,7 +1016,7 @@ export class PageMapGenerator {
1034
1016
  // gRPC Calls
1035
1017
  if (route.grpcCalls && route.grpcCalls.length > 0) {
1036
1018
  html += '<div class="detail-section">';
1037
- html += '<div class="detail-label">🔌 gRPC Calls</div>';
1019
+ html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
1038
1020
  html += '<div class="detail-items">';
1039
1021
  route.grpcCalls.forEach(g => {
1040
1022
  html += '<div class="detail-item"><span class="tag" style="background:#06b6d4">gRPC</span><span class="name" style="font-family:monospace">' + g + '</span></div>';
@@ -1046,7 +1028,7 @@ export class PageMapGenerator {
1046
1028
  const models = route.modelAccess && route.modelAccess.length > 0 ? route.modelAccess : (action?.modelsCalled || []);
1047
1029
  if (models.length > 0) {
1048
1030
  html += '<div class="detail-section">';
1049
- html += '<div class="detail-label">💾 Models Accessed</div>';
1031
+ html += '<div class="detail-label">\u{1F4BE} Models Accessed</div>';
1050
1032
  html += '<div class="detail-items">';
1051
1033
  models.forEach(m => {
1052
1034
  html += '<div class="detail-item"><span class="tag" style="background:#f59e0b">Model</span><span class="name" style="font-family:monospace">' + m + '</span></div>';
@@ -1064,7 +1046,7 @@ export class PageMapGenerator {
1064
1046
 
1065
1047
  if (meaningfulCalls.length > 0) {
1066
1048
  html += '<div class="detail-section">';
1067
- html += '<div class="detail-label">🔗 Method Calls in Action</div>';
1049
+ html += '<div class="detail-label">\u{1F517} Method Calls in Action</div>';
1068
1050
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;max-height:150px;overflow-y:auto">';
1069
1051
  html += '<div style="font-family:monospace;font-size:11px;line-height:1.6">';
1070
1052
  meaningfulCalls.forEach((call, i) => {
@@ -1082,7 +1064,7 @@ export class PageMapGenerator {
1082
1064
 
1083
1065
  // Source Files - NEW
1084
1066
  html += '<div class="detail-section">';
1085
- html += '<div class="detail-label">📁 Source Files</div>';
1067
+ html += '<div class="detail-label">\u{1F4C1} Source Files</div>';
1086
1068
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:11px">';
1087
1069
 
1088
1070
  if (route.line > 0) {
@@ -1111,7 +1093,7 @@ export class PageMapGenerator {
1111
1093
  // Controller Info Summary
1112
1094
  if (ctrl) {
1113
1095
  html += '<div class="detail-section">';
1114
- html += '<div class="detail-label">📋 Controller Info</div>';
1096
+ html += '<div class="detail-label">\u{1F4CB} Controller Info</div>';
1115
1097
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
1116
1098
  html += '<div style="font-weight:600;margin-bottom:4px">' + ctrl.className + '</div>';
1117
1099
  html += '<div style="font-size:11px;color:var(--text2)">extends ' + ctrl.parentClass + '</div>';
@@ -1158,14 +1140,14 @@ export class PageMapGenerator {
1158
1140
 
1159
1141
  let html = '';
1160
1142
  html += '<div style="padding:12px;background:var(--bg3);border-radius:8px;margin-bottom:12px">';
1161
- html += '<div style="font-weight:600;margin-bottom:8px;display:flex;align-items:center;gap:8px"><span style="color:#61dafb">⚛</span> React Components (from Rails)</div>';
1143
+ html += '<div style="font-weight:600;margin-bottom:8px;display:flex;align-items:center;gap:8px"><span style="color:#61dafb">\u269B</span> React Components (from Rails)</div>';
1162
1144
  html += '<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--text2)">';
1163
1145
  html += '<span>' + components.length + ' components</span>';
1164
- html += '<span>•</span>';
1146
+ html += '<span>\u2022</span>';
1165
1147
  html += '<span style="color:#22c55e">' + ssrCount + ' SSR</span>';
1166
- html += '<span>•</span>';
1148
+ html += '<span>\u2022</span>';
1167
1149
  html += '<span style="color:#3b82f6">' + (components.length - ssrCount) + ' client</span>';
1168
- html += '<span>•</span>';
1150
+ html += '<span>\u2022</span>';
1169
1151
  html += '<span style="color:#8b5cf6">' + withUsageCount + ' with usage</span>';
1170
1152
  html += '</div></div>';
1171
1153
 
@@ -1178,8 +1160,8 @@ export class PageMapGenerator {
1178
1160
  if (withEntry.length > 0) {
1179
1161
  html += '<div class="group" data-group="react-with-entry">';
1180
1162
  html += '<div class="group-header" onclick="toggleGroup(this)">';
1181
- html += '<span class="group-toggle">▼</span>';
1182
- html += '<span style="color:#61dafb;margin-right:4px">📥</span>';
1163
+ html += '<span class="group-toggle">\u25BC</span>';
1164
+ html += '<span style="color:#61dafb;margin-right:4px">\u{1F4E5}</span>';
1183
1165
  html += '<span class="group-name">With Entry Points (' + withEntry.length + ')</span>';
1184
1166
  html += '</div>';
1185
1167
  html += '<div class="group-items">';
@@ -1204,14 +1186,14 @@ export class PageMapGenerator {
1204
1186
  urlInfo = '/' + usage.controller.replace(/_/g, '/') + '/' + usage.action;
1205
1187
  }
1206
1188
  } else if (comp.entryFile) {
1207
- // Infer URL from entry file name (e.g., tickets.tsx /tickets)
1189
+ // Infer URL from entry file name (e.g., tickets.tsx \u2192 /tickets)
1208
1190
  const fileName = comp.entryFile.split('/').pop().replace(/\\.(tsx?|jsx?)$/, '');
1209
1191
  urlInfo = '/' + fileName.replace(/_/g, '-');
1210
1192
  }
1211
1193
 
1212
1194
  html += '<div class="page-item" data-path="' + comp.name.toLowerCase() + '" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
1213
1195
  html += '<div class="page-info">';
1214
- html += '<span class="page-name" style="display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px">⚛</span>' + comp.name + '</span>';
1196
+ html += '<span class="page-name" style="display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px">\u269B</span>' + comp.name + '</span>';
1215
1197
  html += '<span class="page-path" style="font-size:10px;color:var(--accent)">' + urlInfo + '</span>';
1216
1198
  html += '</div>';
1217
1199
  html += '<div class="page-tags">' + tags.join('') + '</div>';
@@ -1225,8 +1207,8 @@ export class PageMapGenerator {
1225
1207
  if (withoutEntry.length > 0) {
1226
1208
  html += '<div class="group" data-group="react-view-only">';
1227
1209
  html += '<div class="group-header" onclick="toggleGroup(this)">';
1228
- html += '<span class="group-toggle">▼</span>';
1229
- html += '<span style="color:#f59e0b;margin-right:4px">👁️</span>';
1210
+ html += '<span class="group-toggle">\u25BC</span>';
1211
+ html += '<span style="color:#f59e0b;margin-right:4px">\u{1F441}\uFE0F</span>';
1230
1212
  html += '<span class="group-name">View-only Components (' + withoutEntry.length + ')</span>';
1231
1213
  html += '</div>';
1232
1214
  html += '<div class="group-items">';
@@ -1253,7 +1235,7 @@ export class PageMapGenerator {
1253
1235
 
1254
1236
  html += '<div class="page-item" data-path="' + comp.name.toLowerCase() + '" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
1255
1237
  html += '<div class="page-info">';
1256
- html += '<span class="page-name" style="display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px">⚛</span>' + comp.name + '</span>';
1238
+ html += '<span class="page-name" style="display:flex;align-items:center"><span style="color:#61dafb;margin-right:6px">\u269B</span>' + comp.name + '</span>';
1257
1239
  html += '<span class="page-path" style="font-size:10px;color:var(--accent)">' + (urlInfo || 'View-only') + '</span>';
1258
1240
  html += '</div>';
1259
1241
  html += '<div class="page-tags">' + tags.join('') + '</div>';
@@ -1302,7 +1284,7 @@ export class PageMapGenerator {
1302
1284
  const ctrlName = view.controller.split('/').pop().replace(/_/g, '');
1303
1285
  const ctrl = railsControllers?.find(c => {
1304
1286
  if (c.name === view.controller || c.name === ctrlName) return true;
1305
- const filePathNormalized = c.filePath.replace(/_controller\.rb$/, '').replace(/_/g, '');
1287
+ const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
1306
1288
  return filePathNormalized === view.controller.replace(/_/g, '');
1307
1289
  });
1308
1290
  const action = ctrl?.actions?.find(a => a.name === view.action);
@@ -1364,14 +1346,14 @@ export class PageMapGenerator {
1364
1346
 
1365
1347
  let html = '';
1366
1348
  html += '<div style="padding:12px;background:var(--bg3);border-radius:8px;margin-bottom:12px">';
1367
- html += '<div style="font-weight:600;margin-bottom:8px">🖼️ Rails Screens (View Templates)</div>';
1349
+ html += '<div style="font-weight:600;margin-bottom:8px">\u{1F5BC}\uFE0F Rails Screens (View Templates)</div>';
1368
1350
  html += '<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--text2)">';
1369
1351
  html += '<span>' + enrichedViews.length + ' screens</span>';
1370
- html += '<span>•</span>';
1352
+ html += '<span>\u2022</span>';
1371
1353
  html += '<span>' + sortedControllers.length + ' sections</span>';
1372
- html += '<span>•</span>';
1354
+ html += '<span>\u2022</span>';
1373
1355
  html += '<span style="color:#8b5cf6">' + totalWithServices + ' with services</span>';
1374
- html += '<span>•</span>';
1356
+ html += '<span>\u2022</span>';
1375
1357
  html += '<span style="color:#06b6d4">' + totalWithPartials + ' with partials</span>';
1376
1358
  html += '</div></div>';
1377
1359
 
@@ -1385,8 +1367,8 @@ export class PageMapGenerator {
1385
1367
 
1386
1368
  html += '<div class="group">';
1387
1369
  html += '<div class="group-header" onclick="toggleGroup(this)" style="border-left-color:' + color + '">';
1388
- html += '<span class="group-toggle">▼</span>';
1389
- html += '<span class="group-name">📁 ' + ctrl + '</span>';
1370
+ html += '<span class="group-toggle">\u25BC</span>';
1371
+ html += '<span class="group-name">\u{1F4C1} ' + ctrl + '</span>';
1390
1372
  html += '<span class="group-count">' + controllerViews.length + ' screens</span>';
1391
1373
  html += '</div>';
1392
1374
  html += '<div class="group-items" id="' + screenListId + '">';
@@ -1400,14 +1382,14 @@ export class PageMapGenerator {
1400
1382
  indicators += '<span class="route-tag route-tag-template" title="' + view.template.toUpperCase() + ' template">' + view.template.toUpperCase() + '</span>';
1401
1383
  if (view.reactComponents && view.reactComponents.length > 0) {
1402
1384
  const rcNames = view.reactComponents.map(rc => rc.name).slice(0, 2).join(', ') + (view.reactComponents.length > 2 ? '...' : '');
1403
- indicators += '<span class="route-tag route-tag-react" title="React: ' + rcNames + '">⚛ ' + view.reactComponents.length + '</span>';
1385
+ indicators += '<span class="route-tag route-tag-react" title="React: ' + rcNames + '">\u269B ' + view.reactComponents.length + '</span>';
1404
1386
  }
1405
- if (view.partials.length > 0) indicators += '<span class="route-tag route-tag-partials" title="Uses partials: ' + view.partials.slice(0,3).join(', ') + (view.partials.length > 3 ? '...' : '') + '">🧩 ' + view.partials.length + '</span>';
1406
- if (view.instanceVars.length > 0) indicators += '<span class="route-tag route-tag-vars" title="Instance vars: @' + view.instanceVars.slice(0,5).join(', @') + '">📦 ' + view.instanceVars.length + '</span>';
1387
+ if (view.partials.length > 0) indicators += '<span class="route-tag route-tag-partials" title="Uses partials: ' + view.partials.slice(0,3).join(', ') + (view.partials.length > 3 ? '...' : '') + '">\u{1F9E9} ' + view.partials.length + '</span>';
1388
+ if (view.instanceVars.length > 0) indicators += '<span class="route-tag route-tag-vars" title="Instance vars: @' + view.instanceVars.slice(0,5).join(', @') + '">\u{1F4E6} ' + view.instanceVars.length + '</span>';
1407
1389
  if (view.services.length > 0) indicators += '<span class="route-tag route-tag-svc" title="Services: ' + view.services.join(', ') + '">Svc</span>';
1408
1390
  if (view.grpcCalls.length > 0) indicators += '<span class="route-tag route-tag-grpc" title="gRPC: ' + view.grpcCalls.join(', ') + '">gRPC</span>';
1409
1391
  if (view.modelAccess.length > 0) indicators += '<span class="route-tag route-tag-db" title="Models: ' + view.modelAccess.join(', ') + '">DB</span>';
1410
- if (!view.hasRoute) indicators += '<span class="route-tag route-tag-warn" title="No matching route found">⚠️</span>';
1392
+ if (!view.hasRoute) indicators += '<span class="route-tag route-tag-warn" title="No matching route found">\u26A0\uFE0F</span>';
1411
1393
 
1412
1394
  // Display: URL path (if route exists) or controller/action
1413
1395
  const displayName = view.hasRoute ? view.path.replace(/:([a-z_]+)/g, '<span style="color:#f59e0b">:$1</span>') : view.controller + '#' + view.action;
@@ -1428,7 +1410,7 @@ export class PageMapGenerator {
1428
1410
 
1429
1411
  if (hasMoreScreens) {
1430
1412
  html += '<div id="' + screenListId + '-more" style="padding:8px 12px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + screenListId + '\\', ' + controllerViews.length + ')">';
1431
- html += ' Show ' + (controllerViews.length - screenLimit) + ' more screens';
1413
+ html += '\u25BC Show ' + (controllerViews.length - screenLimit) + ' more screens';
1432
1414
  html += '</div>';
1433
1415
  }
1434
1416
 
@@ -1446,7 +1428,7 @@ export class PageMapGenerator {
1446
1428
 
1447
1429
  // URL/Route info
1448
1430
  html += '<div class="detail-section">';
1449
- html += '<div class="detail-label">🌐 URL Path</div>';
1431
+ html += '<div class="detail-label">\u{1F310} URL Path</div>';
1450
1432
  if (screen.hasRoute) {
1451
1433
  html += '<div class="detail-value" style="font-family:monospace">' + screen.path.replace(/:([a-z_]+)/g, '<span style="color:#f59e0b">:$1</span>') + '</div>';
1452
1434
  } else {
@@ -1456,7 +1438,7 @@ export class PageMapGenerator {
1456
1438
 
1457
1439
  // View Template info
1458
1440
  html += '<div class="detail-section">';
1459
- html += '<div class="detail-label">📄 View Template</div>';
1441
+ html += '<div class="detail-label">\u{1F4C4} View Template</div>';
1460
1442
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:12px">';
1461
1443
  html += '<div style="color:var(--accent)">app/views/' + screen.viewPath + '</div>';
1462
1444
  html += '<div style="margin-top:6px;display:flex;gap:8px">';
@@ -1466,7 +1448,7 @@ export class PageMapGenerator {
1466
1448
  // Instance Variables (data passed to view) with type info
1467
1449
  if (screen.instanceVars && screen.instanceVars.length > 0) {
1468
1450
  html += '<div class="detail-section">';
1469
- html += '<div class="detail-label">📦 Data Available in View (@variables)</div>';
1451
+ html += '<div class="detail-label">\u{1F4E6} Data Available in View (@variables)</div>';
1470
1452
 
1471
1453
  // Build assignment map from controller analysis
1472
1454
  const assignmentMap = {};
@@ -1487,16 +1469,16 @@ export class PageMapGenerator {
1487
1469
 
1488
1470
  // Function to find matching model for a variable name
1489
1471
  function findModelForVar(varName) {
1490
- // Direct match: @company Company
1472
+ // Direct match: @company \u2192 Company
1491
1473
  const pascalCase = varName.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
1492
1474
  if (modelNames.has(pascalCase)) return { model: pascalCase, confidence: 'exact' };
1493
1475
 
1494
- // Singular form: @companies Company
1476
+ // Singular form: @companies \u2192 Company
1495
1477
  const singular = varName.replace(/ies$/, 'y').replace(/s$/, '');
1496
1478
  const singularPascal = singular.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
1497
1479
  if (modelNames.has(singularPascal)) return { model: singularPascal, confidence: 'plural' };
1498
1480
 
1499
- // current_X pattern: @current_user User
1481
+ // current_X pattern: @current_user \u2192 User
1500
1482
  if (varName.startsWith('current_')) {
1501
1483
  const rest = varName.replace('current_', '');
1502
1484
  const restPascal = rest.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
@@ -1571,7 +1553,7 @@ export class PageMapGenerator {
1571
1553
  // "Show more" button
1572
1554
  if (hasMore) {
1573
1555
  html += '<div id="' + listId + '-more" style="margin-top:8px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + listId + '\\', ' + screen.instanceVars.length + ')">';
1574
- html += ' Show ' + (screen.instanceVars.length - initialLimit) + ' more variables';
1556
+ html += '\u25BC Show ' + (screen.instanceVars.length - initialLimit) + ' more variables';
1575
1557
  html += '</div>';
1576
1558
  }
1577
1559
 
@@ -1581,7 +1563,7 @@ export class PageMapGenerator {
1581
1563
  // React Components loaded in this view
1582
1564
  if (screen.reactComponents && screen.reactComponents.length > 0) {
1583
1565
  html += '<div class="detail-section">';
1584
- html += '<div class="detail-label">⚛️ React Components</div>';
1566
+ html += '<div class="detail-label">\u269B\uFE0F React Components</div>';
1585
1567
  html += '<div class="detail-items">';
1586
1568
  screen.reactComponents.forEach(rc => {
1587
1569
  html += '<div class="detail-item">';
@@ -1605,7 +1587,7 @@ export class PageMapGenerator {
1605
1587
  const partialListId = 'partials-' + Math.random().toString(36).substr(2, 9);
1606
1588
 
1607
1589
  html += '<div class="detail-section">';
1608
- html += '<div class="detail-label">🧩 Partials Used (' + screen.partials.length + ')</div>';
1590
+ html += '<div class="detail-label">\u{1F9E9} Partials Used (' + screen.partials.length + ')</div>';
1609
1591
  html += '<div class="detail-items" id="' + partialListId + '">';
1610
1592
  screen.partials.forEach((p, idx) => {
1611
1593
  const hiddenClass = idx >= partialLimit ? ' style="display:none" data-hidden="true"' : '';
@@ -1613,7 +1595,7 @@ export class PageMapGenerator {
1613
1595
  });
1614
1596
  html += '</div>';
1615
1597
  if (hasMorePartials) {
1616
- html += '<div id="' + partialListId + '-more" style="margin-top:6px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + partialListId + '\\', ' + screen.partials.length + ')">▼ Show ' + (screen.partials.length - partialLimit) + ' more</div>';
1598
+ html += '<div id="' + partialListId + '-more" style="margin-top:6px;cursor:pointer;color:var(--accent);font-size:11px" onclick="toggleMoreItems(\\'' + partialListId + '\\', ' + screen.partials.length + ')">\u25BC Show ' + (screen.partials.length - partialLimit) + ' more</div>';
1617
1599
  }
1618
1600
  html += '</div>';
1619
1601
  }
@@ -1621,7 +1603,7 @@ export class PageMapGenerator {
1621
1603
  // Services Called
1622
1604
  if (screen.services && screen.services.length > 0) {
1623
1605
  html += '<div class="detail-section">';
1624
- html += '<div class="detail-label">⚙️ Services Called</div>';
1606
+ html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
1625
1607
  html += '<div class="detail-items">';
1626
1608
  screen.services.forEach(s => {
1627
1609
  html += '<div class="detail-item"><span class="tag" style="background:#8b5cf6">Service</span><span class="name" style="font-family:monospace">' + s + '</span></div>';
@@ -1632,7 +1614,7 @@ export class PageMapGenerator {
1632
1614
  // gRPC Calls
1633
1615
  if (screen.grpcCalls && screen.grpcCalls.length > 0) {
1634
1616
  html += '<div class="detail-section">';
1635
- html += '<div class="detail-label">🔌 gRPC Calls</div>';
1617
+ html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
1636
1618
  html += '<div class="detail-items">';
1637
1619
  screen.grpcCalls.forEach(g => {
1638
1620
  html += '<div class="detail-item"><span class="tag" style="background:#06b6d4">gRPC</span><span class="name" style="font-family:monospace">' + g + '</span></div>';
@@ -1643,7 +1625,7 @@ export class PageMapGenerator {
1643
1625
  // Model Access
1644
1626
  if (screen.modelAccess && screen.modelAccess.length > 0) {
1645
1627
  html += '<div class="detail-section">';
1646
- html += '<div class="detail-label">💾 Models Used</div>';
1628
+ html += '<div class="detail-label">\u{1F4BE} Models Used</div>';
1647
1629
  html += '<div class="detail-items">';
1648
1630
  screen.modelAccess.forEach(m => {
1649
1631
  html += '<div class="detail-item"><span class="tag" style="background:#f59e0b">Model</span><span class="name" style="font-family:monospace">' + m + '</span></div>';
@@ -1653,7 +1635,7 @@ export class PageMapGenerator {
1653
1635
 
1654
1636
  // Controller Action info
1655
1637
  html += '<div class="detail-section">';
1656
- html += '<div class="detail-label">🎮 Controller Action</div>';
1638
+ html += '<div class="detail-label">\u{1F3AE} Controller Action</div>';
1657
1639
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
1658
1640
  html += '<div style="font-family:monospace;font-size:12px">' + screen.controller + '#' + screen.action + '</div>';
1659
1641
  if (screen.controllerInfo) {
@@ -1686,7 +1668,7 @@ export class PageMapGenerator {
1686
1668
 
1687
1669
  if (meaningfulCalls.length > 0) {
1688
1670
  html += '<div class="detail-section">';
1689
- html += '<div class="detail-label">🔗 Method Calls</div>';
1671
+ html += '<div class="detail-label">\u{1F517} Method Calls</div>';
1690
1672
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:11px;max-height:120px;overflow-y:auto">';
1691
1673
  meaningfulCalls.forEach((call, i) => {
1692
1674
  html += '<div style="padding:2px 0;color:var(--accent)">' + (i+1) + '. ' + call + '</div>';
@@ -1695,7 +1677,7 @@ export class PageMapGenerator {
1695
1677
  }
1696
1678
  }
1697
1679
 
1698
- showModal('🖼️ ' + screen.controller + '/' + screen.action, html);
1680
+ showModal('\u{1F5BC}\uFE0F ' + screen.controller + '/' + screen.action, html);
1699
1681
  }
1700
1682
 
1701
1683
  // Show Rails page detail with API info
@@ -1711,7 +1693,7 @@ export class PageMapGenerator {
1711
1693
  const routeCtrlName = routeCtrlParts.pop().replace(/_/g, '');
1712
1694
 
1713
1695
  controllerInfo = railsControllers.find(c => {
1714
- const filePathNormalized = c.filePath.replace(/_controller\.rb$/, '').replace(/_/g, '');
1696
+ const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
1715
1697
  if (filePathNormalized === routeCtrl.replace(/_/g, '')) return true;
1716
1698
  if (c.name === routeCtrlName || c.name.replace(/_/g, '') === routeCtrlName) return true;
1717
1699
  const className = c.className.toLowerCase().replace('controller', '').replace(/::/g, '/');
@@ -1746,7 +1728,7 @@ export class PageMapGenerator {
1746
1728
  // Response Type
1747
1729
  if (actionDetails) {
1748
1730
  html += '<div class="detail-section">';
1749
- html += '<div class="detail-label">📡 Response Type</div>';
1731
+ html += '<div class="detail-label">\u{1F4E1} Response Type</div>';
1750
1732
  html += '<div class="detail-value">';
1751
1733
  const responseTypes = [];
1752
1734
  if (actionDetails.rendersJson) responseTypes.push('<span style="background:#3b82f6;color:white;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px">JSON</span>');
@@ -1762,7 +1744,7 @@ export class PageMapGenerator {
1762
1744
 
1763
1745
  if (actionDetails.redirectsTo) {
1764
1746
  html += '<div class="detail-section">';
1765
- html += '<div class="detail-label">↪️ Redirects To</div>';
1747
+ html += '<div class="detail-label">\u21AA\uFE0F Redirects To</div>';
1766
1748
  html += '<div class="detail-value" style="font-family:monospace;font-size:12px;background:var(--bg3);padding:8px;border-radius:4px">' + actionDetails.redirectsTo + '</div>';
1767
1749
  html += '</div>';
1768
1750
  }
@@ -1772,7 +1754,7 @@ export class PageMapGenerator {
1772
1754
  const view = pageInfo?.view || route.view;
1773
1755
  if (view) {
1774
1756
  html += '<div class="detail-section">';
1775
- html += '<div class="detail-label">📄 View Template</div>';
1757
+ html += '<div class="detail-label">\u{1F4C4} View Template</div>';
1776
1758
  html += '<div class="detail-value" style="font-family:monospace;font-size:12px">app/views/' + view.path + '</div>';
1777
1759
  if (view.partials && view.partials.length > 0) {
1778
1760
  html += '<div style="margin-top:6px;font-size:11px;color:var(--text2)">Partials: ' + view.partials.slice(0, 5).join(', ') + '</div>';
@@ -1786,7 +1768,7 @@ export class PageMapGenerator {
1786
1768
  // Before/After Filters
1787
1769
  if (controllerInfo && (controllerInfo.beforeActions.length > 0 || controllerInfo.afterActions.length > 0)) {
1788
1770
  html += '<div class="detail-section">';
1789
- html += '<div class="detail-label">🔒 Filters Applied</div>';
1771
+ html += '<div class="detail-label">\u{1F512} Filters Applied</div>';
1790
1772
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
1791
1773
 
1792
1774
  const applicableBeforeFilters = controllerInfo.beforeActions.filter(f => {
@@ -1827,7 +1809,7 @@ export class PageMapGenerator {
1827
1809
  const services = pageInfo?.services || actionDetails?.servicesCalled || [];
1828
1810
  if (services.length > 0) {
1829
1811
  html += '<div class="detail-section">';
1830
- html += '<div class="detail-label">⚙️ Services Called</div>';
1812
+ html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
1831
1813
  html += '<div class="detail-items">';
1832
1814
  services.forEach(s => {
1833
1815
  html += '<div class="detail-item"><span class="tag" style="background:#8b5cf6">Service</span><span class="name" style="font-family:monospace">' + s + '</span></div>';
@@ -1839,7 +1821,7 @@ export class PageMapGenerator {
1839
1821
  const grpcCalls = pageInfo?.grpcCalls || [];
1840
1822
  if (grpcCalls.length > 0) {
1841
1823
  html += '<div class="detail-section">';
1842
- html += '<div class="detail-label">🔌 gRPC Calls</div>';
1824
+ html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
1843
1825
  html += '<div class="detail-items">';
1844
1826
  grpcCalls.forEach(g => {
1845
1827
  html += '<div class="detail-item"><span class="tag" style="background:#06b6d4">gRPC</span><span class="name" style="font-family:monospace">' + g + '</span></div>';
@@ -1851,7 +1833,7 @@ export class PageMapGenerator {
1851
1833
  const models = pageInfo?.modelAccess || actionDetails?.modelsCalled || [];
1852
1834
  if (models.length > 0) {
1853
1835
  html += '<div class="detail-section">';
1854
- html += '<div class="detail-label">💾 Models Accessed</div>';
1836
+ html += '<div class="detail-label">\u{1F4BE} Models Accessed</div>';
1855
1837
  html += '<div class="detail-items">';
1856
1838
  models.forEach(m => {
1857
1839
  html += '<div class="detail-item"><span class="tag" style="background:#f59e0b">Model</span><span class="name" style="font-family:monospace">' + m + '</span></div>';
@@ -1868,7 +1850,7 @@ export class PageMapGenerator {
1868
1850
 
1869
1851
  if (meaningfulCalls.length > 0) {
1870
1852
  html += '<div class="detail-section">';
1871
- html += '<div class="detail-label">🔗 Method Calls in Action</div>';
1853
+ html += '<div class="detail-label">\u{1F517} Method Calls in Action</div>';
1872
1854
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;max-height:150px;overflow-y:auto">';
1873
1855
  html += '<div style="font-family:monospace;font-size:11px;line-height:1.6">';
1874
1856
  meaningfulCalls.forEach((call, i) => {
@@ -1886,7 +1868,7 @@ export class PageMapGenerator {
1886
1868
 
1887
1869
  // Source Files
1888
1870
  html += '<div class="detail-section">';
1889
- html += '<div class="detail-label">📁 Source Files</div>';
1871
+ html += '<div class="detail-label">\u{1F4C1} Source Files</div>';
1890
1872
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:11px">';
1891
1873
 
1892
1874
  if (controllerInfo) {
@@ -1908,7 +1890,7 @@ export class PageMapGenerator {
1908
1890
  // Controller Info
1909
1891
  if (controllerInfo) {
1910
1892
  html += '<div class="detail-section">';
1911
- html += '<div class="detail-label">📋 Controller Info</div>';
1893
+ html += '<div class="detail-label">\u{1F4CB} Controller Info</div>';
1912
1894
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
1913
1895
  html += '<div style="font-weight:600;margin-bottom:4px">' + controllerInfo.className + '</div>';
1914
1896
  html += '<div style="font-size:11px;color:var(--text2)">extends ' + controllerInfo.parentClass + '</div>';
@@ -1922,7 +1904,7 @@ export class PageMapGenerator {
1922
1904
 
1923
1905
  if (!pageInfo && !actionDetails) {
1924
1906
  html += '<div style="padding:12px;color:var(--text2);font-size:12px;background:var(--bg3);border-radius:6px;margin-top:8px">';
1925
- html += '⚠️ No detailed action information found. The controller or action may not be analyzed yet.';
1907
+ html += '\u26A0\uFE0F No detailed action information found. The controller or action may not be analyzed yet.';
1926
1908
  html += '</div>';
1927
1909
  }
1928
1910
 
@@ -1971,7 +1953,7 @@ export class PageMapGenerator {
1971
1953
  html += '</div></div>';
1972
1954
  }
1973
1955
 
1974
- showModal('📄 ' + view.controller + '/' + view.action, html);
1956
+ showModal('\u{1F4C4} ' + view.controller + '/' + view.action, html);
1975
1957
  }
1976
1958
 
1977
1959
  function toggleGroup(el) {
@@ -1991,14 +1973,14 @@ export class PageMapGenerator {
1991
1973
  hiddenItems.forEach(item => {
1992
1974
  item.style.display = 'none';
1993
1975
  });
1994
- moreBtn.innerHTML = ' Show ' + hiddenItems.length + ' more variables';
1976
+ moreBtn.innerHTML = '\u25BC Show ' + hiddenItems.length + ' more variables';
1995
1977
  moreBtn.setAttribute('data-expanded', 'false');
1996
1978
  } else {
1997
1979
  // Expand: show all items
1998
1980
  hiddenItems.forEach(item => {
1999
1981
  item.style.display = '';
2000
1982
  });
2001
- moreBtn.innerHTML = ' Show less';
1983
+ moreBtn.innerHTML = '\u25B2 Show less';
2002
1984
  moreBtn.setAttribute('data-expanded', 'true');
2003
1985
  }
2004
1986
  }
@@ -2077,11 +2059,11 @@ export class PageMapGenerator {
2077
2059
  // Parse operations to extract path info and depth
2078
2060
  const parsedOps = graphqlOps.map(df => {
2079
2061
  const rawName = df.operationName || '';
2080
- // Pattern: " QueryName (via HookA)" or " QueryName (via HookA)" etc.
2081
- const arrowCount = (rawName.match(/→/g) || []).length;
2062
+ // Pattern: "\u2192 QueryName (via HookA)" or "\u2192 \u2192 QueryName (via HookA)" etc.
2063
+ const arrowCount = (rawName.match(/\u2192/g) || []).length;
2082
2064
 
2083
2065
  // Extract query name and path
2084
- let queryName = rawName.replace(/^[→\\s]+/, '').replace(/^\\u2192\\s*/g, '');
2066
+ let queryName = rawName.replace(/^[\u2192\\s]+/, '').replace(/^\\u2192\\s*/g, '');
2085
2067
  let sourcePath = '';
2086
2068
 
2087
2069
  // Extract "(via X)" for hook
@@ -2099,7 +2081,7 @@ export class PageMapGenerator {
2099
2081
  }
2100
2082
 
2101
2083
  // Further clean the query name
2102
- queryName = queryName.replace(/^[→\\s]+/, '').trim();
2084
+ queryName = queryName.replace(/^[\u2192\\s]+/, '').trim();
2103
2085
 
2104
2086
  return {
2105
2087
  ...df,
@@ -2162,7 +2144,7 @@ export class PageMapGenerator {
2162
2144
 
2163
2145
  sortedPaths.forEach(pathName => {
2164
2146
  const ops = groupedByPath.get(pathName);
2165
- const depthIndicator = pathName === 'Direct' ? '' : ' ';
2147
+ const depthIndicator = pathName === 'Direct' ? '' : '\u21B3 ';
2166
2148
  const pathLabel = pathName === 'Direct' ? 'Direct (this page)' : pathName;
2167
2149
 
2168
2150
  // Path header with depth visual
@@ -2360,7 +2342,7 @@ export class PageMapGenerator {
2360
2342
  const groupPages = groups[g];
2361
2343
  html += '<div style="margin-bottom:12px">';
2362
2344
  html += '<div class="detail-item" style="cursor:pointer;background:var(--bg3);border-radius:4px" onclick="toggleGroupList(this)">';
2363
- html += '<div class="detail-label" style="display:flex;align-items:center;gap:6px"><span class="group-toggle">▸</span>/'+g+'</div>';
2345
+ html += '<div class="detail-label" style="display:flex;align-items:center;gap:6px"><span class="group-toggle">\u25B8</span>/'+g+'</div>';
2364
2346
  html += '<span style="color:var(--accent)">'+groupPages.length+' pages</span></div>';
2365
2347
  html += '<div class="group-page-list" style="display:none;margin-left:16px;margin-top:4px">';
2366
2348
  groupPages.sort((a,b) => a.path.localeCompare(b.path)).forEach(p => {
@@ -2386,10 +2368,10 @@ export class PageMapGenerator {
2386
2368
  const toggle = el.querySelector('.group-toggle');
2387
2369
  if (list.style.display === 'none') {
2388
2370
  list.style.display = 'block';
2389
- toggle.textContent = '';
2371
+ toggle.textContent = '\u25BE';
2390
2372
  } else {
2391
2373
  list.style.display = 'none';
2392
- toggle.textContent = '';
2374
+ toggle.textContent = '\u25B8';
2393
2375
  }
2394
2376
  };
2395
2377
 
@@ -2434,7 +2416,7 @@ export class PageMapGenerator {
2434
2416
  const indent = depth * 12;
2435
2417
  let html = '<div class="rel-item" style="padding-left:'+(10+indent)+'px" onclick="event.stopPropagation(); selectPage(\\''+page.path+'\\')">';
2436
2418
  html += '<div class="rel-header">';
2437
- html += '<span style="color:var(--text2);font-size:10px">'+''.repeat(depth > 0 ? 1 : 0)+(depth > 0 ? ' ' : '')+'</span>';
2419
+ html += '<span style="color:var(--text2);font-size:10px">'+'\u2500'.repeat(depth > 0 ? 1 : 0)+(depth > 0 ? ' ' : '')+'</span>';
2438
2420
  html += '<span class="rel-path">'+page.path+'</span>';
2439
2421
  if (page.children && page.children.length > 0) {
2440
2422
  html += '<span style="color:var(--text2);font-size:9px;margin-left:auto">'+page.children.length+' children</span>';
@@ -2616,12 +2598,12 @@ export class PageMapGenerator {
2616
2598
  };
2617
2599
 
2618
2600
  function showDataDetail(rawName, sourcePath) {
2619
- // Clean up name: remove " " prefix and " (ComponentName)" suffix
2601
+ // Clean up name: remove "\u2192 " prefix and " (ComponentName)" suffix
2620
2602
  const name = rawName
2621
- .replace(/^[→\\->\\s]+/, '')
2603
+ .replace(/^[\u2192\\->\\s]+/, '')
2622
2604
  .replace(/\\s*\\([^)]+\\)\\s*$/, '');
2623
2605
 
2624
- // Convert SCREAMING_CASE to PascalCase (e.g., COMPANY_QUERY CompanyQuery)
2606
+ // Convert SCREAMING_CASE to PascalCase (e.g., COMPANY_QUERY \u2192 CompanyQuery)
2625
2607
  const toPascalCase = (str) => {
2626
2608
  if (!/^[A-Z][A-Z0-9_]*$/.test(str)) return str;
2627
2609
  return str.toLowerCase().split('_').map(word =>
@@ -2678,7 +2660,7 @@ export class PageMapGenerator {
2678
2660
  }
2679
2661
 
2680
2662
  // Operation Name with copy button
2681
- html += '<div class="detail-section"><h4 style="display:flex;justify-content:space-between;align-items:center">Operation Name<button class="copy-btn" onclick="copyToClipboard(\\''+op.name+'\\', this)" title="Copy operation name">📋</button></h4>';
2663
+ html += '<div class="detail-section"><h4 style="display:flex;justify-content:space-between;align-items:center">Operation Name<button class="copy-btn" onclick="copyToClipboard(\\''+op.name+'\\', this)" title="Copy operation name">\u{1F4CB}</button></h4>';
2682
2664
  html += '<code style="background:#0f172a;color:#93c5fd;padding:4px 8px;border-radius:4px;font-family:monospace">'+op.name+'</code></div>';
2683
2665
 
2684
2666
  if (op.returnType) {
@@ -2697,7 +2679,7 @@ export class PageMapGenerator {
2697
2679
  // Escape for data attribute
2698
2680
  const gqlCodeEscaped = gqlCode.replace(/'/g, "\\\\'").replace(/"/g, '&quot;');
2699
2681
 
2700
- html += '<div class="detail-section"><h4 style="display:flex;justify-content:space-between;align-items:center">GraphQL<button class="copy-btn" onclick="copyGqlCode(this)" data-code="'+gqlCodeEscaped+'" title="Copy GraphQL">📋</button></h4>';
2682
+ html += '<div class="detail-section"><h4 style="display:flex;justify-content:space-between;align-items:center">GraphQL<button class="copy-btn" onclick="copyGqlCode(this)" data-code="'+gqlCodeEscaped+'" title="Copy GraphQL">\u{1F4CB}</button></h4>';
2701
2683
  html += '<pre style="background:#0f172a;color:#e2e8f0;padding:12px;border-radius:6px;font-size:11px;overflow-x:auto;white-space:pre;max-height:300px;overflow-y:auto">' + gqlCode + '</pre></div>';
2702
2684
  } else if (op.variables?.length) {
2703
2685
  html += '<div class="detail-section"><h4>Variables</h4>';
@@ -2709,7 +2691,7 @@ export class PageMapGenerator {
2709
2691
  op.usedIn.slice(0,8).forEach(f => { html += '<div class="detail-item">'+f+'</div>'; });
2710
2692
  if (op.usedIn.length > 8) {
2711
2693
  const remaining = op.usedIn.slice(8);
2712
- html += '<div class="expand-more" onclick="expandMore(\\'usedIn\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:11px;cursor:pointer;padding:4px 0">▸ Show '+(op.usedIn.length-8)+' more files</div>';
2694
+ html += '<div class="expand-more" onclick="expandMore(\\'usedIn\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:11px;cursor:pointer;padding:4px 0">\u25B8 Show '+(op.usedIn.length-8)+' more files</div>';
2713
2695
  }
2714
2696
  html += '</div>';
2715
2697
  }
@@ -2752,7 +2734,7 @@ export class PageMapGenerator {
2752
2734
  });
2753
2735
  if (queries.length > 5) {
2754
2736
  const remaining = queries.slice(5).map(o => ({name: o.name}));
2755
- html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">▸ Show ' + (queries.length - 5) + ' more queries</div>';
2737
+ html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (queries.length - 5) + ' more queries</div>';
2756
2738
  }
2757
2739
  }
2758
2740
 
@@ -2764,7 +2746,7 @@ export class PageMapGenerator {
2764
2746
  });
2765
2747
  if (mutations.length > 5) {
2766
2748
  const remaining = mutations.slice(5).map(o => ({name: o.name}));
2767
- html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">▸ Show ' + (mutations.length - 5) + ' more mutations</div>';
2749
+ html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (mutations.length - 5) + ' more mutations</div>';
2768
2750
  }
2769
2751
  }
2770
2752
 
@@ -2776,7 +2758,7 @@ export class PageMapGenerator {
2776
2758
  });
2777
2759
  if (fragments.length > 3) {
2778
2760
  const remaining = fragments.slice(3).map(o => ({name: o.name}));
2779
- html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">▸ Show ' + (fragments.length - 3) + ' more fragments</div>';
2761
+ html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (fragments.length - 3) + ' more fragments</div>';
2780
2762
  }
2781
2763
  }
2782
2764
 
@@ -2799,9 +2781,9 @@ export class PageMapGenerator {
2799
2781
  html += '</div>';
2800
2782
  }
2801
2783
  } else {
2802
- // Clean up the name: remove " " prefix and " (ComponentName)" suffix
2784
+ // Clean up the name: remove "\u2192 " prefix and " (ComponentName)" suffix
2803
2785
  let cleanName = name
2804
- .replace(/^[→\\->\\s]+/, '') // Remove arrow prefix
2786
+ .replace(/^[\u2192\\->\\s]+/, '') // Remove arrow prefix
2805
2787
  .replace(/\\s*\\([^)]+\\)\\s*$/, ''); // Remove parenthetical component reference
2806
2788
 
2807
2789
  // Extract the core component name - remove ALL common suffixes iteratively
@@ -2892,7 +2874,7 @@ export class PageMapGenerator {
2892
2874
  });
2893
2875
  if (queries.length > 8) {
2894
2876
  const remaining = queries.slice(8).map(o => ({name: o.name}));
2895
- html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">▸ Show ' + (queries.length - 8) + ' more</div>';
2877
+ html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (queries.length - 8) + ' more</div>';
2896
2878
  }
2897
2879
  }
2898
2880
 
@@ -2904,7 +2886,7 @@ export class PageMapGenerator {
2904
2886
  });
2905
2887
  if (mutations.length > 5) {
2906
2888
  const remaining = mutations.slice(5).map(o => ({name: o.name}));
2907
- html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">▸ Show ' + (mutations.length - 5) + ' more</div>';
2889
+ html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (mutations.length - 5) + ' more</div>';
2908
2890
  }
2909
2891
  }
2910
2892
 
@@ -2916,7 +2898,7 @@ export class PageMapGenerator {
2916
2898
  });
2917
2899
  if (fragments.length > 3) {
2918
2900
  const remaining = fragments.slice(3).map(o => ({name: o.name}));
2919
- html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">▸ Show ' + (fragments.length - 3) + ' more</div>';
2901
+ html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" style="color:var(--accent);font-size:10px;cursor:pointer;padding:4px 0">\u25B8 Show ' + (fragments.length - 3) + ' more</div>';
2920
2902
  }
2921
2903
  }
2922
2904
 
@@ -2973,7 +2955,7 @@ export class PageMapGenerator {
2973
2955
  window.copyToClipboard = function(text, btn) {
2974
2956
  navigator.clipboard.writeText(text).then(() => {
2975
2957
  const originalText = btn.textContent;
2976
- btn.textContent = '';
2958
+ btn.textContent = '\u2713';
2977
2959
  btn.classList.add('copied');
2978
2960
  setTimeout(() => {
2979
2961
  btn.textContent = originalText;
@@ -3460,103 +3442,85 @@ export class PageMapGenerator {
3460
3442
  </script>
3461
3443
  </body>
3462
3444
  </html>`;
3463
- }
3464
- buildTreeHtml(groups, allPages) {
3465
- const colors = [
3466
- '#ef4444',
3467
- '#f97316',
3468
- '#eab308',
3469
- '#22c55e',
3470
- '#14b8a6',
3471
- '#3b82f6',
3472
- '#8b5cf6',
3473
- '#ec4899',
3474
- ];
3475
- let idx = 0;
3476
- return Array.from(groups.entries())
3477
- .sort((a, b) => a[0].localeCompare(b[0]))
3478
- .map(([name, pages]) => {
3479
- const color = colors[idx++ % colors.length];
3480
- const sorted = pages.sort((a, b) => a.path.localeCompare(b.path));
3481
- // Build depth map based on actual parent-child relationships
3482
- const pathSet = new Set(sorted.map((p) => p.path));
3483
- const depthMap = new Map();
3484
- // Calculate depth for each page based on closest existing ancestor
3485
- for (const p of sorted) {
3486
- const segments = p.path.split('/').filter(Boolean);
3487
- let depth = 0;
3488
- // Find closest existing ancestor
3489
- for (let i = segments.length - 1; i >= 1; i--) {
3490
- const ancestorPath = '/' + segments.slice(0, i).join('/');
3491
- if (pathSet.has(ancestorPath)) {
3492
- depth = (depthMap.get(ancestorPath) ?? 0) + 1;
3493
- break;
3494
- }
3495
- }
3496
- depthMap.set(p.path, depth);
3497
- }
3498
- const pagesHtml = sorted
3499
- .map((p) => {
3500
- const type = this.getPageType(p.path);
3501
- const queries = (p.dataFetching || []).filter((d) => !d.type?.includes('Mutation')).length;
3502
- const mutations = (p.dataFetching || []).filter((d) => d.type?.includes('Mutation')).length;
3503
- const depth = depthMap.get(p.path) ?? 0;
3504
- const pageNode = p;
3505
- const repoName = pageNode.repo || '';
3506
- // Only show repo tag if there are multiple repositories
3507
- const showRepoTag = allPages.some((pg) => pg.repo && pg.repo !== repoName);
3508
- // Create short name: take last part or abbreviate long names
3509
- const shortRepoName = repoName
3510
- .split('/')
3511
- .pop()
3512
- ?.split('-')
3513
- .map((s) => s.substring(0, 4))
3514
- .join('-') || repoName.substring(0, 8);
3515
- const repoTag = showRepoTag && repoName
3516
- ? `<span class="tag tag-repo" title="${repoName}">${shortRepoName}</span>`
3517
- : '';
3518
- // Detect SPA component pages (PascalCase path or in components/pages)
3519
- const isSpaComponent = /^\/[A-Z]/.test(p.path) || (p.filePath && p.filePath.includes('components/pages'));
3520
- const displayPath = isSpaComponent && p.filePath
3521
- ? p.filePath.replace(/\.tsx?$/, '').replace(/^(frontend\/src\/|src\/)/, '')
3522
- : p.path;
3523
- const spaTag = isSpaComponent
3524
- ? '<span class="tag" style="background:#6366f1;color:white" title="SPA Component Page">SPA</span>'
3525
- : '';
3526
- return `<div class="page-item" data-path="${p.path}" data-repo="${repoName}" onclick="selectPage('${p.path}')" style="--depth:${depth}">
3445
+ }
3446
+ buildTreeHtml(groups, allPages) {
3447
+ const colors = [
3448
+ "#ef4444",
3449
+ "#f97316",
3450
+ "#eab308",
3451
+ "#22c55e",
3452
+ "#14b8a6",
3453
+ "#3b82f6",
3454
+ "#8b5cf6",
3455
+ "#ec4899"
3456
+ ];
3457
+ let idx = 0;
3458
+ return Array.from(groups.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([name, pages]) => {
3459
+ const color = colors[idx++ % colors.length];
3460
+ const sorted = pages.sort((a, b) => a.path.localeCompare(b.path));
3461
+ const pathSet = new Set(sorted.map((p) => p.path));
3462
+ const depthMap = /* @__PURE__ */ new Map();
3463
+ for (const p of sorted) {
3464
+ const segments = p.path.split("/").filter(Boolean);
3465
+ let depth = 0;
3466
+ for (let i = segments.length - 1; i >= 1; i--) {
3467
+ const ancestorPath = "/" + segments.slice(0, i).join("/");
3468
+ if (pathSet.has(ancestorPath)) {
3469
+ depth = (depthMap.get(ancestorPath) ?? 0) + 1;
3470
+ break;
3471
+ }
3472
+ }
3473
+ depthMap.set(p.path, depth);
3474
+ }
3475
+ const pagesHtml = sorted.map((p) => {
3476
+ const type = this.getPageType(p.path);
3477
+ const queries = (p.dataFetching || []).filter(
3478
+ (d) => !d.type?.includes("Mutation")
3479
+ ).length;
3480
+ const mutations = (p.dataFetching || []).filter(
3481
+ (d) => d.type?.includes("Mutation")
3482
+ ).length;
3483
+ const depth = depthMap.get(p.path) ?? 0;
3484
+ const pageNode = p;
3485
+ const repoName = pageNode.repo || "";
3486
+ const showRepoTag = allPages.some(
3487
+ (pg) => pg.repo && pg.repo !== repoName
3488
+ );
3489
+ const shortRepoName = repoName.split("/").pop()?.split("-").map((s) => s.substring(0, 4)).join("-") || repoName.substring(0, 8);
3490
+ const repoTag = showRepoTag && repoName ? `<span class="tag tag-repo" title="${repoName}">${shortRepoName}</span>` : "";
3491
+ const isSpaComponent = /^\/[A-Z]/.test(p.path) || p.filePath && p.filePath.includes("components/pages");
3492
+ const displayPath = isSpaComponent && p.filePath ? p.filePath.replace(/\.tsx?$/, "").replace(/^(frontend\/src\/|src\/)/, "") : p.path;
3493
+ const spaTag = isSpaComponent ? '<span class="tag" style="background:#6366f1;color:white" title="SPA Component Page">SPA</span>' : "";
3494
+ return `<div class="page-item" data-path="${p.path}" data-repo="${repoName}" onclick="selectPage('${p.path}')" style="--depth:${depth}">
3527
3495
  <span class="page-type" style="--type-color:${type.color}">${type.label}</span>
3528
3496
  <span class="page-path">${displayPath}</span>
3529
3497
  <div class="page-tags">
3530
3498
  ${repoTag}
3531
3499
  ${spaTag}
3532
- ${p.authentication?.required ? '<span class="tag tag-auth">AUTH</span>' : ''}
3533
- ${queries > 0 ? `<span class="tag tag-query">Q:${queries}</span>` : ''}
3534
- ${mutations > 0 ? `<span class="tag tag-mutation">M:${mutations}</span>` : ''}
3500
+ ${p.authentication?.required ? '<span class="tag tag-auth">AUTH</span>' : ""}
3501
+ ${queries > 0 ? `<span class="tag tag-query">Q:${queries}</span>` : ""}
3502
+ ${mutations > 0 ? `<span class="tag tag-mutation">M:${mutations}</span>` : ""}
3535
3503
  </div>
3536
3504
  </div>`;
3537
- })
3538
- .join('');
3539
- return `<div class="group">
3505
+ }).join("");
3506
+ return `<div class="group">
3540
3507
  <div class="group-header" onclick="toggleGroup(this)" style="--group-color:${color}">
3541
- <span class="group-arrow">▼</span>
3508
+ <span class="group-arrow">\u25BC</span>
3542
3509
  <span class="group-name">/${name}</span>
3543
3510
  <span class="group-count">${pages.length}</span>
3544
3511
  </div>
3545
3512
  <div class="group-content">${pagesHtml}</div>
3546
3513
  </div>`;
3547
- })
3548
- .join('');
3549
- }
3550
- getPageType(path) {
3551
- const last = path.split('/').filter(Boolean).pop() || '';
3552
- if (last === 'new' || path.endsWith('/new'))
3553
- return { label: 'CREATE', color: '#22c55e' };
3554
- if (last === 'edit' || path.includes('/edit'))
3555
- return { label: 'EDIT', color: '#f59e0b' };
3556
- if (last.startsWith('[') || last.startsWith(':'))
3557
- return { label: 'DETAIL', color: '#3b82f6' };
3558
- if (path.includes('setting'))
3559
- return { label: 'SETTINGS', color: '#6b7280' };
3560
- return { label: 'LIST', color: '#06b6d4' };
3561
- }
3562
- }
3514
+ }).join("");
3515
+ }
3516
+ getPageType(path) {
3517
+ const last = path.split("/").filter(Boolean).pop() || "";
3518
+ if (last === "new" || path.endsWith("/new")) return { label: "CREATE", color: "#22c55e" };
3519
+ if (last === "edit" || path.includes("/edit")) return { label: "EDIT", color: "#f59e0b" };
3520
+ if (last.startsWith("[") || last.startsWith(":")) return { label: "DETAIL", color: "#3b82f6" };
3521
+ if (path.includes("setting")) return { label: "SETTINGS", color: "#6b7280" };
3522
+ return { label: "LIST", color: "#06b6d4" };
3523
+ }
3524
+ };
3525
+
3526
+ export { PageMapGenerator };