@principal-ai/subsystems-react 0.28.5 → 0.28.7

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.
@@ -11,11 +11,17 @@
11
11
  * (e.g. focus the node on the graph, or apply the fix in the report).
12
12
  */
13
13
 
14
- import { useState } from 'react';
14
+ import { useEffect, useState } from 'react';
15
+ import type { LucideIcon } from 'lucide-react';
15
16
  import {
16
17
  ChevronDown,
17
18
  ChevronRight,
18
19
  CircleCheck,
20
+ Component,
21
+ FolderGit2,
22
+ Network,
23
+ Route,
24
+ Server,
19
25
  Wrench,
20
26
  } from 'lucide-react';
21
27
  import { useTheme } from '@principal-ade/industry-theme';
@@ -50,6 +56,18 @@ export const SUBSYSTEM_ISSUE_CATEGORIES: SubsystemIssueCategory[] = [
50
56
  'walkthrough',
51
57
  ];
52
58
 
59
+ /** Per-category header icon — mirrors the Maintainer tab's lane icons. */
60
+ export const SUBSYSTEM_ISSUE_CATEGORY_ICON: Record<
61
+ SubsystemIssueCategory,
62
+ LucideIcon
63
+ > = {
64
+ repo: FolderGit2,
65
+ construct: Component,
66
+ 'static-topology': Network,
67
+ 'runtime-topology': Server,
68
+ walkthrough: Route,
69
+ };
70
+
53
71
  export const SUBSYSTEM_ISSUE_CATEGORY_LABEL: Record<
54
72
  SubsystemIssueCategory,
55
73
  string
@@ -306,11 +324,11 @@ export function groupIssuesByCategory(
306
324
 
307
325
  function severityColor(
308
326
  severity: SubsystemIssueSeverity,
309
- colors: { error: string; info: string },
310
- muted: string,
327
+ colors: { error: string; info: string; warning: string },
311
328
  ): string {
312
329
  if (severity === 'error') return colors.error ?? '#e5534b';
313
- return colors.info ?? muted;
330
+ // `info` severity = unconfirmed findings; amber, matching the lane icons.
331
+ return colors.warning ?? '#d4a017';
314
332
  }
315
333
 
316
334
  const TARGET_KIND_LABEL: Record<SubsystemIssueTargetKind, string> = {
@@ -342,7 +360,7 @@ export function SubsystemIssueCard({
342
360
  const [expanded, setExpanded] = useState(false);
343
361
  const showDetail = expanded;
344
362
  const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
345
- const color = severityColor(issue.severity, theme.colors, muted);
363
+ const color = severityColor(issue.severity, theme.colors);
346
364
  const activate = () => {
347
365
  setExpanded((v) => !v);
348
366
  onSelect?.(issue);
@@ -481,6 +499,14 @@ export interface SubsystemIssueListProps {
481
499
  onSelectIssue?: (issue: SubsystemIssue) => void;
482
500
  onApplyFix?: (issue: SubsystemIssue) => void;
483
501
  onHoverIssue?: (issue: SubsystemIssue | null) => void;
502
+ /**
503
+ * Land the list focused on one verification layer: that category starts
504
+ * expanded and every other category starts collapsed. Used when the list is
505
+ * opened from a lane-specific entry point (e.g. the Maintainer tab's lane
506
+ * badges) so the caller's intent — "show me this layer's findings" — is
507
+ * honoured without an extra click.
508
+ */
509
+ focusCategory?: SubsystemIssueCategory;
484
510
  }
485
511
 
486
512
  export function SubsystemIssueList({
@@ -488,15 +514,32 @@ export function SubsystemIssueList({
488
514
  onSelectIssue,
489
515
  onApplyFix,
490
516
  onHoverIssue,
517
+ focusCategory,
491
518
  }: SubsystemIssueListProps) {
492
519
  const { theme } = useTheme();
493
520
  const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
494
521
  const categories = groupIssuesByCategory(issues);
495
- // Collapsed categories (by id). Everything starts expanded.
522
+ // Collapsed categories (by id). Everything starts expanded, unless the list
523
+ // was opened focused on a single layer — then every other layer starts
524
+ // collapsed so the focused category's findings are what you land on.
496
525
  const [collapsed, setCollapsed] = useState<Set<SubsystemIssueCategory>>(
497
- () => new Set(),
526
+ () =>
527
+ focusCategory
528
+ ? new Set(
529
+ SUBSYSTEM_ISSUE_CATEGORIES.filter((c) => c !== focusCategory),
530
+ )
531
+ : new Set(),
498
532
  );
499
533
  const [hovered, setHovered] = useState<SubsystemIssueCategory | null>(null);
534
+ // Re-apply the focus when it changes on an already-mounted list (e.g. the
535
+ // host switches which lane the open model is focused on).
536
+ useEffect(() => {
537
+ setCollapsed(
538
+ focusCategory
539
+ ? new Set(SUBSYSTEM_ISSUE_CATEGORIES.filter((c) => c !== focusCategory))
540
+ : new Set(),
541
+ );
542
+ }, [focusCategory]);
500
543
  const toggleCollapsed = (category: SubsystemIssueCategory) => {
501
544
  setCollapsed((prev) => {
502
545
  const next = new Set(prev);
@@ -513,7 +556,7 @@ export function SubsystemIssueList({
513
556
  // the header, no count, no cards.
514
557
  const clean = category.count === 0;
515
558
  const accent = category.severity
516
- ? severityColor(category.severity, theme.colors, muted)
559
+ ? severityColor(category.severity, theme.colors)
517
560
  : theme.colors.success;
518
561
  const isCollapsed = collapsed.has(category.category);
519
562
  const isHovered = hovered === category.category;
@@ -550,6 +593,20 @@ export function SubsystemIssueList({
550
593
  fontFamily: theme.fonts.body,
551
594
  }}
552
595
  >
596
+ {(() => {
597
+ const Icon = SUBSYSTEM_ISSUE_CATEGORY_ICON[category.category];
598
+ if (Icon) {
599
+ return (
600
+ <Icon
601
+ size={13}
602
+ color={accent}
603
+ style={{ flexShrink: 0 }}
604
+ aria-hidden="true"
605
+ />
606
+ );
607
+ }
608
+ return null;
609
+ })()}
553
610
  <span
554
611
  style={{
555
612
  flex: 1,
@@ -573,7 +630,7 @@ export function SubsystemIssueList({
573
630
  fontFamily: theme.fonts.monospace,
574
631
  fontSize: theme.fontSizes[0],
575
632
  fontWeight: 600,
576
- color: accent,
633
+ color: muted,
577
634
  flexShrink: 0,
578
635
  }}
579
636
  >
@@ -55,7 +55,11 @@ import { ConstructsCatalog } from './ConstructsCatalog';
55
55
  import type { SubsystemOpenFileOptions } from './declarationRef';
56
56
  import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility } from './nodes';
57
57
  import { SubsystemDiagnosticToggle, type SubsystemDiagnostic } from './DiagnosticToggle';
58
- import { SubsystemIssueList, type SubsystemIssue } from './IssueList';
58
+ import {
59
+ SubsystemIssueList,
60
+ type SubsystemIssue,
61
+ type SubsystemIssueCategory,
62
+ } from './IssueList';
59
63
  import { SubsystemFileTree } from './SubsystemFileTree';
60
64
  import { GraphLayoutCover } from './GraphLayoutCover';
61
65
  import { GRAPH_NAV_PROPS, GraphChrome } from './graphChrome';
@@ -246,6 +250,12 @@ export interface SubsystemComponentGraphProps {
246
250
  * diagnostics on iff `issues` is non-empty, and the title-row chip toggles it.
247
251
  */
248
252
  showIssues?: boolean;
253
+ /**
254
+ * When the issues list is active, land it focused on this verification
255
+ * layer: that category expands, the rest start collapsed. Optional — the
256
+ * plain issues view expands everything.
257
+ */
258
+ focusIssueCategory?: SubsystemIssueCategory;
249
259
  /** Click an issue — focus its target on the graph / open detail. */
250
260
  onSelectIssue?: (issue: SubsystemIssue) => void;
251
261
  /** Apply an issue's deterministic fix. */
@@ -375,7 +385,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
375
385
  measured: { w: number; h: number } | null;
376
386
  }
377
387
 
378
- function Inner({ components, relations, walkthroughs, initialWalkthroughId, onReorderWalkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, edgeView, title, hideSidebar, walkthroughStepMode = 'focus', autoPlayWalkthroughs = false, walkthroughAutoPlayIntervalMs = WALKTHROUGH_PLAY_PAUSE_MS, zoomOnWalkthroughFocus = true, graphTitle, showWalkthroughTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileView, renderFileViewer, renderWalkthroughViewer, onFileSelect, onVerifyComponent, componentVerification, persistKey }: InnerProps) {
388
+ function Inner({ components, relations, walkthroughs, initialWalkthroughId, onReorderWalkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, edgeView, title, hideSidebar, walkthroughStepMode = 'focus', autoPlayWalkthroughs = false, walkthroughAutoPlayIntervalMs = WALKTHROUGH_PLAY_PAUSE_MS, zoomOnWalkthroughFocus = true, graphTitle, showWalkthroughTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, focusIssueCategory, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileView, renderFileViewer, renderWalkthroughViewer, onFileSelect, onVerifyComponent, componentVerification, persistKey }: InnerProps) {
379
389
  const { theme } = useTheme();
380
390
  const { fitView } = useReactFlow();
381
391
  const viewport = useViewport();
@@ -1738,6 +1748,7 @@ function Inner({ components, relations, walkthroughs, initialWalkthroughId, onRe
1738
1748
  {issuesActive ? (
1739
1749
  <SubsystemIssueList
1740
1750
  issues={issues ?? []}
1751
+ focusCategory={focusIssueCategory}
1741
1752
  onSelectIssue={onSelectIssue}
1742
1753
  onApplyFix={onApplyIssueFix}
1743
1754
  onHoverIssue={onHoverIssue}