@anlyx/ui 0.1.5 → 0.1.6-beta.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,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { BaseEdge, Handle, Position, ReactFlow, getSmoothStepPath } from "@xyflow/react";
3
- import { BookOpen, Box, Braces, BriefcaseBusiness, Check, ChevronRight, Circle, Clock3, Code2, Copy, Database, Download, FileCode2, FileText, Flag, Folder, Gauge, Layers3, Languages, Lock, Minus, MousePointerClick, Network, PanelLeft, Plus, RotateCw, Search, ShieldCheck, Workflow, Zap } from "lucide-react";
4
- import { createContext, useContext, useEffect, useMemo, useState } from "react";
3
+ import { BookOpen, Box, Braces, BriefcaseBusiness, Check, ChevronRight, Circle, Clock3, Code2, Copy, Database, Download, FileCode2, FileText, Flag, Folder, Gauge, Layers3, Languages, Lock, Minus, MousePointerClick, Network, PanelLeft, Plus, RotateCw, Search, ShieldCheck, Tag, Workflow, Zap } from "lucide-react";
4
+ import { siExpress, siNodedotjs, siReact, siTypescript } from "simple-icons";
5
+ import { Fragment, createContext, useContext, useEffect, useMemo, useState } from "react";
5
6
  import { ScanTreeMap } from "./ScanTreeMap.js";
6
7
  import { buildProjectWorkspaceViewModel } from "./project-view-model.js";
7
8
  const WorkspaceLocaleContext = createContext("en");
@@ -240,13 +241,6 @@ function useWorkspaceLocale() {
240
241
  function t(locale, key) {
241
242
  return translations[locale][key] ?? translations.en[key];
242
243
  }
243
- const projectChromeLocales = [
244
- { value: "en", label: "English", shortLabel: "EN" },
245
- { value: "ko", label: "한국어", shortLabel: "KO" },
246
- { value: "zh", label: "中文", shortLabel: "ZH" },
247
- { value: "ja", label: "日本語", shortLabel: "JA" },
248
- { value: "fr", label: "Français", shortLabel: "FR" }
249
- ];
250
244
  const projectChromeTranslations = {
251
245
  en: {
252
246
  agent: "AI Agent",
@@ -254,6 +248,10 @@ const projectChromeTranslations = {
254
248
  disabled: "Disabled",
255
249
  language: "Language",
256
250
  lastAnalysis: "Last analysis",
251
+ overview: "Overview",
252
+ capabilities: "Capabilities",
253
+ dataLifecycle: "Data Lifecycle",
254
+ impactMap: "Impact Map",
257
255
  map: "Map",
258
256
  pages: "Pages",
259
257
  source: "Source",
@@ -267,6 +265,10 @@ const projectChromeTranslations = {
267
265
  disabled: "비활성",
268
266
  language: "언어",
269
267
  lastAnalysis: "마지막 분석",
268
+ overview: "Overview",
269
+ capabilities: "Capabilities",
270
+ dataLifecycle: "Data Lifecycle",
271
+ impactMap: "Impact Map",
270
272
  map: "맵",
271
273
  pages: "페이지",
272
274
  source: "소스",
@@ -280,6 +282,10 @@ const projectChromeTranslations = {
280
282
  disabled: "已停用",
281
283
  language: "语言",
282
284
  lastAnalysis: "最后分析",
285
+ overview: "Overview",
286
+ capabilities: "Capabilities",
287
+ dataLifecycle: "Data Lifecycle",
288
+ impactMap: "Impact Map",
283
289
  map: "地图",
284
290
  pages: "页面",
285
291
  source: "来源",
@@ -293,6 +299,10 @@ const projectChromeTranslations = {
293
299
  disabled: "無効",
294
300
  language: "言語",
295
301
  lastAnalysis: "最終分析",
302
+ overview: "Overview",
303
+ capabilities: "Capabilities",
304
+ dataLifecycle: "Data Lifecycle",
305
+ impactMap: "Impact Map",
296
306
  map: "マップ",
297
307
  pages: "ページ",
298
308
  source: "ソース",
@@ -306,6 +316,10 @@ const projectChromeTranslations = {
306
316
  disabled: "Désactivé",
307
317
  language: "Langue",
308
318
  lastAnalysis: "Dernière analyse",
319
+ overview: "Overview",
320
+ capabilities: "Capabilities",
321
+ dataLifecycle: "Data Lifecycle",
322
+ impactMap: "Impact Map",
309
323
  map: "Carte",
310
324
  pages: "Pages",
311
325
  source: "Source",
@@ -342,9 +356,9 @@ const layerIcons = {
342
356
  result: Flag,
343
357
  service: Layers3
344
358
  };
345
- export function WorkspaceApp({ data, projectData, streamUrl = "/_anlyx/events/stream", initialRecords = [] }) {
359
+ export function WorkspaceApp({ data, projectData, projectValidationReport, streamUrl = "/_anlyx/events/stream", initialRecords = [] }) {
346
360
  if (projectData) {
347
- return _jsx(ProjectWorkspacePreview, { data: projectData });
361
+ return (_jsx(ProjectWorkspacePreview, { data: projectData, ...(projectValidationReport ? { validationReport: projectValidationReport } : {}) }));
348
362
  }
349
363
  if (!data) {
350
364
  return (_jsx("main", { className: "project-workspace-preview", role: "application", "aria-label": "Anlyx workspace", children: _jsxs("section", { className: "project-workspace-preview__empty", children: [_jsx("strong", { children: "No Anlyx project data loaded" }), _jsx("span", { children: "Start the local 4777 viewer with anlyx.project.json." })] }) }));
@@ -411,8 +425,9 @@ function LegacyWorkspaceApp({ data, streamUrl, initialRecords }) {
411
425
  }, [streamUrl]);
412
426
  return (_jsx(WorkspaceLocaleContext.Provider, { value: locale, children: _jsxs("main", { className: `live-workspace live-workspace--${activeView === "flows" ? tab : activeView}`, role: "application", "aria-label": t(locale, "appLabel"), children: [_jsx(WorkspaceSidebar, { activeView: activeView, locale: locale, onLocaleChange: setLocale, onViewChange: setActiveView }), _jsx("section", { className: "workspace-main", children: activeView === "json" ? (_jsx(JsonReaderView, { data: data })) : activeView === "map" ? (_jsx(ScanTreeMap, { data: data })) : (_jsxs("div", { className: "timing-layout", id: "workspace", children: [_jsxs("div", { className: "timing-content", children: [_jsx(WorkspaceHeader, { data: data, record: selectedRecord, recordCount: records.length, activeTab: tab, onTabChange: setTab }), _jsx(RequestContextPanel, { data: data, pageContext: pageContext, record: selectedRecord }), selectedRecord ? (_jsx(WorkspaceContent, { record: selectedRecord, tab: tab })) : (_jsx(EmptyWorkspace, { pageContext: pageContext }))] }), _jsx(FlowInspector, { pageContext: pageContext, record: selectedRecord, records: scopedRecords, selectedId: selectedRecord?.id, onSelect: setSelectedId })] })) })] }) }));
413
427
  }
414
- function ProjectWorkspacePreview({ data }) {
428
+ function ProjectWorkspacePreview({ data, validationReport }) {
415
429
  const [activeTab, setActiveTab] = useState("pages");
430
+ const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
416
431
  const [projectLocale, setProjectLocale] = useState(() => projectDefaultLocale(data));
417
432
  const [selectedPageId, setSelectedPageId] = useState(data.pages[0]?.id);
418
433
  const model = useMemo(() => buildProjectWorkspaceViewModel(data, selectedPageId), [data, selectedPageId]);
@@ -420,7 +435,7 @@ function ProjectWorkspacePreview({ data }) {
420
435
  useEffect(() => {
421
436
  setProjectLocale(projectDefaultLocale(data));
422
437
  }, [data]);
423
- return (_jsxs("main", { className: "project-workspace-preview", role: "application", "aria-label": "Anlyx project workspace", children: [_jsx(ProjectTopBar, { model: model, locale: projectLocale, onLocaleChange: setProjectLocale }), _jsx(ProjectTabs, { activeTab: activeTab, locale: projectLocale, onTabChange: setActiveTab }), activeTab === "pages" ? (_jsx(ProjectPagesWorkspace, { model: model, selectedPageId: model.selectedPage?.page.id, onPageSelect: setSelectedPageId, onOpenMap: () => setActiveTab("map") })) : activeTab === "map" ? (_jsx(ProjectMapView, { data: data, model: model })) : (_jsx(ProjectJsonView, { data: data, rawJson: rawJson, model: model })), _jsx(ProjectStatusBar, { locale: projectLocale, model: model })] }));
438
+ return (_jsxs("main", { className: "project-workspace-preview", role: "application", "aria-label": "Anlyx project workspace", children: [_jsx(ProjectTopBar, { model: model, locale: projectLocale, onLocaleChange: setProjectLocale }), _jsxs("div", { className: `project-workspace-shell${isSidebarCollapsed ? " is-sidebar-collapsed" : ""}`, children: [_jsx(ProjectSidebar, { activeTab: activeTab, isCollapsed: isSidebarCollapsed, locale: projectLocale, onTabChange: setActiveTab, onToggleCollapse: () => setIsSidebarCollapsed((value) => !value) }), _jsx("div", { className: "project-workspace-surface", children: activeTab === "pages" ? (_jsx(ProjectPagesWorkspace, { data: data, model: model, ...(validationReport ? { validationReport } : {}), selectedPageId: model.selectedPage?.page.id, onPageSelect: setSelectedPageId, onOpenMap: () => setActiveTab("map") })) : activeTab === "map" ? (_jsx(ProjectMapView, { data: data, model: model })) : activeTab === "overview" ? (_jsx(ProjectOverviewView, { data: data })) : activeTab === "capabilities" ? (_jsx(ProjectCapabilitiesView, { data: data, model: model })) : (_jsx(ProjectJsonView, { data: data, rawJson: rawJson, model: model, ...(validationReport ? { validationReport } : {}) })) })] }), _jsx(ProjectStatusBar, { data: data, locale: projectLocale, model: model, ...(validationReport ? { validationReport } : {}) })] }));
424
439
  }
425
440
  function readProjectMetadataString(model, key) {
426
441
  const value = model.project.metadata?.[key];
@@ -432,157 +447,738 @@ function projectAgentName(model) {
432
447
  function projectSourceFile(model) {
433
448
  return readProjectMetadataString(model, "sourceFile") ?? "anlyx.project.json";
434
449
  }
435
- function ProjectTopBar({ locale, model, onLocaleChange }) {
436
- return (_jsxs("header", { className: "project-topbar", children: [_jsxs("div", { className: "project-topbar__brand", children: [_jsx("img", { alt: "Anlyx", src: "/workspace/anlyx-logo-transparent.png" }), _jsxs("span", { children: ["v", model.schemaVersion] }), _jsx("strong", { children: "OSS" })] }), _jsxs("button", { className: "project-topbar__project", type: "button", children: [_jsx(Folder, { size: 17 }), _jsx("span", { children: model.project.name }), _jsx(ChevronRight, { size: 14 })] }), _jsx("div", { className: "project-topbar__spacer" }), _jsx("div", { className: "project-topbar__actions", children: _jsxs("label", { className: "project-language-select", children: [_jsx(Languages, { size: 15 }), _jsx("span", { children: tp(locale, "language") }), _jsx("select", { "aria-label": tp(locale, "language"), value: locale, onChange: (event) => onLocaleChange(event.currentTarget.value), children: projectChromeLocales.map((item) => (_jsx("option", { value: item.value, children: item.shortLabel }, item.value))) })] }) })] }));
450
+ function ProjectTopBar({ model }) {
451
+ return (_jsxs("header", { className: "project-topbar", children: [_jsxs("div", { className: "project-topbar__brand", children: [_jsx("img", { alt: "Anlyx", src: "/workspace/anlyx-logo-transparent.png" }), _jsxs("span", { children: ["v", model.schemaVersion] })] }), _jsxs("button", { className: "project-topbar__project", type: "button", children: [_jsx(Folder, { size: 17 }), _jsx("span", { children: model.project.name }), _jsx(ChevronRight, { size: 14 })] }), _jsx("div", { className: "project-topbar__spacer" })] }));
452
+ }
453
+ function projectTabLabel(tab, locale) {
454
+ if (tab === "pages")
455
+ return tp(locale, "pages");
456
+ if (tab === "map")
457
+ return tp(locale, "map");
458
+ if (tab === "overview")
459
+ return tp(locale, "overview");
460
+ if (tab === "capabilities")
461
+ return tp(locale, "capabilities");
462
+ return "JSON";
463
+ }
464
+ function ProjectSidebar({ activeTab, isCollapsed, locale, onTabChange, onToggleCollapse }) {
465
+ const items = [
466
+ { icon: FileText, tab: "pages" },
467
+ { icon: Network, tab: "map" },
468
+ { icon: Gauge, tab: "overview" },
469
+ { icon: Workflow, tab: "capabilities" },
470
+ { icon: Braces, tab: "json" }
471
+ ];
472
+ return (_jsxs("aside", { className: `project-sidebar${isCollapsed ? " is-collapsed" : ""}`, "aria-label": "Project navigation", children: [_jsxs("div", { className: "project-sidebar__group", children: [isCollapsed ? null : _jsx("h2", { children: "Project" }), items.map(({ icon: Icon, tab }) => (_jsxs("button", { "aria-current": activeTab === tab ? "page" : undefined, "aria-label": isCollapsed ? projectTabLabel(tab, locale) : undefined, className: activeTab === tab ? "is-active" : "", title: isCollapsed ? projectTabLabel(tab, locale) : undefined, type: "button", onClick: () => onTabChange(tab), children: [_jsx(Icon, { size: 17 }), isCollapsed ? null : _jsx("span", { children: projectTabLabel(tab, locale) })] }, tab)))] }), _jsxs("button", { "aria-label": isCollapsed ? "Expand project navigation" : "Collapse project navigation", className: "project-sidebar__collapse", title: isCollapsed ? "Expand" : undefined, type: "button", onClick: onToggleCollapse, children: [_jsx(PanelLeft, { size: 16 }), isCollapsed ? null : _jsx("span", { children: "Collapse" })] })] }));
473
+ }
474
+ function ProjectOverviewView({ data }) {
475
+ const overview = data.overview;
476
+ const stack = projectOverviewStack(overview.implementation);
477
+ const summary = overview.summary ??
478
+ "Inspect the authored Project JSON to understand pages, flows, requests, architecture, evidence, and unknowns.";
479
+ return (_jsx("section", { className: "anlyx-understanding anlyx-overview", "aria-label": "Project overview", children: _jsx("div", { className: "anlyx-overview-layout", children: _jsxs("div", { className: "anlyx-overview-main", children: [_jsxs("header", { className: "anlyx-readme-header", children: [_jsx("h1", { children: "Anlyx overview" }), _jsx("p", { children: summary })] }), _jsxs("section", { className: "anlyx-readme-section", "aria-label": "Built with", children: [_jsx("h2", { children: "Built with" }), stack.length > 0 ? (_jsx("div", { className: "anlyx-stack-grid", children: stack.map((item) => (_jsxs("div", { className: "anlyx-stack-item", children: [_jsx(ProjectStackIconView, { icon: item.icon, tone: item.tone }), _jsxs("div", { children: [_jsx("strong", { children: item.name }), _jsx("small", { children: item.detail })] })] }, item.name))) })) : (_jsx("p", { className: "project-muted", children: "No implementation stack authored." }))] }), _jsxs("section", { className: "anlyx-readme-section", "aria-label": "What it does", children: [_jsx("h2", { children: "What it does" }), _jsxs("div", { className: "anlyx-feature-list", children: [_jsx(ProjectReadmeFeature, { icon: FileText, title: "Inspect pages", description: "Explore the UI surfaces, components, and states that make up your app." }), _jsx(ProjectReadmeFeature, { icon: Workflow, title: "Trace request flows", description: "Follow the path from UI events to API requests and data responses." }), _jsx(ProjectReadmeFeature, { icon: Braces, title: "Review authored JSON", description: "Open the source Project JSON and understand the modeled structure." }), _jsx(ProjectReadmeFeature, { icon: Search, title: "Check evidence & unknowns", description: "See what\u2019s backed by evidence and what needs review or clarification." })] })] })] }) }) }));
480
+ }
481
+ function ProjectStackIconView({ icon, tone }) {
482
+ if (icon.kind === "lucide") {
483
+ const Icon = icon.icon;
484
+ return (_jsx("span", { className: `anlyx-stack-icon is-${tone}`, "aria-hidden": "true", children: _jsx(Icon, { size: 22 }) }));
485
+ }
486
+ return (_jsx("span", { className: `anlyx-stack-icon is-${tone}`, "aria-hidden": "true", style: { "--stack-icon-color": `#${icon.hex}` }, children: _jsx("svg", { role: "img", viewBox: "0 0 24 24", "aria-label": icon.title, children: _jsx("path", { d: icon.path }) }) }));
487
+ }
488
+ function ProjectReadmeFeature({ description, icon: Icon, title }) {
489
+ return (_jsxs("div", { className: "anlyx-readme-feature", children: [_jsx("span", { children: _jsx(Icon, { size: 20 }) }), _jsxs("div", { children: [_jsx("strong", { children: title }), _jsx("p", { children: description })] })] }));
490
+ }
491
+ function projectOverviewStack(implementation) {
492
+ return implementation.slice(0, 5).map((item) => ({
493
+ detail: item.description ?? capitalize(item.kind),
494
+ icon: stackIcon(item.name, item.kind),
495
+ name: stackDisplayName(item.name),
496
+ tone: stackTone(item.name, item.kind)
497
+ }));
498
+ }
499
+ function stackDisplayName(value) {
500
+ if (/react/i.test(value))
501
+ return "React";
502
+ if (/typescript|ts\b/i.test(value))
503
+ return "TypeScript";
504
+ if (/node|runtime|cli/i.test(value))
505
+ return "Node.js";
506
+ if (/express/i.test(value))
507
+ return "Express";
508
+ if (/schema|project json|contract/i.test(value))
509
+ return "Project JSON";
510
+ return value;
511
+ }
512
+ function stackIcon(name, kind) {
513
+ const value = `${name} ${kind}`;
514
+ if (/react/i.test(value))
515
+ return simpleStackIcon(siReact);
516
+ if (/typescript|ts\b/i.test(value))
517
+ return simpleStackIcon(siTypescript);
518
+ if (/node|runtime|cli/i.test(value))
519
+ return simpleStackIcon(siNodedotjs);
520
+ if (/express/i.test(value))
521
+ return simpleStackIcon(siExpress);
522
+ if (/schema|json|contract/i.test(value))
523
+ return { kind: "lucide", icon: Braces };
524
+ return { kind: "lucide", icon: Code2 };
525
+ }
526
+ function simpleStackIcon(icon) {
527
+ return {
528
+ hex: icon.hex,
529
+ kind: "simple",
530
+ path: icon.path,
531
+ title: icon.title
532
+ };
533
+ }
534
+ function stackTone(name, kind) {
535
+ const value = `${name} ${kind}`;
536
+ if (/react/i.test(value))
537
+ return "react";
538
+ if (/typescript|ts\b/i.test(value))
539
+ return "typescript";
540
+ if (/node|runtime|cli/i.test(value))
541
+ return "node";
542
+ if (/express/i.test(value))
543
+ return "express";
544
+ if (/schema|json|contract/i.test(value))
545
+ return "json";
546
+ return "json";
547
+ }
548
+ function ProjectCapabilitiesView({ data }) {
549
+ const defaultCapability = data.capabilities.find((capability) => /inspect page behavior/i.test(capability.name)) ??
550
+ data.capabilities[0];
551
+ const [selectedId, setSelectedId] = useState(defaultCapability?.id);
552
+ const [capabilityFilter, setCapabilityFilter] = useState("all");
553
+ const visibleCapabilities = data.capabilities.filter((capability) => {
554
+ if (capabilityFilter === "connected")
555
+ return capability.status === "connected";
556
+ if (capabilityFilter === "entry")
557
+ return Boolean(capability.entry);
558
+ if (capabilityFilter === "user")
559
+ return capability.actorRole === "user";
560
+ return true;
561
+ });
562
+ const selected = visibleCapabilities.find((capability) => capability.id === selectedId) ??
563
+ data.capabilities.find((capability) => capability.id === selectedId) ??
564
+ defaultCapability;
565
+ const connected = data.capabilities.filter((capability) => capability.status === "connected").length;
566
+ const userFacing = data.capabilities.filter((capability) => capability.actorRole !== "system").length;
567
+ const unresolved = data.capabilities.filter((capability) => capability.status !== "connected").length;
568
+ return (_jsx("section", { className: "anlyx-understanding anlyx-capabilities", "aria-label": "Project capabilities", children: _jsxs("div", { className: "anlyx-overview-layout", children: [_jsxs("div", { className: "anlyx-overview-main", children: [_jsxs("div", { className: "anlyx-metric-row", children: [_jsx(ProjectUnderstandingMetric, { icon: Workflow, label: "Total capabilities", value: String(data.capabilities.length), detail: "Authored" }), _jsx(ProjectUnderstandingMetric, { icon: Check, label: "Connected", value: String(connected), detail: `${percentage(connected, data.capabilities.length)}% of total` }), _jsx(ProjectUnderstandingMetric, { icon: MousePointerClick, label: "User-facing", value: String(userFacing), detail: `${percentage(userFacing, data.capabilities.length)}% of total` }), _jsx(ProjectUnderstandingMetric, { icon: Gauge, label: "Unresolved", value: String(unresolved), detail: "Requires review" })] }), _jsxs("div", { className: "anlyx-filter-row", "aria-label": "Filter capabilities by actor", children: [_jsx("span", { children: "Filter by" }), _jsx("button", { className: capabilityFilter === "all" ? "is-selected" : "", type: "button", onClick: () => setCapabilityFilter("all"), children: "All" }), _jsx("button", { className: capabilityFilter === "user" ? "is-selected" : "", type: "button", onClick: () => setCapabilityFilter("user"), children: "User" }), _jsx("button", { className: capabilityFilter === "entry" ? "is-selected" : "", type: "button", onClick: () => setCapabilityFilter("entry"), children: "Entry surface" }), _jsx("button", { className: capabilityFilter === "connected" ? "is-selected" : "", type: "button", onClick: () => setCapabilityFilter("connected"), children: "Connected only" })] }), data.capabilities.length === 0 ? (_jsx(ProjectSurfaceEmpty, { title: "No capabilities authored", description: "Add capabilities to describe product behavior without opening source code." })) : (_jsxs("div", { className: "anlyx-capability-table", role: "table", "aria-label": "Capabilities", children: [_jsxs("div", { className: "anlyx-capability-row is-header", role: "row", children: [_jsx("span", { children: "Actor" }), _jsx("span", { children: "Capability" }), _jsx("span", { children: "Entry surface" }), _jsx("span", { children: "Request" }), _jsx("span", { children: "Data touched" }), _jsx("span", { children: "Status" })] }), visibleCapabilities.map((capability) => (_jsxs("button", { className: `anlyx-capability-row${capability.id === selected?.id ? " is-selected" : ""}`, type: "button", onClick: () => setSelectedId(capability.id), children: [_jsxs("span", { className: `anlyx-role-badge is-${capability.actorRole}`, children: [_jsx("span", { className: `anlyx-role-dot is-${capability.actorRole}` }), capitalize(capability.actorRole)] }), _jsxs("span", { className: "anlyx-capability-name", children: [_jsx("strong", { children: capability.name }), _jsx("em", { children: capability.description ?? capability.visibleResult ?? "No capability description authored." })] }), _jsx("span", { children: capability.entry?.label ?? "No entry authored" }), _jsx("span", { children: requestSummary(data, capability.requestIds) }), _jsx("span", { children: capability.dataRefs.map((ref) => ref.name).join(", ") || "No data refs" }), _jsx(ProjectStatusPill, { status: capability.status })] }, capability.id))), _jsxs("div", { className: "anlyx-capability-footer", children: ["Showing ", visibleCapabilities.length, " of ", data.capabilities.length, " capabilities"] })] }))] }), _jsx("aside", { className: "anlyx-surface-rail", "aria-label": "Capability details", children: selected ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "anlyx-rail-section is-tinted", children: [_jsx("h2", { children: selected.name }), _jsx(ProjectStatusPill, { status: selected.status })] }), _jsxs("div", { className: "anlyx-rail-section", children: [_jsx("h3", { children: "Why this matters" }), _jsx("p", { children: capabilityWhyThisMatters(selected) })] }), _jsxs("div", { className: "anlyx-rail-section", children: [_jsx("h3", { children: "Trace summary" }), _jsxs("dl", { className: "anlyx-capability-compact-facts", children: [_jsxs("div", { children: [_jsx("dt", { children: "Actor" }), _jsx("dd", { children: capitalize(selected.actorRole) })] }), _jsxs("div", { children: [_jsx("dt", { children: "Request" }), _jsx("dd", { children: requestSummary(data, selected.requestIds) })] }), _jsxs("div", { children: [_jsx("dt", { children: "Confidence" }), _jsx("dd", { children: capitalize(selected.confidence ?? "unknown") })] })] })] }), _jsxs("div", { className: "anlyx-rail-section", children: [_jsx("h3", { children: "Evidence summary" }), _jsx(ProjectCapabilityEvidenceSummary, { capability: selected })] })] })) : (_jsx(ProjectSurfaceEmpty, { title: "No capability selected", description: "Select a capability row." })) })] }) }));
569
+ }
570
+ function ProjectCapabilityEvidenceSummary({ capability }) {
571
+ const pageCount = new Set(capability.pageIds).size;
572
+ const flowCount = new Set(capability.flowIds).size;
573
+ const dataCount = capability.dataRefs.length;
574
+ return (_jsxs("div", { className: "anlyx-evidence-summary-list", children: [_jsxs("span", { children: [_jsx(FileText, { size: 14 }), pageCount, " pages analyzed"] }), _jsxs("span", { children: [_jsx(Workflow, { size: 14 }), flowCount, " flows connected"] }), _jsxs("span", { children: [_jsx(Database, { size: 14 }), dataCount, " data objects referenced"] })] }));
575
+ }
576
+ function ProjectTrustSummary({ data, validationReport }) {
577
+ const coverage = projectPageCoverageSummary(data, validationReport);
578
+ const coverageStatus = validationReport?.summary.coverageStatus ?? data.coverage?.status;
579
+ const sourceIssueCount = validationReport?.summary.sourceIssueCount;
580
+ const sourceIssueDetails = validationReport
581
+ ? formatSourceIssueDetails(validationReport.summary.sourceIssueBreakdown)
582
+ : "";
583
+ const issueCount = validationReport?.issues.length;
584
+ const shouldShow = Boolean(coverage.detected) ||
585
+ coverageStatus === "partial" ||
586
+ coverageStatus === "unknown" ||
587
+ (sourceIssueCount ?? 0) > 0 ||
588
+ (issueCount ?? 0) > 0;
589
+ if (!shouldShow) {
590
+ return null;
591
+ }
592
+ return (_jsxs("section", { className: "project-trust-summary", "aria-label": "Project analysis coverage", children: [_jsxs("div", { children: [_jsx("span", { className: "project-trust-summary__eyebrow", children: "Analysis scope" }), _jsx("strong", { children: coverageStatus === "partial" ? "Partial analysis" : "Coverage summary" })] }), _jsxs("dl", { children: [_jsxs("div", { children: [_jsx("dt", { children: "Pages" }), _jsx("dd", { children: coverage.value })] }), sourceIssueCount !== undefined ? (_jsxs("div", { children: [_jsx("dt", { children: "Source issues" }), _jsxs("dd", { className: sourceIssueCount > 0 ? "is-warning" : "is-ok", children: [sourceIssueCount, sourceIssueDetails ? _jsx("small", { children: sourceIssueDetails }) : null] })] })) : null, issueCount !== undefined ? (_jsxs("div", { children: [_jsx("dt", { children: "Validation issues" }), _jsx("dd", { className: issueCount > 0 ? "is-warning" : "is-ok", children: issueCount })] })) : null] })] }));
593
+ }
594
+ function capabilityWhyThisMatters(capability) {
595
+ if (/inspect page behavior/i.test(capability.name)) {
596
+ return "This capability lets a user inspect authored pages, primary requests, selected flow layers, evidence, and unknowns—providing clarity into how data moves and where gaps exist.";
597
+ }
598
+ return capability.visibleResult ?? capability.description ?? "This capability ties a user-facing action to authored requests, data, evidence, and confidence.";
437
599
  }
438
- function ProjectTabs({ activeTab, locale, onTabChange }) {
439
- return (_jsx("div", { className: "project-tabs", role: "tablist", "aria-label": "Project workspace views", children: ["pages", "map", "json"].map((tab) => (_jsx("button", { "aria-selected": activeTab === tab, className: activeTab === tab ? "is-active" : "", role: "tab", type: "button", onClick: () => onTabChange(tab), children: tab === "pages" ? tp(locale, "pages") : tab === "map" ? tp(locale, "map") : "JSON" }, tab))) }));
600
+ function ProjectUnderstandingMetric({ detail, icon: Icon, label, value }) {
601
+ return (_jsxs("div", { className: "anlyx-understanding-metric", children: [_jsx("span", { children: _jsx(Icon, { size: 20 }) }), _jsxs("div", { children: [_jsx("small", { children: label }), _jsx("strong", { children: value }), _jsx("em", { children: detail })] })] }));
440
602
  }
441
- function ProjectPagesWorkspace({ model, onOpenMap, onPageSelect, selectedPageId }) {
442
- return (_jsxs("div", { className: "project-pages-layout", children: [_jsx(ProjectPageIndex, { model: model, selectedPageId: selectedPageId, onPageSelect: onPageSelect }), _jsx(ProjectPagesView, { selectedPage: model.selectedPage, onOpenMap: onOpenMap })] }));
603
+ function ProjectSurfaceEmpty({ description, title }) {
604
+ return (_jsxs("div", { className: "anlyx-surface-empty", children: [_jsx(Minus, { size: 18 }), _jsx("strong", { children: title }), _jsx("p", { children: description })] }));
443
605
  }
444
- function ProjectPageIndex({ model, onPageSelect, selectedPageId }) {
445
- return (_jsxs("aside", { className: "project-page-index-panel", "aria-label": "Page index", children: [_jsxs("div", { className: "project-panel-title", children: [_jsx("h2", { children: "Page Index" }), _jsx("button", { "aria-label": "Filter pages", type: "button", children: _jsx(PanelLeft, { size: 15 }) })] }), _jsxs("div", { className: "project-page-index__search", children: [_jsx(Search, { size: 16 }), _jsx("span", { children: "Search pages..." }), _jsx("kbd", { children: "/" })] }), _jsx("nav", { className: "project-page-index", "aria-label": "Project pages", children: model.pageGroups.map((group) => (_jsxs("section", { children: [_jsxs("h2", { children: [group.name, _jsx("span", { children: group.pages.length })] }), group.pages.map((page) => (_jsxs("button", { className: selectedPageId === page.id ? "is-active" : "", type: "button", onClick: () => onPageSelect(page.id), children: [_jsx(BookOpen, { size: 15 }), _jsx("span", { children: page.title }), _jsx("em", { children: page.path })] }, page.id)))] }, group.id))) }), _jsxs("div", { className: "project-page-index-panel__footer", children: [_jsx("span", { children: "Total pages" }), _jsx("strong", { children: model.totals.pages })] })] }));
606
+ function ProjectStatusPill({ status }) {
607
+ return _jsx("span", { className: `anlyx-status-pill is-${status}`, children: status });
446
608
  }
447
- function ProjectPagesView({ onOpenMap, selectedPage }) {
448
- const firstRequestId = selectedPage?.requests[0]?.id;
449
- const [selectedRequestId, setSelectedRequestId] = useState(firstRequestId);
609
+ function percentage(value, total) {
610
+ if (total <= 0)
611
+ return 0;
612
+ return Math.round((value / total) * 100);
613
+ }
614
+ function requestSummary(data, requestIds) {
615
+ const requests = requestIds
616
+ .map((id) => data.requests.find((request) => request.id === id))
617
+ .filter((request) => Boolean(request));
618
+ if (requests.length === 0)
619
+ return "No request authored";
620
+ return requests
621
+ .slice(0, 2)
622
+ .map((request) => `${request.method ?? "REQ"} ${request.path ?? request.label ?? request.id}`)
623
+ .join(", ");
624
+ }
625
+ function ProjectPagesWorkspace({ data, model, onOpenMap, onPageSelect, selectedPageId, validationReport }) {
626
+ const [isPageIndexCollapsed, setIsPageIndexCollapsed] = useState(false);
627
+ return (_jsxs("div", { className: `anlyx-pages project-pages-layout${isPageIndexCollapsed ? " is-index-collapsed" : ""}`, children: [_jsx(ProjectPageIndex, { data: data, isCollapsed: isPageIndexCollapsed, model: model, ...(validationReport ? { validationReport } : {}), selectedPageId: selectedPageId, onPageSelect: onPageSelect, onToggleCollapse: () => setIsPageIndexCollapsed((value) => !value) }), _jsx(ProjectPagesView, { data: data, model: model, ...(validationReport ? { validationReport } : {}), selectedPage: model.selectedPage, onOpenMap: onOpenMap })] }));
628
+ }
629
+ function ProjectPageIndex({ data, isCollapsed, model, onPageSelect, onToggleCollapse, selectedPageId, validationReport }) {
630
+ const pages = model.pageGroups.flatMap((group) => group.pages);
631
+ const pageCoverage = projectPageCoverageSummary(data, validationReport);
632
+ const [query, setQuery] = useState("");
633
+ const normalizedQuery = query.trim().toLowerCase();
634
+ const visibleGroups = normalizedQuery
635
+ ? model.pageGroups
636
+ .map((group) => ({
637
+ ...group,
638
+ pages: group.pages.filter((page) => page.title.toLowerCase().includes(normalizedQuery) ||
639
+ page.path.toLowerCase().includes(normalizedQuery))
640
+ }))
641
+ .filter((group) => group.pages.length > 0)
642
+ : model.pageGroups;
643
+ return (_jsxs("aside", { className: `anlyx-page-index project-page-index-panel${isCollapsed ? " is-collapsed" : ""}`, "aria-label": "Page index", children: [_jsxs("div", { className: "project-panel-title", children: [isCollapsed ? null : (_jsxs("div", { children: [_jsx("h2", { children: "Page Index" }), _jsx("span", { children: pageCoverage.label })] })), _jsx("button", { "aria-expanded": !isCollapsed, "aria-label": isCollapsed ? "Expand page index" : "Collapse page index", type: "button", onClick: onToggleCollapse, children: _jsx(PanelLeft, { size: 15 }) })] }), isCollapsed ? (_jsx("nav", { className: "project-page-index--collapsed", "aria-label": "Project pages", children: pages.map((page) => (_jsx("button", { "aria-label": `${page.title} ${page.path}`, className: selectedPageId === page.id ? "is-active" : "", title: `${page.title} ${page.path}`, type: "button", onClick: () => onPageSelect(page.id), children: _jsx(BookOpen, { size: 16 }) }, page.id))) })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "project-page-index__search", children: [_jsx(Search, { size: 16 }), _jsx("input", { "aria-label": "Search pages", placeholder: "Search pages...", type: "search", value: query, onChange: (event) => setQuery(event.currentTarget.value) }), _jsx("kbd", { children: "/" })] }), _jsx("nav", { className: "project-page-index", "aria-label": "Project pages", children: visibleGroups.map((group) => (_jsxs("section", { children: [_jsxs("h2", { children: [_jsx("span", { children: group.name }), _jsx("span", { children: group.pages.length })] }), group.pages.map((page) => (_jsxs("button", { className: selectedPageId === page.id ? "is-active" : "", type: "button", onClick: () => onPageSelect(page.id), children: [_jsx(BookOpen, { size: 15 }), _jsx("span", { children: page.title }), _jsx("em", { children: page.path })] }, page.id)))] }, group.id))) }), _jsxs("div", { className: "project-page-index-panel__footer", children: [_jsx("span", { children: "Total pages" }), _jsx("strong", { children: model.totals.pages })] })] }))] }));
644
+ }
645
+ function ProjectPagesView({ data, model, onOpenMap, selectedPage, validationReport }) {
646
+ const defaultRequestId = getDefaultPageRequestId(selectedPage);
647
+ const [selectedRequestId, setSelectedRequestId] = useState(defaultRequestId);
648
+ const [isRailOpen, setIsRailOpen] = useState(false);
450
649
  useEffect(() => {
451
- setSelectedRequestId(firstRequestId);
452
- }, [firstRequestId, selectedPage?.page.id]);
650
+ setSelectedRequestId(defaultRequestId);
651
+ }, [defaultRequestId, selectedPage?.page.id]);
453
652
  if (!selectedPage) {
454
653
  return (_jsxs("section", { className: "project-workspace-preview__empty", children: [_jsx("strong", { children: "No pages authored yet" }), _jsx("span", { children: "Ask the project Agent to write pages into anlyx.project.json." })] }));
455
654
  }
456
655
  const selectedRequest = selectedPage.requests.find((request) => request.id === selectedRequestId) ??
457
- selectedPage.requests[0];
656
+ getDefaultPageRequest(selectedPage);
458
657
  const selectedRequestLabel = selectedRequest?.path ?? selectedRequest?.label ?? selectedRequest?.id ?? "selected request";
658
+ const requestGroups = groupRequestsByRole(selectedPage.requests);
459
659
  const selectedRequestFlows = selectedRequest
460
660
  ? selectedPage.flows.filter((flow) => flow.request?.id === selectedRequest.id ||
461
661
  (flow.request?.path && flow.request.path === selectedRequest.path))
462
662
  : [];
463
663
  const visibleFlows = selectedRequest ? selectedRequestFlows : selectedPage.flows;
464
- return (_jsxs("div", { className: "project-page-workspace", children: [_jsxs("header", { className: "project-page-workspace__title", children: [_jsxs("div", { children: [_jsxs("span", { children: [selectedPage.areaName, " / Page detail"] }), _jsx("h1", { children: selectedPage.page.title }), _jsx("code", { children: selectedPage.page.path })] }), _jsxs("div", { children: [_jsx("span", { className: "project-badge project-badge--green", children: "AI-authored" }), _jsxs("button", { type: "button", onClick: onOpenMap, children: [_jsx(Network, { size: 16 }), "View in Map"] })] })] }), _jsx(ProjectSection, { title: "Story", children: _jsx("p", { className: "project-story-copy", children: selectedPage.page.description ??
465
- "No story was authored for this page yet. Ask the project Agent to describe what this page does and why it exists." }) }), _jsx(ProjectSection, { title: "Features", children: _jsx("div", { className: "project-feature-grid", children: selectedPage.features.length > 0 ? (selectedPage.features.map((feature) => (_jsx(ProjectFeatureCard, { feature: feature }, feature.id)))) : (_jsx("p", { className: "project-muted", children: "No features authored for this page." })) }) }), _jsxs(ProjectSection, { title: "Requests", children: [_jsxs("div", { className: "project-section__summary", children: [_jsxs("span", { children: [selectedPage.requests.length, " requests"] }), _jsx("span", { children: "Select a request to show its backend path." })] }), _jsx("div", { className: "project-request-grid", children: selectedPage.requests.length > 0 ? (selectedPage.requests.map((request) => (_jsx(ProjectRequestCard, { isSelected: selectedRequest?.id === request.id, request: request, onSelect: () => setSelectedRequestId(request.id) }, request.id)))) : (_jsx("p", { className: "project-muted", children: "No requests linked to this page." })) })] }), _jsx(ProjectSection, { meta: selectedRequest
466
- ? `${selectedRequest.method ?? "HTTP"} ${selectedRequestLabel}`
467
- : undefined, title: "Flow summary", children: visibleFlows.length > 0 ? (visibleFlows.map((flow) => _jsx(ProjectFlowTrace, { flow: flow }, flow.id))) : (_jsx("p", { className: "project-muted", children: "No backend flow linked to this request." })) }), _jsx(ProjectSection, { title: "Evidence summary", children: _jsxs("div", { className: "project-evidence-row", children: [_jsx(EvidenceChip, { label: "Observed", value: selectedPage.evidenceSummary.observed }), _jsx(EvidenceChip, { label: "Found in code", value: selectedPage.evidenceSummary.sourceMatched }), _jsx(EvidenceChip, { label: "Agent inferred", value: selectedPage.evidenceSummary.agentInferred }), _jsx(EvidenceChip, { label: "Not proven", value: selectedPage.evidenceSummary.notProven }), _jsx(EvidenceChip, { label: "Unknown", value: selectedPage.evidenceSummary.unknown })] }) })] }));
664
+ const unknownEvidence = selectedPage.evidence.filter((item) => item.status === "not-proven" || item.status === "unknown");
665
+ const selectedFlow = visibleFlows[0];
666
+ return (_jsxs("div", { className: `project-page-workspace${isRailOpen ? "" : " is-rail-collapsed"}`, children: [_jsxs("main", { className: "anlyx-page-report project-page-workspace__main", children: [!isRailOpen ? (_jsxs("button", { className: "anlyx-rail-reopen", type: "button", onClick: () => setIsRailOpen(true), children: [_jsx(PanelLeft, { size: 14 }), "Details"] })) : null, _jsx(ProjectTrustSummary, { data: data, ...(validationReport ? { validationReport } : {}) }), _jsx(PageBrief, { selectedPage: selectedPage }), _jsxs("div", { className: "project-page-two-up", children: [_jsx(ProjectSection, { title: "Story", children: _jsx("p", { className: "project-story-copy", children: selectedPage.page.description ??
667
+ "No story was authored for this page yet. Ask the project Agent to describe what this page does and why it exists." }) }), _jsx(ProjectSection, { className: "anlyx-user-actions", title: "User actions", children: _jsx("div", { className: "anlyx-action-list project-action-list", children: selectedPage.features.length > 0 ? (selectedPage.features.map((feature) => (_jsx(ProjectFeatureCard, { feature: feature }, feature.id)))) : (_jsx("p", { className: "project-muted", children: "No user actions authored for this page." })) }) })] }), _jsx(RequestsByRole, { groups: requestGroups, selectedRequest: selectedRequest, onRequestSelect: setSelectedRequestId }), _jsx(ProjectSection, { className: "anlyx-flow-section", meta: selectedRequest
668
+ ? `${selectedRequest.method ?? "HTTP"} ${selectedRequestLabel}`
669
+ : undefined, title: "Selected Flow", children: selectedFlow ? (_jsx(ProjectFlowTrace, { flow: selectedFlow })) : (_jsx("p", { className: "project-muted", children: "No backend flow linked to this request." })) }), _jsxs("div", { className: "anlyx-trust-unknowns project-page-two-up project-page-two-up--trust", children: [_jsx(ProjectSection, { className: "anlyx-trust-breakdown", title: "Trust Breakdown", children: _jsxs("div", { className: "anlyx-trust-grid project-evidence-row", children: [_jsx(EvidenceChip, { label: "Source-matched", total: selectedPage.evidenceSummary.total, value: selectedPage.evidenceSummary.sourceMatched }), _jsx(EvidenceChip, { label: "Agent-inferred", total: selectedPage.evidenceSummary.total, value: selectedPage.evidenceSummary.agentInferred }), _jsx(EvidenceChip, { label: "Observed", total: selectedPage.evidenceSummary.total, value: selectedPage.evidenceSummary.observed }), _jsx(EvidenceChip, { label: "Not-proven", total: selectedPage.evidenceSummary.total, value: selectedPage.evidenceSummary.notProven }), _jsx(EvidenceChip, { label: "Unknown", total: selectedPage.evidenceSummary.total, value: selectedPage.evidenceSummary.unknown })] }) }), _jsx(ProjectSection, { className: "anlyx-unknowns", title: "Unknowns", children: unknownEvidence.length > 0 ? (_jsx("ul", { className: "anlyx-unknown-list project-unknown-list", children: unknownEvidence.slice(0, 4).map((item) => (_jsx("li", { children: item.detail ?? item.label }, item.id))) })) : (_jsxs("div", { className: "anlyx-unknown-empty", children: [_jsx("p", { children: "No unknown or not-proven evidence authored for this page." }), _jsx("p", { children: "All detected elements are source-matched." })] })) })] })] }), isRailOpen ? (_jsx(PageDetailsRail, { model: model, selectedPage: selectedPage, onCollapse: () => setIsRailOpen(false), onOpenMap: onOpenMap })) : null] }));
670
+ }
671
+ function PageBrief({ selectedPage }) {
672
+ return (_jsx("header", { className: "project-page-brief", children: _jsx("div", { className: "project-page-brief__body", children: _jsxs("div", { className: "project-page-brief__title", children: [_jsxs("div", { children: [_jsx("h1", { children: selectedPage.page.title }), _jsx("code", { children: selectedPage.page.path })] }), _jsx("p", { children: selectedPage.page.description ??
673
+ "No page story was authored yet. Ask the project Agent to describe what this page does and why it exists." })] }) }) }));
674
+ }
675
+ function RequestsByRole({ groups, onRequestSelect, selectedRequest }) {
676
+ return (_jsx(ProjectSection, { title: "Requests", children: _jsx("div", { className: "anlyx-requests-grid project-request-columns", children: groups.some((group) => group.requests.length > 0) ? (groups.map((group) => (_jsxs("section", { className: "project-request-column", children: [_jsxs("header", { children: [_jsx("strong", { children: group.label }), _jsx("span", { children: group.requests.length })] }), group.requests.length > 0 ? (group.requests.map((request) => (_jsx(ProjectRequestCard, { isSelected: selectedRequest?.id === request.id, request: request, onSelect: () => onRequestSelect(request.id) }, request.id)))) : (_jsxs("div", { className: "anlyx-request-empty", children: [_jsx(Minus, { size: 18 }), _jsxs("strong", { children: ["No ", group.label.toLowerCase(), " requests"] }), _jsx("span", { children: "None detected for this page." })] }))] }, group.role)))) : (_jsx("p", { className: "project-muted", children: "No requests linked to this page." })) }) }));
677
+ }
678
+ function getDefaultPageRequestId(selectedPage) {
679
+ return getDefaultPageRequest(selectedPage)?.id;
680
+ }
681
+ function getDefaultPageRequest(selectedPage) {
682
+ if (!selectedPage)
683
+ return undefined;
684
+ return (selectedPage.requests.find((request) => request.role === "primary") ??
685
+ selectedPage.requests.find((request) => request.role === "supporting") ??
686
+ selectedPage.requests[0]);
687
+ }
688
+ function groupRequestsByRole(requests) {
689
+ const groups = [
690
+ { role: "primary", label: "Primary", requests: [] },
691
+ { role: "supporting", label: "Supporting", requests: [] },
692
+ { role: "background", label: "Background", requests: [] },
693
+ { role: "external", label: "External", requests: [] }
694
+ ];
695
+ for (const request of requests) {
696
+ const group = groups.find((item) => item.role === request.role);
697
+ group?.requests.push(request);
698
+ }
699
+ const externalGroup = groups.find((group) => group.role === "external");
700
+ const baseGroups = groups.filter((group) => group.role !== "external");
701
+ return externalGroup && externalGroup.requests.length > 0
702
+ ? [...baseGroups, externalGroup]
703
+ : baseGroups;
468
704
  }
469
- function ProjectSection({ children, meta, title }) {
470
- return (_jsxs("section", { className: "project-section", children: [_jsxs("header", { className: "project-section__header", children: [_jsx("h3", { children: title }), meta ? _jsx("span", { children: meta }) : null] }), children] }));
705
+ function ProjectSection({ children, className, index, meta, title }) {
706
+ return (_jsxs("section", { className: `anlyx-report-section project-section${className ? ` ${className}` : ""}`, children: [_jsxs("header", { className: "project-section__header", children: [_jsxs("h3", { children: [index ? (_jsx("span", { className: "project-section__number", "aria-hidden": "true", children: index })) : null, _jsx("span", { children: title })] }), meta ? _jsx("span", { children: meta }) : null] }), children] }));
471
707
  }
472
708
  function ProjectFeatureCard({ feature }) {
473
- return (_jsxs("article", { className: "project-feature-card", children: [_jsx(FileText, { size: 20 }), _jsxs("div", { children: [_jsx("strong", { children: feature.name }), _jsx("p", { children: feature.description ?? "No feature description authored yet." })] }), _jsxs("span", { children: [feature.requests.length, " requests"] })] }));
709
+ return (_jsx("article", { className: "anlyx-action-item project-feature-card", children: _jsxs("div", { children: [_jsx("strong", { children: feature.name }), _jsx("p", { children: feature.description ?? "No feature description authored yet." })] }) }));
474
710
  }
475
711
  function ProjectRequestCard({ isSelected, onSelect, request }) {
476
712
  const label = request.path ?? request.label ?? request.id;
477
- return (_jsxs("button", { className: `project-request-card project-request-card--${request.role} ${isSelected ? "is-selected" : ""}`, type: "button", onClick: onSelect, children: [_jsx("header", { children: _jsx("span", { children: request.role }) }), _jsxs("strong", { children: [_jsx("em", { children: request.method ?? "HTTP" }), label] }), _jsxs("footer", { children: [_jsx("small", { children: "Purpose" }), _jsx("b", { children: request.purpose })] })] }));
713
+ return (_jsxs("button", { className: `anlyx-request-card project-request-card project-request-card--${request.role} ${isSelected ? "is-selected" : ""}`, type: "button", onClick: onSelect, children: [_jsxs("header", { children: [_jsx("span", { className: `project-method-badge project-method-badge--${request.method ?? "HTTP"}`, children: request.method ?? "HTTP" }), isSelected ? _jsx(Check, { size: 15 }) : null] }), _jsx("strong", { children: label }), _jsx("p", { children: request.description ?? request.purpose }), _jsx("footer", { children: _jsx("b", { children: request.role }) })] }));
478
714
  }
479
715
  function ProjectFlowTrace({ flow }) {
480
716
  const layers = flow.layers.length > 0 ? flow.layers : [];
481
- return (_jsxs("article", { className: "project-flow-trace", children: [_jsxs("header", { children: [_jsx("span", { children: flow.request?.role ?? "flow" }), _jsx("strong", { children: flow.name ?? flow.request?.path ?? flow.id })] }), _jsx("div", { className: "project-flow-trace__layers", children: layers.map((layer, index) => (_jsx(ProjectFlowLayerCard, { index: index, layer: layer }, layer.id))) })] }));
717
+ const [expandedLayerId, setExpandedLayerId] = useState();
718
+ return (_jsxs("article", { className: "anlyx-flow-trace project-flow-trace", children: [_jsxs("header", { className: "project-flow-trace__header", children: [_jsx("span", { children: flow.request?.role ?? "flow" }), _jsx("strong", { children: flow.name ?? flow.request?.path ?? flow.id })] }), _jsx("div", { className: "anlyx-flow-scroll", children: _jsx("div", { className: "anlyx-flow-track project-flow-trace__layers", children: layers.map((layer, index) => (_jsxs(Fragment, { children: [_jsx(ProjectFlowLayerCard, { isExpanded: expandedLayerId === layer.id, layer: layer, onToggle: () => setExpandedLayerId((current) => (current === layer.id ? undefined : layer.id)) }), index < layers.length - 1 ? (_jsx("span", { className: "anlyx-flow-arrow", "aria-hidden": "true", children: _jsx(ChevronRight, { size: 18 }) })) : null] }, layer.id))) }) })] }));
719
+ }
720
+ function ProjectFlowLayerCard({ isExpanded, onToggle, layer }) {
721
+ const source = formatSourceLocation(layer.source) ?? formatLayerHint(layer);
722
+ return (_jsxs("button", { "aria-expanded": isExpanded, className: `anlyx-flow-node project-flow-layer project-flow-layer--${layer.kind}${isExpanded ? " is-expanded" : ""}`, title: source, type: "button", onClick: onToggle, children: [_jsx("span", { className: "anlyx-flow-layer", children: titleCase(layer.kind) }), _jsx("strong", { className: "anlyx-flow-title", children: layer.label }), _jsx("em", { className: "anlyx-flow-source", children: source }), _jsx("small", { className: "anlyx-evidence-pill", children: evidenceStatusLabel(layer.status) })] }));
723
+ }
724
+ function EvidenceChip({ label, total, value }) {
725
+ const percent = total > 0 ? Math.round((value / total) * 100) : 0;
726
+ const status = label.toLowerCase().replace(/[^a-z]+/g, "-").replace(/^-|-$/g, "");
727
+ return (_jsxs("span", { className: `anlyx-trust-card anlyx-trust-card--${status} project-evidence-chip`, children: [_jsxs("span", { children: [_jsx("i", { "aria-hidden": "true" }), label] }), _jsx("strong", { children: value }), _jsxs("em", { children: [percent, "%"] })] }));
728
+ }
729
+ function PageDetailsRail({ model, onCollapse, onOpenMap, selectedPage }) {
730
+ const page = selectedPage.page;
731
+ const relatedPages = getRelatedPages(model, page.metadata?.["relatedPageIds"]);
732
+ const tags = getStringList(page.metadata?.["tags"]);
733
+ const evidenceLabel = formatEvidenceSummary(selectedPage.evidenceSummary);
734
+ return (_jsxs("aside", { className: "anlyx-page-rail project-page-details-panel", "aria-label": "Page details", children: [_jsxs("div", { className: "anlyx-page-rail__header", children: [_jsx("h2", { children: "Page Details" }), _jsx("button", { "aria-label": "Collapse page details", type: "button", onClick: onCollapse, children: _jsx(PanelLeft, { size: 14 }) })] }), _jsxs("dl", { children: [_jsx(ProjectDetailField, { label: "Area", value: selectedPage.areaName }), _jsx(ProjectDetailField, { label: "Route", value: page.path }), _jsx(ProjectDetailField, { label: "Page Type", value: metadataString(page.metadata?.["pageType"]) ?? "Not authored" }), _jsx(ProjectDetailField, { label: "Auth Required", value: metadataBooleanLabel(page.metadata?.["authRequired"]) }), _jsx(ProjectDetailField, { label: "Layout", value: metadataString(page.metadata?.["layout"]) ?? "Not authored" }), _jsx(ProjectDetailField, { label: "Last Seen", value: formatAnalysisTime(model.project.analyzedAt) ?? "Not authored" })] }), _jsxs("section", { children: [_jsx("h3", { children: "Evidence" }), _jsx("p", { children: evidenceLabel }), page.source ? _jsx("p", { children: formatSourceLocation(page.source) }) : null] }), _jsxs("section", { children: [_jsx("h3", { children: "Related Pages" }), relatedPages.length > 0 ? (_jsx("ul", { children: relatedPages.map((relatedPage) => (_jsxs("li", { children: [_jsx("strong", { children: relatedPage.title }), _jsx("span", { children: relatedPage.path })] }, relatedPage.id))) })) : (_jsx("p", { children: "No related pages authored." }))] }), _jsxs("section", { children: [_jsx("h3", { children: "Tags" }), tags.length > 0 ? (_jsx("div", { className: "project-page-tags", children: tags.map((tag) => (_jsxs("span", { children: [_jsx(Tag, { size: 12 }), tag] }, tag))) })) : (_jsx("p", { children: "No tags authored." }))] }), _jsxs("button", { type: "button", onClick: onOpenMap, children: [_jsx(Network, { size: 15 }), "View in Map"] })] }));
735
+ }
736
+ function ProjectDetailField({ label, value }) {
737
+ return (_jsxs("div", { children: [_jsx("dt", { children: label }), _jsx("dd", { children: value })] }));
738
+ }
739
+ function getRelatedPages(model, value) {
740
+ const ids = getStringList(value);
741
+ const pages = model.pageGroups.flatMap((group) => group.pages);
742
+ return ids.flatMap((id) => {
743
+ const page = pages.find((item) => item.id === id);
744
+ return page ? [{ id: page.id, path: page.path, title: page.title }] : [];
745
+ });
746
+ }
747
+ function getStringList(value) {
748
+ return Array.isArray(value)
749
+ ? value.filter((item) => typeof item === "string" && item.trim().length > 0)
750
+ : [];
751
+ }
752
+ function metadataString(value) {
753
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
754
+ }
755
+ function metadataBooleanLabel(value) {
756
+ if (typeof value === "boolean")
757
+ return value ? "Yes" : "No";
758
+ if (typeof value === "string" && value.trim().length > 0)
759
+ return value;
760
+ return "Not authored";
761
+ }
762
+ function formatAnalysisTime(value) {
763
+ if (!value)
764
+ return undefined;
765
+ const date = new Date(value);
766
+ if (Number.isNaN(date.getTime()))
767
+ return value;
768
+ return date.toLocaleString("en", {
769
+ day: "2-digit",
770
+ hour: "2-digit",
771
+ minute: "2-digit",
772
+ month: "short",
773
+ year: "numeric"
774
+ });
482
775
  }
483
- function ProjectFlowLayerCard({ index, layer }) {
484
- return (_jsxs("div", { className: `project-flow-layer project-flow-layer--${layer.kind}`, children: [_jsx("span", { children: index + 1 }), _jsx("strong", { children: layer.kind }), _jsx("em", { children: layer.label }), _jsx("small", { children: evidenceStatusLabel(layer.status) })] }));
776
+ function formatEvidenceSummary(summary) {
777
+ if (summary.total === 0)
778
+ return "Not authored";
779
+ const strongest = summary.sourceMatched > 0
780
+ ? "Source-matched"
781
+ : summary.observed > 0
782
+ ? "Observed"
783
+ : summary.agentInferred > 0
784
+ ? "Agent-inferred"
785
+ : summary.notProven > 0
786
+ ? "Not-proven"
787
+ : "Unknown";
788
+ return `${strongest} (${summary.total})`;
789
+ }
790
+ function formatSourceLocation(source) {
791
+ if (!source)
792
+ return undefined;
793
+ const line = source.lineStart ? `:${source.lineStart}` : "";
794
+ const symbol = source.symbol ? ` ${source.symbol}` : "";
795
+ return `${source.filePath}${line}${symbol}`;
796
+ }
797
+ function formatLayerHint(layer) {
798
+ return metadataString(layer.metadata?.["module"]) ?? metadataString(layer.metadata?.["file"]) ?? layer.id;
485
799
  }
486
- function EvidenceChip({ label, value }) {
487
- return (_jsxs("span", { className: "project-evidence-chip", children: [_jsx(Check, { size: 15 }), label, _jsx("strong", { children: value })] }));
800
+ function titleCase(value) {
801
+ return value
802
+ .split("-")
803
+ .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
804
+ .join("-");
488
805
  }
489
806
  function ProjectMapView({ data, model }) {
490
- const graph = useMemo(() => buildProjectArchitectureReactFlow(data), [data]);
807
+ const map = useMemo(() => buildProjectArchitectureMap(data), [data]);
808
+ const [selectedNodeId, setSelectedNodeId] = useState(undefined);
809
+ const [inspectorOpen, setInspectorOpen] = useState(false);
810
+ const selectedNode = map.nodes.find((node) => node.id === selectedNodeId);
811
+ const focus = useMemo(() => buildProjectArchitectureFocus(map, selectedNode?.id), [map, selectedNode?.id]);
812
+ const selectNode = (nodeId) => {
813
+ setSelectedNodeId(nodeId);
814
+ setInspectorOpen(true);
815
+ };
491
816
  if (data.architecture.nodes.length === 0) {
492
817
  return (_jsxs("section", { className: "project-map-placeholder", "aria-label": "Project architecture map", children: [_jsxs("div", { children: [_jsx(Network, { size: 24 }), _jsx("h2", { children: "Map" }), _jsx("p", { children: "No architecture graph was authored yet. The viewer will not invent mock project data." })] }), _jsxs("dl", { children: [_jsxs("div", { children: [_jsx("dt", { children: "Nodes" }), _jsx("dd", { children: "0" })] }), _jsxs("div", { children: [_jsx("dt", { children: "Edges" }), _jsx("dd", { children: "0" })] })] })] }));
493
818
  }
494
- return (_jsxs("section", { className: "project-architecture-map project-screen-panel", "aria-label": "Project architecture map", children: [_jsxs("header", { className: "project-architecture-map__toolbar", children: [_jsxs("div", { children: [_jsx("h1", { children: "Map" }), _jsx("p", { children: "Agent-authored architecture map. The viewer renders only nodes and edges from project JSON." })] }), _jsxs("dl", { children: [_jsxs("div", { children: [_jsx("dt", { children: "Nodes" }), _jsx("dd", { children: data.architecture.nodes.length })] }), _jsxs("div", { children: [_jsx("dt", { children: "Edges" }), _jsx("dd", { children: graph.edges.length })] }), _jsxs("div", { children: [_jsx("dt", { children: "Pages" }), _jsx("dd", { children: model.totals.pages })] })] })] }), _jsx("div", { className: "project-architecture-map__canvas", "data-testid": "project-architecture-map", children: _jsx(ReactFlow, { className: "project-architecture-react-flow", edgeTypes: projectArchitectureEdgeTypes, edges: graph.edges, fitView: true, fitViewOptions: { padding: 0.18 }, maxZoom: 1.45, minZoom: 0.45, nodes: graph.nodes, nodesConnectable: false, nodesDraggable: false, nodeTypes: projectArchitectureNodeTypes, proOptions: { hideAttribution: true } }) })] }));
495
- }
496
- const projectArchitectureNodeTypes = { projectArchitectureNode: ProjectArchitectureNode };
497
- const projectArchitectureEdgeTypes = { projectArchitectureEdge: ProjectArchitectureEdge };
498
- const projectArchitectureLayerX = {
499
- frontend: 32,
500
- request: 220,
501
- api: 410,
502
- controller: 600,
503
- handler: 600,
504
- middleware: 600,
505
- service: 810,
506
- policy: 810,
507
- mapper: 810,
508
- cache: 810,
509
- queue: 810,
510
- job: 810,
511
- external: 810,
512
- repository: 1030,
513
- database: 1240,
514
- result: 1450,
515
- unknown: 810
516
- };
517
- function buildProjectArchitectureReactFlow(data) {
819
+ return (_jsxs("section", { className: "project-architecture-map anlyx-map-shell", "aria-label": "Project architecture map", children: [_jsxs("header", { className: "project-architecture-map__toolbar", children: [_jsxs("div", { children: [_jsx("h1", { children: "Map" }), _jsx("p", { children: "Agent-authored architecture map. The viewer renders nodes and edges from project JSON." })] }), _jsx("div", { className: "anlyx-map-header-actions", children: _jsx(ProjectArchitectureMapStats, { map: map, model: model }) })] }), _jsxs("div", { className: `anlyx-map-body${inspectorOpen ? " has-inspector" : ""}`, children: [_jsx("div", { className: `project-architecture-map__canvas${selectedNode ? " is-focused" : ""}${map.isCompact ? " is-small-graph" : ""}`, "data-testid": "project-architecture-map", style: {
820
+ "--anlyx-map-width": `${map.width}px`,
821
+ "--anlyx-map-height": `${map.height}px`
822
+ }, children: _jsxs("div", { className: "anlyx-map-stage", children: [_jsx("svg", { "aria-hidden": "true", className: "anlyx-map-edges", height: map.height, viewBox: `0 0 ${map.width} ${map.height}`, width: map.width, children: map.edges.map((edge) => (_jsx(ProjectArchitectureMapEdge, { edge: edge, focus: focus }, edge.id))) }), _jsx("div", { className: "anlyx-map-columns", "aria-hidden": "true", children: map.columns.map((column) => (_jsxs("div", { className: "anlyx-map-column-guide", style: { left: column.x, width: column.width }, children: [_jsx("strong", { children: column.label }), _jsx("span", { children: column.count })] }, column.id))) }), map.nodes.map((node) => (_jsx(ProjectArchitectureMapNode, { focus: focus, node: node, onSelect: selectNode, selected: node.id === selectedNode?.id }, node.id))), _jsxs("div", { className: "anlyx-map-legend", children: [_jsxs("span", { children: [_jsx("i", { className: "is-primary" }), " Selected path"] }), _jsxs("span", { children: [_jsx("i", { className: "is-shared" }), " Shared dependency"] }), _jsxs("span", { children: [_jsx("i", { className: "is-muted" }), " Other / Unknown"] })] })] }) }), inspectorOpen ? (_jsx(ProjectArchitectureInspector, { focus: focus, node: selectedNode, nodesById: map.nodesById, onClose: () => {
823
+ setInspectorOpen(false);
824
+ setSelectedNodeId(undefined);
825
+ } })) : null] })] }));
826
+ }
827
+ const projectArchitectureColumns = [
828
+ { id: "pages", label: "Pages / Features", kinds: ["frontend"] },
829
+ { id: "requests", label: "Requests", kinds: ["request"] },
830
+ { id: "api", label: "API", kinds: ["api"] },
831
+ { id: "controllers", label: "Controllers", kinds: ["controller", "handler", "middleware"] },
832
+ { id: "services", label: "Services", kinds: ["service", "cache", "queue", "job", "external"] },
833
+ { id: "repository", label: "Repository / Mapper", kinds: ["repository", "mapper"] },
834
+ { id: "policy", label: "Policy / Schema", kinds: ["policy"] },
835
+ { id: "data", label: "Data / JSON", kinds: ["database"] },
836
+ { id: "result", label: "Results", kinds: ["result", "unknown"] }
837
+ ];
838
+ function buildProjectArchitectureMap(data) {
518
839
  const nodeIds = new Set(data.architecture.nodes.map((node) => node.id));
519
840
  const validEdges = data.architecture.edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target));
841
+ const evidenceById = new Map(data.evidence.map((evidence) => [evidence.id, evidence]));
520
842
  const connectionCounts = new Map();
843
+ const upstreamCounts = new Map();
844
+ const downstreamCounts = new Map();
521
845
  validEdges.forEach((edge) => {
522
846
  connectionCounts.set(edge.source, (connectionCounts.get(edge.source) ?? 0) + 1);
523
847
  connectionCounts.set(edge.target, (connectionCounts.get(edge.target) ?? 0) + 1);
848
+ downstreamCounts.set(edge.source, (downstreamCounts.get(edge.source) ?? 0) + 1);
849
+ upstreamCounts.set(edge.target, (upstreamCounts.get(edge.target) ?? 0) + 1);
850
+ });
851
+ const columnIdByKind = new Map(projectArchitectureColumns.flatMap((column) => column.kinds.map((kind) => [kind, column.id])));
852
+ const isCompact = data.architecture.nodes.length <= 12;
853
+ const filledColumnWidth = isCompact ? 176 : 196;
854
+ const nodeHeight = isCompact ? 90 : 96;
855
+ const columnGap = isCompact ? 28 : 34;
856
+ const rowGap = isCompact ? 16 : 22;
857
+ const top = isCompact ? 62 : 82;
858
+ const left = 18;
859
+ const grouped = new Map();
860
+ for (const node of data.architecture.nodes) {
861
+ const columnId = columnIdByKind.get(node.kind) ?? "result";
862
+ const nodes = grouped.get(columnId) ?? [];
863
+ nodes.push(node);
864
+ grouped.set(columnId, nodes);
865
+ }
866
+ const columnCounts = new Map();
867
+ for (const column of projectArchitectureColumns) {
868
+ columnCounts.set(column.id, grouped.get(column.id)?.length ?? 0);
869
+ }
870
+ let currentX = left;
871
+ const columns = projectArchitectureColumns
872
+ .filter((column) => (columnCounts.get(column.id) ?? 0) > 0)
873
+ .map((column) => {
874
+ const count = columnCounts.get(column.id) ?? 0;
875
+ const width = filledColumnWidth;
876
+ const positionedColumn = {
877
+ ...column,
878
+ x: currentX,
879
+ width,
880
+ count
881
+ };
882
+ currentX += width + columnGap;
883
+ return positionedColumn;
884
+ });
885
+ const mapNodes = [];
886
+ for (const column of columns) {
887
+ const nodes = grouped.get(column.id) ?? [];
888
+ nodes
889
+ .sort((a, b) => {
890
+ const degreeDelta = (connectionCounts.get(b.id) ?? 0) - (connectionCounts.get(a.id) ?? 0);
891
+ return degreeDelta || a.label.localeCompare(b.label);
892
+ })
893
+ .forEach((node, index) => {
894
+ const evidenceStatus = projectArchitectureEvidenceStatus(node.evidenceIds, evidenceById);
895
+ const source = formatProjectArchitectureSource(node.source);
896
+ mapNodes.push({
897
+ ...node,
898
+ columnId: column.id,
899
+ x: column.x,
900
+ y: top + index * (nodeHeight + rowGap),
901
+ width: column.width,
902
+ height: nodeHeight,
903
+ connectionCount: connectionCounts.get(node.id) ?? 0,
904
+ upstreamCount: upstreamCounts.get(node.id) ?? 0,
905
+ downstreamCount: downstreamCounts.get(node.id) ?? 0,
906
+ evidenceStatus,
907
+ evidenceLabel: node.evidenceIds.length > 0 ? evidenceStatusLabel(evidenceStatus) : "Unknown",
908
+ subtitle: source ?? projectArchitectureNodeSubtitle(node)
909
+ });
910
+ });
911
+ }
912
+ const nodesById = new Map(mapNodes.map((node) => [node.id, node]));
913
+ const logicalEdges = validEdges
914
+ .map((edge) => {
915
+ const sourceNode = nodesById.get(edge.source);
916
+ const targetNode = nodesById.get(edge.target);
917
+ if (!sourceNode || !targetNode)
918
+ return undefined;
919
+ return sourceNode && targetNode ? { ...edge, sourceNode, targetNode, routeX: 0 } : undefined;
920
+ })
921
+ .filter((edge) => Boolean(edge));
922
+ const mapEdgesWithoutLanes = logicalEdges.filter((edge) => {
923
+ if (edge.sourceNode.columnId === edge.targetNode.columnId)
924
+ return false;
925
+ return edge.sourceNode.x + edge.sourceNode.width < edge.targetNode.x;
524
926
  });
525
- const domains = Array.from(new Set(data.architecture.nodes.map((node) => node.domain ?? "Project")));
526
- const domainIndex = new Map(domains.map((domain, index) => [domain, index]));
527
- const slotCounts = new Map();
528
- const nodes = data.architecture.nodes.map((node) => {
529
- const domain = node.domain ?? "Project";
530
- const layer = node.kind in projectArchitectureLayerX ? node.kind : "unknown";
531
- const layerX = projectArchitectureLayerX[layer] ?? 810;
532
- const slotKey = `${domain}:${layer}`;
533
- const slot = slotCounts.get(slotKey) ?? 0;
534
- slotCounts.set(slotKey, slot + 1);
927
+ const gutterCounts = new Map();
928
+ const mapEdges = mapEdgesWithoutLanes.map((edge) => {
929
+ const sourceRight = edge.sourceNode.x + edge.sourceNode.width;
930
+ const gap = Math.max(1, edge.targetNode.x - sourceRight);
931
+ const gutterKey = `${edge.sourceNode.columnId}->${edge.targetNode.columnId}`;
932
+ const gutterIndex = gutterCounts.get(gutterKey) ?? 0;
933
+ gutterCounts.set(gutterKey, gutterIndex + 1);
934
+ const laneOffset = (gutterIndex - 1) * 6;
935
+ const routeX = sourceRight + gap / 2 + laneOffset;
535
936
  return {
536
- id: node.id,
537
- type: "projectArchitectureNode",
538
- position: {
539
- x: layerX,
540
- y: 54 + (domainIndex.get(domain) ?? 0) * 138 + slot * 46
541
- },
542
- data: {
543
- node: {
544
- ...node,
545
- connectionCount: connectionCounts.get(node.id) ?? 0
546
- }
547
- },
548
- draggable: false,
549
- selectable: false
937
+ ...edge,
938
+ routeX: Math.max(sourceRight + 14, Math.min(edge.targetNode.x - 14, routeX))
550
939
  };
551
940
  });
941
+ const maxColumnCount = Math.max(1, ...columns.map((column) => column.count));
552
942
  return {
553
- nodes,
554
- edges: validEdges.map((edge) => ({
555
- id: edge.id,
556
- source: edge.source,
557
- sourceHandle: "right",
558
- target: edge.target,
559
- targetHandle: "left",
560
- type: "projectArchitectureEdge",
561
- data: { edge },
562
- focusable: false,
563
- selectable: false
564
- }))
943
+ columns,
944
+ columnCounts,
945
+ nodes: mapNodes,
946
+ edges: mapEdges,
947
+ logicalEdges,
948
+ nodesById,
949
+ width: Math.max(0, currentX - columnGap + left),
950
+ height: top + maxColumnCount * (nodeHeight + rowGap) + (isCompact ? 34 : 80),
951
+ nodeWidth: filledColumnWidth,
952
+ isCompact
565
953
  };
566
954
  }
567
- function ProjectArchitectureNode({ data }) {
568
- const node = data.node;
569
- const label = node.displayLabel ?? node.label;
955
+ function buildProjectArchitectureFocus(map, selectedNodeId) {
956
+ if (!selectedNodeId) {
957
+ return { nodeIds: new Set(), edgeIds: new Set(), upstreamIds: [], downstreamIds: [] };
958
+ }
959
+ const upstreamIds = collectArchitectureReachable(map.logicalEdges, selectedNodeId, "upstream");
960
+ const downstreamIds = collectArchitectureReachable(map.logicalEdges, selectedNodeId, "downstream");
961
+ const nodeIds = new Set([selectedNodeId, ...upstreamIds, ...downstreamIds]);
962
+ const edgeIds = new Set(map.logicalEdges
963
+ .filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target))
964
+ .map((edge) => edge.id));
965
+ return { nodeIds, edgeIds, upstreamIds, downstreamIds };
966
+ }
967
+ function collectArchitectureReachable(edges, nodeId, direction) {
968
+ const result = [];
969
+ const visited = new Set([nodeId]);
970
+ const queue = [nodeId];
971
+ while (queue.length > 0) {
972
+ const current = queue.shift() ?? "";
973
+ const nextEdges = direction === "downstream"
974
+ ? edges.filter((edge) => edge.source === current)
975
+ : edges.filter((edge) => edge.target === current);
976
+ for (const edge of nextEdges) {
977
+ const nextId = direction === "downstream" ? edge.target : edge.source;
978
+ if (visited.has(nextId))
979
+ continue;
980
+ visited.add(nextId);
981
+ result.push(nextId);
982
+ queue.push(nextId);
983
+ }
984
+ }
985
+ return result;
986
+ }
987
+ function ProjectArchitectureMapStats({ map, model }) {
988
+ const stats = [
989
+ { label: "Pages", value: model.totals.pages },
990
+ { label: "Requests", value: map.columnCounts.get("requests") ?? 0 },
991
+ { label: "API", value: map.columnCounts.get("api") ?? 0 },
992
+ { label: "Controllers", value: map.columnCounts.get("controllers") ?? 0 },
993
+ { label: "Services", value: map.columnCounts.get("services") ?? 0 },
994
+ { label: "Mappers", value: map.columnCounts.get("repository") ?? 0 },
995
+ { label: "Results", value: map.columnCounts.get("result") ?? 0 }
996
+ ];
997
+ return (_jsx("dl", { children: stats.map((stat) => (_jsxs("div", { children: [_jsx("dt", { children: stat.label }), _jsx("dd", { children: stat.value })] }, stat.label))) }));
998
+ }
999
+ function ProjectArchitectureMapNode({ focus, node, onSelect, selected }) {
570
1000
  const Icon = projectArchitectureNodeIcon(node.kind);
571
- return (_jsxs("article", { className: `project-architecture-node project-architecture-node--${node.kind}`, title: `${node.label}${node.source?.filePath ? ` · ${node.source.filePath}` : ""}`, children: [_jsx(Handle, { className: "project-architecture-handle", id: "left", position: Position.Left, type: "target" }), _jsx("span", { className: "project-architecture-node__icon", children: _jsx(Icon, { size: 15 }) }), _jsxs("div", { children: [_jsx("strong", { children: label }), _jsx("span", { children: node.kind })] }), node.connectionCount > 2 ? _jsx("em", { children: node.connectionCount }) : null, _jsx(Handle, { className: "project-architecture-handle", id: "right", position: Position.Right, type: "source" })] }));
1001
+ const muted = focus.nodeIds.size > 0 && !focus.nodeIds.has(node.id);
1002
+ const shared = node.upstreamCount > 1 || node.downstreamCount > 1;
1003
+ return (_jsxs("button", { className: `anlyx-map-node${selected ? " is-selected" : ""}${muted ? " is-muted" : ""}`, onClick: () => onSelect(node.id), style: { left: node.x, top: node.y, width: node.width, minHeight: node.height }, title: `${node.label}${node.source?.filePath ? ` · ${node.source.filePath}` : ""}`, type: "button", children: [_jsx("span", { className: "anlyx-map-node__icon", "aria-hidden": "true", children: _jsx(Icon, { size: 15 }) }), _jsxs("span", { className: "anlyx-map-node__body", children: [_jsx("span", { className: "anlyx-map-node__layer", children: projectArchitectureKindLabel(node.kind) }), _jsx("strong", { children: node.displayLabel ?? node.label }), _jsx("small", { children: node.subtitle })] }), _jsx("span", { className: `anlyx-map-evidence anlyx-map-evidence--${node.evidenceStatus}`, children: node.evidenceLabel }), shared ? _jsx("span", { className: "anlyx-map-node__degree", children: node.connectionCount }) : null] }));
1004
+ }
1005
+ function ProjectArchitectureMapEdge({ edge, focus }) {
1006
+ const selected = focus.edgeIds.has(edge.id);
1007
+ const muted = focus.edgeIds.size > 0 && !selected;
1008
+ const sourceX = edge.sourceNode.x + edge.sourceNode.width;
1009
+ const sourceY = edge.sourceNode.y + edge.sourceNode.height / 2;
1010
+ const targetX = edge.targetNode.x;
1011
+ const targetY = edge.targetNode.y + edge.targetNode.height / 2;
1012
+ const path = createProjectArchitectureOrthogonalPath(sourceX, sourceY, edge.routeX, targetX, targetY);
1013
+ const role = edge.role ?? "primary";
1014
+ const status = projectArchitectureEdgeStatus(edge);
1015
+ const visualRole = status === "not-proven" || status === "unknown"
1016
+ ? "unknown"
1017
+ : role === "shared" || edge.targetNode.upstreamCount > 1
1018
+ ? "shared"
1019
+ : "primary";
1020
+ return (_jsx("path", { className: `anlyx-map-edge anlyx-map-edge--${visualRole}${selected ? " is-selected" : ""}${muted ? " is-muted" : ""}`, d: path }));
1021
+ }
1022
+ function createProjectArchitectureOrthogonalPath(sourceX, sourceY, routeX, targetX, targetY) {
1023
+ return `M ${sourceX} ${sourceY} H ${routeX} V ${targetY} H ${targetX}`;
1024
+ }
1025
+ function projectArchitectureEdgeStatus(edge) {
1026
+ if (edge.confidence === "low")
1027
+ return "not-proven";
1028
+ if (edge.sourceNode.evidenceStatus === "not-proven" || edge.targetNode.evidenceStatus === "not-proven") {
1029
+ return "not-proven";
1030
+ }
1031
+ if (edge.sourceNode.evidenceStatus === "unknown" || edge.targetNode.evidenceStatus === "unknown") {
1032
+ return "unknown";
1033
+ }
1034
+ return edge.targetNode.evidenceStatus;
1035
+ }
1036
+ function ProjectArchitectureInspector({ focus, node, nodesById, onClose }) {
1037
+ if (!node) {
1038
+ return (_jsxs("aside", { className: "anlyx-map-inspector", "aria-label": "Map inspector", children: [_jsx("button", { className: "anlyx-map-inspector__close", onClick: onClose, type: "button", children: "Close" }), _jsx("h2", { children: "No node selected" })] }));
1039
+ }
1040
+ const upstream = focus.upstreamIds.map((id) => nodesById.get(id)).filter(Boolean);
1041
+ const downstream = focus.downstreamIds.map((id) => nodesById.get(id)).filter(Boolean);
1042
+ const contract = projectArchitectureNodeContract(node);
1043
+ return (_jsxs("aside", { className: "anlyx-map-inspector", "aria-label": "Map inspector", children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("span", { children: "Selected" }), _jsx("h2", { children: node.displayLabel ?? node.label })] }), _jsx("button", { className: "anlyx-map-inspector__close", onClick: onClose, type: "button", children: "Close" })] }), _jsxs("dl", { className: "anlyx-map-inspector__meta", children: [_jsxs("div", { children: [_jsx("dt", { children: "Layer" }), _jsx("dd", { children: projectArchitectureKindLabel(node.kind) })] }), _jsxs("div", { children: [_jsx("dt", { children: "Title" }), _jsx("dd", { children: node.label })] }), _jsxs("div", { children: [_jsx("dt", { children: "Path" }), _jsx("dd", { children: formatProjectArchitectureSource(node.source) ?? "Not authored" })] }), _jsxs("div", { children: [_jsx("dt", { children: "Evidence" }), _jsx("dd", { children: node.evidenceLabel })] })] }), _jsx(ProjectArchitectureContractSection, { contract: contract }), _jsxs("section", { children: [_jsx("h3", { children: "Upstream" }), _jsx(ProjectArchitectureInspectorList, { emptyLabel: "No upstream nodes.", nodes: upstream })] }), _jsxs("section", { children: [_jsx("h3", { children: "Downstream path" }), _jsx(ProjectArchitectureInspectorList, { emptyLabel: "No downstream nodes.", nodes: downstream, ordered: true })] })] }));
1044
+ }
1045
+ function ProjectArchitectureContractSection({ contract }) {
1046
+ const hasContract = Boolean(contract.endpoint) ||
1047
+ Boolean(contract.inputName) ||
1048
+ Boolean(contract.outputName) ||
1049
+ contract.relatedModels.length > 0 ||
1050
+ contract.transforms.length > 0 ||
1051
+ Boolean(contract.mapping) ||
1052
+ Boolean(contract.inputShape) ||
1053
+ Boolean(contract.outputShape);
1054
+ return (_jsxs("section", { className: "anlyx-map-contract", children: [_jsx("h3", { children: "Data Contract" }), hasContract ? (_jsxs("div", { className: "anlyx-map-contract__body", children: [contract.endpoint ? (_jsx(ProjectArchitectureContractField, { label: "Endpoint", value: contract.endpoint })) : null, _jsx(ProjectArchitectureContractField, { label: "Input", value: formatProjectArchitectureContractName(contract.inputName, contract.inputKind) }), _jsx(ProjectArchitectureContractField, { label: "Output", value: formatProjectArchitectureContractName(contract.outputName, contract.outputKind) }), _jsx(ProjectArchitectureShapePreview, { shape: contract.outputShape ?? contract.inputShape }), contract.mapping ? (_jsx(ProjectArchitectureContractField, { label: "Mapping", value: contract.mapping })) : null, contract.transforms.length > 0 ? (_jsxs("div", { className: "anlyx-map-contract__group", children: [_jsx("span", { children: "Transforms" }), _jsx("ul", { children: contract.transforms.slice(0, 6).map((transform) => (_jsx("li", { children: transform }, transform))) })] })) : null, contract.relatedModels.length > 0 ? (_jsxs("div", { className: "anlyx-map-contract__group", children: [_jsx("span", { children: "Models" }), _jsx("ul", { children: contract.relatedModels.slice(0, 8).map((model) => (_jsx("li", { children: model }, model))) })] })) : null] })) : (_jsx("p", { className: "project-muted", children: "No contract authored." }))] }));
1055
+ }
1056
+ function ProjectArchitectureContractField({ label, value }) {
1057
+ return (_jsxs("div", { className: "anlyx-map-contract__field", children: [_jsx("span", { children: label }), _jsx("strong", { children: value ?? "No contract authored" })] }));
1058
+ }
1059
+ function ProjectArchitectureShapePreview({ shape }) {
1060
+ if (!shape) {
1061
+ return null;
1062
+ }
1063
+ const entries = Object.entries(shape).slice(0, 8);
1064
+ if (entries.length === 0) {
1065
+ return null;
1066
+ }
1067
+ return (_jsxs("div", { className: "anlyx-map-contract__shape", children: [_jsx("span", { children: "Shape" }), _jsxs("pre", { children: [entries.map(([key, value]) => `${key}: ${value}`).join("\n"), Object.keys(shape).length > entries.length ? "\n..." : ""] })] }));
572
1068
  }
573
- function ProjectArchitectureEdge({ data, sourcePosition, sourceX, sourceY, targetPosition, targetX, targetY }) {
574
- const [path] = getSmoothStepPath({
575
- borderRadius: 12,
576
- offset: 18,
577
- sourcePosition,
578
- sourceX,
579
- sourceY,
580
- targetPosition,
581
- targetX,
582
- targetY
583
- });
584
- const role = data?.edge.role ?? "primary";
585
- return (_jsx(BaseEdge, { className: `project-architecture-edge project-architecture-edge--${role}`, path: path }));
1069
+ function ProjectArchitectureInspectorList({ emptyLabel, nodes, ordered = false }) {
1070
+ const visibleNodes = nodes.filter((node) => Boolean(node));
1071
+ if (visibleNodes.length === 0) {
1072
+ return _jsx("p", { className: "project-muted", children: emptyLabel });
1073
+ }
1074
+ return (_jsx("ol", { className: ordered ? "anlyx-map-path-list" : "anlyx-map-node-list", children: visibleNodes.slice(0, 8).map((node) => (_jsxs("li", { children: [_jsx("strong", { children: node.displayLabel ?? node.label }), _jsx("span", { children: projectArchitectureKindLabel(node.kind) })] }, node.id))) }));
1075
+ }
1076
+ function projectArchitectureNodeContract(node) {
1077
+ const metadata = recordFromUnknown(node.metadata);
1078
+ const contractRoot = recordFromUnknown(metadata?.["contracts"]) ?? metadata;
1079
+ const input = contractItemFromUnknown(contractRoot?.["input"]);
1080
+ const output = contractItemFromUnknown(contractRoot?.["output"]);
1081
+ return {
1082
+ endpoint: metadataString(contractRoot?.["endpoint"]),
1083
+ inputName: input.name,
1084
+ inputKind: input.kind,
1085
+ inputShape: input.shape,
1086
+ outputName: output.name,
1087
+ outputKind: output.kind,
1088
+ outputShape: output.shape,
1089
+ relatedModels: getStringList(contractRoot?.["relatedModels"]).length > 0
1090
+ ? getStringList(contractRoot?.["relatedModels"])
1091
+ : getStringList(contractRoot?.["models"]),
1092
+ transforms: getStringList(contractRoot?.["transforms"]),
1093
+ mapping: metadataString(contractRoot?.["mapping"])
1094
+ };
1095
+ }
1096
+ function contractItemFromUnknown(value) {
1097
+ if (typeof value === "string") {
1098
+ return { name: value, kind: undefined, shape: undefined };
1099
+ }
1100
+ const record = recordFromUnknown(value);
1101
+ if (!record) {
1102
+ return { name: undefined, kind: undefined, shape: undefined };
1103
+ }
1104
+ return {
1105
+ name: metadataString(record["name"]),
1106
+ kind: metadataString(record["kind"]),
1107
+ shape: shapeFromUnknown(record["shape"])
1108
+ };
1109
+ }
1110
+ function shapeFromUnknown(value) {
1111
+ const record = recordFromUnknown(value);
1112
+ if (!record) {
1113
+ return undefined;
1114
+ }
1115
+ const entries = Object.entries(record)
1116
+ .map(([key, item]) => [key, typeof item === "string" ? item : JSON.stringify(item)])
1117
+ .filter((entry) => typeof entry[1] === "string");
1118
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
1119
+ }
1120
+ function recordFromUnknown(value) {
1121
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1122
+ return undefined;
1123
+ }
1124
+ return value;
1125
+ }
1126
+ function formatProjectArchitectureContractName(name, kind) {
1127
+ if (!name) {
1128
+ return undefined;
1129
+ }
1130
+ return kind ? `${name} (${kind})` : name;
1131
+ }
1132
+ function projectArchitectureEvidenceStatus(evidenceIds, evidenceById) {
1133
+ const statuses = evidenceIds
1134
+ .map((id) => evidenceById.get(id)?.status)
1135
+ .filter((status) => Boolean(status));
1136
+ if (statuses.includes("source-matched"))
1137
+ return "source-matched";
1138
+ if (statuses.includes("observed"))
1139
+ return "observed";
1140
+ if (statuses.includes("measured"))
1141
+ return "measured";
1142
+ if (statuses.includes("agent-inferred"))
1143
+ return "agent-inferred";
1144
+ if (statuses.includes("not-proven"))
1145
+ return "not-proven";
1146
+ return "unknown";
1147
+ }
1148
+ function projectArchitectureNodeSubtitle(node) {
1149
+ const module = metadataString(node.metadata?.["module"]);
1150
+ const path = metadataString(node.metadata?.["path"]);
1151
+ const detail = metadataString(node.metadata?.["detail"]);
1152
+ return module ?? path ?? detail ?? node.domain ?? node.id;
1153
+ }
1154
+ function formatProjectArchitectureSource(source) {
1155
+ if (!source)
1156
+ return undefined;
1157
+ const line = source.lineStart ? `:${source.lineStart}` : "";
1158
+ const symbol = source.symbol ? ` ${source.symbol}` : "";
1159
+ return `${source.filePath}${line}${symbol}`;
1160
+ }
1161
+ function projectArchitectureKindLabel(kind) {
1162
+ const labels = {
1163
+ frontend: "Page",
1164
+ request: "HTTP",
1165
+ api: "API",
1166
+ controller: "CTRL",
1167
+ handler: "Handler",
1168
+ middleware: "Middleware",
1169
+ service: "SRV",
1170
+ policy: "Policy",
1171
+ mapper: "Map",
1172
+ repository: "Repo",
1173
+ database: "Data",
1174
+ cache: "Cache",
1175
+ queue: "Queue",
1176
+ job: "Job",
1177
+ external: "External",
1178
+ result: "UI",
1179
+ unknown: "Unknown"
1180
+ };
1181
+ return labels[kind] ?? titleCase(kind);
586
1182
  }
587
1183
  function projectArchitectureNodeIcon(kind) {
588
1184
  if (kind === "database") {
@@ -605,24 +1201,47 @@ function projectArchitectureNodeIcon(kind) {
605
1201
  }
606
1202
  return Circle;
607
1203
  }
608
- function ProjectJsonView({ data, model, rawJson }) {
609
- const jsonFiles = useMemo(() => projectJsonFiles(data, model, rawJson), [data, model, rawJson]);
1204
+ function ProjectJsonView({ data, model, rawJson, validationReport }) {
1205
+ const jsonFiles = useMemo(() => projectJsonFiles(data, model, rawJson, validationReport), [data, model, rawJson, validationReport]);
610
1206
  const [selectedJsonFileId, setSelectedJsonFileId] = useState(jsonFiles[0]?.id ?? "project");
611
1207
  const selectedJsonFile = jsonFiles.find((file) => file.id === selectedJsonFileId) ?? jsonFiles[0];
612
1208
  const activeJson = selectedJsonFile?.content ?? rawJson;
613
1209
  const lines = activeJson.split("\n");
614
1210
  const inventory = useMemo(() => projectJsonInventoryItems(data, model), [data, model]);
615
1211
  const EditorFileIcon = selectedJsonFile?.icon ?? Code2;
1212
+ const validationTone = validationReport
1213
+ ? validationReport.valid
1214
+ ? validationReport.issues.length > 0
1215
+ ? "amber"
1216
+ : "green"
1217
+ : "red"
1218
+ : "green";
1219
+ const validationLabel = validationReport
1220
+ ? validationReport.valid
1221
+ ? validationReport.issues.length > 0
1222
+ ? "Valid with warnings"
1223
+ : "Valid"
1224
+ : "Invalid"
1225
+ : "Valid";
616
1226
  return (_jsxs("section", { className: "project-json-workspace", "aria-label": "Project JSON", children: [_jsxs("aside", { className: "project-json-outline", "aria-label": "JSON files", children: [_jsx("div", { className: "project-panel-title", children: _jsx("h2", { children: "JSON Files" }) }), _jsx("span", { className: "project-badge project-badge--green", children: "AI-authored" }), _jsx("div", { className: "project-json-file-list", role: "list", children: jsonFiles.map((file) => {
617
1227
  const Icon = file.icon;
618
1228
  return (_jsxs("button", { className: file.id === selectedJsonFile?.id ? "is-selected" : "", type: "button", onClick: () => setSelectedJsonFileId(file.id), children: [_jsx(Icon, { size: 17 }), _jsxs("span", { children: [_jsx("strong", { children: file.name }), _jsx("small", { children: file.description })] }), _jsx("em", { children: file.countLabel })] }, file.id));
619
- }) })] }), _jsxs("article", { className: "project-json-editor", "aria-label": "Raw project JSON", children: [_jsxs("header", { children: [_jsxs("span", { className: "project-json-file", children: [_jsx(EditorFileIcon, { size: 16 }), selectedJsonFile?.name ?? "anlyx.project.json"] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => void navigator.clipboard?.writeText(activeJson), children: [_jsx(Copy, { size: 15 }), "Copy JSON"] }), _jsx("button", { type: "button", onClick: () => downloadProjectJson(activeJson, selectedJsonFile?.name), children: _jsx(Download, { size: 15 }) })] })] }), _jsx("pre", { children: lines.map((line, index) => (_jsxs("span", { children: [_jsx("em", { children: index + 1 }), _jsx("code", { children: line || " " })] }, `${index}:${line}`))) })] }), _jsxs("aside", { className: "project-json-details", "aria-label": "JSON details", children: [_jsxs(JsonDetailsCard, { title: "Schema", children: [_jsx(ProjectDetailRow, { label: "Version", value: data.schemaVersion }), _jsx(ProjectDetailRow, { label: "Validation", value: "Valid", tone: "green" })] }), _jsxs(JsonDetailsCard, { title: "Project", children: [_jsx(ProjectDetailRow, { label: "Name", value: data.project.name }), _jsx(ProjectDetailRow, { label: "ID", value: data.project.id }), _jsx(ProjectDetailRow, { label: "Root", value: data.project.root ?? "not provided" })] }), _jsx(JsonDetailsCard, { title: "Counts", children: _jsx("div", { className: "project-json-counts", children: inventory.map((item) => (_jsx(JsonInventoryItem, { item: item }, item.id))) }) })] })] }));
1229
+ }) })] }), _jsxs("article", { className: "project-json-editor", "aria-label": "Raw project JSON", children: [_jsxs("header", { children: [_jsxs("span", { className: "project-json-file", children: [_jsx(EditorFileIcon, { size: 16 }), selectedJsonFile?.name ?? "anlyx.project.json"] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => void navigator.clipboard?.writeText(activeJson), children: [_jsx(Copy, { size: 15 }), "Copy JSON"] }), _jsx("button", { type: "button", onClick: () => downloadProjectJson(activeJson, selectedJsonFile?.name), children: _jsx(Download, { size: 15 }) })] })] }), _jsx("pre", { children: lines.map((line, index) => (_jsxs("span", { children: [_jsx("em", { children: index + 1 }), _jsx("code", { children: line || " " })] }, `${index}:${line}`))) })] }), _jsxs("aside", { className: "project-json-details", "aria-label": "JSON details", children: [_jsxs(JsonDetailsCard, { title: "Schema", children: [_jsx(ProjectDetailRow, { label: "Version", value: data.schemaVersion }), _jsx(ProjectDetailRow, { label: "Validation", value: validationLabel, tone: validationTone })] }), validationReport ? (_jsxs(JsonDetailsCard, { title: "Trust checks", children: [_jsx(ProjectDetailRow, { label: "Source issues", value: String(validationReport.summary.sourceIssueCount), tone: validationReport.summary.sourceIssueCount > 0 ? "amber" : "green" }), validationReport.summary.sourceIssueBreakdown ? (_jsxs(_Fragment, { children: [_jsx(ProjectDetailRow, { label: "Missing files", value: String(validationReport.summary.sourceIssueBreakdown.missingFiles), tone: validationReport.summary.sourceIssueBreakdown.missingFiles > 0
1230
+ ? "amber"
1231
+ : "green" }), _jsx(ProjectDetailRow, { label: "Line issues", value: String(validationReport.summary.sourceIssueBreakdown.placeholderLines +
1232
+ validationReport.summary.sourceIssueBreakdown.outOfRangeLines), tone: validationReport.summary.sourceIssueBreakdown.placeholderLines +
1233
+ validationReport.summary.sourceIssueBreakdown.outOfRangeLines >
1234
+ 0
1235
+ ? "amber"
1236
+ : "green" }), _jsx(ProjectDetailRow, { label: "Symbol issues", value: String(validationReport.summary.sourceIssueBreakdown.missingSymbols), tone: validationReport.summary.sourceIssueBreakdown.missingSymbols > 0
1237
+ ? "amber"
1238
+ : "green" })] })) : null, _jsx(ProjectDetailRow, { label: "Coverage", value: validationReport.summary.coverageStatus, tone: validationReport.summary.coverageStatus === "partial" ? "amber" : "green" }), _jsx(ProjectDetailRow, { label: "Issues", value: String(validationReport.issues.length), tone: validationReport.issues.length > 0 ? "amber" : "green" })] })) : null, data.coverage || validationReport ? (_jsxs(JsonDetailsCard, { title: "Coverage", children: [_jsx(ProjectDetailRow, { label: "Pages", value: coverageValue(validationReport?.summary.modeled.pages ?? data.pages.length, validationReport?.summary.detected?.pages ?? data.coverage?.detected?.pages) }), _jsx(ProjectDetailRow, { label: "Requests", value: coverageValue(validationReport?.summary.modeled.requests ?? data.requests.length, data.coverage?.detected?.requests ?? data.coverage?.detected?.backendEndpoints) }), _jsx(ProjectDetailRow, { label: "Flows", value: coverageValue(validationReport?.summary.modeled.flows ?? data.flows.length, validationReport?.summary.detected?.flows ?? data.coverage?.detected?.flows) })] })) : null, _jsxs(JsonDetailsCard, { title: "Project", children: [_jsx(ProjectDetailRow, { label: "Name", value: data.project.name }), _jsx(ProjectDetailRow, { label: "ID", value: data.project.id }), _jsx(ProjectDetailRow, { label: "Root", value: data.project.root ?? "not provided" })] }), _jsx(JsonDetailsCard, { title: "Counts", children: _jsx("div", { className: "project-json-counts", children: inventory.map((item) => (_jsx(JsonInventoryItem, { item: item }, item.id))) }) })] })] }));
620
1239
  }
621
1240
  function JsonInventoryItem({ item }) {
622
1241
  const Icon = item.icon;
623
1242
  return (_jsxs("a", { className: "project-json-count-item", href: `#json-${item.id}`, children: [_jsx(Icon, { size: 16 }), _jsx("span", { children: item.label }), _jsx("strong", { children: item.value })] }));
624
1243
  }
625
- function projectJsonFiles(data, model, rawJson) {
1244
+ function projectJsonFiles(data, model, rawJson, validationReport) {
626
1245
  const files = [
627
1246
  {
628
1247
  id: "project",
@@ -664,6 +1283,22 @@ function projectJsonFiles(data, model, rawJson) {
664
1283
  content: projectJsonString(data.features),
665
1284
  icon: FileText
666
1285
  },
1286
+ {
1287
+ id: "overview",
1288
+ name: ".anlyx/project/overview.json",
1289
+ description: "Human project summary",
1290
+ countLabel: data.overview.summary ? "authored" : "empty",
1291
+ content: projectJsonString(data.overview),
1292
+ icon: BookOpen
1293
+ },
1294
+ {
1295
+ id: "capabilities",
1296
+ name: ".anlyx/project/capabilities.json",
1297
+ description: "Readable product capabilities",
1298
+ countLabel: String(data.capabilities.length),
1299
+ content: projectJsonString(data.capabilities),
1300
+ icon: Workflow
1301
+ },
667
1302
  {
668
1303
  id: "requests",
669
1304
  name: ".anlyx/project/requests.json",
@@ -696,6 +1331,30 @@ function projectJsonFiles(data, model, rawJson) {
696
1331
  content: projectJsonString(data.evidence),
697
1332
  icon: ShieldCheck
698
1333
  },
1334
+ {
1335
+ id: "data-lifecycles",
1336
+ name: ".anlyx/project/data-lifecycles.json",
1337
+ description: "Core data lifecycle maps",
1338
+ countLabel: String(data.dataLifecycles.length),
1339
+ content: projectJsonString(data.dataLifecycles),
1340
+ icon: Database
1341
+ },
1342
+ {
1343
+ id: "impact-maps",
1344
+ name: ".anlyx/project/impact-maps.json",
1345
+ description: "Product impact maps",
1346
+ countLabel: String(data.impactMaps.length),
1347
+ content: projectJsonString(data.impactMaps),
1348
+ icon: Network
1349
+ },
1350
+ {
1351
+ id: "coverage",
1352
+ name: ".anlyx/project/coverage.json",
1353
+ description: "Detected and modeled coverage",
1354
+ countLabel: data.coverage?.status ?? "unknown",
1355
+ content: projectJsonString(data.coverage ?? { status: "unknown" }),
1356
+ icon: Gauge
1357
+ },
699
1358
  {
700
1359
  id: "dictionary",
701
1360
  name: ".anlyx/project/dictionary.json",
@@ -715,8 +1374,33 @@ function projectJsonFiles(data, model, rawJson) {
715
1374
  icon: Clock3
716
1375
  });
717
1376
  }
1377
+ if (validationReport) {
1378
+ files.push({
1379
+ id: "validation-report",
1380
+ name: ".anlyx/validation-report.json",
1381
+ description: "Source and coverage validation",
1382
+ countLabel: `${validationReport.issues.length} issues`,
1383
+ content: projectJsonString(validationReport),
1384
+ icon: ShieldCheck
1385
+ });
1386
+ }
718
1387
  return files;
719
1388
  }
1389
+ function coverageValue(modeled, detected) {
1390
+ return detected === undefined ? String(modeled) : `${modeled} / ${detected}`;
1391
+ }
1392
+ function formatSourceIssueDetails(breakdown) {
1393
+ if (!breakdown) {
1394
+ return "";
1395
+ }
1396
+ const lineIssues = breakdown.placeholderLines + breakdown.outOfRangeLines;
1397
+ const parts = [
1398
+ breakdown.missingFiles > 0 ? `${breakdown.missingFiles} missing file` : "",
1399
+ breakdown.missingSymbols > 0 ? `${breakdown.missingSymbols} symbol` : "",
1400
+ lineIssues > 0 ? `${lineIssues} line` : ""
1401
+ ].filter(Boolean);
1402
+ return parts.join(" / ");
1403
+ }
720
1404
  function projectJsonInventoryItems(data, model) {
721
1405
  return [
722
1406
  { id: "schemaVersion", label: "schemaVersion", value: data.schemaVersion, icon: Braces },
@@ -724,6 +1408,18 @@ function projectJsonInventoryItems(data, model) {
724
1408
  { id: "areas", label: "areas", value: String(model.totals.areas), icon: BriefcaseBusiness },
725
1409
  { id: "pages", label: "pages", value: String(model.totals.pages), icon: BookOpen },
726
1410
  { id: "features", label: "features", value: String(model.totals.features), icon: FileText },
1411
+ {
1412
+ id: "overview",
1413
+ label: "overview",
1414
+ value: data.overview.summary ? "authored" : "empty",
1415
+ icon: BookOpen
1416
+ },
1417
+ {
1418
+ id: "capabilities",
1419
+ label: "capabilities",
1420
+ value: String(data.capabilities.length),
1421
+ icon: Workflow
1422
+ },
727
1423
  { id: "requests", label: "requests", value: String(model.totals.requests), icon: Network },
728
1424
  { id: "flows", label: "flows", value: String(model.totals.flows), icon: Workflow },
729
1425
  {
@@ -733,6 +1429,18 @@ function projectJsonInventoryItems(data, model) {
733
1429
  icon: Layers3
734
1430
  },
735
1431
  { id: "evidence", label: "evidence", value: String(model.totals.evidence), icon: ShieldCheck },
1432
+ {
1433
+ id: "dataLifecycles",
1434
+ label: "dataLifecycles",
1435
+ value: String(data.dataLifecycles.length),
1436
+ icon: Database
1437
+ },
1438
+ {
1439
+ id: "impactMaps",
1440
+ label: "impactMaps",
1441
+ value: String(data.impactMaps.length),
1442
+ icon: Network
1443
+ },
736
1444
  {
737
1445
  id: "measurements",
738
1446
  label: "measurements",
@@ -754,12 +1462,27 @@ function JsonDetailsCard({ children, title }) {
754
1462
  return (_jsxs("section", { className: "project-json-details-card", children: [_jsx("h2", { children: title }), children] }));
755
1463
  }
756
1464
  function ProjectDetailRow({ label, tone, value }) {
757
- return (_jsxs("div", { className: "project-detail-row", children: [_jsx("dt", { children: label }), _jsx("dd", { className: tone === "green" ? "is-green" : "", children: value })] }));
1465
+ return (_jsxs("div", { className: "project-detail-row", children: [_jsx("dt", { children: label }), _jsx("dd", { className: tone ? `is-${tone}` : "", children: value })] }));
758
1466
  }
759
- function ProjectStatusBar({ locale, model }) {
1467
+ function ProjectStatusBar({ data, locale, model, validationReport }) {
760
1468
  const sourceFile = projectSourceFile(model);
761
1469
  const generatedBy = projectAgentName(model);
762
- return (_jsxs("div", { className: "project-statusbar", "aria-label": "Project source status", children: [_jsxs("div", { className: "project-statusbar__provenance", children: [_jsxs("span", { children: [_jsx(Code2, { size: 15 }), tp(locale, "source"), ": ", _jsx("strong", { children: sourceFile })] }), _jsxs("span", { children: [_jsx(Zap, { size: 15 }), tp(locale, "agent"), ": ", _jsx("strong", { children: generatedBy })] })] }), _jsxs("div", { className: "project-statusbar__summary", children: [_jsxs("strong", { children: [model.totals.pages, " ", tp(locale, "pagesAnalyzed")] }), _jsxs("span", { children: [_jsx(Clock3, { size: 15 }), tp(locale, "lastAnalysis"), ": ", formatDateTime(model.project.analyzedAt)] }), _jsxs("span", { className: "project-status project-status--green", children: [_jsx(Check, { size: 15 }), tp(locale, "upToDate")] })] })] }));
1470
+ const pageCoverage = projectPageCoverageSummary(data, validationReport);
1471
+ const coverageStatus = validationReport?.summary.coverageStatus ?? data.coverage?.status;
1472
+ const sourceIssueCount = validationReport?.summary.sourceIssueCount ?? 0;
1473
+ const hasTrustWarning = coverageStatus === "partial" || sourceIssueCount > 0;
1474
+ return (_jsxs("div", { className: "project-statusbar", "aria-label": "Project source status", children: [_jsxs("div", { className: "project-statusbar__provenance", children: [_jsxs("span", { children: [_jsx(Code2, { size: 15 }), tp(locale, "source"), ": ", _jsx("strong", { children: sourceFile })] }), _jsxs("span", { children: [_jsx(Zap, { size: 15 }), tp(locale, "agent"), ": ", _jsx("strong", { children: generatedBy })] })] }), _jsxs("div", { className: "project-statusbar__summary", children: [_jsx("strong", { children: pageCoverage.label }), coverageStatus ? (_jsxs("span", { className: `project-status ${hasTrustWarning ? "project-status--amber" : "project-status--green"}`, children: [_jsx(ShieldCheck, { size: 15 }), coverageStatus === "partial" ? "Partial analysis" : "Coverage checked"] })) : null, _jsxs("span", { children: [_jsx(Clock3, { size: 15 }), tp(locale, "lastAnalysis"), ": ", formatDateTime(model.project.analyzedAt)] }), _jsxs("span", { className: "project-status project-status--green", children: [_jsx(Check, { size: 15 }), tp(locale, "upToDate")] })] })] }));
1475
+ }
1476
+ function projectPageCoverageSummary(data, validationReport) {
1477
+ const modeled = validationReport?.summary.modeled.pages ?? data.coverage?.modeled?.pages ?? data.pages.length;
1478
+ const detected = validationReport?.summary.detected?.pages ?? data.coverage?.detected?.pages;
1479
+ const value = coverageValue(modeled, detected);
1480
+ return {
1481
+ detected,
1482
+ label: detected !== undefined ? `${value} pages modeled` : `${modeled} pages analyzed`,
1483
+ modeled,
1484
+ value
1485
+ };
763
1486
  }
764
1487
  function evidenceStatusLabel(status) {
765
1488
  if (status === "source-matched")