@checkstack/ui 1.2.1 → 1.3.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @checkstack/ui
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 765b764: Optimize AmbientBackground performance by replacing thousand-div grid with a single-element CSS mask and hardware-accelerated Aurora Mesh animations.
8
+
9
+ ## 1.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 26d8bae: Distributed satellite health checks and Assignment IDE page
14
+
15
+ **Satellite System**
16
+
17
+ - New `satellite-backend`, `satellite-common`, `satellite-frontend`, and `satellite` agent packages for distributed health check execution
18
+ - WebSocket-based satellite connectivity with authentication, heartbeats, and live configuration push
19
+ - Satellite management UI with create dialog, status badges, and list page
20
+
21
+ **Live Configuration Updates**
22
+
23
+ - Added `assignmentChanged` hook to `healthcheck-backend` for cross-plugin communication
24
+ - `satellite-backend` subscribes to assignment changes and pushes config updates to connected satellites in real-time
25
+
26
+ **Assignment IDE Page**
27
+
28
+ - Replaced the 1028-line modal-based `SystemHealthCheckAssignment` component with a full-page IDE layout
29
+ - New modular components: `AssignmentTree`, `GeneralPanel`, `ThresholdsPanel`, `RetentionPanel`, `ExecutionPanel`
30
+ - Added unassign capability and sorted assignment lists for stable ordering
31
+
32
+ **Shared IDE Primitives**
33
+
34
+ - Extracted `IDETreeNode`, `IDETreeSection`, `IDEStatusBar`, `IDELayout` to `@checkstack/ui` for cross-plugin reuse
35
+ - Migrated existing health check IDE editor to use shared primitives
36
+
37
+ **Infrastructure**
38
+
39
+ - Added `Dockerfile.satellite` for containerized satellite deployment
40
+ - WebSocket route registry in `@checkstack/backend` and `@checkstack/backend-api`
41
+
3
42
  ## 1.2.1
4
43
 
5
44
  ### Patch Changes
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@checkstack/ui",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "dependencies": {
7
- "@checkstack/common": "0.6.4",
8
- "@checkstack/frontend-api": "0.3.8",
7
+ "@checkstack/common": "0.6.5",
8
+ "@checkstack/frontend-api": "0.3.9",
9
9
  "@monaco-editor/react": "^4.7.0",
10
10
  "@radix-ui/react-accordion": "^1.2.12",
11
11
  "@radix-ui/react-dialog": "^1.1.15",
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useEffect, useState } from "react";
1
+ import React from "react";
2
2
  import { cn } from "../utils";
3
3
 
4
4
  interface AmbientBackgroundProps {
@@ -6,53 +6,15 @@ interface AmbientBackgroundProps {
6
6
  className?: string;
7
7
  }
8
8
 
9
- const TILE_SIZE = 48;
10
-
11
9
  /**
12
- * AmbientBackground - Animated checkerboard pattern
13
- * Features a chess-inspired grid where random tiles glow with the primary color.
14
- * Dynamically adapts to screen size.
10
+ * AmbientBackground - High-performance background pattern
11
+ * Features an "Inverse Glow Grid" where aurora effects shine through transparent grid lines.
12
+ * Provides a premium "glowing grid" aesthetic with minimal performance impact.
15
13
  */
16
14
  export const AmbientBackground: React.FC<AmbientBackgroundProps> = ({
17
15
  children,
18
16
  className,
19
17
  }) => {
20
- const [dimensions, setDimensions] = useState({ cols: 40, rows: 25 });
21
-
22
- // Calculate grid size based on viewport
23
- useEffect(() => {
24
- const updateDimensions = () => {
25
- const cols = Math.ceil(globalThis.innerWidth / TILE_SIZE) + 2;
26
- const rows = Math.ceil(globalThis.innerHeight / TILE_SIZE) + 2;
27
- setDimensions({ cols, rows });
28
- };
29
-
30
- updateDimensions();
31
- globalThis.addEventListener("resize", updateDimensions);
32
- return () => globalThis.removeEventListener("resize", updateDimensions);
33
- }, []);
34
-
35
- // Generate tile grid with staggered animation delays
36
- const tiles = useMemo(() => {
37
- const { cols, rows } = dimensions;
38
- const result: Array<{ key: string; delay: number; isLight: boolean }> = [];
39
-
40
- for (let row = 0; row < rows; row++) {
41
- for (let col = 0; col < cols; col++) {
42
- const isLight = (row + col) % 2 === 0;
43
- // Negative delay so tiles start at different points in their 12s cycle
44
- // This creates a gentle, staggered breathing effect across the grid
45
- const delay = -((row * 7 + col * 13 + row * col) % 24) * 0.5;
46
- result.push({
47
- key: `${row}-${col}`,
48
- delay,
49
- isLight,
50
- });
51
- }
52
- }
53
- return result;
54
- }, [dimensions]);
55
-
56
18
  return (
57
19
  <div
58
20
  className={cn(
@@ -60,40 +22,49 @@ export const AmbientBackground: React.FC<AmbientBackgroundProps> = ({
60
22
  className
61
23
  )}
62
24
  >
63
- {/* Checkerboard grid - covers full viewport */}
64
- <div
65
- className="pointer-events-none fixed inset-0 overflow-hidden"
66
- style={{
67
- display: "grid",
68
- gridTemplateColumns: `repeat(${dimensions.cols}, ${TILE_SIZE}px)`,
69
- gridTemplateRows: `repeat(${dimensions.rows}, ${TILE_SIZE}px)`,
70
- }}
71
- >
72
- {tiles.map(({ key, delay, isLight }) => (
73
- <div
74
- key={key}
75
- className="tile-glow"
76
- style={
77
- {
78
- width: TILE_SIZE,
79
- height: TILE_SIZE,
80
- backgroundColor: isLight
81
- ? "hsl(var(--muted-foreground) / 0.12)"
82
- : "hsl(var(--muted-foreground) / 0.04)",
83
- "--glow-delay": `${delay}s`,
84
- } as React.CSSProperties
85
- }
86
- />
87
- ))}
25
+ {/* Performance optimized background layers */}
26
+ <div className="pointer-events-none fixed inset-0 overflow-hidden">
27
+ {/* Layer 1: Aurora Blobs (Bottom) - Hardware accelerated via transform/compositor */}
28
+ <div
29
+ className="aurora-blob absolute w-[50%] h-[50%] -top-[10%] -left-[10%]"
30
+ style={{
31
+ background: "radial-gradient(circle at center, hsl(var(--primary) / 0.8), transparent 60%)",
32
+ animation: "aurora-float-1 25s ease-in-out infinite",
33
+ }}
34
+ />
35
+ <div
36
+ className="aurora-blob absolute w-[40%] h-[40%] bottom-[10%] right-[10%]"
37
+ style={{
38
+ background: "radial-gradient(circle at center, hsl(var(--chart-2) / 0.7), transparent 60%)",
39
+ animation: "aurora-float-2 20s ease-in-out infinite",
40
+ }}
41
+ />
42
+ <div
43
+ className="aurora-blob absolute w-[35%] h-[35%] top-[30%] left-[40%]"
44
+ style={{
45
+ background: "radial-gradient(circle at center, hsl(var(--primary) / 0.6), transparent 60%)",
46
+ animation: "aurora-float-3 30s ease-in-out infinite",
47
+ }}
48
+ />
49
+ <div
50
+ className="aurora-blob absolute w-[45%] h-[45%] bottom-[20%] left-[10%]"
51
+ style={{
52
+ background: "radial-gradient(circle at center, hsl(var(--chart-1) / 0.5), transparent 60%)",
53
+ animation: "aurora-float-4 35s ease-in-out infinite",
54
+ }}
55
+ />
56
+
57
+ {/* Layer 2: Inverse Grid Mask (Top) - Transparent lines on a solid background */}
58
+ <div className="ambient-grid-inverse absolute inset-0 overflow-hidden" />
88
59
  </div>
89
60
 
90
- {/* Edge vignette to fade out the grid smoothly */}
61
+ {/* Layer 3: Edge vignette to fade out the background smoothly */}
91
62
  <div
92
63
  className="pointer-events-none fixed inset-0"
93
64
  style={{
94
65
  background: `
95
- linear-gradient(to right, hsl(var(--background)) 0%, transparent 5%, transparent 95%, hsl(var(--background)) 100%),
96
- linear-gradient(to bottom, hsl(var(--background)) 0%, transparent 8%, transparent 92%, hsl(var(--background)) 100%)
66
+ linear-gradient(to right, hsl(var(--background)) 0%, transparent 15%, transparent 85%, hsl(var(--background)) 100%),
67
+ linear-gradient(to bottom, hsl(var(--background)) 0%, transparent 15%, transparent 85%, hsl(var(--background)) 100%)
97
68
  `,
98
69
  }}
99
70
  />
@@ -0,0 +1,200 @@
1
+ import React from "react";
2
+ import { AlertCircle, CheckCircle2 } from "lucide-react";
3
+ import { Badge } from "../Badge";
4
+
5
+ // =============================================================================
6
+ // SHARED TYPES
7
+ // =============================================================================
8
+
9
+ /**
10
+ * Generic validation issue for IDE-style editors.
11
+ * nodeId identifies which tree node the issue belongs to.
12
+ */
13
+ export interface ValidationIssue {
14
+ nodeId: string;
15
+ message: string;
16
+ }
17
+
18
+ // =============================================================================
19
+ // VALIDATION DOT
20
+ // =============================================================================
21
+
22
+ /**
23
+ * Small red dot indicator shown on tree nodes with validation issues.
24
+ */
25
+ export function IDEValidationDot({
26
+ nodeId,
27
+ issues,
28
+ }: {
29
+ nodeId: string;
30
+ issues: ValidationIssue[];
31
+ }) {
32
+ const nodeIssues = issues.filter((i) => i.nodeId === nodeId);
33
+ if (nodeIssues.length === 0) return;
34
+
35
+ return (
36
+ <span className="ml-auto flex h-2 w-2 rounded-full bg-destructive shrink-0" />
37
+ );
38
+ }
39
+
40
+ // =============================================================================
41
+ // TREE NODE
42
+ // =============================================================================
43
+
44
+ interface IDETreeNodeProps {
45
+ nodeId: string;
46
+ label: string;
47
+ icon: React.ElementType;
48
+ selected: boolean;
49
+ onClick: () => void;
50
+ issues?: ValidationIssue[];
51
+ indent?: boolean;
52
+ badge?: string;
53
+ }
54
+
55
+ /**
56
+ * Generic tree node for IDE-style left panel navigation.
57
+ * Highlights when selected, shows validation dots and optional badges.
58
+ */
59
+ export const IDETreeNode: React.FC<IDETreeNodeProps> = ({
60
+ nodeId,
61
+ label,
62
+ icon: Icon,
63
+ selected,
64
+ onClick,
65
+ issues = [],
66
+ indent = false,
67
+ badge,
68
+ }) => {
69
+ return (
70
+ <button
71
+ type="button"
72
+ onClick={onClick}
73
+ className={`flex items-center gap-2 w-full px-3 py-2 text-sm text-left transition-colors ${
74
+ indent ? "pl-7" : ""
75
+ } ${
76
+ selected
77
+ ? "bg-primary/10 text-primary border-l-2 border-primary"
78
+ : "hover:bg-muted/50 border-l-2 border-transparent"
79
+ }`}
80
+ >
81
+ <Icon className="h-4 w-4 shrink-0 opacity-60" />
82
+ <span className="truncate flex-1">{label}</span>
83
+ {badge && (
84
+ <Badge variant="secondary" className="text-[10px] shrink-0">
85
+ {badge}
86
+ </Badge>
87
+ )}
88
+ <IDEValidationDot nodeId={nodeId} issues={issues} />
89
+ </button>
90
+ );
91
+ };
92
+
93
+ // =============================================================================
94
+ // TREE SECTION HEADER
95
+ // =============================================================================
96
+
97
+ /**
98
+ * Section header for grouping tree nodes (e.g., "Check Items", "Permissions").
99
+ */
100
+ export const IDETreeSection: React.FC<{ label: string }> = ({ label }) => (
101
+ <div className="px-3 pt-4 pb-1">
102
+ <span className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider">
103
+ {label}
104
+ </span>
105
+ </div>
106
+ );
107
+
108
+ // =============================================================================
109
+ // STATUS BAR
110
+ // =============================================================================
111
+
112
+ interface IDEStatusBarProps {
113
+ issues: ValidationIssue[];
114
+ onIssueClick: (nodeId: string) => void;
115
+ }
116
+
117
+ /**
118
+ * Bottom status bar showing validation issue count with clickable navigation.
119
+ */
120
+ export const IDEStatusBar: React.FC<IDEStatusBarProps> = ({
121
+ issues,
122
+ onIssueClick,
123
+ }) => {
124
+ if (issues.length === 0) {
125
+ return (
126
+ <div className="flex items-center gap-2 px-4 py-2 mt-2 rounded-md border bg-card text-xs text-muted-foreground">
127
+ <CheckCircle2 className="h-3.5 w-3.5 text-green-500" />
128
+ <span>No issues found</span>
129
+ </div>
130
+ );
131
+ }
132
+
133
+ return (
134
+ <div className="flex items-center gap-3 px-4 py-2 mt-2 rounded-md border bg-card text-xs">
135
+ <div className="flex items-center gap-1.5 text-destructive shrink-0">
136
+ <AlertCircle className="h-3.5 w-3.5" />
137
+ <span className="font-medium">
138
+ {issues.length} {issues.length === 1 ? "issue" : "issues"}
139
+ </span>
140
+ </div>
141
+ <div className="flex items-center gap-2 overflow-x-auto">
142
+ {issues.map((issue, i) => (
143
+ <button
144
+ key={`${issue.nodeId}-${i}`}
145
+ type="button"
146
+ onClick={() => onIssueClick(issue.nodeId)}
147
+ className="text-muted-foreground hover:text-foreground transition-colors whitespace-nowrap underline-offset-2 hover:underline"
148
+ >
149
+ {issue.message}
150
+ </button>
151
+ ))}
152
+ </div>
153
+ </div>
154
+ );
155
+ };
156
+
157
+ // =============================================================================
158
+ // IDE LAYOUT
159
+ // =============================================================================
160
+
161
+ interface IDELayoutProps {
162
+ /** Left panel tree content */
163
+ tree: React.ReactNode;
164
+ /** Right panel editor content */
165
+ panel: React.ReactNode;
166
+ /** Optional status bar issues (renders IDEStatusBar automatically) */
167
+ issues?: ValidationIssue[];
168
+ /** Callback when a status bar issue is clicked */
169
+ onIssueClick?: (nodeId: string) => void;
170
+ }
171
+
172
+ /**
173
+ * Two-column IDE layout shell: left tree panel + right editor panel + status bar.
174
+ * Used by health check configuration editor and assignment editor.
175
+ */
176
+ export const IDELayout: React.FC<IDELayoutProps> = ({
177
+ tree,
178
+ panel,
179
+ issues,
180
+ onIssueClick,
181
+ }) => {
182
+ return (
183
+ <>
184
+ <div className="flex flex-col lg:flex-row gap-0 min-h-[60vh] border rounded-lg bg-card overflow-hidden">
185
+ {/* Explorer Tree — Left Panel */}
186
+ <div className="w-full lg:w-64 shrink-0 border-b lg:border-b-0 lg:border-r bg-muted/30">
187
+ {tree}
188
+ </div>
189
+
190
+ {/* Editor Panel — Right Panel */}
191
+ <div className="flex-1 min-w-0">{panel}</div>
192
+ </div>
193
+
194
+ {/* Status Bar */}
195
+ {issues && onIssueClick && (
196
+ <IDEStatusBar issues={issues} onIssueClick={onIssueClick} />
197
+ )}
198
+ </>
199
+ );
200
+ };
package/src/index.ts CHANGED
@@ -53,3 +53,4 @@ export * from "./components/AmbientBackground";
53
53
  export * from "./components/CodeEditor";
54
54
  export * from "./components/AnimatedNumber";
55
55
  export * from "./hooks/useAnimatedNumber";
56
+ export * from "./components/IDELayout";
package/src/themes.css CHANGED
@@ -143,41 +143,15 @@
143
143
 
144
144
  /* Custom animations */
145
145
  @keyframes bell-ring {
146
- 0% {
147
- transform: rotate(0deg);
148
- }
149
-
150
- 10% {
151
- transform: rotate(15deg);
152
- }
153
-
154
- 20% {
155
- transform: rotate(-15deg);
156
- }
157
-
158
- 30% {
159
- transform: rotate(10deg);
160
- }
161
-
162
- 40% {
163
- transform: rotate(-10deg);
164
- }
165
-
166
- 50% {
167
- transform: rotate(5deg);
168
- }
169
-
170
- 60% {
171
- transform: rotate(-5deg);
172
- }
173
-
174
- 70% {
175
- transform: rotate(0deg);
176
- }
177
-
178
- 100% {
179
- transform: rotate(0deg);
180
- }
146
+ 0% { transform: rotate(0deg); }
147
+ 10% { transform: rotate(15deg); }
148
+ 20% { transform: rotate(-15deg); }
149
+ 30% { transform: rotate(10deg); }
150
+ 40% { transform: rotate(-10deg); }
151
+ 50% { transform: rotate(5deg); }
152
+ 60% { transform: rotate(-5deg); }
153
+ 70% { transform: rotate(0deg); }
154
+ 100% { transform: rotate(0deg); }
181
155
  }
182
156
 
183
157
  .animate-bell-ring {
@@ -185,20 +159,48 @@
185
159
  transform-origin: top center;
186
160
  }
187
161
 
188
- /* Subtle tile pulse animation - gentle breathing effect */
189
- @keyframes tile-pulse {
162
+ /* High-performance ambient background styles */
163
+ @keyframes aurora-float-1 {
164
+ 0% { transform: translate(-15vw, -10vh) rotate(0deg); }
165
+ 50% { transform: translate(35vw, 25vh) rotate(180deg); }
166
+ 100% { transform: translate(-15vw, -10vh) rotate(360deg); }
167
+ }
190
168
 
191
- 0%,
192
- 100% {
193
- opacity: 1;
194
- }
169
+ @keyframes aurora-float-2 {
170
+ 0% { transform: translate(0, 0) scale(1); }
171
+ 50% { transform: translate(-30vw, 25vh) scale(1.2); }
172
+ 100% { transform: translate(0, 0) scale(1); }
173
+ }
195
174
 
196
- 50% {
197
- opacity: 0.4;
198
- }
175
+ @keyframes aurora-float-3 {
176
+ 0% { transform: translate(0, 0) rotate(0deg); }
177
+ 50% { transform: translate(25vw, -30vh) rotate(-180deg); }
178
+ 100% { transform: translate(0, 0) rotate(-360deg); }
179
+ }
180
+
181
+ @keyframes aurora-float-4 {
182
+ 0% { transform: translate(0, 0) scale(1.1); }
183
+ 50% { transform: translate(-20vw, -20vh) scale(1); }
184
+ 100% { transform: translate(0, 0) scale(1.1); }
185
+ }
186
+
187
+ .ambient-grid {
188
+ background-image:
189
+ linear-gradient(to right, hsl(var(--muted-foreground) / 0.15) 1px, transparent 1px),
190
+ linear-gradient(to bottom, hsl(var(--muted-foreground) / 0.15) 1px, transparent 1px);
191
+ background-size: 48px 48px;
192
+ }
193
+
194
+ .ambient-grid-inverse {
195
+ background: hsl(var(--background));
196
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='48' height='48' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1' y='1' width='47' height='47' fill='black'/%3E%3C/svg%3E");
197
+ mask-image: url("data:image/svg+xml,%3Csvg width='48' height='48' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1' y='1' width='47' height='47' fill='black'/%3E%3C/svg%3E");
199
198
  }
200
199
 
201
- .tile-glow {
202
- animation: tile-pulse 12s ease-in-out infinite;
203
- animation-delay: var(--glow-delay, 0s);
200
+ .aurora-blob {
201
+ position: absolute;
202
+ filter: blur(60px);
203
+ opacity: 0.6;
204
+ pointer-events: none;
205
+ will-change: transform;
204
206
  }