@skyhook-io/radar-app 1.14.0 → 1.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/package.json +2 -2
  2. package/src/App.tsx +6 -4
  3. package/src/api/client.ts +3 -0
  4. package/src/api/diagnose.ts +31 -1
  5. package/src/components/CloudConnectFlow.tsx +173 -13
  6. package/src/components/CloudFunnelButton.tsx +4 -2
  7. package/src/components/ConnectionErrorView.tsx +9 -10
  8. package/src/components/DebugOverlay.tsx +10 -6
  9. package/src/components/cloudConnectHandoff.test.ts +109 -4
  10. package/src/components/cloudConnectHandoff.ts +154 -2
  11. package/src/components/curl/ServiceCurlButton.tsx +19 -26
  12. package/src/components/diagnose/AgentCase.tsx +18 -5
  13. package/src/components/diagnose/AnalysisStory.test.tsx +308 -0
  14. package/src/components/diagnose/AnalysisStory.tsx +564 -0
  15. package/src/components/diagnose/DiagnoseContext.test.ts +10 -0
  16. package/src/components/diagnose/DiagnoseContext.tsx +25 -8
  17. package/src/components/diagnose/DiagnoseSurface.tsx +20 -14
  18. package/src/components/diagnose/InvestigationEvidencePane.story.test.tsx +771 -0
  19. package/src/components/diagnose/InvestigationEvidencePane.test.tsx +8 -33
  20. package/src/components/diagnose/InvestigationEvidencePane.tsx +809 -168
  21. package/src/components/diagnose/InvestigationResourceEvidence.test.tsx +30 -0
  22. package/src/components/diagnose/InvestigationResourceEvidence.tsx +20 -0
  23. package/src/components/diagnose/InvestigationView.tsx +493 -529
  24. package/src/components/diagnose/investigationCase.test.tsx +267 -129
  25. package/src/components/diagnose/investigationCase.ts +122 -77
  26. package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +27 -0
  27. package/src/components/diagnose/investigationEvidence/adapters/resource.ts +28 -0
  28. package/src/components/diagnose/investigationEvidence/builder.ts +11 -2
  29. package/src/components/diagnose/investigationEvidence/identity.test.ts +25 -7
  30. package/src/components/diagnose/investigationEvidence/identity.ts +4 -4
  31. package/src/components/diagnose/investigationEvidence/observations.ts +24 -0
  32. package/src/components/diagnose/investigationEvidence/projection.test.ts +36 -3
  33. package/src/components/diagnose/investigationEvidence/types.ts +2 -0
  34. package/src/components/diagnose/investigationEvidencePresentation.test.ts +2 -0
  35. package/src/components/diagnose/investigationEvidencePresentation.ts +3 -0
  36. package/src/components/diagnose/investigationState.test.ts +316 -58
  37. package/src/components/diagnose/investigationState.ts +257 -44
  38. package/src/components/diagnose/investigationStory.test.ts +161 -0
  39. package/src/components/diagnose/investigationStory.ts +207 -0
  40. package/src/components/diagnose/parts.test.tsx +9 -6
  41. package/src/components/diagnose/parts.tsx +977 -193
  42. package/src/components/diagnose/storyParts.test.tsx +426 -0
  43. package/src/components/diagnose/toolCallLabel.test.ts +51 -0
  44. package/src/components/diagnose/toolCallLabel.ts +135 -0
  45. package/src/components/diagnose/useDisclosureReveal.ts +10 -11
  46. package/src/components/helm/HelmCompareRoute.tsx +18 -4
  47. package/src/components/helm/HelmReleaseDrawer.tsx +11 -26
  48. package/src/components/helm/InstallWizard.tsx +8 -3
  49. package/src/components/home/MCPSetupDialog.tsx +15 -9
  50. package/src/components/portforward/PortForwardManager.tsx +6 -13
  51. package/src/components/resource/PrometheusChartsGrid.tsx +3 -3
  52. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +3 -3
  53. package/src/components/resource/WorkloadMetricsSection.tsx +15 -21
  54. package/src/components/resources/PodFilePreview.tsx +3 -3
  55. package/src/components/resources/renderers/ServiceRenderer.tsx +2 -1
  56. package/src/components/settings/SettingsDialog.tsx +4 -2
  57. package/src/components/ui/CommandPalette.tsx +10 -6
  58. package/src/components/ui/DiagnosticsOverlay.tsx +10 -6
  59. package/src/components/ui/Disclosure.tsx +47 -0
  60. package/src/components/ui/Markdown.tsx +23 -11
  61. package/src/components/ui/ShortcutHelpOverlay.tsx +3 -1
  62. package/src/components/ui/UpdateNotification.tsx +18 -2
  63. package/src/index.css +19 -6
@@ -0,0 +1,47 @@
1
+ import { useState, type ReactNode } from 'react'
2
+ import { clsx } from 'clsx'
3
+ import { Collapse, CollapseChevron, useDisclosure } from '@skyhook-io/k8s-ui/components/ui/Collapse'
4
+
5
+ // In-flow disclosure with the app-standard motion — the replacement for native
6
+ // <details>/<summary> on primary paths. Native details snaps open with no
7
+ // transition and its marker ignores the caret language; this pairs a button
8
+ // header (aria-expanded / aria-controls via useDisclosure) with CollapseChevron
9
+ // and an animated Collapse panel. Content stays mounted while closed, exactly
10
+ // like native details, so server-rendered markup and in-page search still see
11
+ // the collapsed copy.
12
+ export function Disclosure({
13
+ summary,
14
+ children,
15
+ className,
16
+ summaryClassName,
17
+ chevronClassName = 'h-3.5 w-3.5',
18
+ defaultOpen = false,
19
+ }: {
20
+ summary: ReactNode
21
+ children: ReactNode
22
+ /** Wrapper (what the old <details> carried). */
23
+ className?: string
24
+ /** Header button (what the old <summary> carried). */
25
+ summaryClassName?: string
26
+ chevronClassName?: string
27
+ defaultOpen?: boolean
28
+ }) {
29
+ const [open, setOpen] = useState(defaultOpen)
30
+ const disclosure = useDisclosure(open)
31
+ return (
32
+ <div className={className}>
33
+ <button
34
+ type="button"
35
+ {...disclosure.buttonProps}
36
+ onClick={() => setOpen((value) => !value)}
37
+ className={clsx('flex w-full cursor-pointer items-center gap-1.5 text-left', summaryClassName)}
38
+ >
39
+ <CollapseChevron open={open} className={chevronClassName} />
40
+ {summary}
41
+ </button>
42
+ <Collapse open={open} id={disclosure.panelId}>
43
+ {children}
44
+ </Collapse>
45
+ </div>
46
+ )
47
+ }
@@ -2,12 +2,20 @@ import ReactMarkdown from 'react-markdown'
2
2
  import remarkGfm from 'remark-gfm'
3
3
  import { clsx } from 'clsx'
4
4
 
5
+ import type { ReactNode } from 'react'
6
+
5
7
  interface MarkdownProps {
6
8
  children: string
7
9
  className?: string
10
+ /**
11
+ * Intercepts links before the default anchor renders. Return null to fall
12
+ * through. Lets a caller turn a reserved href (an in-page evidence
13
+ * reference, say) into a control instead of an external link.
14
+ */
15
+ linkRenderer?: (href: string | undefined, children: ReactNode) => ReactNode | null
8
16
  }
9
17
 
10
- export function Markdown({ children, className }: MarkdownProps) {
18
+ export function Markdown({ children, className, linkRenderer }: MarkdownProps) {
11
19
  return (
12
20
  <div className={clsx('markdown-content', className)}>
13
21
  <ReactMarkdown
@@ -28,16 +36,20 @@ export function Markdown({ children, className }: MarkdownProps) {
28
36
  p: ({ children }) => (
29
37
  <p className="text-theme-text-secondary my-2 leading-relaxed">{children}</p>
30
38
  ),
31
- a: ({ href, children }) => (
32
- <a
33
- href={href}
34
- target="_blank"
35
- rel="noopener noreferrer"
36
- className="text-blue-400 hover:text-blue-300 underline underline-offset-2"
37
- >
38
- {children}
39
- </a>
40
- ),
39
+ a: ({ href, children }) => {
40
+ const custom = linkRenderer?.(href, children)
41
+ if (custom !== null && custom !== undefined) return <>{custom}</>
42
+ return (
43
+ <a
44
+ href={href}
45
+ target="_blank"
46
+ rel="noopener noreferrer"
47
+ className="text-blue-400 hover:text-blue-300 underline underline-offset-2"
48
+ >
49
+ {children}
50
+ </a>
51
+ )
52
+ },
41
53
  ul: ({ children }) => (
42
54
  <ul className="list-disc list-outside pl-4 my-2 space-y-1 text-theme-text-secondary">{children}</ul>
43
55
  ),
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useRef } from 'react'
2
2
  import { X } from 'lucide-react'
3
3
  import { clsx } from 'clsx'
4
- import { TRANSITION_BACKDROP, TRANSITION_PANEL } from '../../utils/animation'
4
+ import { TRANSITION_BACKDROP, TRANSITION_PANEL, overlayTransitionStyle } from '../../utils/animation'
5
5
  import { useActiveShortcuts, type ShortcutCategory } from '../../hooks/useKeyboardShortcuts'
6
6
 
7
7
  interface ShortcutHelpOverlayProps {
@@ -232,6 +232,7 @@ export function ShortcutHelpOverlay({ onClose, currentView, isOpen = true }: Sho
232
232
  TRANSITION_BACKDROP,
233
233
  isOpen ? 'opacity-100' : 'opacity-0'
234
234
  )}
235
+ style={overlayTransitionStyle(isOpen, 'dialog')}
235
236
  onClick={onClose}
236
237
  />
237
238
 
@@ -243,6 +244,7 @@ export function ShortcutHelpOverlay({ onClose, currentView, isOpen = true }: Sho
243
244
  TRANSITION_PANEL,
244
245
  isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-[0.97]'
245
246
  )}
247
+ style={overlayTransitionStyle(isOpen, 'dialog')}
246
248
  >
247
249
  {/* Header */}
248
250
  <div className="flex items-center justify-between px-5 py-3.5 border-b border-theme-border">
@@ -1,4 +1,5 @@
1
1
  import { useState, useEffect } from 'react'
2
+ import { clsx } from 'clsx'
2
3
  import { Download, X, Copy, Check, RotateCw, ArrowDownToLine, Loader2 } from 'lucide-react'
3
4
  import { useQueryClient } from '@tanstack/react-query'
4
5
  import {
@@ -10,6 +11,8 @@ import {
10
11
  } from '../../api/client'
11
12
  import type { DesktopUpdateState } from '../../api/client'
12
13
  import { WithTooltip } from './Tooltip'
14
+ import { TRANSITION_MENU, overlayExitMs, overlayTransitionStyle } from '../../utils/animation'
15
+ import { useAnimatedUnmount } from '../../hooks/useAnimatedUnmount'
13
16
 
14
17
  const DISMISSED_KEY = 'radar-update-dismissed'
15
18
 
@@ -103,7 +106,11 @@ export function UpdateNotification() {
103
106
 
104
107
  // Shared in-cluster viewers get a persistent Home notice instead of a
105
108
  // floating action prompt they may not be able to act on.
106
- if (!versionInfo?.updateAvailable || dismissed || deploymentMode === undefined || deploymentMode === 'in-cluster' || deploymentMode === 'cloud') {
109
+ const show = !!versionInfo?.updateAvailable && !dismissed && deploymentMode !== undefined && deploymentMode !== 'in-cluster' && deploymentMode !== 'cloud'
110
+ // Presence outlives `show` by the menu exit so a dismiss fades the chip out
111
+ // instead of snapping it away; the enter runs the same transition in reverse.
112
+ const { shouldRender, isOpen } = useAnimatedUnmount(show, overlayExitMs('menu'))
113
+ if (!shouldRender || !versionInfo) {
107
114
  return null
108
115
  }
109
116
 
@@ -111,7 +118,16 @@ export function UpdateNotification() {
111
118
  const effectiveState: DesktopUpdateState = updateStatus?.state ?? 'idle'
112
119
 
113
120
  return (
114
- <div className="fixed bottom-4 right-4 z-50 max-w-sm bg-theme-surface border border-accent/50 rounded-lg shadow-xl p-4 animate-in slide-in-from-right">
121
+ <div
122
+ inert={!show || undefined}
123
+ className={clsx(
124
+ 'fixed bottom-4 right-4 z-50 max-w-sm bg-theme-surface border border-accent/50 rounded-lg shadow-xl p-4 origin-bottom-right',
125
+ TRANSITION_MENU,
126
+ isOpen ? 'opacity-100 translate-y-0 scale-100' : 'opacity-0 translate-y-1 scale-[0.97]',
127
+ !show && 'pointer-events-none',
128
+ )}
129
+ style={overlayTransitionStyle(isOpen, 'menu')}
130
+ >
115
131
  <div className="flex items-start gap-3">
116
132
  <div className="flex items-center justify-center w-8 h-8 bg-accent-muted rounded-full shrink-0">
117
133
  <UpdateIcon state={effectiveState} />
package/src/index.css CHANGED
@@ -344,10 +344,26 @@ body {
344
344
  animation: status-enter 140ms ease-out both;
345
345
  }
346
346
 
347
+ /* Reduced motion is an app-level policy, not a per-class list. Durations and
348
+ delays go to ~0 (a delay left in place would hold a fill-mode:both element
349
+ invisible), every loop runs once (an infinite animation at 0.01ms is still an
350
+ active animation — pulse, ping, Monaco's progress bit), and status spinners
351
+ keep turning because they convey state, not motion. Components that coordinate motion in
352
+ JS (presence hooks, drawer morph) check prefersReducedMotion() themselves and
353
+ dispose at logical close. */
347
354
  @media (prefers-reduced-motion: reduce) {
348
- .animate-transcript-enter,
349
- .animate-status-enter {
350
- animation: none;
355
+ *, *::before, *::after {
356
+ animation-duration: 0.01ms !important;
357
+ animation-delay: 0ms !important;
358
+ animation-iteration-count: 1 !important;
359
+ transition-duration: 0.01ms !important;
360
+ transition-delay: 0ms !important;
361
+ scroll-behavior: auto !important;
362
+ }
363
+ /* The one exception: a spinner that stops reads as "done", not "calm". */
364
+ .animate-spin {
365
+ animation-duration: 1s !important;
366
+ animation-iteration-count: infinite !important;
351
367
  }
352
368
  }
353
369
 
@@ -403,9 +419,6 @@ body {
403
419
  -webkit-text-fill-color: currentColor;
404
420
  color: var(--text-secondary);
405
421
  }
406
- .animate-result-in {
407
- animation: none;
408
- }
409
422
  }
410
423
 
411
424
  /* Gentle colorize when syntax-highlighted code swaps in over plain text — opacity