@wtdlee/repomap 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/analyzers/index.d.ts +69 -5
  2. package/dist/analyzers/index.js +1 -5
  3. package/dist/{server/doc-server.js → chunk-4K4MGTPV.js} +41 -329
  4. package/dist/chunk-6F4PWJZI.js +0 -0
  5. package/dist/chunk-J2CM7T7U.js +1 -0
  6. package/dist/{generators/page-map-generator.js → chunk-MOEA75XK.js} +278 -503
  7. package/dist/{generators/rails-map-generator.js → chunk-SL2GMDBN.js} +48 -129
  8. package/dist/chunk-UJT5KTVK.js +36 -0
  9. package/dist/chunk-VV3A3UE3.js +1 -0
  10. package/dist/chunk-XWZH2RDG.js +19 -0
  11. package/dist/cli.d.ts +0 -1
  12. package/dist/cli.js +29 -499
  13. package/dist/dataflow-analyzer-BfAiqVUp.d.ts +180 -0
  14. package/dist/env-detector-BIWJ7OYF.js +1 -0
  15. package/dist/generators/assets/common.css +564 -23
  16. package/dist/generators/index.d.ts +431 -3
  17. package/dist/generators/index.js +1 -3
  18. package/dist/index.d.ts +53 -10
  19. package/dist/index.js +1 -11
  20. package/dist/page-map-generator-XNZ4TDJT.js +1 -0
  21. package/dist/rails-TJCDGBBF.js +1 -0
  22. package/dist/rails-map-generator-JL5PKHYP.js +1 -0
  23. package/dist/server/index.d.ts +33 -1
  24. package/dist/server/index.js +1 -1
  25. package/dist/types.d.ts +39 -37
  26. package/dist/types.js +1 -5
  27. package/package.json +4 -2
  28. package/dist/analyzers/base-analyzer.d.ts +0 -45
  29. package/dist/analyzers/base-analyzer.js +0 -47
  30. package/dist/analyzers/dataflow-analyzer.d.ts +0 -29
  31. package/dist/analyzers/dataflow-analyzer.js +0 -425
  32. package/dist/analyzers/graphql-analyzer.d.ts +0 -22
  33. package/dist/analyzers/graphql-analyzer.js +0 -386
  34. package/dist/analyzers/pages-analyzer.d.ts +0 -84
  35. package/dist/analyzers/pages-analyzer.js +0 -1695
  36. package/dist/analyzers/rails/index.d.ts +0 -46
  37. package/dist/analyzers/rails/index.js +0 -145
  38. package/dist/analyzers/rails/rails-controller-analyzer.d.ts +0 -82
  39. package/dist/analyzers/rails/rails-controller-analyzer.js +0 -478
  40. package/dist/analyzers/rails/rails-grpc-analyzer.d.ts +0 -44
  41. package/dist/analyzers/rails/rails-grpc-analyzer.js +0 -262
  42. package/dist/analyzers/rails/rails-model-analyzer.d.ts +0 -88
  43. package/dist/analyzers/rails/rails-model-analyzer.js +0 -493
  44. package/dist/analyzers/rails/rails-react-analyzer.d.ts +0 -41
  45. package/dist/analyzers/rails/rails-react-analyzer.js +0 -529
  46. package/dist/analyzers/rails/rails-routes-analyzer.d.ts +0 -62
  47. package/dist/analyzers/rails/rails-routes-analyzer.js +0 -540
  48. package/dist/analyzers/rails/rails-view-analyzer.d.ts +0 -49
  49. package/dist/analyzers/rails/rails-view-analyzer.js +0 -386
  50. package/dist/analyzers/rails/ruby-parser.d.ts +0 -63
  51. package/dist/analyzers/rails/ruby-parser.js +0 -212
  52. package/dist/analyzers/rest-api-analyzer.d.ts +0 -65
  53. package/dist/analyzers/rest-api-analyzer.js +0 -479
  54. package/dist/core/cache.d.ts +0 -47
  55. package/dist/core/cache.js +0 -151
  56. package/dist/core/engine.d.ts +0 -46
  57. package/dist/core/engine.js +0 -319
  58. package/dist/core/index.d.ts +0 -2
  59. package/dist/core/index.js +0 -2
  60. package/dist/generators/markdown-generator.d.ts +0 -25
  61. package/dist/generators/markdown-generator.js +0 -782
  62. package/dist/generators/mermaid-generator.d.ts +0 -35
  63. package/dist/generators/mermaid-generator.js +0 -364
  64. package/dist/generators/page-map-generator.d.ts +0 -22
  65. package/dist/generators/rails-map-generator.d.ts +0 -21
  66. package/dist/server/doc-server.d.ts +0 -30
  67. package/dist/utils/env-detector.d.ts +0 -31
  68. package/dist/utils/env-detector.js +0 -188
  69. package/dist/utils/parallel.d.ts +0 -23
  70. package/dist/utils/parallel.js +0 -70
  71. package/dist/utils/port.d.ts +0 -15
  72. package/dist/utils/port.js +0 -41
@@ -1,142 +1,4 @@
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,
46
- });
47
- }
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
- }
95
- }
96
- }
97
- const rootPages = pages.filter((p) => !p.parent).sort((a, b) => a.path.localeCompare(b.path));
98
- return { rootPages, relations };
99
- }
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>
1
+ var S=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,r){let c=[],v=r?.envResult,t=r?.railsAnalysis,l=r?.activeTab||"pages",e=s.repositories[0]?.displayName||s.repositories[0]?.name||"Repository";for(let p of s.repositories){this.graphqlOps.push(...p.analysis?.graphqlOperations||[]),this.apiCalls.push(...p.analysis?.apiCalls||[]);let f=p.analysis?.components||[];for(let a of f)this.components.push({name:a.name,filePath:a.filePath,type:a.type,dependencies:a.dependencies||[]});}for(let p of s.repositories){let f=p.analysis?.pages||[];for(let a of f)c.push({...a,repo:p.name,children:[],parent:null,depth:0});}let{rootPages:i,relations:o}=this.buildHierarchy(c);return this.renderPageMapHtml(c,i,o,e,{envResult:v,railsAnalysis:t,activeTab:l})}buildHierarchy(s){let r=new Map,c=[];for(let t of s)r.set(t.path,t);for(let t of s){let l=t.path.split("/").filter(Boolean);for(let e=l.length-1;e>=1;e--){let i="/"+l.slice(0,e).join("/"),o=r.get(i);if(o){t.parent=i,t.depth=o.depth+1,o.children.includes(t.path)||o.children.push(t.path),c.push({from:i,to:t.path,type:"parent-child",description:`Sub-page of ${i}`});break}}if(t.parent||(t.depth=Math.max(0,l.length-1)),t.layout)for(let e of s)e.path!==t.path&&e.layout===t.layout&&(c.find(o=>o.type==="same-layout"&&(o.from===t.path&&o.to===e.path||o.from===e.path&&o.to===t.path))||c.push({from:t.path,to:e.path,type:"same-layout",description:`Both use ${t.layout}`}));}return {rootPages:s.filter(t=>!t.parent).sort((t,l)=>t.path.localeCompare(l.path)),relations:c}}renderPageMapHtml(s,r,c,v,t){let l=t?.envResult,e=t?.railsAnalysis,i=t?.activeTab||"pages",o=JSON.stringify(this.graphqlOps.map(d=>({name:d.name,type:d.type,variables:d.variables,fields:d.fields,returnType:d.returnType,usedIn:d.usedIn}))),p=JSON.stringify(this.components),f=e?JSON.stringify(e.routes.routes):"[]",a=e?JSON.stringify(e.controllers.controllers):"[]",m=e?JSON.stringify(e.models.models):"[]",u=e?JSON.stringify(e.views):'{ "views": [], "pages": [], "summary": {} }',h=e?JSON.stringify(e.react):'{ "components": [], "entryPoints": [], "summary": {} }',y=e?JSON.stringify(e.grpc):'{ "services": [] }',P=e?JSON.stringify(e.summary):"null",n=l?.hasRails||false,w=l?.hasNextjs||false,C=l?.hasReact||false,x=new Map;for(let d of s){let b=d.path.split("/").filter(Boolean)[0]||"root";x.has(b)||x.set(b,[]),x.get(b)?.push(d);}return `<!DOCTYPE html>
140
2
  <html lang="en">
141
3
  <head>
142
4
  <meta charset="UTF-8">
@@ -147,23 +9,21 @@ export class PageMapGenerator {
147
9
  <body>
148
10
  <header class="header">
149
11
  <div style="display:flex;align-items:center;gap:24px">
150
- <h1 style="cursor:pointer" onclick="location.href='/'">📊 ${repoName}</h1>
12
+ <h1 style="cursor:pointer" onclick="location.href='/'">\u{1F4CA} ${v}</h1>
151
13
  <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>` : ''}
14
+ <a href="/page-map" class="nav-link ${i==="pages"?"active":""}">Page Map</a>
15
+ ${n?`<a href="/rails-map" class="nav-link ${i==="rails"?"active":""}">Rails Map</a>`:""}
154
16
  <a href="/docs" class="nav-link">Docs</a>
155
17
  <a href="/api/report" class="nav-link" target="_blank">API</a>
156
18
  </nav>
157
19
  </div>
158
20
  <div style="display:flex;gap:12px;align-items:center">
159
21
  <!-- Environment filter badges -->
160
- ${hasRails && hasNextjs
161
- ? `<div class="env-filters" style="display:flex;gap:4px;margin-right:8px">
22
+ ${n&&w?`<div class="env-filters" style="display:flex;gap:4px;margin-right:8px">
162
23
  <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
- : ''}
24
+ <button class="env-badge" data-env="nextjs" onclick="filterByEnv('nextjs')">\u269B\uFE0F Next.js</button>
25
+ <button class="env-badge" data-env="rails" onclick="filterByEnv('rails')">\u{1F6E4}\uFE0F Rails</button>
26
+ </div>`:""}
167
27
  <input class="search" type="text" placeholder="Search pages, queries..." oninput="filter(this.value)">
168
28
  <div class="tabs">
169
29
  <button class="tab active" onclick="setView('tree')">List</button>
@@ -198,42 +58,40 @@ export class PageMapGenerator {
198
58
  <!-- Frontend Stats -->
199
59
  <h3 style="margin-top:16px;font-size:10px;text-transform:uppercase;color:var(--text2);letter-spacing:1px">Frontend</h3>
200
60
  <div class="stats" id="stats-container">
201
- <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>
61
+ <div class="stat" data-filter="pages"><div class="stat-val">${s.length}</div><div class="stat-label">Pages</div></div>
62
+ <div class="stat" data-filter="hierarchies"><div class="stat-val">${c.filter(d=>d.type==="parent-child").length}</div><div class="stat-label">Hierarchies</div></div>
203
63
  <div class="stat" data-filter="graphql"><div class="stat-val">${this.graphqlOps.length}</div><div class="stat-label">GraphQL</div></div>
204
64
  <div class="stat" data-filter="restapi"><div class="stat-val">${this.apiCalls.length}</div><div class="stat-label">REST API</div></div>
205
65
  </div>
206
66
 
207
- ${hasRails && railsAnalysis
208
- ? `
67
+ ${n&&e?`
209
68
  <!-- Rails Stats -->
210
69
  <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
70
  <div class="stats" id="rails-stats">
212
- <div class="stat" data-filter="rails-routes" onclick="switchToRailsTab()"><div class="stat-val">${railsAnalysis.summary.totalRoutes}</div><div class="stat-label">Routes</div></div>
213
- <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
- <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
- <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>
71
+ <div class="stat" data-filter="rails-routes" onclick="switchToRailsTab()"><div class="stat-val">${e.summary.totalRoutes}</div><div class="stat-label">Routes</div></div>
72
+ <div class="stat" data-filter="rails-controllers" onclick="showRailsControllers(); this.blur();"><div class="stat-val">${e.summary.totalControllers}</div><div class="stat-label">Controllers</div></div>
73
+ <div class="stat" data-filter="rails-models" onclick="showRailsModels(); this.blur();"><div class="stat-val">${e.summary.totalModels}</div><div class="stat-label">Models</div></div>
74
+ <div class="stat" data-filter="rails-grpc" onclick="showRailsGrpc(); this.blur();"><div class="stat-val">${e.summary.totalGrpcServices}</div><div class="stat-label">gRPC</div></div>
75
+ <div class="stat" data-filter="rails-react" onclick="showReactComponents(); this.blur();"><div class="stat-val">${e.summary.totalReactComponents}</div><div class="stat-label">\u269B React</div></div>
217
76
  </div>
218
- `
219
- : ''}
77
+ `:""}
220
78
  </aside>
221
79
 
222
80
  <div class="content">
223
81
  <!-- 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' : ''}">
82
+ <div class="tree-view ${i==="pages"?"active":""}" id="tree-view" data-tab="pages">
83
+ ${s.length>0?this.buildTreeHtml(x,s):""}
84
+ <div id="page-map-react-components-section" style="${n?"margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px":""}">
227
85
  </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>' : ''}
86
+ <div id="page-map-rails-section" style="${s.length>0&&n?"margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px":""}">
87
+ ${n&&s.length===0?'<div class="empty-state-sm">Loading screens...</div>':""}
230
88
  </div>
231
89
  </div>
232
90
 
233
91
  <!-- Rails Routes View (dedicated) -->
234
- <div class="tree-view ${activeTab === 'rails' ? 'active' : ''}" id="rails-tree-view" data-tab="rails">
92
+ <div class="tree-view ${i==="rails"?"active":""}" id="rails-tree-view" data-tab="rails">
235
93
  <div id="rails-routes-container">
236
- ${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>'}
94
+ ${n?'<div class="empty-state-sm">Loading Rails routes...</div>':'<div class="empty-state">No Rails environment detected</div>'}
237
95
  </div>
238
96
  </div>
239
97
 
@@ -254,7 +112,7 @@ export class PageMapGenerator {
254
112
  <div class="detail" id="detail">
255
113
  <div class="detail-header">
256
114
  <div class="detail-title" id="detail-title"></div>
257
- <button class="detail-close" onclick="closeDetail()">×</button>
115
+ <button class="detail-close" onclick="closeDetail()">\xD7</button>
258
116
  </div>
259
117
  <div class="detail-body" id="detail-body"></div>
260
118
  </div>
@@ -263,10 +121,10 @@ export class PageMapGenerator {
263
121
  <div class="modal-box">
264
122
  <div class="modal-head">
265
123
  <div style="display:flex;align-items:center;gap:8px">
266
- <button id="modal-back" class="modal-back" onclick="modalBack()" style="display:none">←</button>
124
+ <button id="modal-back" class="modal-back" onclick="modalBack()" style="display:none">\u2190</button>
267
125
  <h3 id="modal-title"></h3>
268
126
  </div>
269
- <button class="modal-close" onclick="closeModal()">×</button>
127
+ <button class="modal-close" onclick="closeModal()">\xD7</button>
270
128
  </div>
271
129
  <div class="modal-body" id="modal-body"></div>
272
130
  </div>
@@ -275,16 +133,16 @@ export class PageMapGenerator {
275
133
  <script>
276
134
  // Environment detection results
277
135
  const envInfo = {
278
- hasRails: ${hasRails},
279
- hasNextjs: ${hasNextjs},
280
- hasReact: ${hasReact}
136
+ hasRails: ${n},
137
+ hasNextjs: ${w},
138
+ hasReact: ${C}
281
139
  };
282
140
 
283
141
  // Frontend data
284
- const pages = ${JSON.stringify(allPages)};
285
- const relations = ${JSON.stringify(relations)};
286
- const graphqlOps = ${graphqlOpsJson};
287
- const components = ${componentsJson};
142
+ const pages = ${JSON.stringify(s)};
143
+ const relations = ${JSON.stringify(c)};
144
+ const graphqlOps = ${o};
145
+ const components = ${p};
288
146
  const apiCallsData = ${JSON.stringify(this.apiCalls)};
289
147
  window.apiCalls = apiCallsData;
290
148
  const pageMap = new Map(pages.map(p => [p.path, p]));
@@ -292,16 +150,16 @@ export class PageMapGenerator {
292
150
  const compMap = new Map(components.map(c => [c.name, c]));
293
151
 
294
152
  // Rails data (if available)
295
- const railsRoutes = ${railsRoutesJson};
296
- const railsControllers = ${railsControllersJson};
297
- const railsModels = ${railsModelsJson};
298
- const railsViews = ${railsViewsJson};
299
- const railsReact = ${railsReactJson};
300
- const railsGrpc = ${railsGrpcJson};
301
- const railsSummary = ${railsSummaryJson};
153
+ const railsRoutes = ${f};
154
+ const railsControllers = ${a};
155
+ const railsModels = ${m};
156
+ const railsViews = ${u};
157
+ const railsReact = ${h};
158
+ const railsGrpc = ${y};
159
+ const railsSummary = ${P};
302
160
 
303
161
  // Current active tab state
304
- let currentMainTab = '${activeTab}';
162
+ let currentMainTab = '${i}';
305
163
 
306
164
  // Modal history stack for back navigation
307
165
  const modalHistory = [];
@@ -395,17 +253,17 @@ export class PageMapGenerator {
395
253
  routesByNamespace.get(ns).push(r);
396
254
  });
397
255
 
398
- let html = '<div style="max-height:60vh;overflow-y:auto">';
256
+ let html = '<div class="max-h-60vh overflow-y-auto">';
399
257
  for (const [ns, routes] of routesByNamespace) {
400
258
  html += '<div style="margin-bottom:16px">';
401
- html += '<div style="font-weight:600;margin-bottom:8px;color:var(--accent)">📂 ' + ns + ' (' + routes.length + ')</div>';
259
+ html += '<div style="font-weight:600;margin-bottom:8px;color:var(--accent)">\u{1F4C2} ' + ns + ' (' + routes.length + ')</div>';
402
260
  html += '<table style="width:100%;border-collapse:collapse;font-size:12px">';
403
- 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>';
261
+ html += '<tr class="bg-surface"><th class="cell">Method</th><th class="cell">Path</th><th class="cell">Controller#Action</th></tr>';
404
262
  routes.slice(0, 20).forEach(r => {
405
263
  const methodColor = {GET:'#22c55e',POST:'#3b82f6',PUT:'#f59e0b',PATCH:'#f59e0b',DELETE:'#ef4444'}[r.method] || '#888';
406
264
  html += '<tr style="border-bottom:1px solid var(--border)">';
407
265
  html += '<td style="padding:6px"><span style="background:' + methodColor + ';color:white;padding:2px 6px;border-radius:3px;font-size:10px">' + r.method + '</span></td>';
408
- html += '<td style="padding:6px;font-family:monospace">' + r.path.replace(/:([a-z_]+)/g, '<span style="color:#f59e0b">:$1</span>') + '</td>';
266
+ html += '<td style="padding:6px;font-family:monospace">' + r.path.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>') + '</td>';
409
267
  html += '<td style="padding:6px;color:var(--text2)">' + r.controller + '#' + r.action + '</td>';
410
268
  html += '</tr>';
411
269
  });
@@ -416,7 +274,7 @@ export class PageMapGenerator {
416
274
  }
417
275
  html += '</div>';
418
276
 
419
- showModal('🛤️ Rails Routes (' + railsRoutes.length + ')', html);
277
+ showModal('\u{1F6E4}\uFE0F Rails Routes (' + railsRoutes.length + ')', html);
420
278
  }
421
279
 
422
280
  function showRailsControllers() {
@@ -425,13 +283,13 @@ export class PageMapGenerator {
425
283
  return;
426
284
  }
427
285
 
428
- let html = '<div style="max-height:60vh;overflow-y:auto">';
286
+ let html = '<div class="max-h-60vh overflow-y-auto">';
429
287
  railsControllers.forEach(ctrl => {
430
- html += '<div style="background:var(--bg3);padding:12px;border-radius:6px;margin-bottom:8px">';
431
- html += '<div style="font-weight:600;margin-bottom:4px">' + ctrl.className + '</div>';
432
- html += '<div style="font-size:11px;color:var(--text2);margin-bottom:8px">extends ' + ctrl.parentClass + '</div>';
288
+ html += '<div class="info-box">';
289
+ html += '<div class="section-title">' + ctrl.className + '</div>';
290
+ html += '<div class="hint mb-3">extends ' + ctrl.parentClass + '</div>';
433
291
  if (ctrl.actions && ctrl.actions.length > 0) {
434
- html += '<div style="display:flex;flex-wrap:wrap;gap:4px">';
292
+ html += '<div class="flex flex-wrap gap-1">';
435
293
  ctrl.actions.slice(0, 10).forEach(action => {
436
294
  const color = action.visibility === 'public' ? '#22c55e' : action.visibility === 'private' ? '#ef4444' : '#f59e0b';
437
295
  html += '<span style="background:rgba(255,255,255,0.1);padding:2px 8px;border-radius:4px;font-size:11px;border-left:2px solid ' + color + '">' + action.name + '</span>';
@@ -443,7 +301,7 @@ export class PageMapGenerator {
443
301
  });
444
302
  html += '</div>';
445
303
 
446
- showModal('🎮 Rails Controllers (' + railsControllers.length + ')', html);
304
+ showModal('\u{1F3AE} Rails Controllers (' + railsControllers.length + ')', html);
447
305
  }
448
306
 
449
307
  function showRailsModels() {
@@ -452,16 +310,16 @@ export class PageMapGenerator {
452
310
  return;
453
311
  }
454
312
 
455
- let html = '<div style="max-height:60vh;overflow-y:auto">';
313
+ let html = '<div class="max-h-60vh overflow-y-auto">';
456
314
  railsModels.forEach(model => {
457
- 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>';
459
- 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>';
315
+ html += '<div class="info-box">';
316
+ html += '<div class="section-title">\u{1F4E6} ' + model.className + '</div>';
317
+ html += '<div class="flex gap-3 hint mb-3">';
318
+ html += '<span>\u{1F4CE} ' + (model.associations?.length || 0) + ' associations</span>';
319
+ html += '<span>\u2713 ' + (model.validations?.length || 0) + ' validations</span>';
462
320
  html += '</div>';
463
321
  if (model.associations && model.associations.length > 0) {
464
- html += '<div style="display:flex;flex-wrap:wrap;gap:4px">';
322
+ html += '<div class="flex flex-wrap gap-1">';
465
323
  model.associations.slice(0, 8).forEach(assoc => {
466
324
  const typeColor = {belongs_to:'#3b82f6',has_many:'#22c55e',has_one:'#f59e0b'}[assoc.type] || '#888';
467
325
  html += '<span style="background:rgba(255,255,255,0.1);padding:2px 8px;border-radius:4px;font-size:10px"><span style="color:' + typeColor + '">' + assoc.type + '</span> :' + assoc.name + '</span>';
@@ -473,7 +331,7 @@ export class PageMapGenerator {
473
331
  });
474
332
  html += '</div>';
475
333
 
476
- showModal('📦 Rails Models (' + railsModels.length + ')', html);
334
+ showModal('\u{1F4E6} Rails Models (' + railsModels.length + ')', html);
477
335
  }
478
336
 
479
337
  function showReactComponents() {
@@ -487,34 +345,34 @@ export class PageMapGenerator {
487
345
  (b.usedIn?.length || 0) - (a.usedIn?.length || 0)
488
346
  );
489
347
 
490
- let html = '<div style="max-height:60vh;overflow-y:auto">';
348
+ let html = '<div class="max-h-60vh overflow-y-auto">';
491
349
 
492
350
  // Stats
493
351
  html += '<div style="display:flex;gap:16px;margin-bottom:16px;padding:12px;background:var(--bg3);border-radius:8px">';
494
- html += '<div style="text-align:center"><div style="font-size:20px;font-weight:bold;color:var(--accent)">' + railsReact.summary.totalComponents + '</div><div style="font-size:10px;color:var(--text2)">Components</div></div>';
495
- html += '<div style="text-align:center"><div style="font-size:20px;font-weight:bold;color:#22c55e">' + railsReact.summary.ssrComponents + '</div><div style="font-size:10px;color:var(--text2)">SSR</div></div>';
496
- html += '<div style="text-align:center"><div style="font-size:20px;font-weight:bold;color:#3b82f6">' + railsReact.summary.clientComponents + '</div><div style="font-size:10px;color:var(--text2)">Client</div></div>';
497
- html += '<div style="text-align:center"><div style="font-size:20px;font-weight:bold;color:#f59e0b">' + railsReact.summary.totalEntryPoints + '</div><div style="font-size:10px;color:var(--text2)">Entry Points</div></div>';
352
+ html += '<div class="text-center"><div style="font-size:20px;font-weight:bold;color:var(--accent)">' + railsReact.summary.totalComponents + '</div><div style="font-size:10px;color:var(--text2)">Components</div></div>';
353
+ html += '<div class="text-center"><div style="font-size:20px;font-weight:bold;color:#22c55e">' + railsReact.summary.ssrComponents + '</div><div style="font-size:10px;color:var(--text2)">SSR</div></div>';
354
+ html += '<div class="text-center"><div style="font-size:20px;font-weight:bold;color:#3b82f6">' + railsReact.summary.clientComponents + '</div><div style="font-size:10px;color:var(--text2)">Client</div></div>';
355
+ html += '<div class="text-center"><div style="font-size:20px;font-weight:bold;color:#f59e0b">' + railsReact.summary.totalEntryPoints + '</div><div style="font-size:10px;color:var(--text2)">Entry Points</div></div>';
498
356
  html += '</div>';
499
357
 
500
358
  sortedComponents.forEach(comp => {
501
359
  const usageCount = comp.usedIn?.length || 0;
502
- const ssrBadge = comp.ssr ? '<span style="margin-left:6px;font-size:9px;background:#22c55e;color:white;padding:1px 4px;border-radius:2px">SSR</span>' : '';
360
+ const ssrBadge = comp.ssr ? '<span class="badge-success">SSR</span>' : '';
503
361
 
504
362
  html += '<div style="background:var(--bg3);padding:12px;border-radius:6px;margin-bottom:8px;cursor:pointer" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
505
363
  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>';
507
- html += '<span style="font-size:11px;color:var(--text2)">' + usageCount + ' usage' + (usageCount !== 1 ? 's' : '') + '</span>';
364
+ html += '<div style="font-weight:600;display:flex;align-items:center"><span class="text-react mr-2">\u269B</span>' + comp.name + ssrBadge + '</div>';
365
+ html += '<span class="hint">' + usageCount + ' usage' + (usageCount !== 1 ? 's' : '') + '</span>';
508
366
  html += '</div>';
509
367
 
510
368
  // Entry point info
511
369
  if (comp.entryFile) {
512
- html += '<div style="font-size:10px;color:var(--text2);margin-top:4px;font-family:monospace">📥 entries/' + comp.entryFile + '</div>';
370
+ html += '<div style="font-size:10px;color:var(--text2);margin-top:4px;font-family:monospace">\u{1F4E5} entries/' + comp.entryFile + '</div>';
513
371
  }
514
372
 
515
373
  // Source file info
516
374
  if (comp.sourceFile) {
517
- html += '<div style="font-size:10px;color:var(--accent);margin-top:2px;font-family:monospace">📄 ' + comp.sourceFile + '</div>';
375
+ html += '<div style="font-size:10px;color:var(--accent);margin-top:2px;font-family:monospace">\u{1F4C4} ' + comp.sourceFile + '</div>';
518
376
  }
519
377
 
520
378
  // Usage preview
@@ -525,7 +383,7 @@ export class PageMapGenerator {
525
383
  html += '<span style="background:rgba(255,255,255,0.1);padding:2px 6px;border-radius:3px;font-size:10px;border-left:2px solid ' + patternColor + '">' + usage.controller + '/' + usage.action + '</span>';
526
384
  });
527
385
  if (comp.usedIn.length > 3) {
528
- html += '<span style="font-size:10px;color:var(--text2)">+' + (comp.usedIn.length - 3) + ' more</span>';
386
+ html += '<span class="hint-sm">+' + (comp.usedIn.length - 3) + ' more</span>';
529
387
  }
530
388
  html += '</div>';
531
389
  }
@@ -534,7 +392,7 @@ export class PageMapGenerator {
534
392
  });
535
393
 
536
394
  html += '</div>';
537
- showModal(' React Components (' + railsReact.components.length + ')', html);
395
+ showModal('\u269B React Components (' + railsReact.components.length + ')', html);
538
396
  }
539
397
 
540
398
  function showReactComponentDetail(encodedData) {
@@ -544,7 +402,7 @@ export class PageMapGenerator {
544
402
 
545
403
  // Component Info
546
404
  html += '<div class="detail-section">';
547
- html += '<div class="detail-label">⚛ Component Name</div>';
405
+ html += '<div class="detail-label">\u269B Component Name</div>';
548
406
  html += '<div style="display:flex;align-items:center;gap:8px">';
549
407
  html += '<span style="font-family:monospace;font-size:16px;font-weight:600">' + comp.name + '</span>';
550
408
  if (comp.ssr) {
@@ -557,7 +415,7 @@ export class PageMapGenerator {
557
415
  // Entry Point
558
416
  if (comp.entryFile) {
559
417
  html += '<div class="detail-section">';
560
- html += '<div class="detail-label">📥 Entry Point</div>';
418
+ html += '<div class="detail-label">\u{1F4E5} Entry Point</div>';
561
419
  html += '<div class="code-path">';
562
420
  html += comp.entryFile;
563
421
  html += '</div></div>';
@@ -566,7 +424,7 @@ export class PageMapGenerator {
566
424
  // Source File
567
425
  if (comp.sourceFile || comp.importPath) {
568
426
  html += '<div class="detail-section">';
569
- html += '<div class="detail-label">📄 Source File</div>';
427
+ html += '<div class="detail-label">\u{1F4C4} Source File</div>';
570
428
  html += '<div class="code-path" style="color:var(--accent)">';
571
429
  html += comp.sourceFile || comp.importPath;
572
430
  html += '</div></div>';
@@ -575,7 +433,7 @@ export class PageMapGenerator {
575
433
  // Usage in Views
576
434
  if (comp.usedIn && comp.usedIn.length > 0) {
577
435
  html += '<div class="detail-section">';
578
- html += '<div class="detail-label">📍 Used in Views (' + comp.usedIn.length + ')</div>';
436
+ html += '<div class="detail-label">\u{1F4CD} Used in Views (' + comp.usedIn.length + ')</div>';
579
437
  html += '<div class="detail-items">';
580
438
 
581
439
  comp.usedIn.forEach(usage => {
@@ -600,7 +458,7 @@ export class PageMapGenerator {
600
458
  html += '</div></div>';
601
459
  }
602
460
 
603
- showModal(' ' + comp.name, html, true);
461
+ showModal('\u269B ' + comp.name, html, true);
604
462
  }
605
463
 
606
464
  function showRailsGrpc() {
@@ -609,17 +467,17 @@ export class PageMapGenerator {
609
467
  return;
610
468
  }
611
469
 
612
- let html = '<div style="max-height:60vh;overflow-y:auto">';
470
+ let html = '<div class="max-h-60vh overflow-y-auto">';
613
471
  railsGrpc.services.forEach(svc => {
614
- 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>';
472
+ html += '<div class="info-box">';
473
+ html += '<div class="section-title">\u{1F50C} ' + svc.className + '</div>';
616
474
  if (svc.namespace) {
617
- html += '<div style="font-size:11px;color:var(--text2);margin-bottom:8px">namespace: ' + svc.namespace + '</div>';
475
+ html += '<div class="hint mb-3">namespace: ' + svc.namespace + '</div>';
618
476
  }
619
477
  if (svc.rpcs && svc.rpcs.length > 0) {
620
- html += '<div style="display:flex;flex-wrap:wrap;gap:4px">';
478
+ html += '<div class="flex flex-wrap gap-1">';
621
479
  svc.rpcs.slice(0, 15).forEach(rpc => {
622
- html += '<span class="tag" style="background:var(--accent);font-size:10px">' + rpc.name + '</span>';
480
+ html += '<span class="tag tag-rpc tag-sm">' + rpc.name + '</span>';
623
481
  });
624
482
  if (svc.rpcs.length > 15) html += '<span style="color:var(--text2);font-size:11px">+' + (svc.rpcs.length - 15) + ' more</span>';
625
483
  html += '</div>';
@@ -628,7 +486,7 @@ export class PageMapGenerator {
628
486
  });
629
487
  html += '</div>';
630
488
 
631
- showModal('🔌 gRPC Services (' + railsGrpc.services.length + ')', html);
489
+ showModal('\u{1F50C} gRPC Services (' + railsGrpc.services.length + ')', html);
632
490
  }
633
491
 
634
492
  // Render Rails routes in tree view
@@ -641,7 +499,7 @@ export class PageMapGenerator {
641
499
  const routes = railsRoutes || [];
642
500
 
643
501
  if (pages.length === 0 && routes.length === 0) {
644
- container.innerHTML = '<div style="padding:40px;text-align:center;color:var(--text2)">No Rails pages or routes found</div>';
502
+ container.innerHTML = '<div class="empty-state">No Rails pages or routes found</div>';
645
503
  return;
646
504
  }
647
505
 
@@ -674,7 +532,7 @@ export class PageMapGenerator {
674
532
  controllerInfo = railsControllers.find(c => {
675
533
  // Strategy 1: Match by filePath (most accurate)
676
534
  // filePath: "api/v1/users_controller.rb" or "users_controller.rb"
677
- const filePathNormalized = c.filePath.replace(/_controller\.rb$/, '').replace(/_/g, '');
535
+ const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
678
536
  if (filePathNormalized === routeCtrl.replace(/_/g, '')) return true;
679
537
 
680
538
  // Strategy 2: Match by controller name (without namespace)
@@ -771,7 +629,7 @@ export class PageMapGenerator {
771
629
  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
630
  const coverageClass = coverage > 70 ? 'coverage-high' : coverage > 40 ? 'coverage-mid' : 'coverage-low';
773
631
  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>';
632
+ html += '<div class="coverage-text" title="' + coverageTooltip + '">Action Details Coverage: <span class="' + coverageClass + '">' + coverage + '%</span> (' + totalWithActionInfo + '/' + combinedData.length + ' routes analyzed) \u2139\uFE0F</div>';
775
633
  html += '</div>';
776
634
  }
777
635
  html += '</div>';
@@ -782,8 +640,8 @@ export class PageMapGenerator {
782
640
 
783
641
  html += '<div class="group">';
784
642
  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>';
643
+ html += '<span class="group-toggle">\u25BC</span>';
644
+ html += '<span class="group-name">\u{1F4C2} ' + ns + '</span>';
787
645
  html += '<span class="group-count">' + routes.length + '</span>';
788
646
  html += '</div>';
789
647
  const routeListId = 'routes-' + ns.replace(/[^a-zA-Z0-9]/g, '-');
@@ -803,12 +661,12 @@ export class PageMapGenerator {
803
661
  if (displayPath.includes('=>') || displayPath.includes('redirect')) {
804
662
  // Extract just the route pattern before any redirect logic
805
663
  const match = displayPath.match(/^([^"]+)"/);
806
- displayPath = match ? match[1].trim() + ' redirect' : displayPath.slice(0, 60) + '...';
664
+ displayPath = match ? match[1].trim() + ' \u2192 redirect' : displayPath.slice(0, 60) + '...';
807
665
  }
808
666
  if (displayPath.length > 80) {
809
667
  displayPath = displayPath.slice(0, 77) + '...';
810
668
  }
811
- const pathHighlighted = displayPath.replace(/:([a-z_]+)/g, '<span style="color:#f59e0b">:$1</span>');
669
+ const pathHighlighted = displayPath.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>');
812
670
 
813
671
  // Indicators for view, API, and response types
814
672
  let indicators = '';
@@ -817,7 +675,7 @@ export class PageMapGenerator {
817
675
  if (action) {
818
676
  if (action.rendersJson) indicators += '<span class="route-tag route-tag-json" title="Returns JSON">JSON</span>';
819
677
  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>';
678
+ if (action.redirectsTo) indicators += '<span class="route-tag route-tag-redirect" title="Redirects">\u2192</span>';
821
679
  }
822
680
  if (route.hasView) indicators += '<span class="route-tag route-tag-view" title="Has View Template">View</span>';
823
681
  if (route.services.length > 0) indicators += '<span class="route-tag route-tag-svc" title="Uses Services: ' + route.services.join(', ') + '">Svc</span>';
@@ -838,7 +696,7 @@ export class PageMapGenerator {
838
696
 
839
697
  html += '<div class="page-item rails-route-item" data-path="' + searchPath + '"' + hiddenAttr + ' ' + filterAttrs.join(' ') + ' onclick="showRailsRouteDetail(\\''+encodeURIComponent(JSON.stringify(route))+'\\', true)" style="cursor:pointer;' + hiddenStyle + '">';
840
698
  html += '<span class="page-type" style="background:' + methodColor + ';min-width:50px;text-align:center">' + route.method + '</span>';
841
- html += '<span class="page-path" style="font-family:monospace;font-size:12px;flex:1">' + pathHighlighted + '</span>';
699
+ html += '<span class="page-path">' + pathHighlighted + '</span>';
842
700
  html += indicators;
843
701
  html += '</div>';
844
702
  });
@@ -846,7 +704,7 @@ export class PageMapGenerator {
846
704
  html += '</div>';
847
705
 
848
706
  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>';
707
+ 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
708
  }
851
709
 
852
710
  html += '</div>';
@@ -924,7 +782,7 @@ export class PageMapGenerator {
924
782
 
925
783
  html += '<div class="detail-section">';
926
784
  html += '<div class="detail-label">Path</div>';
927
- html += '<div class="detail-value" style="font-family:monospace">' + route.path.replace(/:([a-z_]+)/g, '<span style="color:#f59e0b">:$1</span>') + '</div>';
785
+ html += '<div class="detail-value" style="font-family:monospace">' + route.path.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>') + '</div>';
928
786
  html += '</div>';
929
787
 
930
788
  html += '<div class="detail-section">';
@@ -935,7 +793,7 @@ export class PageMapGenerator {
935
793
  // Response Type - NEW
936
794
  if (action) {
937
795
  html += '<div class="detail-section">';
938
- html += '<div class="detail-label">📡 Response Type</div>';
796
+ html += '<div class="detail-label">\u{1F4E1} Response Type</div>';
939
797
  html += '<div class="detail-value">';
940
798
  const responseTypes = [];
941
799
  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>');
@@ -946,14 +804,14 @@ export class PageMapGenerator {
946
804
  responseTypes.push('<span style="background:#8b5cf6;color:white;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px">' + f.toUpperCase() + '</span>');
947
805
  });
948
806
  }
949
- html += responseTypes.length > 0 ? responseTypes.join('') : '<span style="color:var(--text2)">Unknown</span>';
807
+ html += responseTypes.length > 0 ? responseTypes.join('') : '<span class="text-muted">Unknown</span>';
950
808
  html += '</div></div>';
951
809
 
952
810
  // Redirect destination if exists
953
811
  if (action.redirectsTo) {
954
812
  html += '<div class="detail-section">';
955
- html += '<div class="detail-label">↪️ Redirects To</div>';
956
- html += '<div class="detail-value" style="font-family:monospace;font-size:12px;background:var(--bg3);padding:8px;border-radius:4px">' + action.redirectsTo + '</div>';
813
+ html += '<div class="detail-label">\u21AA\uFE0F Redirects To</div>';
814
+ html += '<div class="detail-value-block">' + action.redirectsTo + '</div>';
957
815
  html += '</div>';
958
816
  }
959
817
  }
@@ -961,10 +819,10 @@ export class PageMapGenerator {
961
819
  // View info
962
820
  if (route.hasView && route.view) {
963
821
  html += '<div class="detail-section">';
964
- html += '<div class="detail-label">📄 View Template</div>';
965
- html += '<div class="detail-value" style="font-family:monospace;font-size:12px">app/views/' + route.view.path + '</div>';
822
+ html += '<div class="detail-label">\u{1F4C4} View Template</div>';
823
+ html += '<div class="detail-value">app/views/' + route.view.path + '</div>';
966
824
  if (route.view.partials && route.view.partials.length > 0) {
967
- 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>';
825
+ html += '<div class="subtext">Partials: ' + route.view.partials.slice(0, 5).join(', ') + (route.view.partials.length > 5 ? '...' : '') + '</div>';
968
826
  }
969
827
  if (route.view.instanceVars && route.view.instanceVars.length > 0) {
970
828
  html += '<div style="margin-top:4px;font-size:11px;color:var(--text2)">Instance vars: @' + route.view.instanceVars.slice(0, 5).join(', @') + (route.view.instanceVars.length > 5 ? '...' : '') + '</div>';
@@ -975,8 +833,8 @@ export class PageMapGenerator {
975
833
  // Before/After Filters - NEW
976
834
  if (ctrl && (ctrl.beforeActions.length > 0 || ctrl.afterActions.length > 0)) {
977
835
  html += '<div class="detail-section">';
978
- html += '<div class="detail-label">🔒 Filters Applied to This Action</div>';
979
- html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
836
+ html += '<div class="detail-label">\u{1F512} Filters Applied to This Action</div>';
837
+ html += '<div class="code-block">';
980
838
 
981
839
  // Filter before_actions that apply to this action
982
840
  const applicableBeforeFilters = ctrl.beforeActions.filter(f => {
@@ -989,7 +847,7 @@ export class PageMapGenerator {
989
847
  html += '<div style="font-size:11px;margin-bottom:6px"><span style="color:#22c55e;font-weight:600">Before:</span></div>';
990
848
  html += '<div class="detail-items" style="margin-left:8px">';
991
849
  applicableBeforeFilters.forEach(f => {
992
- let filterInfo = '<span class="tag" style="background:#22c55e;font-size:10px">before</span><span class="name">' + f.name + '</span>';
850
+ let filterInfo = '<span class="tag tag-before tag-sm">before</span><span class="name">' + f.name + '</span>';
993
851
  if (f.if) filterInfo += '<span style="font-size:10px;color:var(--text2);margin-left:4px">if: ' + f.if + '</span>';
994
852
  if (f.unless) filterInfo += '<span style="font-size:10px;color:var(--text2);margin-left:4px">unless: ' + f.unless + '</span>';
995
853
  html += '<div class="detail-item">' + filterInfo + '</div>';
@@ -1007,7 +865,7 @@ export class PageMapGenerator {
1007
865
  html += '<div style="font-size:11px;margin-top:8px;margin-bottom:6px"><span style="color:#f59e0b;font-weight:600">After:</span></div>';
1008
866
  html += '<div class="detail-items" style="margin-left:8px">';
1009
867
  applicableAfterFilters.forEach(f => {
1010
- let filterInfo = '<span class="tag" style="background:#f59e0b;font-size:10px">after</span><span class="name">' + f.name + '</span>';
868
+ let filterInfo = '<span class="tag tag-after tag-sm">after</span><span class="name">' + f.name + '</span>';
1011
869
  html += '<div class="detail-item">' + filterInfo + '</div>';
1012
870
  });
1013
871
  html += '</div>';
@@ -1023,10 +881,10 @@ export class PageMapGenerator {
1023
881
  const services = route.services && route.services.length > 0 ? route.services : (action?.servicesCalled || []);
1024
882
  if (services.length > 0) {
1025
883
  html += '<div class="detail-section">';
1026
- html += '<div class="detail-label">⚙️ Services Called</div>';
884
+ html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
1027
885
  html += '<div class="detail-items">';
1028
886
  services.forEach(s => {
1029
- html += '<div class="detail-item"><span class="tag" style="background:#8b5cf6">Service</span><span class="name" style="font-family:monospace">' + s + '</span></div>';
887
+ html += '<div class="detail-item"><span class="tag tag-service">Service</span><span class="name">' + s + '</span></div>';
1030
888
  });
1031
889
  html += '</div></div>';
1032
890
  }
@@ -1034,10 +892,10 @@ export class PageMapGenerator {
1034
892
  // gRPC Calls
1035
893
  if (route.grpcCalls && route.grpcCalls.length > 0) {
1036
894
  html += '<div class="detail-section">';
1037
- html += '<div class="detail-label">🔌 gRPC Calls</div>';
895
+ html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
1038
896
  html += '<div class="detail-items">';
1039
897
  route.grpcCalls.forEach(g => {
1040
- html += '<div class="detail-item"><span class="tag" style="background:#06b6d4">gRPC</span><span class="name" style="font-family:monospace">' + g + '</span></div>';
898
+ html += '<div class="detail-item"><span class="tag tag-grpc">gRPC</span><span class="name">' + g + '</span></div>';
1041
899
  });
1042
900
  html += '</div></div>';
1043
901
  }
@@ -1046,10 +904,10 @@ export class PageMapGenerator {
1046
904
  const models = route.modelAccess && route.modelAccess.length > 0 ? route.modelAccess : (action?.modelsCalled || []);
1047
905
  if (models.length > 0) {
1048
906
  html += '<div class="detail-section">';
1049
- html += '<div class="detail-label">💾 Models Accessed</div>';
907
+ html += '<div class="detail-label">\u{1F4BE} Models Accessed</div>';
1050
908
  html += '<div class="detail-items">';
1051
909
  models.forEach(m => {
1052
- html += '<div class="detail-item"><span class="tag" style="background:#f59e0b">Model</span><span class="name" style="font-family:monospace">' + m + '</span></div>';
910
+ html += '<div class="detail-item"><span class="tag tag-model">Model</span><span class="name">' + m + '</span></div>';
1053
911
  });
1054
912
  html += '</div></div>';
1055
913
  }
@@ -1064,17 +922,17 @@ export class PageMapGenerator {
1064
922
 
1065
923
  if (meaningfulCalls.length > 0) {
1066
924
  html += '<div class="detail-section">';
1067
- html += '<div class="detail-label">🔗 Method Calls in Action</div>';
925
+ html += '<div class="detail-label">\u{1F517} Method Calls in Action</div>';
1068
926
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;max-height:150px;overflow-y:auto">';
1069
927
  html += '<div style="font-family:monospace;font-size:11px;line-height:1.6">';
1070
928
  meaningfulCalls.forEach((call, i) => {
1071
- html += '<div style="padding:2px 0;border-bottom:1px solid var(--bg1)">';
929
+ html += '<div class="accordion-item">';
1072
930
  html += '<span style="color:var(--text2);margin-right:8px">' + (i+1) + '.</span>';
1073
- html += '<span style="color:var(--accent)">' + call + '</span>';
931
+ html += '<span class="text-accent">' + call + '</span>';
1074
932
  html += '</div>';
1075
933
  });
1076
934
  if (action.methodCalls.length > 15) {
1077
- html += '<div style="padding:4px 0;color:var(--text2);font-style:italic">...and ' + (action.methodCalls.length - 15) + ' more calls</div>';
935
+ html += '<div class="note">...and ' + (action.methodCalls.length - 15) + ' more calls</div>';
1078
936
  }
1079
937
  html += '</div></div></div>';
1080
938
  }
@@ -1082,27 +940,27 @@ export class PageMapGenerator {
1082
940
 
1083
941
  // Source Files - NEW
1084
942
  html += '<div class="detail-section">';
1085
- html += '<div class="detail-label">📁 Source Files</div>';
943
+ html += '<div class="detail-label">\u{1F4C1} Source Files</div>';
1086
944
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:11px">';
1087
945
 
1088
946
  if (route.line > 0) {
1089
- html += '<div style="padding:4px 0;display:flex;align-items:center">';
1090
- html += '<span style="color:var(--text2);width:80px">Route:</span>';
1091
- html += '<span>config/routes.rb:<span style="color:#22c55e">' + route.line + '</span></span>';
947
+ html += '<div class="detail-item flex items-center py-1">';
948
+ html += '<span class="text-muted w-20">Route:</span>';
949
+ html += '<span>config/routes.rb:<span class="text-success">' + route.line + '</span></span>';
1092
950
  html += '</div>';
1093
951
  }
1094
952
 
1095
953
  if (ctrl) {
1096
- html += '<div style="padding:4px 0;display:flex;align-items:center">';
1097
- html += '<span style="color:var(--text2);width:80px">Controller:</span>';
954
+ html += '<div class="detail-item flex items-center py-1">';
955
+ html += '<span class="text-muted w-20">Controller:</span>';
1098
956
  html += '<span>app/controllers/' + ctrl.filePath;
1099
- if (action && action.line) html += ':<span style="color:#22c55e">' + action.line + '</span>';
957
+ if (action && action.line) html += ':<span class="text-success">' + action.line + '</span>';
1100
958
  html += '</span></div>';
1101
959
  }
1102
960
 
1103
961
  if (route.hasView && route.view) {
1104
- html += '<div style="padding:4px 0;display:flex;align-items:center">';
1105
- html += '<span style="color:var(--text2);width:80px">View:</span>';
962
+ html += '<div class="detail-item flex items-center py-1">';
963
+ html += '<span class="text-muted w-20">View:</span>';
1106
964
  html += '<span>app/views/' + route.view.path + '</span>';
1107
965
  html += '</div>';
1108
966
  }
@@ -1111,13 +969,13 @@ export class PageMapGenerator {
1111
969
  // Controller Info Summary
1112
970
  if (ctrl) {
1113
971
  html += '<div class="detail-section">';
1114
- html += '<div class="detail-label">📋 Controller Info</div>';
1115
- html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
1116
- html += '<div style="font-weight:600;margin-bottom:4px">' + ctrl.className + '</div>';
972
+ html += '<div class="detail-label">\u{1F4CB} Controller Info</div>';
973
+ html += '<div class="code-block">';
974
+ html += '<div class="section-title">' + ctrl.className + '</div>';
1117
975
  html += '<div style="font-size:11px;color:var(--text2)">extends ' + ctrl.parentClass + '</div>';
1118
976
  if (ctrl.concerns && ctrl.concerns.length > 0) {
1119
977
  html += '<div style="margin-top:6px;font-size:11px">';
1120
- html += '<span style="color:var(--text2)">Concerns:</span> ' + ctrl.concerns.join(', ');
978
+ html += '<span class="text-muted">Concerns:</span> ' + ctrl.concerns.join(', ');
1121
979
  html += '</div>';
1122
980
  }
1123
981
  html += '</div></div>';
@@ -1157,15 +1015,15 @@ export class PageMapGenerator {
1157
1015
  const withUsageCount = components.filter(c => c.usedIn && c.usedIn.length > 0).length;
1158
1016
 
1159
1017
  let html = '';
1160
- 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>';
1018
+ html += '<div class="info-box mb-3">';
1019
+ 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
1020
  html += '<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--text2)">';
1163
1021
  html += '<span>' + components.length + ' components</span>';
1164
- html += '<span>•</span>';
1165
- html += '<span style="color:#22c55e">' + ssrCount + ' SSR</span>';
1166
- html += '<span>•</span>';
1022
+ html += '<span>\u2022</span>';
1023
+ html += '<span class="text-success">' + ssrCount + ' SSR</span>';
1024
+ html += '<span>\u2022</span>';
1167
1025
  html += '<span style="color:#3b82f6">' + (components.length - ssrCount) + ' client</span>';
1168
- html += '<span>•</span>';
1026
+ html += '<span>\u2022</span>';
1169
1027
  html += '<span style="color:#8b5cf6">' + withUsageCount + ' with usage</span>';
1170
1028
  html += '</div></div>';
1171
1029
 
@@ -1178,8 +1036,8 @@ export class PageMapGenerator {
1178
1036
  if (withEntry.length > 0) {
1179
1037
  html += '<div class="group" data-group="react-with-entry">';
1180
1038
  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>';
1039
+ html += '<span class="group-toggle">\u25BC</span>';
1040
+ html += '<span style="color:#61dafb;margin-right:4px">\u{1F4E5}</span>';
1183
1041
  html += '<span class="group-name">With Entry Points (' + withEntry.length + ')</span>';
1184
1042
  html += '</div>';
1185
1043
  html += '<div class="group-items">';
@@ -1187,9 +1045,9 @@ export class PageMapGenerator {
1187
1045
  withEntry.forEach(comp => {
1188
1046
  const usageCount = comp.usedIn?.length || 0;
1189
1047
  const tags = [];
1190
- if (comp.ssr) tags.push('<span class="tag" style="background:#166534;color:#86efac" title="Server-Side Rendering">SSR</span>');
1191
- if (usageCount > 0) tags.push('<span class="tag" style="background:#5b21b6;color:#c4b5fd" title="Used in ' + usageCount + ' view(s)">View:' + usageCount + '</span>');
1192
- if (comp.sourceFile) tags.push('<span class="tag" style="background:#1e3a5f;color:#93c5fd" title="Has source file">SRC</span>');
1048
+ if (comp.ssr) tags.push('<span class="tag tag-ssr" title="Server-Side Rendering">SSR</span>');
1049
+ if (usageCount > 0) tags.push('<span class="tag tag-view" title="Used in ' + usageCount + ' view(s)">View:' + usageCount + '</span>');
1050
+ if (comp.sourceFile) tags.push('<span class="tag tag-src" title="Has source file">SRC</span>');
1193
1051
 
1194
1052
  // Find URL from routes based on controller/action OR infer from entry file
1195
1053
  let urlInfo = '';
@@ -1204,14 +1062,14 @@ export class PageMapGenerator {
1204
1062
  urlInfo = '/' + usage.controller.replace(/_/g, '/') + '/' + usage.action;
1205
1063
  }
1206
1064
  } else if (comp.entryFile) {
1207
- // Infer URL from entry file name (e.g., tickets.tsx /tickets)
1065
+ // Infer URL from entry file name (e.g., tickets.tsx \u2192 /tickets)
1208
1066
  const fileName = comp.entryFile.split('/').pop().replace(/\\.(tsx?|jsx?)$/, '');
1209
1067
  urlInfo = '/' + fileName.replace(/_/g, '-');
1210
1068
  }
1211
1069
 
1212
1070
  html += '<div class="page-item" data-path="' + comp.name.toLowerCase() + '" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
1213
1071
  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>';
1072
+ html += '<span class="page-name" style="display:flex;align-items:center"><span class="text-react mr-2">\u269B</span>' + comp.name + '</span>';
1215
1073
  html += '<span class="page-path" style="font-size:10px;color:var(--accent)">' + urlInfo + '</span>';
1216
1074
  html += '</div>';
1217
1075
  html += '<div class="page-tags">' + tags.join('') + '</div>';
@@ -1225,8 +1083,8 @@ export class PageMapGenerator {
1225
1083
  if (withoutEntry.length > 0) {
1226
1084
  html += '<div class="group" data-group="react-view-only">';
1227
1085
  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>';
1086
+ html += '<span class="group-toggle">\u25BC</span>';
1087
+ html += '<span style="color:#f59e0b;margin-right:4px">\u{1F441}\uFE0F</span>';
1230
1088
  html += '<span class="group-name">View-only Components (' + withoutEntry.length + ')</span>';
1231
1089
  html += '</div>';
1232
1090
  html += '<div class="group-items">';
@@ -1234,8 +1092,8 @@ export class PageMapGenerator {
1234
1092
  withoutEntry.forEach(comp => {
1235
1093
  const usageCount = comp.usedIn?.length || 0;
1236
1094
  const tags = [];
1237
- if (comp.ssr) tags.push('<span class="tag" style="background:#166534;color:#86efac" title="Server-Side Rendering">SSR</span>');
1238
- if (usageCount > 0) tags.push('<span class="tag" style="background:#5b21b6;color:#c4b5fd" title="Used in ' + usageCount + ' view(s)">View:' + usageCount + '</span>');
1095
+ if (comp.ssr) tags.push('<span class="tag tag-ssr" title="Server-Side Rendering">SSR</span>');
1096
+ if (usageCount > 0) tags.push('<span class="tag tag-view" title="Used in ' + usageCount + ' view(s)">View:' + usageCount + '</span>');
1239
1097
 
1240
1098
  // Find URL from routes based on controller/action
1241
1099
  let urlInfo = '';
@@ -1253,7 +1111,7 @@ export class PageMapGenerator {
1253
1111
 
1254
1112
  html += '<div class="page-item" data-path="' + comp.name.toLowerCase() + '" onclick="showReactComponentDetail(\\'' + encodeURIComponent(JSON.stringify(comp)) + '\\')">';
1255
1113
  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>';
1114
+ html += '<span class="page-name" style="display:flex;align-items:center"><span class="text-react mr-2">\u269B</span>' + comp.name + '</span>';
1257
1115
  html += '<span class="page-path" style="font-size:10px;color:var(--accent)">' + (urlInfo || 'View-only') + '</span>';
1258
1116
  html += '</div>';
1259
1117
  html += '<div class="page-tags">' + tags.join('') + '</div>';
@@ -1302,7 +1160,7 @@ export class PageMapGenerator {
1302
1160
  const ctrlName = view.controller.split('/').pop().replace(/_/g, '');
1303
1161
  const ctrl = railsControllers?.find(c => {
1304
1162
  if (c.name === view.controller || c.name === ctrlName) return true;
1305
- const filePathNormalized = c.filePath.replace(/_controller\.rb$/, '').replace(/_/g, '');
1163
+ const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
1306
1164
  return filePathNormalized === view.controller.replace(/_/g, '');
1307
1165
  });
1308
1166
  const action = ctrl?.actions?.find(a => a.name === view.action);
@@ -1363,15 +1221,15 @@ export class PageMapGenerator {
1363
1221
  const totalWithPartials = enrichedViews.filter(v => v.partials.length > 0).length;
1364
1222
 
1365
1223
  let html = '';
1366
- 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>';
1224
+ html += '<div class="info-box mb-3">';
1225
+ html += '<div style="font-weight:600;margin-bottom:8px">\u{1F5BC}\uFE0F Rails Screens (View Templates)</div>';
1368
1226
  html += '<div style="display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--text2)">';
1369
1227
  html += '<span>' + enrichedViews.length + ' screens</span>';
1370
- html += '<span>•</span>';
1228
+ html += '<span>\u2022</span>';
1371
1229
  html += '<span>' + sortedControllers.length + ' sections</span>';
1372
- html += '<span>•</span>';
1230
+ html += '<span>\u2022</span>';
1373
1231
  html += '<span style="color:#8b5cf6">' + totalWithServices + ' with services</span>';
1374
- html += '<span>•</span>';
1232
+ html += '<span>\u2022</span>';
1375
1233
  html += '<span style="color:#06b6d4">' + totalWithPartials + ' with partials</span>';
1376
1234
  html += '</div></div>';
1377
1235
 
@@ -1385,8 +1243,8 @@ export class PageMapGenerator {
1385
1243
 
1386
1244
  html += '<div class="group">';
1387
1245
  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>';
1246
+ html += '<span class="group-toggle">\u25BC</span>';
1247
+ html += '<span class="group-name">\u{1F4C1} ' + ctrl + '</span>';
1390
1248
  html += '<span class="group-count">' + controllerViews.length + ' screens</span>';
1391
1249
  html += '</div>';
1392
1250
  html += '<div class="group-items" id="' + screenListId + '">';
@@ -1400,17 +1258,17 @@ export class PageMapGenerator {
1400
1258
  indicators += '<span class="route-tag route-tag-template" title="' + view.template.toUpperCase() + ' template">' + view.template.toUpperCase() + '</span>';
1401
1259
  if (view.reactComponents && view.reactComponents.length > 0) {
1402
1260
  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>';
1261
+ indicators += '<span class="route-tag route-tag-react" title="React: ' + rcNames + '">\u269B ' + view.reactComponents.length + '</span>';
1404
1262
  }
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>';
1263
+ 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>';
1264
+ 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
1265
  if (view.services.length > 0) indicators += '<span class="route-tag route-tag-svc" title="Services: ' + view.services.join(', ') + '">Svc</span>';
1408
1266
  if (view.grpcCalls.length > 0) indicators += '<span class="route-tag route-tag-grpc" title="gRPC: ' + view.grpcCalls.join(', ') + '">gRPC</span>';
1409
1267
  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>';
1268
+ if (!view.hasRoute) indicators += '<span class="route-tag route-tag-warn" title="No matching route found">\u26A0\uFE0F</span>';
1411
1269
 
1412
1270
  // Display: URL path (if route exists) or controller/action
1413
- const displayName = view.hasRoute ? view.path.replace(/:([a-z_]+)/g, '<span style="color:#f59e0b">:$1</span>') : view.controller + '#' + view.action;
1271
+ const displayName = view.hasRoute ? view.path.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>') : view.controller + '#' + view.action;
1414
1272
 
1415
1273
  // Search-friendly data-path includes path, controller, action
1416
1274
  const searchPath = [view.path || '', view.controller || '', view.action || '', view.viewPath || ''].join(' ').toLowerCase();
@@ -1428,7 +1286,7 @@ export class PageMapGenerator {
1428
1286
 
1429
1287
  if (hasMoreScreens) {
1430
1288
  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';
1289
+ html += '\u25BC Show ' + (controllerViews.length - screenLimit) + ' more screens';
1432
1290
  html += '</div>';
1433
1291
  }
1434
1292
 
@@ -1446,9 +1304,9 @@ export class PageMapGenerator {
1446
1304
 
1447
1305
  // URL/Route info
1448
1306
  html += '<div class="detail-section">';
1449
- html += '<div class="detail-label">🌐 URL Path</div>';
1307
+ html += '<div class="detail-label">\u{1F310} URL Path</div>';
1450
1308
  if (screen.hasRoute) {
1451
- html += '<div class="detail-value" style="font-family:monospace">' + screen.path.replace(/:([a-z_]+)/g, '<span style="color:#f59e0b">:$1</span>') + '</div>';
1309
+ html += '<div class="detail-value" style="font-family:monospace">' + screen.path.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>') + '</div>';
1452
1310
  } else {
1453
1311
  html += '<div class="detail-value" style="color:var(--text2)">No route defined (orphan view)</div>';
1454
1312
  }
@@ -1456,7 +1314,7 @@ export class PageMapGenerator {
1456
1314
 
1457
1315
  // View Template info
1458
1316
  html += '<div class="detail-section">';
1459
- html += '<div class="detail-label">📄 View Template</div>';
1317
+ html += '<div class="detail-label">\u{1F4C4} View Template</div>';
1460
1318
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:12px">';
1461
1319
  html += '<div style="color:var(--accent)">app/views/' + screen.viewPath + '</div>';
1462
1320
  html += '<div style="margin-top:6px;display:flex;gap:8px">';
@@ -1466,7 +1324,7 @@ export class PageMapGenerator {
1466
1324
  // Instance Variables (data passed to view) with type info
1467
1325
  if (screen.instanceVars && screen.instanceVars.length > 0) {
1468
1326
  html += '<div class="detail-section">';
1469
- html += '<div class="detail-label">📦 Data Available in View (@variables)</div>';
1327
+ html += '<div class="detail-label">\u{1F4E6} Data Available in View (@variables)</div>';
1470
1328
 
1471
1329
  // Build assignment map from controller analysis
1472
1330
  const assignmentMap = {};
@@ -1487,16 +1345,16 @@ export class PageMapGenerator {
1487
1345
 
1488
1346
  // Function to find matching model for a variable name
1489
1347
  function findModelForVar(varName) {
1490
- // Direct match: @company Company
1348
+ // Direct match: @company \u2192 Company
1491
1349
  const pascalCase = varName.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
1492
1350
  if (modelNames.has(pascalCase)) return { model: pascalCase, confidence: 'exact' };
1493
1351
 
1494
- // Singular form: @companies Company
1352
+ // Singular form: @companies \u2192 Company
1495
1353
  const singular = varName.replace(/ies$/, 'y').replace(/s$/, '');
1496
1354
  const singularPascal = singular.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
1497
1355
  if (modelNames.has(singularPascal)) return { model: singularPascal, confidence: 'plural' };
1498
1356
 
1499
- // current_X pattern: @current_user User
1357
+ // current_X pattern: @current_user \u2192 User
1500
1358
  if (varName.startsWith('current_')) {
1501
1359
  const rest = varName.replace('current_', '');
1502
1360
  const restPascal = rest.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
@@ -1541,7 +1399,7 @@ export class PageMapGenerator {
1541
1399
 
1542
1400
  const tooltip = assignment && assignment.assignedValue ? assignment.assignedValue.replace(/"/g, '&quot;') : '';
1543
1401
  html += '<div class="detail-item"' + hiddenClass + ' title="' + tooltip + '">';
1544
- html += '<span class="tag" style="background:#8b5cf6;font-size:10px">@</span>';
1402
+ html += '<span class="tag tag-var tag-sm">@</span>';
1545
1403
  html += '<span class="name" style="font-family:monospace;font-weight:500">' + v + '</span>';
1546
1404
 
1547
1405
  if (linkedModel) {
@@ -1571,7 +1429,7 @@ export class PageMapGenerator {
1571
1429
  // "Show more" button
1572
1430
  if (hasMore) {
1573
1431
  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';
1432
+ html += '\u25BC Show ' + (screen.instanceVars.length - initialLimit) + ' more variables';
1575
1433
  html += '</div>';
1576
1434
  }
1577
1435
 
@@ -1581,14 +1439,14 @@ export class PageMapGenerator {
1581
1439
  // React Components loaded in this view
1582
1440
  if (screen.reactComponents && screen.reactComponents.length > 0) {
1583
1441
  html += '<div class="detail-section">';
1584
- html += '<div class="detail-label">⚛️ React Components</div>';
1442
+ html += '<div class="detail-label">\u269B\uFE0F React Components</div>';
1585
1443
  html += '<div class="detail-items">';
1586
1444
  screen.reactComponents.forEach(rc => {
1587
1445
  html += '<div class="detail-item">';
1588
- html += '<span class="tag" style="background:#61dafb;color:#222;font-size:10px;font-weight:600">React</span>';
1446
+ html += '<span class="tag tag-react tag-sm" style="font-weight:600">React</span>';
1589
1447
  html += '<span class="name" style="font-family:monospace;font-weight:500">' + rc.name + '</span>';
1590
1448
  if (rc.ssr) {
1591
- html += '<span style="margin-left:6px;font-size:9px;background:#22c55e;color:white;padding:1px 4px;border-radius:2px">SSR</span>';
1449
+ html += '<span class="badge-success">SSR</span>';
1592
1450
  }
1593
1451
  if (rc.propsVar) {
1594
1452
  html += '<span style="margin-left:auto;font-size:10px;color:var(--text2);font-family:monospace">props: ' + rc.propsVar + '</span>';
@@ -1605,15 +1463,15 @@ export class PageMapGenerator {
1605
1463
  const partialListId = 'partials-' + Math.random().toString(36).substr(2, 9);
1606
1464
 
1607
1465
  html += '<div class="detail-section">';
1608
- html += '<div class="detail-label">🧩 Partials Used (' + screen.partials.length + ')</div>';
1466
+ html += '<div class="detail-label">\u{1F9E9} Partials Used (' + screen.partials.length + ')</div>';
1609
1467
  html += '<div class="detail-items" id="' + partialListId + '">';
1610
1468
  screen.partials.forEach((p, idx) => {
1611
1469
  const hiddenClass = idx >= partialLimit ? ' style="display:none" data-hidden="true"' : '';
1612
- html += '<div class="detail-item"' + hiddenClass + '><span class="tag" style="background:#06b6d4;font-size:10px">PARTIAL</span><span class="name" style="font-family:monospace;font-size:11px">' + p + '</span></div>';
1470
+ html += '<div class="detail-item"' + hiddenClass + '><span class="tag tag-partial tag-sm">PARTIAL</span><span class="name" style="font-family:monospace;font-size:11px">' + p + '</span></div>';
1613
1471
  });
1614
1472
  html += '</div>';
1615
1473
  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>';
1474
+ 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
1475
  }
1618
1476
  html += '</div>';
1619
1477
  }
@@ -1621,10 +1479,10 @@ export class PageMapGenerator {
1621
1479
  // Services Called
1622
1480
  if (screen.services && screen.services.length > 0) {
1623
1481
  html += '<div class="detail-section">';
1624
- html += '<div class="detail-label">⚙️ Services Called</div>';
1482
+ html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
1625
1483
  html += '<div class="detail-items">';
1626
1484
  screen.services.forEach(s => {
1627
- html += '<div class="detail-item"><span class="tag" style="background:#8b5cf6">Service</span><span class="name" style="font-family:monospace">' + s + '</span></div>';
1485
+ html += '<div class="detail-item"><span class="tag tag-service">Service</span><span class="name">' + s + '</span></div>';
1628
1486
  });
1629
1487
  html += '</div></div>';
1630
1488
  }
@@ -1632,10 +1490,10 @@ export class PageMapGenerator {
1632
1490
  // gRPC Calls
1633
1491
  if (screen.grpcCalls && screen.grpcCalls.length > 0) {
1634
1492
  html += '<div class="detail-section">';
1635
- html += '<div class="detail-label">🔌 gRPC Calls</div>';
1493
+ html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
1636
1494
  html += '<div class="detail-items">';
1637
1495
  screen.grpcCalls.forEach(g => {
1638
- html += '<div class="detail-item"><span class="tag" style="background:#06b6d4">gRPC</span><span class="name" style="font-family:monospace">' + g + '</span></div>';
1496
+ html += '<div class="detail-item"><span class="tag tag-grpc">gRPC</span><span class="name">' + g + '</span></div>';
1639
1497
  });
1640
1498
  html += '</div></div>';
1641
1499
  }
@@ -1643,21 +1501,21 @@ export class PageMapGenerator {
1643
1501
  // Model Access
1644
1502
  if (screen.modelAccess && screen.modelAccess.length > 0) {
1645
1503
  html += '<div class="detail-section">';
1646
- html += '<div class="detail-label">💾 Models Used</div>';
1504
+ html += '<div class="detail-label">\u{1F4BE} Models Used</div>';
1647
1505
  html += '<div class="detail-items">';
1648
1506
  screen.modelAccess.forEach(m => {
1649
- html += '<div class="detail-item"><span class="tag" style="background:#f59e0b">Model</span><span class="name" style="font-family:monospace">' + m + '</span></div>';
1507
+ html += '<div class="detail-item"><span class="tag tag-model">Model</span><span class="name">' + m + '</span></div>';
1650
1508
  });
1651
1509
  html += '</div></div>';
1652
1510
  }
1653
1511
 
1654
1512
  // Controller Action info
1655
1513
  html += '<div class="detail-section">';
1656
- html += '<div class="detail-label">🎮 Controller Action</div>';
1657
- html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
1514
+ html += '<div class="detail-label">\u{1F3AE} Controller Action</div>';
1515
+ html += '<div class="code-block">';
1658
1516
  html += '<div style="font-family:monospace;font-size:12px">' + screen.controller + '#' + screen.action + '</div>';
1659
1517
  if (screen.controllerInfo) {
1660
- html += '<div style="margin-top:6px;font-size:11px;color:var(--text2)">app/controllers/' + screen.controllerInfo.filePath;
1518
+ html += '<div class="subtext">app/controllers/' + screen.controllerInfo.filePath;
1661
1519
  if (screen.actionLine) html += ':' + screen.actionLine;
1662
1520
  html += '</div>';
1663
1521
 
@@ -1670,7 +1528,7 @@ export class PageMapGenerator {
1670
1528
  });
1671
1529
  if (applicableFilters.length > 0) {
1672
1530
  html += '<div style="margin-top:8px;font-size:11px">';
1673
- html += '<span style="color:#22c55e">Before filters:</span> ' + applicableFilters.map(f => f.name).join(', ');
1531
+ html += '<span class="text-success">Before filters:</span> ' + applicableFilters.map(f => f.name).join(', ');
1674
1532
  html += '</div>';
1675
1533
  }
1676
1534
  }
@@ -1686,7 +1544,7 @@ export class PageMapGenerator {
1686
1544
 
1687
1545
  if (meaningfulCalls.length > 0) {
1688
1546
  html += '<div class="detail-section">';
1689
- html += '<div class="detail-label">🔗 Method Calls</div>';
1547
+ html += '<div class="detail-label">\u{1F517} Method Calls</div>';
1690
1548
  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
1549
  meaningfulCalls.forEach((call, i) => {
1692
1550
  html += '<div style="padding:2px 0;color:var(--accent)">' + (i+1) + '. ' + call + '</div>';
@@ -1695,7 +1553,7 @@ export class PageMapGenerator {
1695
1553
  }
1696
1554
  }
1697
1555
 
1698
- showModal('🖼️ ' + screen.controller + '/' + screen.action, html);
1556
+ showModal('\u{1F5BC}\uFE0F ' + screen.controller + '/' + screen.action, html);
1699
1557
  }
1700
1558
 
1701
1559
  // Show Rails page detail with API info
@@ -1711,7 +1569,7 @@ export class PageMapGenerator {
1711
1569
  const routeCtrlName = routeCtrlParts.pop().replace(/_/g, '');
1712
1570
 
1713
1571
  controllerInfo = railsControllers.find(c => {
1714
- const filePathNormalized = c.filePath.replace(/_controller\.rb$/, '').replace(/_/g, '');
1572
+ const filePathNormalized = c.filePath.replace(/_controller.rb$/, '').replace(/_/g, '');
1715
1573
  if (filePathNormalized === routeCtrl.replace(/_/g, '')) return true;
1716
1574
  if (c.name === routeCtrlName || c.name.replace(/_/g, '') === routeCtrlName) return true;
1717
1575
  const className = c.className.toLowerCase().replace('controller', '').replace(/::/g, '/');
@@ -1735,7 +1593,7 @@ export class PageMapGenerator {
1735
1593
  html += '<div class="detail-label">Method & Path</div>';
1736
1594
  html += '<div class="detail-value">';
1737
1595
  html += '<span style="background:' + methodColor + ';color:white;padding:2px 8px;border-radius:4px;font-weight:600;margin-right:8px">' + (route.method || 'GET') + '</span>';
1738
- html += '<span style="font-family:monospace">' + route.path.replace(/:([a-z_]+)/g, '<span style="color:#f59e0b">:$1</span>') + '</span>';
1596
+ html += '<span class="mono">' + route.path.replace(/:([a-z_]+)/g, '<span class="text-warning">:$1</span>') + '</span>';
1739
1597
  html += '</div></div>';
1740
1598
 
1741
1599
  html += '<div class="detail-section">';
@@ -1746,7 +1604,7 @@ export class PageMapGenerator {
1746
1604
  // Response Type
1747
1605
  if (actionDetails) {
1748
1606
  html += '<div class="detail-section">';
1749
- html += '<div class="detail-label">📡 Response Type</div>';
1607
+ html += '<div class="detail-label">\u{1F4E1} Response Type</div>';
1750
1608
  html += '<div class="detail-value">';
1751
1609
  const responseTypes = [];
1752
1610
  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>');
@@ -1757,12 +1615,12 @@ export class PageMapGenerator {
1757
1615
  responseTypes.push('<span style="background:#8b5cf6;color:white;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px">' + f.toUpperCase() + '</span>');
1758
1616
  });
1759
1617
  }
1760
- html += responseTypes.length > 0 ? responseTypes.join('') : '<span style="color:var(--text2)">Unknown</span>';
1618
+ html += responseTypes.length > 0 ? responseTypes.join('') : '<span class="text-muted">Unknown</span>';
1761
1619
  html += '</div></div>';
1762
1620
 
1763
1621
  if (actionDetails.redirectsTo) {
1764
1622
  html += '<div class="detail-section">';
1765
- html += '<div class="detail-label">↪️ Redirects To</div>';
1623
+ html += '<div class="detail-label">\u21AA\uFE0F Redirects To</div>';
1766
1624
  html += '<div class="detail-value" style="font-family:monospace;font-size:12px;background:var(--bg3);padding:8px;border-radius:4px">' + actionDetails.redirectsTo + '</div>';
1767
1625
  html += '</div>';
1768
1626
  }
@@ -1772,10 +1630,10 @@ export class PageMapGenerator {
1772
1630
  const view = pageInfo?.view || route.view;
1773
1631
  if (view) {
1774
1632
  html += '<div class="detail-section">';
1775
- html += '<div class="detail-label">📄 View Template</div>';
1633
+ html += '<div class="detail-label">\u{1F4C4} View Template</div>';
1776
1634
  html += '<div class="detail-value" style="font-family:monospace;font-size:12px">app/views/' + view.path + '</div>';
1777
1635
  if (view.partials && view.partials.length > 0) {
1778
- html += '<div style="margin-top:6px;font-size:11px;color:var(--text2)">Partials: ' + view.partials.slice(0, 5).join(', ') + '</div>';
1636
+ html += '<div class="subtext">Partials: ' + view.partials.slice(0, 5).join(', ') + '</div>';
1779
1637
  }
1780
1638
  if (view.instanceVars && view.instanceVars.length > 0) {
1781
1639
  html += '<div style="margin-top:4px;font-size:11px;color:var(--text2)">Instance vars: @' + view.instanceVars.slice(0, 5).join(', @') + '</div>';
@@ -1786,8 +1644,8 @@ export class PageMapGenerator {
1786
1644
  // Before/After Filters
1787
1645
  if (controllerInfo && (controllerInfo.beforeActions.length > 0 || controllerInfo.afterActions.length > 0)) {
1788
1646
  html += '<div class="detail-section">';
1789
- html += '<div class="detail-label">🔒 Filters Applied</div>';
1790
- html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
1647
+ html += '<div class="detail-label">\u{1F512} Filters Applied</div>';
1648
+ html += '<div class="code-block">';
1791
1649
 
1792
1650
  const applicableBeforeFilters = controllerInfo.beforeActions.filter(f => {
1793
1651
  if (f.only && f.only.length > 0) return f.only.includes(route.action);
@@ -1799,7 +1657,7 @@ export class PageMapGenerator {
1799
1657
  html += '<div style="font-size:11px;margin-bottom:4px"><span style="color:#22c55e;font-weight:600">Before:</span> ';
1800
1658
  html += applicableBeforeFilters.map(f => {
1801
1659
  let info = f.name;
1802
- if (f.if) info += ' <span style="color:var(--text2)">(if: ' + f.if + ')</span>';
1660
+ if (f.if) info += ' <span class="text-muted">(if: ' + f.if + ')</span>';
1803
1661
  return info;
1804
1662
  }).join(', ');
1805
1663
  html += '</div>';
@@ -1827,10 +1685,10 @@ export class PageMapGenerator {
1827
1685
  const services = pageInfo?.services || actionDetails?.servicesCalled || [];
1828
1686
  if (services.length > 0) {
1829
1687
  html += '<div class="detail-section">';
1830
- html += '<div class="detail-label">⚙️ Services Called</div>';
1688
+ html += '<div class="detail-label">\u2699\uFE0F Services Called</div>';
1831
1689
  html += '<div class="detail-items">';
1832
1690
  services.forEach(s => {
1833
- html += '<div class="detail-item"><span class="tag" style="background:#8b5cf6">Service</span><span class="name" style="font-family:monospace">' + s + '</span></div>';
1691
+ html += '<div class="detail-item"><span class="tag tag-service">Service</span><span class="name">' + s + '</span></div>';
1834
1692
  });
1835
1693
  html += '</div></div>';
1836
1694
  }
@@ -1839,10 +1697,10 @@ export class PageMapGenerator {
1839
1697
  const grpcCalls = pageInfo?.grpcCalls || [];
1840
1698
  if (grpcCalls.length > 0) {
1841
1699
  html += '<div class="detail-section">';
1842
- html += '<div class="detail-label">🔌 gRPC Calls</div>';
1700
+ html += '<div class="detail-label">\u{1F50C} gRPC Calls</div>';
1843
1701
  html += '<div class="detail-items">';
1844
1702
  grpcCalls.forEach(g => {
1845
- html += '<div class="detail-item"><span class="tag" style="background:#06b6d4">gRPC</span><span class="name" style="font-family:monospace">' + g + '</span></div>';
1703
+ html += '<div class="detail-item"><span class="tag tag-grpc">gRPC</span><span class="name">' + g + '</span></div>';
1846
1704
  });
1847
1705
  html += '</div></div>';
1848
1706
  }
@@ -1851,10 +1709,10 @@ export class PageMapGenerator {
1851
1709
  const models = pageInfo?.modelAccess || actionDetails?.modelsCalled || [];
1852
1710
  if (models.length > 0) {
1853
1711
  html += '<div class="detail-section">';
1854
- html += '<div class="detail-label">💾 Models Accessed</div>';
1712
+ html += '<div class="detail-label">\u{1F4BE} Models Accessed</div>';
1855
1713
  html += '<div class="detail-items">';
1856
1714
  models.forEach(m => {
1857
- html += '<div class="detail-item"><span class="tag" style="background:#f59e0b">Model</span><span class="name" style="font-family:monospace">' + m + '</span></div>';
1715
+ html += '<div class="detail-item"><span class="tag tag-model">Model</span><span class="name">' + m + '</span></div>';
1858
1716
  });
1859
1717
  html += '</div></div>';
1860
1718
  }
@@ -1868,17 +1726,17 @@ export class PageMapGenerator {
1868
1726
 
1869
1727
  if (meaningfulCalls.length > 0) {
1870
1728
  html += '<div class="detail-section">';
1871
- html += '<div class="detail-label">🔗 Method Calls in Action</div>';
1729
+ html += '<div class="detail-label">\u{1F517} Method Calls in Action</div>';
1872
1730
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;max-height:150px;overflow-y:auto">';
1873
1731
  html += '<div style="font-family:monospace;font-size:11px;line-height:1.6">';
1874
1732
  meaningfulCalls.forEach((call, i) => {
1875
- html += '<div style="padding:2px 0;border-bottom:1px solid var(--bg1)">';
1733
+ html += '<div class="accordion-item">';
1876
1734
  html += '<span style="color:var(--text2);margin-right:8px">' + (i+1) + '.</span>';
1877
- html += '<span style="color:var(--accent)">' + call + '</span>';
1735
+ html += '<span class="text-accent">' + call + '</span>';
1878
1736
  html += '</div>';
1879
1737
  });
1880
1738
  if (actionDetails.methodCalls.length > 15) {
1881
- html += '<div style="padding:4px 0;color:var(--text2);font-style:italic">...and ' + (actionDetails.methodCalls.length - 15) + ' more</div>';
1739
+ html += '<div class="note">...and ' + (actionDetails.methodCalls.length - 15) + ' more</div>';
1882
1740
  }
1883
1741
  html += '</div></div></div>';
1884
1742
  }
@@ -1886,20 +1744,20 @@ export class PageMapGenerator {
1886
1744
 
1887
1745
  // Source Files
1888
1746
  html += '<div class="detail-section">';
1889
- html += '<div class="detail-label">📁 Source Files</div>';
1747
+ html += '<div class="detail-label">\u{1F4C1} Source Files</div>';
1890
1748
  html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px;font-family:monospace;font-size:11px">';
1891
1749
 
1892
1750
  if (controllerInfo) {
1893
- html += '<div style="padding:4px 0;display:flex;align-items:center">';
1894
- html += '<span style="color:var(--text2);width:80px">Controller:</span>';
1751
+ html += '<div class="detail-item flex items-center py-1">';
1752
+ html += '<span class="text-muted w-20">Controller:</span>';
1895
1753
  html += '<span>app/controllers/' + controllerInfo.filePath;
1896
- if (actionDetails && actionDetails.line) html += ':<span style="color:#22c55e">' + actionDetails.line + '</span>';
1754
+ if (actionDetails && actionDetails.line) html += ':<span class="text-success">' + actionDetails.line + '</span>';
1897
1755
  html += '</span></div>';
1898
1756
  }
1899
1757
 
1900
1758
  if (view) {
1901
- html += '<div style="padding:4px 0;display:flex;align-items:center">';
1902
- html += '<span style="color:var(--text2);width:80px">View:</span>';
1759
+ html += '<div class="detail-item flex items-center py-1">';
1760
+ html += '<span class="text-muted w-20">View:</span>';
1903
1761
  html += '<span>app/views/' + view.path + '</span>';
1904
1762
  html += '</div>';
1905
1763
  }
@@ -1908,13 +1766,13 @@ export class PageMapGenerator {
1908
1766
  // Controller Info
1909
1767
  if (controllerInfo) {
1910
1768
  html += '<div class="detail-section">';
1911
- html += '<div class="detail-label">📋 Controller Info</div>';
1912
- html += '<div style="background:var(--bg3);padding:10px;border-radius:6px;margin-top:6px">';
1913
- html += '<div style="font-weight:600;margin-bottom:4px">' + controllerInfo.className + '</div>';
1769
+ html += '<div class="detail-label">\u{1F4CB} Controller Info</div>';
1770
+ html += '<div class="code-block">';
1771
+ html += '<div class="section-title">' + controllerInfo.className + '</div>';
1914
1772
  html += '<div style="font-size:11px;color:var(--text2)">extends ' + controllerInfo.parentClass + '</div>';
1915
1773
  if (controllerInfo.concerns && controllerInfo.concerns.length > 0) {
1916
1774
  html += '<div style="margin-top:6px;font-size:11px">';
1917
- html += '<span style="color:var(--text2)">Concerns:</span> ' + controllerInfo.concerns.join(', ');
1775
+ html += '<span class="text-muted">Concerns:</span> ' + controllerInfo.concerns.join(', ');
1918
1776
  html += '</div>';
1919
1777
  }
1920
1778
  html += '</div></div>';
@@ -1922,7 +1780,7 @@ export class PageMapGenerator {
1922
1780
 
1923
1781
  if (!pageInfo && !actionDetails) {
1924
1782
  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.';
1783
+ html += '\u26A0\uFE0F No detailed action information found. The controller or action may not be analyzed yet.';
1926
1784
  html += '</div>';
1927
1785
  }
1928
1786
 
@@ -1953,7 +1811,7 @@ export class PageMapGenerator {
1953
1811
  html += '<div class="detail-label">Partials Used</div>';
1954
1812
  html += '<div class="detail-items">';
1955
1813
  view.partials.forEach(p => {
1956
- html += '<div class="detail-item"><span class="tag" style="background:#6b7280">PARTIAL</span><span class="name">' + p + '</span></div>';
1814
+ html += '<div class="detail-item"><span class="tag tag-partial">PARTIAL</span><span class="name">' + p + '</span></div>';
1957
1815
  });
1958
1816
  html += '</div></div>';
1959
1817
  }
@@ -1963,7 +1821,7 @@ export class PageMapGenerator {
1963
1821
  html += '<div class="detail-label">Instance Variables</div>';
1964
1822
  html += '<div class="detail-items">';
1965
1823
  view.instanceVars.slice(0, 10).forEach(v => {
1966
- html += '<div class="detail-item"><span class="tag" style="background:#f59e0b">@</span><span class="name">' + v + '</span></div>';
1824
+ html += '<div class="detail-item"><span class="tag tag-var">@</span><span class="name">' + v + '</span></div>';
1967
1825
  });
1968
1826
  if (view.instanceVars.length > 10) {
1969
1827
  html += '<div style="padding:4px 8px;color:var(--text2);font-size:11px">...and ' + (view.instanceVars.length - 10) + ' more</div>';
@@ -1971,7 +1829,7 @@ export class PageMapGenerator {
1971
1829
  html += '</div></div>';
1972
1830
  }
1973
1831
 
1974
- showModal('📄 ' + view.controller + '/' + view.action, html);
1832
+ showModal('\u{1F4C4} ' + view.controller + '/' + view.action, html);
1975
1833
  }
1976
1834
 
1977
1835
  function toggleGroup(el) {
@@ -1991,14 +1849,14 @@ export class PageMapGenerator {
1991
1849
  hiddenItems.forEach(item => {
1992
1850
  item.style.display = 'none';
1993
1851
  });
1994
- moreBtn.innerHTML = ' Show ' + hiddenItems.length + ' more variables';
1852
+ moreBtn.innerHTML = '\u25BC Show ' + hiddenItems.length + ' more variables';
1995
1853
  moreBtn.setAttribute('data-expanded', 'false');
1996
1854
  } else {
1997
1855
  // Expand: show all items
1998
1856
  hiddenItems.forEach(item => {
1999
1857
  item.style.display = '';
2000
1858
  });
2001
- moreBtn.innerHTML = ' Show less';
1859
+ moreBtn.innerHTML = '\u25B2 Show less';
2002
1860
  moreBtn.setAttribute('data-expanded', 'true');
2003
1861
  }
2004
1862
  }
@@ -2077,11 +1935,11 @@ export class PageMapGenerator {
2077
1935
  // Parse operations to extract path info and depth
2078
1936
  const parsedOps = graphqlOps.map(df => {
2079
1937
  const rawName = df.operationName || '';
2080
- // Pattern: " QueryName (via HookA)" or " QueryName (via HookA)" etc.
2081
- const arrowCount = (rawName.match(/→/g) || []).length;
1938
+ // Pattern: "\u2192 QueryName (via HookA)" or "\u2192 \u2192 QueryName (via HookA)" etc.
1939
+ const arrowCount = (rawName.match(/\u2192/g) || []).length;
2082
1940
 
2083
1941
  // Extract query name and path
2084
- let queryName = rawName.replace(/^[→\\s]+/, '').replace(/^\\u2192\\s*/g, '');
1942
+ let queryName = rawName.replace(/^[\u2192\\s]+/, '').replace(/^\\u2192\\s*/g, '');
2085
1943
  let sourcePath = '';
2086
1944
 
2087
1945
  // Extract "(via X)" for hook
@@ -2099,7 +1957,7 @@ export class PageMapGenerator {
2099
1957
  }
2100
1958
 
2101
1959
  // Further clean the query name
2102
- queryName = queryName.replace(/^[→\\s]+/, '').trim();
1960
+ queryName = queryName.replace(/^[\u2192\\s]+/, '').trim();
2103
1961
 
2104
1962
  return {
2105
1963
  ...df,
@@ -2162,13 +2020,13 @@ export class PageMapGenerator {
2162
2020
 
2163
2021
  sortedPaths.forEach(pathName => {
2164
2022
  const ops = groupedByPath.get(pathName);
2165
- const depthIndicator = pathName === 'Direct' ? '' : ' ';
2023
+ const depthIndicator = pathName === 'Direct' ? '' : '\u21B3 ';
2166
2024
  const pathLabel = pathName === 'Direct' ? 'Direct (this page)' : pathName;
2167
2025
 
2168
2026
  // Path header with depth visual
2169
2027
  dataHtml += '<div class="data-path-group" style="margin:8px 0">' +
2170
2028
  '<div class="data-path-header" style="font-size:11px;color:var(--text2);margin-bottom:4px;padding-left:'+(ops[0]?.depth * 8)+'px">' +
2171
- depthIndicator + '<span style="color:var(--accent)">' + pathLabel + '</span> (' + ops.length + ')' +
2029
+ depthIndicator + '<span class="text-accent">' + pathLabel + '</span> (' + ops.length + ')' +
2172
2030
  '</div>';
2173
2031
 
2174
2032
  ops.forEach(op => {
@@ -2189,7 +2047,7 @@ export class PageMapGenerator {
2189
2047
  dataHtml += '<div class="detail-section"><h4>Used Components</h4>';
2190
2048
  uniqueComponentRefs.forEach(df => {
2191
2049
  const name = df.operationName || '';
2192
- dataHtml += '<div class="detail-item" style="cursor:default"><span class="tag" style="background:var(--text2);color:var(--bg)">COMPONENT</span> '+name+'</div>';
2050
+ dataHtml += '<div class="detail-item" style="cursor:default"><span class="tag tag-default">COMPONENT</span> '+name+'</div>';
2193
2051
  });
2194
2052
  dataHtml += '</div>';
2195
2053
  }
@@ -2214,7 +2072,7 @@ export class PageMapGenerator {
2214
2072
  const methodColors = {GET:'#22c55e',POST:'#3b82f6',PUT:'#f59e0b',DELETE:'#ef4444',PATCH:'#8b5cf6'};
2215
2073
  const color = methodColors[api.method] || '#6b7280';
2216
2074
  restApiHtml += '<div class="detail-item api-item" onclick="event.stopPropagation(); showApiDetail(\\''+api.id.replace(/'/g, "\\\\'")+'\\')">' +
2217
- '<div class="api-row"><span class="tag" style="background:'+color+'">'+api.method+'</span><span class="api-url">'+api.url+'</span></div>' +
2075
+ '<div class="api-row"><span class="tag" style="background:'+color+';color:white">'+api.method+'</span><span class="api-url">'+api.url+'</span></div>' +
2218
2076
  (api.filePath ? '<div class="api-route">'+api.callType+' in '+api.filePath+'</div>' : '') +
2219
2077
  '</div>';
2220
2078
  });
@@ -2360,8 +2218,8 @@ export class PageMapGenerator {
2360
2218
  const groupPages = groups[g];
2361
2219
  html += '<div style="margin-bottom:12px">';
2362
2220
  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>';
2364
- html += '<span style="color:var(--accent)">'+groupPages.length+' pages</span></div>';
2221
+ html += '<div class="detail-label" style="display:flex;align-items:center;gap:6px"><span class="group-toggle">\u25B8</span>/'+g+'</div>';
2222
+ html += '<span class="text-accent">'+groupPages.length+' pages</span></div>';
2365
2223
  html += '<div class="group-page-list" style="display:none;margin-left:16px;margin-top:4px">';
2366
2224
  groupPages.sort((a,b) => a.path.localeCompare(b.path)).forEach(p => {
2367
2225
  const isAuth = p.authentication?.required;
@@ -2369,7 +2227,7 @@ export class PageMapGenerator {
2369
2227
  html += '<div class="detail-item rel-item" style="cursor:pointer;padding:6px 8px" onclick="event.stopPropagation(); selectPage(\\''+p.path+'\\')">'+
2370
2228
  '<span style="font-family:monospace;font-size:11px;color:var(--text)">'+p.path+'</span>'+
2371
2229
  (isAuth ? '<span class="tag tag-auth" style="margin-left:6px;font-size:9px">AUTH</span>' : '')+
2372
- (isDynamic ? '<span class="tag" style="margin-left:6px;font-size:9px;background:#6366f1">DYNAMIC</span>' : '')+
2230
+ (isDynamic ? '<span class="tag tag-info" style="margin-left:6px" title="Dynamic Route">DYNAMIC</span>' : '')+
2373
2231
  '</div>';
2374
2232
  });
2375
2233
  html += '</div></div>';
@@ -2386,10 +2244,10 @@ export class PageMapGenerator {
2386
2244
  const toggle = el.querySelector('.group-toggle');
2387
2245
  if (list.style.display === 'none') {
2388
2246
  list.style.display = 'block';
2389
- toggle.textContent = '';
2247
+ toggle.textContent = '\u25BE';
2390
2248
  } else {
2391
2249
  list.style.display = 'none';
2392
- toggle.textContent = '';
2250
+ toggle.textContent = '\u25B8';
2393
2251
  }
2394
2252
  };
2395
2253
 
@@ -2434,7 +2292,7 @@ export class PageMapGenerator {
2434
2292
  const indent = depth * 12;
2435
2293
  let html = '<div class="rel-item" style="padding-left:'+(10+indent)+'px" onclick="event.stopPropagation(); selectPage(\\''+page.path+'\\')">';
2436
2294
  html += '<div class="rel-header">';
2437
- html += '<span style="color:var(--text2);font-size:10px">'+''.repeat(depth > 0 ? 1 : 0)+(depth > 0 ? ' ' : '')+'</span>';
2295
+ html += '<span style="color:var(--text2);font-size:10px">'+'\u2500'.repeat(depth > 0 ? 1 : 0)+(depth > 0 ? ' ' : '')+'</span>';
2438
2296
  html += '<span class="rel-path">'+page.path+'</span>';
2439
2297
  if (page.children && page.children.length > 0) {
2440
2298
  html += '<span style="color:var(--text2);font-size:9px;margin-left:auto">'+page.children.length+' children</span>';
@@ -2482,7 +2340,7 @@ export class PageMapGenerator {
2482
2340
  const fragments = Array.from(gqlMap.values()).filter(o => o.type === 'fragment');
2483
2341
 
2484
2342
  if (queries.length > 0) {
2485
- html += '<div style="margin:8px 0;font-size:11px;color:var(--accent)">Queries ('+queries.length+')</div>';
2343
+ html += '<div class="subtext-accent">Queries ('+queries.length+')</div>';
2486
2344
  queries.slice(0, 20).forEach(op => {
2487
2345
  html += '<div class="detail-item data-op" onclick="event.stopPropagation(); showDataDetail(\\''+op.name.replace(/'/g, "\\\\'")+'\\')">' +
2488
2346
  '<span class="tag tag-query">QUERY</span> '+op.name+'</div>';
@@ -2493,7 +2351,7 @@ export class PageMapGenerator {
2493
2351
  }
2494
2352
 
2495
2353
  if (mutations.length > 0) {
2496
- html += '<div style="margin:8px 0;font-size:11px;color:var(--accent)">Mutations ('+mutations.length+')</div>';
2354
+ html += '<div class="subtext-accent">Mutations ('+mutations.length+')</div>';
2497
2355
  mutations.slice(0, 10).forEach(op => {
2498
2356
  html += '<div class="detail-item data-op" onclick="event.stopPropagation(); showDataDetail(\\''+op.name.replace(/'/g, "\\\\'")+'\\')">' +
2499
2357
  '<span class="tag tag-mutation">MUTATION</span> '+op.name+'</div>';
@@ -2504,10 +2362,10 @@ export class PageMapGenerator {
2504
2362
  }
2505
2363
 
2506
2364
  if (fragments.length > 0) {
2507
- html += '<div style="margin:8px 0;font-size:11px;color:var(--accent)">Fragments ('+fragments.length+')</div>';
2365
+ html += '<div class="subtext-accent">Fragments ('+fragments.length+')</div>';
2508
2366
  fragments.slice(0, 5).forEach(op => {
2509
2367
  html += '<div class="detail-item data-op" onclick="event.stopPropagation(); showDataDetail(\\''+op.name.replace(/'/g, "\\\\'")+'\\')">' +
2510
- '<span class="tag" style="background:#6b7280">FRAGMENT</span> '+op.name+'</div>';
2368
+ '<span class="tag tag-default">FRAGMENT</span> '+op.name+'</div>';
2511
2369
  });
2512
2370
  if (fragments.length > 5) {
2513
2371
  html += '<div style="color:var(--text2);font-size:10px;padding:4px">... and '+(fragments.length-5)+' more fragments</div>';
@@ -2532,7 +2390,7 @@ export class PageMapGenerator {
2532
2390
  const methodColors = {GET:'#22c55e',POST:'#3b82f6',PUT:'#f59e0b',DELETE:'#ef4444',PATCH:'#8b5cf6'};
2533
2391
  const color = methodColors[api.method] || '#6b7280';
2534
2392
  html += '<div class="detail-item api-item" onclick="event.stopPropagation(); showApiDetail(\\''+api.id.replace(/'/g, "\\\\'")+'\\')">' +
2535
- '<div class="api-row"><span class="tag" style="background:'+color+'">'+api.method+'</span><span class="api-url">'+api.url+'</span></div>' +
2393
+ '<div class="api-row"><span class="tag" style="background:'+color+';color:white">'+api.method+'</span><span class="api-url">'+api.url+'</span></div>' +
2536
2394
  '<div class="api-route">'+api.callType+' in '+api.filePath+'</div>' +
2537
2395
  '</div>';
2538
2396
  });
@@ -2567,7 +2425,7 @@ export class PageMapGenerator {
2567
2425
 
2568
2426
  if (api.category && api.category !== 'internal') {
2569
2427
  html += '<div class="detail-section"><h4>Category</h4>' +
2570
- '<span class="tag" style="background:var(--accent);color:white">'+api.category.toUpperCase()+'</span></div>';
2428
+ '<span class="tag tag-accent">'+api.category.toUpperCase()+'</span></div>';
2571
2429
  }
2572
2430
 
2573
2431
  // Show in modal
@@ -2616,12 +2474,12 @@ export class PageMapGenerator {
2616
2474
  };
2617
2475
 
2618
2476
  function showDataDetail(rawName, sourcePath) {
2619
- // Clean up name: remove " " prefix and " (ComponentName)" suffix
2477
+ // Clean up name: remove "\u2192 " prefix and " (ComponentName)" suffix
2620
2478
  const name = rawName
2621
- .replace(/^[→\\->\\s]+/, '')
2479
+ .replace(/^[\u2192\\->\\s]+/, '')
2622
2480
  .replace(/\\s*\\([^)]+\\)\\s*$/, '');
2623
2481
 
2624
- // Convert SCREAMING_CASE to PascalCase (e.g., COMPANY_QUERY CompanyQuery)
2482
+ // Convert SCREAMING_CASE to PascalCase (e.g., COMPANY_QUERY \u2192 CompanyQuery)
2625
2483
  const toPascalCase = (str) => {
2626
2484
  if (!/^[A-Z][A-Z0-9_]*$/.test(str)) return str;
2627
2485
  return str.toLowerCase().split('_').map(word =>
@@ -2674,11 +2532,11 @@ export class PageMapGenerator {
2674
2532
  // Source info
2675
2533
  if (sourcePath) {
2676
2534
  const isHook = sourcePath.startsWith('use');
2677
- html += '<div class="detail-section"><h4>Source</h4><div class="detail-item" style="font-size:12px">via '+(isHook?'Hook':'Component')+': <span style="color:var(--accent)">'+sourcePath+'</span></div></div>';
2535
+ html += '<div class="detail-section"><h4>Source</h4><div class="detail-item" style="font-size:12px">via '+(isHook?'Hook':'Component')+': <span class="text-accent">'+sourcePath+'</span></div></div>';
2678
2536
  }
2679
2537
 
2680
2538
  // 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>';
2539
+ 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
2540
  html += '<code style="background:#0f172a;color:#93c5fd;padding:4px 8px;border-radius:4px;font-family:monospace">'+op.name+'</code></div>';
2683
2541
 
2684
2542
  if (op.returnType) {
@@ -2697,7 +2555,7 @@ export class PageMapGenerator {
2697
2555
  // Escape for data attribute
2698
2556
  const gqlCodeEscaped = gqlCode.replace(/'/g, "\\\\'").replace(/"/g, '&quot;');
2699
2557
 
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>';
2558
+ 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
2559
  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
2560
  } else if (op.variables?.length) {
2703
2561
  html += '<div class="detail-section"><h4>Variables</h4>';
@@ -2709,7 +2567,7 @@ export class PageMapGenerator {
2709
2567
  op.usedIn.slice(0,8).forEach(f => { html += '<div class="detail-item">'+f+'</div>'; });
2710
2568
  if (op.usedIn.length > 8) {
2711
2569
  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>';
2570
+ 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
2571
  }
2714
2572
  html += '</div>';
2715
2573
  }
@@ -2752,7 +2610,7 @@ export class PageMapGenerator {
2752
2610
  });
2753
2611
  if (queries.length > 5) {
2754
2612
  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>';
2613
+ html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" class="expand-more">\u25B8 Show ' + (queries.length - 5) + ' more queries</div>';
2756
2614
  }
2757
2615
  }
2758
2616
 
@@ -2764,7 +2622,7 @@ export class PageMapGenerator {
2764
2622
  });
2765
2623
  if (mutations.length > 5) {
2766
2624
  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>';
2625
+ html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" class="expand-more">\u25B8 Show ' + (mutations.length - 5) + ' more mutations</div>';
2768
2626
  }
2769
2627
  }
2770
2628
 
@@ -2772,11 +2630,11 @@ export class PageMapGenerator {
2772
2630
  html += '<div style="margin:8px 0;font-size:10px;color:var(--text2)">Fragments ('+fragments.length+')</div>';
2773
2631
  fragments.slice(0,3).forEach(op => {
2774
2632
  html += '<div class="detail-item data-op" onclick="showDataDetail(\\''+op.name.replace(/'/g, "\\\\'")+'\\')">' +
2775
- '<span class="tag" style="background:#6b7280">FRAGMENT</span> '+op.name+'</div>';
2633
+ '<span class="tag tag-default">FRAGMENT</span> '+op.name+'</div>';
2776
2634
  });
2777
2635
  if (fragments.length > 3) {
2778
2636
  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>';
2637
+ html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" class="expand-more">\u25B8 Show ' + (fragments.length - 3) + ' more fragments</div>';
2780
2638
  }
2781
2639
  }
2782
2640
 
@@ -2799,9 +2657,9 @@ export class PageMapGenerator {
2799
2657
  html += '</div>';
2800
2658
  }
2801
2659
  } else {
2802
- // Clean up the name: remove " " prefix and " (ComponentName)" suffix
2660
+ // Clean up the name: remove "\u2192 " prefix and " (ComponentName)" suffix
2803
2661
  let cleanName = name
2804
- .replace(/^[→\\->\\s]+/, '') // Remove arrow prefix
2662
+ .replace(/^[\u2192\\->\\s]+/, '') // Remove arrow prefix
2805
2663
  .replace(/\\s*\\([^)]+\\)\\s*$/, ''); // Remove parenthetical component reference
2806
2664
 
2807
2665
  // Extract the core component name - remove ALL common suffixes iteratively
@@ -2892,7 +2750,7 @@ export class PageMapGenerator {
2892
2750
  });
2893
2751
  if (queries.length > 8) {
2894
2752
  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>';
2753
+ html += '<div class="expand-more" onclick="expandMore(\\'query\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" class="expand-more">\u25B8 Show ' + (queries.length - 8) + ' more</div>';
2896
2754
  }
2897
2755
  }
2898
2756
 
@@ -2904,7 +2762,7 @@ export class PageMapGenerator {
2904
2762
  });
2905
2763
  if (mutations.length > 5) {
2906
2764
  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>';
2765
+ html += '<div class="expand-more" onclick="expandMore(\\'mutation\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" class="expand-more">\u25B8 Show ' + (mutations.length - 5) + ' more</div>';
2908
2766
  }
2909
2767
  }
2910
2768
 
@@ -2912,11 +2770,11 @@ export class PageMapGenerator {
2912
2770
  html += '<div style="margin:8px 0 6px;font-size:10px;color:var(--text2)">Fragments ('+fragments.length+')</div>';
2913
2771
  fragments.slice(0, 3).forEach(op => {
2914
2772
  html += '<div class="detail-item data-op" onclick="showDataDetail(\\''+op.name.replace(/'/g, "\\\\'")+'\\')">' +
2915
- '<span class="tag" style="background:#6b7280">FRAGMENT</span> '+op.name+'</div>';
2773
+ '<span class="tag tag-default">FRAGMENT</span> '+op.name+'</div>';
2916
2774
  });
2917
2775
  if (fragments.length > 3) {
2918
2776
  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>';
2777
+ html += '<div class="expand-more" onclick="expandMore(\\'fragment\\', '+JSON.stringify(remaining).replace(/"/g, '&quot;')+', this)" class="expand-more">\u25B8 Show ' + (fragments.length - 3) + ' more</div>';
2920
2778
  }
2921
2779
  }
2922
2780
 
@@ -2973,7 +2831,7 @@ export class PageMapGenerator {
2973
2831
  window.copyToClipboard = function(text, btn) {
2974
2832
  navigator.clipboard.writeText(text).then(() => {
2975
2833
  const originalText = btn.textContent;
2976
- btn.textContent = '';
2834
+ btn.textContent = '\u2713';
2977
2835
  btn.classList.add('copied');
2978
2836
  setTimeout(() => {
2979
2837
  btn.textContent = originalText;
@@ -3459,104 +3317,21 @@ export class PageMapGenerator {
3459
3317
  }
3460
3318
  </script>
3461
3319
  </body>
3462
- </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}">
3527
- <span class="page-type" style="--type-color:${type.color}">${type.label}</span>
3528
- <span class="page-path">${displayPath}</span>
3320
+ </html>`}buildTreeHtml(s,r){let c=["#ef4444","#f97316","#eab308","#22c55e","#14b8a6","#3b82f6","#8b5cf6","#ec4899"],v=0;return Array.from(s.entries()).sort((t,l)=>t[0].localeCompare(l[0])).map(([t,l])=>{let e=c[v++%c.length],i=l.sort((a,m)=>a.path.localeCompare(m.path)),o=new Set(i.map(a=>a.path)),p=new Map;for(let a of i){let m=a.path.split("/").filter(Boolean),u=0;for(let h=m.length-1;h>=1;h--){let y="/"+m.slice(0,h).join("/");if(o.has(y)){u=(p.get(y)??0)+1;break}}p.set(a.path,u);}let f=i.map(a=>{let m=this.getPageType(a.path),u=(a.dataFetching||[]).filter(g=>!g.type?.includes("Mutation")).length,h=(a.dataFetching||[]).filter(g=>g.type?.includes("Mutation")).length,y=p.get(a.path)??0,n=a.repo||"",w=r.some(g=>g.repo&&g.repo!==n),C=n.split("/").pop()?.split("-").map(g=>g.substring(0,4)).join("-")||n.substring(0,8),x=w&&n?`<span class="tag tag-repo" title="${n}">${C}</span>`:"",d=/^\/[A-Z]/.test(a.path)||a.filePath&&a.filePath.includes("components/pages"),b=d&&a.filePath?a.filePath.replace(/\.tsx?$/,"").replace(/^(frontend\/src\/|src\/)/,""):a.path,R=d?'<span class="tag tag-info" title="SPA Component Page">SPA</span>':"";return `<div class="page-item" data-path="${a.path}" data-repo="${n}" onclick="selectPage('${a.path}')" style="--depth:${y}">
3321
+ <span class="page-type" style="--type-color:${m.color}">${m.label}</span>
3322
+ <span class="page-path">${b}</span>
3529
3323
  <div class="page-tags">
3530
- ${repoTag}
3531
- ${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>` : ''}
3324
+ ${x}
3325
+ ${R}
3326
+ ${a.authentication?.required?'<span class="tag tag-auth">AUTH</span>':""}
3327
+ ${u>0?`<span class="tag tag-query">Q:${u}</span>`:""}
3328
+ ${h>0?`<span class="tag tag-mutation">M:${h}</span>`:""}
3535
3329
  </div>
3536
- </div>`;
3537
- })
3538
- .join('');
3539
- return `<div class="group">
3540
- <div class="group-header" onclick="toggleGroup(this)" style="--group-color:${color}">
3541
- <span class="group-arrow">▼</span>
3542
- <span class="group-name">/${name}</span>
3543
- <span class="group-count">${pages.length}</span>
3330
+ </div>`}).join("");return `<div class="group">
3331
+ <div class="group-header" onclick="toggleGroup(this)" style="--group-color:${e}">
3332
+ <span class="group-arrow">\u25BC</span>
3333
+ <span class="group-name">/${t}</span>
3334
+ <span class="group-count">${l.length}</span>
3544
3335
  </div>
3545
- <div class="group-content">${pagesHtml}</div>
3546
- </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
- }
3336
+ <div class="group-content">${f}</div>
3337
+ </div>`}).join("")}getPageType(s){let r=s.split("/").filter(Boolean).pop()||"";return r==="new"||s.endsWith("/new")?{label:"CREATE",color:"#22c55e"}:r==="edit"||s.includes("/edit")?{label:"EDIT",color:"#f59e0b"}:r.startsWith("[")||r.startsWith(":")?{label:"DETAIL",color:"#3b82f6"}:s.includes("setting")?{label:"SETTINGS",color:"#6b7280"}:{label:"LIST",color:"#06b6d4"}}};export{S as a};