@wtdlee/repomap 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';var n=class{constructor(t){this.rootPath=t;}result=null;async generate(t={}){if(!this.rootPath)throw new Error("Root path required for analysis");let{title:e="Rails Application Map"}=t;this.result=await k(this.rootPath);let s=this.generateHTML(e);return t.outputPath&&(r.writeFileSync(t.outputPath,s),console.log(`
2
- \u{1F4C4} Generated: ${t.outputPath}`)),s}generateFromResult(t,e="Rails Application Map"){return this.result=t,this.generateHTML(e)}generateHTML(t){if(!this.result)throw new Error("Analysis not run");let{routes:e,controllers:s,models:a,grpc:l,summary:i}=this.result;return `<!DOCTYPE html>
1
+ import {k}from'./chunk-H7VVRHQZ.js';import*as c from'fs';import*as d from'path';var i=class{constructor(t){this.rootPath=t;}result=null;async generate(t={}){if(!this.rootPath)throw new Error("Root path required for analysis");let{title:e="Rails Application Map"}=t;this.result=await k(this.rootPath);let s=this.generateHTML(e);return t.outputPath&&(c.writeFileSync(t.outputPath,s),console.log(`
2
+ \u{1F4C4} Generated: ${t.outputPath}`)),s}generateFromResult(t,e="Rails Application Map"){return this.result=t,this.generateHTML(e)}generateHTML(t){if(!this.result)throw new Error("Analysis not run");let{routes:e,controllers:s,models:a,grpc:l,summary:n}=this.result;return `<!DOCTYPE html>
3
3
  <html lang="en">
4
4
  <head>
5
5
  <meta charset="UTF-8">
@@ -23,25 +23,25 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
23
23
  <div class="stats-bar">
24
24
  <div class="stat active" data-view="routes">
25
25
  <div>
26
- <div class="stat-value">${i.totalRoutes.toLocaleString()}</div>
26
+ <div class="stat-value">${n.totalRoutes.toLocaleString()}</div>
27
27
  <div class="stat-label">Routes</div>
28
28
  </div>
29
29
  </div>
30
30
  <div class="stat" data-view="controllers">
31
31
  <div>
32
- <div class="stat-value">${i.totalControllers}</div>
32
+ <div class="stat-value">${n.totalControllers}</div>
33
33
  <div class="stat-label">Controllers</div>
34
34
  </div>
35
35
  </div>
36
36
  <div class="stat" data-view="models">
37
37
  <div>
38
- <div class="stat-value">${i.totalModels}</div>
38
+ <div class="stat-value">${n.totalModels}</div>
39
39
  <div class="stat-label">Models</div>
40
40
  </div>
41
41
  </div>
42
42
  <div class="stat" data-view="grpc">
43
43
  <div>
44
- <div class="stat-value">${i.totalGrpcServices}</div>
44
+ <div class="stat-value">${n.totalGrpcServices}</div>
45
45
  <div class="stat-label">gRPC</div>
46
46
  </div>
47
47
  </div>
@@ -62,7 +62,7 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
62
62
  </div>
63
63
 
64
64
  <div class="sidebar-section namespaces" id="namespaceFilter">
65
- <div class="sidebar-title">Namespaces (${i.namespaces.length})</div>
65
+ <div class="sidebar-title">Namespaces (${n.namespaces.length})</div>
66
66
  <div class="namespace-list">
67
67
  <div class="namespace-item active" data-namespace="all">
68
68
  <span>All</span>
@@ -103,6 +103,11 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
103
103
  let currentView = 'routes';
104
104
  let selectedNamespaces = new Set(['all']);
105
105
  let selectedMethods = new Set(['all']);
106
+ let selectedControllerFlags = new Set(['all']);
107
+ let selectedModelNamespaces = new Set(['all']);
108
+ let selectedModelFlags = new Set(['all']);
109
+ let selectedGrpcNamespaces = new Set(['all']);
110
+ let selectedGrpcFlags = new Set(['all']);
106
111
  let searchQuery = '';
107
112
  let routesDisplayCount = 200;
108
113
  let controllersDisplayCount = 50;
@@ -177,68 +182,83 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
177
182
  });
178
183
  });
179
184
 
180
- // Namespace filter (multi-select with Ctrl/Cmd)
181
- document.querySelectorAll('.namespace-item[data-namespace]').forEach(item => {
182
- item.addEventListener('click', (e) => {
183
- const ns = item.dataset.namespace;
184
- if (e.ctrlKey || e.metaKey) {
185
- // Multi-select
186
- if (ns === 'all') {
187
- selectedNamespaces = new Set(['all']);
188
- } else {
189
- selectedNamespaces.delete('all');
190
- if (selectedNamespaces.has(ns)) {
191
- selectedNamespaces.delete(ns);
192
- if (selectedNamespaces.size === 0) selectedNamespaces.add('all');
193
- } else {
194
- selectedNamespaces.add(ns);
195
- }
196
- }
197
- } else {
198
- // Single select
199
- selectedNamespaces = new Set([ns]);
200
- }
201
- updateFilterUI();
185
+ // Sidebar filter click handler (works even after sidebar re-render)
186
+ document.addEventListener('click', (e) => {
187
+ const target = e.target instanceof Element ? e.target : e.target?.parentElement;
188
+ if (!target) return;
189
+ const item = target.closest('.namespace-item');
190
+ if (!item) return;
191
+
192
+ const filterType = item.dataset.filterType;
193
+ const value = item.dataset.filterValue;
194
+ if (!filterType || value === undefined) return;
195
+
196
+ const multi = e.ctrlKey || e.metaKey;
197
+
198
+ function toggleMulti(setRef, v) {
199
+ if (v === 'all') return new Set(['all']);
200
+ const next = new Set(setRef);
201
+ next.delete('all');
202
+ if (next.has(v)) next.delete(v);
203
+ else next.add(v);
204
+ if (next.size === 0) next.add('all');
205
+ return next;
206
+ }
207
+
208
+ function toggleSingle(v) {
209
+ return new Set([v]);
210
+ }
211
+
212
+ if (filterType === 'routeNamespace') {
213
+ selectedNamespaces = multi ? toggleMulti(selectedNamespaces, value) : toggleSingle(value);
202
214
  routesDisplayCount = 200;
203
215
  saveStateToUrl();
204
- renderMainPanel();
205
- });
206
- });
207
-
208
- // Method filter (multi-select with Ctrl/Cmd)
209
- document.querySelectorAll('.namespace-item[data-method]').forEach(item => {
210
- item.addEventListener('click', (e) => {
211
- const method = item.dataset.method;
212
- if (e.ctrlKey || e.metaKey) {
213
- if (selectedMethods.has('all')) {
214
- selectedMethods = new Set([method]);
215
- } else if (selectedMethods.has(method)) {
216
- selectedMethods.delete(method);
217
- if (selectedMethods.size === 0) selectedMethods.add('all');
218
- } else {
219
- selectedMethods.add(method);
220
- }
221
- } else {
222
- if (selectedMethods.has(method) && selectedMethods.size === 1) {
223
- selectedMethods = new Set(['all']);
224
- } else {
225
- selectedMethods = new Set([method]);
226
- }
227
- }
228
- updateFilterUI();
216
+ } else if (filterType === 'routeMethod') {
217
+ // Methods behave similarly to namespaces
218
+ selectedMethods = multi ? toggleMulti(selectedMethods, value) : toggleSingle(value);
229
219
  routesDisplayCount = 200;
230
220
  saveStateToUrl();
231
- renderMainPanel();
232
- });
221
+ } else if (filterType === 'controllerFlag') {
222
+ selectedControllerFlags = multi
223
+ ? toggleMulti(selectedControllerFlags, value)
224
+ : toggleSingle(value);
225
+ controllersDisplayCount = 50;
226
+ } else if (filterType === 'modelNamespace') {
227
+ selectedModelNamespaces = multi
228
+ ? toggleMulti(selectedModelNamespaces, value)
229
+ : toggleSingle(value);
230
+ modelsDisplayCount = 50;
231
+ } else if (filterType === 'modelFlag') {
232
+ selectedModelFlags = multi ? toggleMulti(selectedModelFlags, value) : toggleSingle(value);
233
+ modelsDisplayCount = 50;
234
+ } else if (filterType === 'grpcNamespace') {
235
+ selectedGrpcNamespaces = multi ? toggleMulti(selectedGrpcNamespaces, value) : toggleSingle(value);
236
+ grpcDisplayCount = 50;
237
+ } else if (filterType === 'grpcFlag') {
238
+ selectedGrpcFlags = multi ? toggleMulti(selectedGrpcFlags, value) : toggleSingle(value);
239
+ grpcDisplayCount = 50;
240
+ }
241
+
242
+ updateFilterUI();
243
+ renderMainPanel();
233
244
  });
234
245
 
235
246
  function updateFilterUI() {
236
- document.querySelectorAll('.namespace-item[data-namespace]').forEach(item => {
237
- item.classList.toggle('active', selectedNamespaces.has(item.dataset.namespace));
238
- });
239
- document.querySelectorAll('.namespace-item[data-method]').forEach(item => {
240
- // Only highlight selected methods, not all when 'all' is selected
241
- item.classList.toggle('active', selectedMethods.has(item.dataset.method));
247
+ document.querySelectorAll('.namespace-item[data-filter-type][data-filter-value]').forEach(item => {
248
+ const t = item.dataset.filterType;
249
+ const v = item.dataset.filterValue;
250
+ if (!t || v === undefined) return;
251
+
252
+ let active = false;
253
+ if (t === 'routeNamespace') active = selectedNamespaces.has(v);
254
+ else if (t === 'routeMethod') active = selectedMethods.has(v);
255
+ else if (t === 'controllerFlag') active = selectedControllerFlags.has(v);
256
+ else if (t === 'modelNamespace') active = selectedModelNamespaces.has(v);
257
+ else if (t === 'modelFlag') active = selectedModelFlags.has(v);
258
+ else if (t === 'grpcNamespace') active = selectedGrpcNamespaces.has(v);
259
+ else if (t === 'grpcFlag') active = selectedGrpcFlags.has(v);
260
+
261
+ item.classList.toggle('active', active);
242
262
  });
243
263
  }
244
264
 
@@ -250,19 +270,10 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
250
270
 
251
271
  // Render Functions
252
272
  function renderMainPanel() {
253
- // Disable filters for models/diagram tabs
273
+ // Update sidebar filters per view (routes/controllers vs models vs grpc)
254
274
  const namespaceFilter = document.getElementById('namespaceFilter');
255
275
  const methodFilter = document.getElementById('methodFilter');
256
- const filtersDisabled = currentView === 'models' || currentView === 'diagram';
257
-
258
- if (namespaceFilter) {
259
- namespaceFilter.style.opacity = filtersDisabled ? '0.4' : '1';
260
- namespaceFilter.style.pointerEvents = filtersDisabled ? 'none' : 'auto';
261
- }
262
- if (methodFilter) {
263
- methodFilter.style.opacity = filtersDisabled ? '0.4' : '1';
264
- methodFilter.style.pointerEvents = filtersDisabled ? 'none' : 'auto';
265
- }
276
+ renderSidebarFilters(namespaceFilter, methodFilter);
266
277
 
267
278
  switch (currentView) {
268
279
  case 'routes':
@@ -285,6 +296,289 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
285
296
  attachEventListeners();
286
297
  }
287
298
 
299
+ function renderSidebarFilters(namespaceFilter, methodFilter) {
300
+ if (!namespaceFilter || !methodFilter) return;
301
+
302
+ function sectionHtml(title, listClass, inner) {
303
+ return (
304
+ '<div class="sidebar-title">' +
305
+ title +
306
+ '</div>' +
307
+ '<div class="' +
308
+ listClass +
309
+ '">' +
310
+ inner +
311
+ '</div>'
312
+ );
313
+ }
314
+
315
+ if (currentView === 'routes') {
316
+ namespaceFilter.style.opacity = '1';
317
+ namespaceFilter.style.pointerEvents = 'auto';
318
+ methodFilter.style.opacity = '1';
319
+ methodFilter.style.pointerEvents = 'auto';
320
+
321
+ namespaceFilter.innerHTML = sectionHtml(
322
+ 'Namespaces',
323
+ 'namespace-list',
324
+ renderRouteNamespaceFilters()
325
+ );
326
+ methodFilter.innerHTML = sectionHtml(
327
+ 'HTTP Methods',
328
+ 'namespace-list methods-list',
329
+ renderRouteMethodFilters()
330
+ );
331
+ return;
332
+ }
333
+
334
+ if (currentView === 'controllers') {
335
+ namespaceFilter.style.opacity = '1';
336
+ namespaceFilter.style.pointerEvents = 'auto';
337
+ methodFilter.style.opacity = '1';
338
+ methodFilter.style.pointerEvents = 'auto';
339
+
340
+ namespaceFilter.innerHTML = sectionHtml(
341
+ 'Controller Namespaces',
342
+ 'namespace-list',
343
+ renderControllerNamespaceFilters()
344
+ );
345
+ methodFilter.innerHTML = sectionHtml(
346
+ 'Controller Filters',
347
+ 'namespace-list methods-list',
348
+ renderControllerFlagFilters()
349
+ );
350
+ return;
351
+ }
352
+
353
+ if (currentView === 'models') {
354
+ namespaceFilter.style.opacity = '1';
355
+ namespaceFilter.style.pointerEvents = 'auto';
356
+ methodFilter.style.opacity = '1';
357
+ methodFilter.style.pointerEvents = 'auto';
358
+
359
+ namespaceFilter.innerHTML = sectionHtml(
360
+ 'Model Namespaces',
361
+ 'namespace-list',
362
+ renderModelNamespaceFilters()
363
+ );
364
+ methodFilter.innerHTML = sectionHtml(
365
+ 'Model Filters',
366
+ 'namespace-list methods-list',
367
+ renderModelFlagFilters()
368
+ );
369
+ return;
370
+ }
371
+
372
+ if (currentView === 'grpc') {
373
+ namespaceFilter.style.opacity = '1';
374
+ namespaceFilter.style.pointerEvents = 'auto';
375
+ methodFilter.style.opacity = '1';
376
+ methodFilter.style.pointerEvents = 'auto';
377
+
378
+ namespaceFilter.innerHTML = sectionHtml(
379
+ 'gRPC Namespaces',
380
+ 'namespace-list',
381
+ renderGrpcNamespaceFilters()
382
+ );
383
+ methodFilter.innerHTML = sectionHtml(
384
+ 'gRPC Filters',
385
+ 'namespace-list methods-list',
386
+ renderGrpcFlagFilters()
387
+ );
388
+ return;
389
+ }
390
+
391
+ // diagram: keep disabled (no meaningful sidebar filters)
392
+ namespaceFilter.style.opacity = '0.4';
393
+ namespaceFilter.style.pointerEvents = 'none';
394
+ methodFilter.style.opacity = '0.4';
395
+ methodFilter.style.pointerEvents = 'none';
396
+ }
397
+
398
+ function escapeHtml(s) {
399
+ return String(s)
400
+ .replace(/&/g, '&amp;')
401
+ .replace(/</g, '&lt;')
402
+ .replace(/>/g, '&gt;')
403
+ .replace(/"/g, '&quot;');
404
+ }
405
+
406
+ function renderFilterItem(label, count, filterType, filterValue, isActive) {
407
+ const safeLabel = escapeHtml(label);
408
+ const safeType = escapeHtml(filterType);
409
+ const safeValue = escapeHtml(filterValue);
410
+ return (
411
+ '<div class="namespace-item ' +
412
+ (isActive ? 'active' : '') +
413
+ '" data-filter-type="' +
414
+ safeType +
415
+ '" data-filter-value="' +
416
+ safeValue +
417
+ '">' +
418
+ '<span>' +
419
+ safeLabel +
420
+ '</span>' +
421
+ '<span class="namespace-count">' +
422
+ count +
423
+ '</span>' +
424
+ '</div>'
425
+ );
426
+ }
427
+
428
+ function renderRouteNamespaceFilters() {
429
+ const counts = new Map();
430
+ routes.forEach(r => {
431
+ const ns = r.namespace || '';
432
+ counts.set(ns, (counts.get(ns) || 0) + 1);
433
+ });
434
+ const entries = [...counts.entries()].sort((a, b) => b[1] - a[1]);
435
+ const allCount = routes.length;
436
+ return [
437
+ renderFilterItem('All', allCount, 'routeNamespace', 'all', selectedNamespaces.has('all')),
438
+ ...entries.map(([ns, count]) =>
439
+ renderFilterItem(ns || 'root', count, 'routeNamespace', ns, selectedNamespaces.has(ns))
440
+ ),
441
+ ].join('');
442
+ }
443
+
444
+ function renderControllerNamespaceFilters() {
445
+ const counts = new Map();
446
+ controllers.forEach(c => {
447
+ const ns = c.namespace || '';
448
+ counts.set(ns, (counts.get(ns) || 0) + 1);
449
+ });
450
+ const entries = [...counts.entries()].sort((a, b) => b[1] - a[1]);
451
+ const allCount = controllers.length;
452
+ return [
453
+ renderFilterItem('All', allCount, 'routeNamespace', 'all', selectedNamespaces.has('all')),
454
+ ...entries.map(([ns, count]) =>
455
+ renderFilterItem(ns || 'root', count, 'routeNamespace', ns, selectedNamespaces.has(ns))
456
+ ),
457
+ ].join('');
458
+ }
459
+
460
+ function renderRouteMethodFilters() {
461
+ const counts = new Map();
462
+ routes.forEach(r => {
463
+ const m = r.method || 'ALL';
464
+ counts.set(m, (counts.get(m) || 0) + 1);
465
+ });
466
+ const methods = ['ALL', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
467
+ const allCount = routes.length;
468
+ return methods.map(m => {
469
+ const label = m === 'ALL' ? 'All' : m;
470
+ const value = m === 'ALL' ? 'all' : m;
471
+ const count = m === 'ALL' ? allCount : (counts.get(m) || 0);
472
+ const active = selectedMethods.has(value);
473
+ return renderFilterItem(label, count, 'routeMethod', value, active);
474
+ }).join('');
475
+ }
476
+
477
+ function renderControllerFlagFilters() {
478
+ const flags = [
479
+ {
480
+ key: 'json',
481
+ label: 'Renders JSON',
482
+ test: (c) => (c.actions || []).some((a) => a.rendersJson),
483
+ },
484
+ {
485
+ key: 'redirect',
486
+ label: 'Has Redirect',
487
+ test: (c) => (c.actions || []).some((a) => a.redirectsTo),
488
+ },
489
+ {
490
+ key: 'private',
491
+ label: 'Has Private Actions',
492
+ test: (c) => (c.actions || []).some((a) => a.visibility === 'private'),
493
+ },
494
+ ];
495
+ const allCount = controllers.length;
496
+ const items = [
497
+ renderFilterItem('All', allCount, 'controllerFlag', 'all', selectedControllerFlags.has('all')),
498
+ ];
499
+ flags.forEach(f => {
500
+ const count = controllers.filter(f.test).length;
501
+ items.push(renderFilterItem(f.label, count, 'controllerFlag', f.key, selectedControllerFlags.has(f.key)));
502
+ });
503
+ return items.join('');
504
+ }
505
+
506
+ function getModelNamespace(model) {
507
+ const p = (model.filePath || '').split('/');
508
+ if (p.length <= 1) return '';
509
+ return p.slice(0, -1).join('/');
510
+ }
511
+
512
+ function renderModelNamespaceFilters() {
513
+ const counts = new Map();
514
+ models.forEach(m => {
515
+ const ns = getModelNamespace(m);
516
+ counts.set(ns, (counts.get(ns) || 0) + 1);
517
+ });
518
+ const entries = [...counts.entries()].sort((a, b) => b[1] - a[1]);
519
+ const allCount = models.length;
520
+ return [
521
+ renderFilterItem('All', allCount, 'modelNamespace', 'all', selectedModelNamespaces.has('all')),
522
+ ...entries.map(([ns, count]) =>
523
+ renderFilterItem(ns || 'root', count, 'modelNamespace', ns, selectedModelNamespaces.has(ns))
524
+ ),
525
+ ].join('');
526
+ }
527
+
528
+ function renderModelFlagFilters() {
529
+ const flags = [
530
+ { key: 'assoc', label: 'Has associations', test: (m) => (m.associations || []).length > 0 },
531
+ { key: 'valid', label: 'Has validations', test: (m) => (m.validations || []).length > 0 },
532
+ { key: 'cb', label: 'Has callbacks', test: (m) => (m.callbacks || []).length > 0 },
533
+ { key: 'concern', label: 'Includes concerns', test: (m) => (m.concerns || []).length > 0 },
534
+ { key: 'enum', label: 'Has enums', test: (m) => (m.enums || []).length > 0 },
535
+ ];
536
+ const allCount = models.length;
537
+ const items = [
538
+ renderFilterItem('All', allCount, 'modelFlag', 'all', selectedModelFlags.has('all')),
539
+ ];
540
+ flags.forEach(f => {
541
+ const count = models.filter(f.test).length;
542
+ items.push(renderFilterItem(f.label, count, 'modelFlag', f.key, selectedModelFlags.has(f.key)));
543
+ });
544
+ return items.join('');
545
+ }
546
+
547
+ function renderGrpcNamespaceFilters() {
548
+ const counts = new Map();
549
+ grpcServices.forEach(s => {
550
+ const ns = s.namespace || '';
551
+ counts.set(ns, (counts.get(ns) || 0) + 1);
552
+ });
553
+ const entries = [...counts.entries()].sort((a, b) => b[1] - a[1]);
554
+ const allCount = grpcServices.length;
555
+ return [
556
+ renderFilterItem('All', allCount, 'grpcNamespace', 'all', selectedGrpcNamespaces.has('all')),
557
+ ...entries.map(([ns, count]) =>
558
+ renderFilterItem(ns || 'root', count, 'grpcNamespace', ns, selectedGrpcNamespaces.has(ns))
559
+ ),
560
+ ].join('');
561
+ }
562
+
563
+ function renderGrpcFlagFilters() {
564
+ const flags = [
565
+ { key: 'policies', label: 'Has policies', test: (s) => (s.policies || []).length > 0 },
566
+ { key: 'serializers', label: 'Has serializers', test: (s) => (s.serializers || []).length > 0 },
567
+ { key: 'concerns', label: 'Includes concerns', test: (s) => (s.concerns || []).length > 0 },
568
+ { key: 'modelsUsed', label: 'RPC uses models', test: (s) => (s.rpcs || []).some(r => (r.modelsUsed || []).length > 0) },
569
+ { key: 'servicesUsed', label: 'RPC uses services', test: (s) => (s.rpcs || []).some(r => (r.servicesUsed || []).length > 0) },
570
+ ];
571
+ const allCount = grpcServices.length;
572
+ const items = [
573
+ renderFilterItem('All', allCount, 'grpcFlag', 'all', selectedGrpcFlags.has('all')),
574
+ ];
575
+ flags.forEach(f => {
576
+ const count = grpcServices.filter(f.test).length;
577
+ items.push(renderFilterItem(f.label, count, 'grpcFlag', f.key, selectedGrpcFlags.has(f.key)));
578
+ });
579
+ return items.join('');
580
+ }
581
+
288
582
  function filterRoutes() {
289
583
  return routes.filter(route => {
290
584
  // Namespace filter (multi-select)
@@ -355,6 +649,18 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
355
649
  if (!selectedNamespaces.has('all')) {
356
650
  filteredControllers = filteredControllers.filter(c => selectedNamespaces.has(c.namespace || ''));
357
651
  }
652
+ if (!selectedControllerFlags.has('all')) {
653
+ filteredControllers = filteredControllers.filter((c) => {
654
+ const rendersJson = (c.actions || []).some((a) => a.rendersJson);
655
+ const hasRedirect = (c.actions || []).some((a) => a.redirectsTo);
656
+ const hasPrivate = (c.actions || []).some((a) => a.visibility === 'private');
657
+
658
+ if (selectedControllerFlags.has('json') && !rendersJson) return false;
659
+ if (selectedControllerFlags.has('redirect') && !hasRedirect) return false;
660
+ if (selectedControllerFlags.has('private') && !hasPrivate) return false;
661
+ return true;
662
+ });
663
+ }
358
664
  const displayed = filteredControllers.slice(0, controllersDisplayCount);
359
665
  const hasMore = filteredControllers.length > controllersDisplayCount;
360
666
 
@@ -406,6 +712,27 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
406
712
  m.className.toLowerCase().includes(searchQuery)
407
713
  );
408
714
  }
715
+ // Model namespace filter
716
+ if (!selectedModelNamespaces.has('all')) {
717
+ filteredModels = filteredModels.filter(m => selectedModelNamespaces.has(getModelNamespace(m)));
718
+ }
719
+ // Model flag filter
720
+ if (!selectedModelFlags.has('all')) {
721
+ filteredModels = filteredModels.filter(m => {
722
+ const hasAssoc = (m.associations || []).length > 0;
723
+ const hasValid = (m.validations || []).length > 0;
724
+ const hasCb = (m.callbacks || []).length > 0;
725
+ const hasConcern = (m.concerns || []).length > 0;
726
+ const hasEnum = (m.enums || []).length > 0;
727
+
728
+ if (selectedModelFlags.has('assoc') && !hasAssoc) return false;
729
+ if (selectedModelFlags.has('valid') && !hasValid) return false;
730
+ if (selectedModelFlags.has('cb') && !hasCb) return false;
731
+ if (selectedModelFlags.has('concern') && !hasConcern) return false;
732
+ if (selectedModelFlags.has('enum') && !hasEnum) return false;
733
+ return true;
734
+ });
735
+ }
409
736
  const displayed = filteredModels.slice(0, modelsDisplayCount);
410
737
  const hasMore = filteredModels.length > modelsDisplayCount;
411
738
 
@@ -431,6 +758,7 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
431
758
  \${hasMore ? \`
432
759
  <div class="show-more-container">
433
760
  <button class="show-more-btn" onclick="loadMoreModels()">Show More (+50)</button>
761
+ <button class="show-more-btn" onclick="showAllModels()">Show All</button>
434
762
  <span class="show-more-count">\${modelsDisplayCount} / \${filteredModels.length}</span>
435
763
  </div>
436
764
  \` : ''}
@@ -442,6 +770,11 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
442
770
  renderMainPanel();
443
771
  };
444
772
 
773
+ window.showAllModels = function() {
774
+ modelsDisplayCount = filteredModels.length;
775
+ renderMainPanel();
776
+ };
777
+
445
778
  let grpcDisplayCount = 50;
446
779
  let filteredGrpc = grpcServices;
447
780
 
@@ -454,6 +787,25 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
454
787
  (svc.rpcs && svc.rpcs.some(rpc => rpc.name && rpc.name.toLowerCase().includes(searchQuery)))
455
788
  );
456
789
  }
790
+ if (!selectedGrpcNamespaces.has('all')) {
791
+ filteredGrpc = filteredGrpc.filter(svc => selectedGrpcNamespaces.has(svc.namespace || ''));
792
+ }
793
+ if (!selectedGrpcFlags.has('all')) {
794
+ filteredGrpc = filteredGrpc.filter(svc => {
795
+ const hasPolicies = (svc.policies || []).length > 0;
796
+ const hasSerializers = (svc.serializers || []).length > 0;
797
+ const hasConcerns = (svc.concerns || []).length > 0;
798
+ const usesModels = (svc.rpcs || []).some(r => (r.modelsUsed || []).length > 0);
799
+ const usesServices = (svc.rpcs || []).some(r => (r.servicesUsed || []).length > 0);
800
+
801
+ if (selectedGrpcFlags.has('policies') && !hasPolicies) return false;
802
+ if (selectedGrpcFlags.has('serializers') && !hasSerializers) return false;
803
+ if (selectedGrpcFlags.has('concerns') && !hasConcerns) return false;
804
+ if (selectedGrpcFlags.has('modelsUsed') && !usesModels) return false;
805
+ if (selectedGrpcFlags.has('servicesUsed') && !usesServices) return false;
806
+ return true;
807
+ });
808
+ }
457
809
 
458
810
  const displayedGrpc = filteredGrpc.slice(0, grpcDisplayCount);
459
811
 
@@ -461,7 +813,7 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
461
813
  <div class="panel-header">
462
814
  <div class="panel-title">gRPC Services <span class="panel-count">(\${Math.min(grpcDisplayCount, filteredGrpc.length)} / \${filteredGrpc.length})</span></div>
463
815
  </div>
464
- <div style="display:grid;gap:12px">
816
+ <div class="model-card-grid">
465
817
  \${displayedGrpc.map((svc, idx) => \`
466
818
  <div class="model-card" onclick="showGrpcDetail(\${idx})">
467
819
  <div class="model-name">
@@ -1419,5 +1771,5 @@ import {k}from'./chunk-H7VVRHQZ.js';import*as r from'fs';import*as c from'path';
1419
1771
  `).join("")}
1420
1772
  </tbody>
1421
1773
  </table>
1422
- `}highlightParams(t){return t.replace(/:([a-zA-Z_]+)/g,'<span class="param">:$1</span>')}};async function m(){let o=process.argv[2]||process.cwd(),t=process.argv[3]||c.join(o,"rails-map.html");await new n(o).generate({title:"Rails Application Map",outputPath:t});}var p=import.meta.url===`file://${process.argv[1]}`;p&&m().catch(console.error);
1423
- export{n as a};
1774
+ `}highlightParams(t){return t.replace(/:([a-zA-Z_]+)/g,'<span class="param">:$1</span>')}};async function m(){let o=process.argv[2]||process.cwd(),t=process.argv[3]||d.join(o,"rails-map.html");await new i(o).generate({title:"Rails Application Map",outputPath:t});}var p=import.meta.url===`file://${process.argv[1]}`;p&&m().catch(console.error);
1775
+ export{i as a};