Skip to content

Commit 3fe70d5

Browse files
committed
Add Zod for payload validation, update dependencies, and enhance animations
1 parent 37bf091 commit 3fe70d5

11 files changed

Lines changed: 194 additions & 69 deletions

File tree

memory-bank

package-lock.json

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"react": "^19.1.0",
3434
"react-dom": "^19.1.1",
3535
"superjson": "^2.2.2",
36-
"webextension-polyfill": "^0.12.0"
36+
"webextension-polyfill": "^0.12.0",
37+
"zod": "^4.0.14"
3738
},
3839
"devDependencies": {
3940
"@eslint/js": "^9.33.0",

src/background/background.ts

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import "webextension-polyfill";
2+
import { z } from "zod";
23
import { StorageManager } from "../shared/storage-manager";
34
import { safeDeserialize } from "../utils/serialization";
45
import type { QueryState } from "../types/storage";
56
import type { UpdateMessage, QueryActionResult } from "../types/messages";
67

7-
// Define serialized payload type
8-
interface SerializedPayload {
9-
serialized: string;
10-
usedSuperjson: boolean;
11-
isSerializedPayload: boolean;
12-
}
8+
// Zod schema for serialized payload validation
9+
const SerializedPayloadSchema = z.object({
10+
serialized: z.string(),
11+
usedSuperjson: z.boolean(),
12+
isSerializedPayload: z.literal(true),
13+
});
1314

1415
// Define possible background messages
1516
type BackgroundMessage =
@@ -63,17 +64,12 @@ chrome.runtime.onMessage.addListener(
6364
// Deserialize queries if they are serialized
6465
if (processedPayload.queries) {
6566
try {
66-
if (
67-
typeof processedPayload.queries === "object" &&
68-
processedPayload.queries !== null &&
69-
"isSerializedPayload" in processedPayload.queries &&
70-
(processedPayload.queries as SerializedPayload)
71-
.isSerializedPayload
72-
) {
73-
const serializedData =
74-
processedPayload.queries as SerializedPayload;
67+
const parseResult = SerializedPayloadSchema.safeParse(
68+
processedPayload.queries,
69+
);
70+
if (parseResult.success) {
7571
const deserializedQueries = safeDeserialize(
76-
serializedData.serialized,
72+
parseResult.data.serialized,
7773
);
7874
if (Array.isArray(deserializedQueries)) {
7975
processedPayload.queries = deserializedQueries;
@@ -91,17 +87,12 @@ chrome.runtime.onMessage.addListener(
9187
// Deserialize mutations if they are serialized
9288
if (processedPayload.mutations) {
9389
try {
94-
if (
95-
typeof processedPayload.mutations === "object" &&
96-
processedPayload.mutations !== null &&
97-
"isSerializedPayload" in processedPayload.mutations &&
98-
(processedPayload.mutations as SerializedPayload)
99-
.isSerializedPayload
100-
) {
101-
const serializedData =
102-
processedPayload.mutations as SerializedPayload;
90+
const parseResult = SerializedPayloadSchema.safeParse(
91+
processedPayload.mutations,
92+
);
93+
if (parseResult.success) {
10394
const deserializedMutations = safeDeserialize(
104-
serializedData.serialized,
95+
parseResult.data.serialized,
10596
);
10697
if (Array.isArray(deserializedMutations)) {
10798
processedPayload.mutations = deserializedMutations;
@@ -131,6 +122,12 @@ chrome.runtime.onMessage.addListener(
131122
if (processedPayload.tanStackQueryDetected !== undefined) {
132123
updateData.tanStackQueryDetected =
133124
processedPayload.tanStackQueryDetected;
125+
126+
// Clear artificial states when QueryClient is freshly detected
127+
// This handles page refreshes and navigations
128+
if (processedPayload.tanStackQueryDetected === true) {
129+
updateData.artificialStates = {};
130+
}
134131
}
135132

136133
StorageManager.updatePartialState(updateData)

src/components/query/QueryActions.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { QueryData } from "../../types/query";
2+
import type { QueryAction } from "../../types/messages";
23
import { formatQueryKeyShort } from "../../utils/formatters";
34
import { buttonVariants } from "../../lib/variants";
45

56
interface QueryActionsProps {
67
selectedQuery: QueryData;
7-
onAction: (action: string, queryHash: string) => void;
8+
onAction: (action: QueryAction["type"], queryHash: string) => void;
89
actionLoading: string | null;
910
setActionLoading: (action: string | null) => void;
1011
artificialStates: Map<string, "loading" | "error">;
@@ -17,7 +18,7 @@ export function QueryActions({
1718
setActionLoading,
1819
artificialStates,
1920
}: QueryActionsProps) {
20-
const handleAction = async (action: string) => {
21+
const handleAction = async (action: QueryAction["type"]) => {
2122
setActionLoading(action);
2223
try {
2324
await onAction(action, selectedQuery.queryHash);

src/components/query/QueryDetails.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import { useState } from "react";
22
import { useDetailsAnimation } from "../../hooks/useDetailsAnimation";
33
import type { QueryData } from "../../types/query";
4+
import type { QueryAction } from "../../types/messages";
45
import { DataExplorer } from "../common/DataExplorer";
56
import { QueryActions } from "./QueryActions";
67
import { QueryExplorer } from "./QueryExplorer";
78
import { QueryHeader } from "./QueryHeader";
89

910
interface QueryDetailsProps {
1011
selectedQuery: QueryData | null;
11-
onAction: (action: string, queryHash: string, newValue?: unknown) => void;
12+
onAction: (
13+
action: QueryAction["type"],
14+
queryHash: string,
15+
newValue?: unknown,
16+
) => void;
1217
artificialStates: Map<string, "loading" | "error">;
1318
}
1419

src/components/status/StatusBadge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function StatusBadge({
2424
return (
2525
<div
2626
className={`
27-
flex items-center justify-center text-white text-xs font-bold rounded
27+
flex items-center justify-center text-xs font-bold rounded
2828
${statusBadgeVariants({ status: status.variant })} ${transitionClass} ${className}
2929
`}
3030
onAnimationEnd={handleTransitionEnd}

src/components/status/StatusText.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function StatusText({
2020
return (
2121
<div
2222
className={`
23-
px-3 py-1 rounded text-white text-sm font-medium min-w-[85px] text-center
23+
px-3 py-1 rounded text-sm font-medium min-w-[85px] text-center
2424
status-badge-animated status-transition ${status.bgColor} ${transitionClass} ${className}
2525
`}
2626
onAnimationEnd={handleTransitionEnd}

src/styles/animations.css

Lines changed: 115 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,112 @@
1+
/* CSS Custom Properties for Dark Mode Support */
2+
:root {
3+
/* Light theme pulse animation colors */
4+
--pulse-bg-start: theme("colors.blue.100");
5+
--pulse-bg-mid: theme("colors.blue.200");
6+
--pulse-text-start: theme("colors.blue.800");
7+
--pulse-text-mid: theme("colors.blue.900");
8+
}
9+
10+
@media (prefers-color-scheme: dark) {
11+
:root {
12+
/* Dark theme pulse animation colors */
13+
--pulse-bg-start: theme("colors.blue.900");
14+
--pulse-bg-mid: theme("colors.blue.800");
15+
--pulse-text-start: theme("colors.blue.100");
16+
--pulse-text-mid: theme("colors.blue.200");
17+
}
18+
}
19+
20+
@theme {
21+
--animate-pulse-blue: pulse-blue 2s ease-in-out infinite;
22+
--animate-selection-pulse: selection-pulse 2s ease-in-out infinite;
23+
--animate-bounce-error: bounce-error 0.6s ease-out;
24+
--animate-status-to-success: statusToSuccess 600ms ease-out forwards;
25+
--animate-status-attention: statusAttention 600ms ease-out forwards;
26+
--animate-query-item-status-change: queryItemStatusChange 300ms ease-out
27+
forwards;
28+
29+
@keyframes pulse-blue {
30+
0%,
31+
100% {
32+
background-color: var(--pulse-bg-start);
33+
color: var(--pulse-text-start);
34+
}
35+
50% {
36+
background-color: var(--pulse-bg-mid);
37+
color: var(--pulse-text-mid);
38+
}
39+
}
40+
41+
@keyframes statusToSuccess {
42+
0% {
43+
transform: scale(1);
44+
box-shadow: 0 0 0 0 rgb(theme("colors.green.500") / 0.3);
45+
}
46+
50% {
47+
transform: scale(1.05);
48+
box-shadow: 0 0 0 4px rgb(theme("colors.green.500") / 0.1);
49+
}
50+
100% {
51+
transform: scale(1);
52+
box-shadow: 0 0 0 0 rgb(theme("colors.green.500") / 0);
53+
}
54+
}
55+
56+
@keyframes statusAttention {
57+
0% {
58+
transform: scale(1);
59+
box-shadow: 0 0 0 0 rgb(theme("colors.red.500") / 0.4);
60+
}
61+
50% {
62+
transform: scale(1.15);
63+
box-shadow: 0 0 0 6px rgb(theme("colors.red.500") / 0.2);
64+
}
65+
100% {
66+
transform: scale(1);
67+
box-shadow: 0 0 0 0 rgb(theme("colors.red.500") / 0);
68+
}
69+
}
70+
71+
@keyframes selection-pulse {
72+
0%,
73+
100% {
74+
box-shadow: 0 0 0 0 rgb(theme("colors.blue.500") / 0.4);
75+
}
76+
50% {
77+
box-shadow: 0 0 0 8px rgb(theme("colors.blue.500") / 0);
78+
}
79+
}
80+
81+
@keyframes bounce-error {
82+
0%,
83+
20%,
84+
50%,
85+
80%,
86+
100% {
87+
transform: translateY(0);
88+
}
89+
40% {
90+
transform: translateY(-4px);
91+
}
92+
60% {
93+
transform: translateY(-2px);
94+
}
95+
}
96+
97+
@keyframes queryItemStatusChange {
98+
0% {
99+
background-color: transparent;
100+
}
101+
50% {
102+
background-color: rgb(theme("colors.blue.50") / 0.3);
103+
}
104+
100% {
105+
background-color: transparent;
106+
}
107+
}
108+
}
109+
1110
@layer animations {
2111
/* Reusable Collapsible Component Animation */
3112
.collapsible-container {
@@ -66,7 +175,7 @@
66175

67176
/* Selection Animations */
68177
.card-selection-animated.card-selected {
69-
animation: selection-pulse 2s ease-in-out infinite;
178+
@apply animate-selection-pulse;
70179
}
71180

72181
/* List Item Micro-Interactions */
@@ -103,11 +212,11 @@
103212
}
104213

105214
.status-badge-animated.status-blue {
106-
animation: pulse-blue 2s ease-in-out infinite;
215+
@apply animate-pulse-blue;
107216
}
108217

109218
.status-badge-animated.status-red {
110-
animation: bounce-error 0.6s ease-out;
219+
@apply animate-bounce-error;
111220
}
112221

113222
/* List Item Stagger Animations */
@@ -262,11 +371,6 @@
262371
will-change: transform, opacity;
263372
}
264373

265-
.loading-refresh {
266-
animation: pulse-blue 1s ease-in-out 2;
267-
will-change: background-color;
268-
}
269-
270374
.loading-background {
271375
opacity: 0.7;
272376
animation: skeleton-pulse 3s ease-in-out infinite;
@@ -358,18 +462,6 @@
358462
}
359463
}
360464

361-
@keyframes pulse-blue {
362-
0%,
363-
100% {
364-
background-color: rgb(var(--color-blue-100));
365-
color: rgb(var(--color-blue-800));
366-
}
367-
50% {
368-
background-color: rgb(var(--color-blue-200));
369-
color: rgb(var(--color-blue-900));
370-
}
371-
}
372-
373465
@keyframes bounce-error {
374466
0%,
375467
20%,
@@ -448,7 +540,7 @@
448540
}
449541

450542
.status-to-success {
451-
animation: statusToSuccess 600ms ease-out forwards;
543+
@apply animate-status-to-success;
452544
}
453545

454546
.status-to-stale {
@@ -464,7 +556,7 @@
464556
}
465557

466558
.status-attention {
467-
animation: statusAttention 600ms ease-out forwards;
559+
@apply animate-status-attention;
468560
}
469561

470562
.status-fade-gentle {
@@ -473,7 +565,7 @@
473565

474566
/* Container animations for state changes */
475567
.query-item-status-change {
476-
animation: queryItemStatusChange 300ms ease-out forwards;
568+
@apply animate-query-item-status-change;
477569
}
478570

479571
/* State Transition Keyframes - Phase 6D */
@@ -619,7 +711,6 @@
619711
.skeleton-json,
620712
.loading-sequence,
621713
.loading-initial,
622-
.loading-refresh,
623714
.loading-background,
624715
.skeleton-to-content,
625716
.status-transition,

0 commit comments

Comments
 (0)