@checkstack/ui 1.4.0 → 1.5.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,30 @@
1
1
  # @checkstack/ui
2
2
 
3
+ ## 1.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - c4e7560: Fix data integrity, cache invalidation, and mobile UI issues
8
+
9
+ - **Centralized mutation cache invalidation**: Every mutation now automatically invalidates its plugin's query cache on success via the shared `createProcedureHook` in `orpc-query.tsx`. This ensures all views stay in sync without requiring individual components to remember manual `invalidateQueries` calls.
10
+ - **Fixed oRPC query key matching**: Query keys use nested arrays (`[["pluginId"]]`) to correctly match oRPC's `[pathArray, options]` key structure. Fixed the broken flat-string pattern in `SystemBadgeDataProvider`.
11
+ - **Fixed hourly aggregation duplication**: Added `NULLS NOT DISTINCT` to the `health_check_aggregates` unique constraint so local runs (`source_id = NULL`) correctly conflict-match instead of creating duplicate hourly buckets. Includes a migration to clean up existing duplicates.
12
+ - **Fixed modal scrolling on mobile**: Added `max-height` + `overflow-y-auto` to `ConfirmationModal`, and refactored `Dialog` from translate-centering to flex-centering with `dvh` units for reliable mobile scroll containment.
13
+
14
+ - Updated dependencies [c4e7560]
15
+ - @checkstack/frontend-api@0.3.10
16
+
17
+ ## 1.5.0
18
+
19
+ ### Minor Changes
20
+
21
+ - 3da7582: Fix favicon not loading in production container and add NotFound page
22
+
23
+ - **Backend**: Fix static file serving so root-level files like `/favicon.svg` are served from the dist directory before the SPA fallback catches them
24
+ - **UI**: Add `NotFound` component with stacked-checkmark logo, physics-inspired falling "4" animation, and low-power device fallback
25
+ - **Frontend**: Add catch-all `*` route to display the NotFound page for unmatched routes, and add the Checkstack logo to the navbar
26
+ - **Favicon**: Redesign with stacked checkmarks in the brand purple/indigo palette
27
+
3
28
  ## 1.4.0
4
29
 
5
30
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/ui",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "dependencies": {
@@ -65,7 +65,7 @@ export const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
65
65
  onClick={handleBackdropClick}
66
66
  >
67
67
  <div
68
- className="bg-background rounded-lg shadow-xl max-w-md w-full mx-4 animate-in fade-in zoom-in duration-200 pointer-events-auto"
68
+ className="bg-background rounded-lg shadow-xl max-w-md w-full mx-4 my-4 max-h-[calc(100dvh-2rem)] overflow-y-auto animate-in fade-in zoom-in duration-200 pointer-events-auto"
69
69
  role="dialog"
70
70
  aria-modal="true"
71
71
  aria-labelledby="modal-title"
@@ -32,7 +32,7 @@ const DialogOverlay = React.forwardRef<
32
32
  DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
33
33
 
34
34
  const dialogContentVariants = cva(
35
- "fixed left-[50%] top-[50%] z-50 grid w-full translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background text-foreground p-6 shadow-lg sm:rounded-lg max-h-[85vh] overflow-y-auto overflow-x-visible",
35
+ "w-full gap-4 border border-border bg-background text-foreground p-6 shadow-lg sm:rounded-lg max-h-[85dvh] overflow-y-auto overflow-x-visible",
36
36
  {
37
37
  variants: {
38
38
  size: {
@@ -65,14 +65,21 @@ const DialogContent = React.forwardRef<
65
65
  <DialogPrimitive.Content
66
66
  ref={ref}
67
67
  className={cn(
68
- dialogContentVariants({ size }),
69
- !isLowPower && "duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
70
- className
68
+ "fixed inset-0 z-50 flex items-center justify-center p-4",
69
+ !isLowPower && "duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
71
70
  )}
72
71
  {...props}
73
72
  >
74
- {/* Wrapper with negative margin and positive padding to allow focus rings to extend */}
75
- <div className="-mx-2 px-2">{children}</div>
73
+ <div
74
+ className={cn(
75
+ dialogContentVariants({ size }),
76
+ !isLowPower && "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
77
+ className,
78
+ )}
79
+ >
80
+ {/* Wrapper with negative margin and positive padding to allow focus rings to extend */}
81
+ <div className="-mx-2 px-2">{children}</div>
82
+ </div>
76
83
  </DialogPrimitive.Content>
77
84
  </DialogPortal>
78
85
  );
@@ -0,0 +1,154 @@
1
+ import React, { useState, useEffect, useMemo } from "react";
2
+ import { useNavigate } from "react-router-dom";
3
+ import { ArrowLeft } from "lucide-react";
4
+ import { cn } from "../utils";
5
+ import { usePerformance } from "./PerformanceProvider";
6
+
7
+ /** Rotating tech-insider quips shown on the 404 page. */
8
+ const NOT_FOUND_QUIPS = [
9
+ // DevOps / infra humor
10
+ "Looks like this route didn't pass the health check.",
11
+ "This endpoint has an uptime of exactly 0%.",
12
+ "We checked the stack. This page isn't in it.",
13
+ "Our monitoring confirms: this page is down. Permanently.",
14
+ "This page was last seen in a git stash from 2019.",
15
+ String.raw`Incident report: page not found. Severity: ¯\_(ツ)_/¯`,
16
+ "DNS resolved. TCP connected. Page? Gone.",
17
+ "kubectl get page — error: resource not found.",
18
+ "This page is in a pending state. It may never resolve.",
19
+ "The deployment was successful. The page was not.",
20
+ "This route has been deprecated without notice.",
21
+ "Alert triggered: page_exists = false.",
22
+ // Programming jokes
23
+ "The page you're looking for is in another castle.",
24
+ "404: The page was found, then garbage collected.",
25
+ "Segfault at 0x00000404.",
26
+ "The page exists in the dev environment, we promise.",
27
+ "Works on my machine ™",
28
+ "Have you tried turning the URL off and on again?",
29
+ "This page is not a bug, it's an undocumented feature.",
30
+ "Error 404: Coffee not found. Page also missing.",
31
+ "git log --all --oneline | grep 'this page' → no results.",
32
+ // Pop culture references
33
+ "These aren't the pages you're looking for. — Obi-Wan Kenobi",
34
+ "I am inevitable. This page is not. — Thanos",
35
+ "One does not simply navigate to a page that doesn't exist.",
36
+ "In case I don't see ya: good afternoon, good evening, and good 404.",
37
+ "It's a feature, not a bug. — Every PM ever",
38
+ "Ah yes, the 404. The page that lived… briefly.",
39
+ "I've seen things you people wouldn't believe. But not this page.",
40
+ "To 404, or not to 404. That is the question.",
41
+ "Houston, we have a 404.",
42
+ ] as const;
43
+
44
+ export const NotFound: React.FC<{
45
+ message?: string;
46
+ className?: string;
47
+ }> = ({ message, className }) => {
48
+ const { isLowPower } = usePerformance();
49
+ const navigate = useNavigate();
50
+ const [hasFallen, setHasFallen] = useState(false);
51
+
52
+ const quip = useMemo(
53
+ () =>
54
+ message ??
55
+ NOT_FOUND_QUIPS[Math.floor(Math.random() * NOT_FOUND_QUIPS.length)],
56
+ [message],
57
+ );
58
+
59
+ useEffect(() => {
60
+ if (isLowPower) return;
61
+ const timer = setTimeout(() => setHasFallen(true), 1800);
62
+ return () => clearTimeout(timer);
63
+ }, [isLowPower]);
64
+
65
+ return (
66
+ <div
67
+ className={cn(
68
+ "flex flex-col items-center justify-center min-h-[60vh] p-8 select-none overflow-hidden",
69
+ className,
70
+ )}
71
+ >
72
+ {/* Physics keyframes for the falling "4" */}
73
+ {!isLowPower && (
74
+ <style>{`
75
+ @keyframes wobble-fall {
76
+ 0% { transform: rotate(0deg) translateY(0); }
77
+ 15% { transform: rotate(-2deg) translateY(0); }
78
+ 30% { transform: rotate(3deg) translateY(0); }
79
+ 42% { transform: rotate(6deg) translateY(2px); }
80
+ 65% { transform: rotate(55deg) translateY(30px); }
81
+ 78% { transform: rotate(80deg) translateY(50px); }
82
+ 86% { transform: rotate(72deg) translateY(45px); }
83
+ 93% { transform: rotate(78deg) translateY(52px); }
84
+ 100% { transform: rotate(76deg) translateY(50px); opacity: 0.25; }
85
+ }
86
+ `}</style>
87
+ )}
88
+
89
+ {/* 404 display */}
90
+ <div className="relative mb-8">
91
+ {/* Glow effect */}
92
+ {!isLowPower && (
93
+ <div
94
+ className="absolute inset-0 blur-3xl opacity-15 bg-primary rounded-full scale-150"
95
+ aria-hidden="true"
96
+ />
97
+ )}
98
+ <div className="relative grid grid-cols-[1fr_auto_1fr] items-center">
99
+ <span className="text-center text-[8rem] md:text-[12rem] font-black leading-none tabular-nums text-muted-foreground/50">
100
+ 4
101
+ </span>
102
+ {/* Checkstack logo as the "0" */}
103
+ <div className="flex items-center justify-center w-28 h-28 md:w-44 md:h-44 mx-1 md:mx-3">
104
+ <img
105
+ src="/favicon.svg"
106
+ alt=""
107
+ aria-hidden="true"
108
+ className="w-full h-full"
109
+ />
110
+ </div>
111
+ <span
112
+ className="text-center text-[8rem] md:text-[12rem] font-black leading-none tabular-nums text-muted-foreground/50"
113
+ style={
114
+ !isLowPower && hasFallen
115
+ ? {
116
+ animation:
117
+ "wobble-fall 1.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards",
118
+ transformOrigin: "bottom left",
119
+ }
120
+ : undefined
121
+ }
122
+ >
123
+ 4
124
+ </span>
125
+ </div>
126
+ </div>
127
+
128
+ {/* Text content */}
129
+ <div className="text-center space-y-3 max-w-md">
130
+ <h2 className="text-xl md:text-2xl font-semibold text-foreground">
131
+ Route not found
132
+ </h2>
133
+ <p className="text-sm md:text-base text-muted-foreground leading-relaxed">
134
+ {quip}
135
+ </p>
136
+ </div>
137
+
138
+ {/* Action */}
139
+ <button
140
+ type="button"
141
+ onClick={() => navigate("/")}
142
+ className={cn(
143
+ "mt-8 inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium cursor-pointer",
144
+ "bg-primary/10 text-primary border border-primary/20",
145
+ "hover:bg-primary/20 hover:border-primary/30",
146
+ !isLowPower && "transition-colors duration-200",
147
+ )}
148
+ >
149
+ <ArrowLeft className="w-4 h-4" />
150
+ Back to Dashboard
151
+ </button>
152
+ </div>
153
+ );
154
+ };
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export * from "./components/Card";
4
4
  export * from "./components/Label";
5
5
  export * from "./components/NavItem";
6
6
  export * from "./components/AccessDenied";
7
+ export * from "./components/NotFound";
7
8
  export * from "./components/AccessGate";
8
9
  export * from "./components/SectionHeader";
9
10
  export * from "./components/StatusCard";