Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ updates:
schedule:
interval: daily
open-pull-requests-limit: 10
groups:
react:
patterns: ["react*", "@types/react*"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: daily
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ jobs:
command: pnpm compile
- name: build
command: pnpm build
- name: test
command: pnpm test
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7.0.1

- uses: pnpm/action-setup@v6
- uses: pnpm/action-setup@v6.1.0

- uses: actions/setup-node@v7
- uses: actions/setup-node@v7.1.0
with:
node-version-file: .nvmrc
cache: pnpm
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v24.19.0
v24.21.0
2 changes: 1 addition & 1 deletion components/CodeSnippet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function CodeSnippet({
<button
type="button"
onClick={handleCopy}
className="absolute top-2 right-2 p-1.5 rounded-md bg-gray-200/80 dark:bg-gray-700/80 opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
className="absolute top-2 right-2 p-1.5 rounded-md bg-gray-200/80 dark:bg-gray-700/80 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity cursor-pointer"
aria-label={copied ? "Copied" : "Copy to clipboard"}
>
{copied ? (
Expand Down
88 changes: 48 additions & 40 deletions components/ConnectionStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,64 @@
import type { ActionType, PathSegment } from "@/types/messages";
import type { LayoutVariant, MutationEntry, QueryEntry } from "@/types/ui";
import type { ReactNode } from "react";
import type {
LayoutVariant,
MutationEntry,
QueryEntry,
SyncStatus,
} from "@/types/ui";
import { PageLayout } from "./PageLayout";
import { SetupMessage } from "./SetupMessage";
import { SyncStatusMessage } from "./SyncStatusMessage";

interface ConnectionStatusProps {
variant: LayoutVariant;
queries: readonly QueryEntry[];
mutations: readonly MutationEntry[];
connected: boolean;
sendAction?: (action: ActionType, queryHash: string) => void;
sendSetData?: (
queryHash: string,
path: readonly PathSegment[],
value: string | number | boolean,
) => void;
sendDeleteData?: (queryHash: string, path: readonly PathSegment[]) => void;
sendRemoveAllQueries?: () => void;
sendClearArray?: (queryHash: string, path: readonly PathSegment[]) => void;
sendClearMutationCache?: () => void;
status: SyncStatus;
}

function Centered({ children }: { readonly children: ReactNode }) {
return (
<div className="h-full flex-1 flex items-center justify-center p-2">
{children}
</div>
);
}

export function ConnectionStatus({
variant,
queries,
mutations,
connected,
sendAction,
sendSetData,
sendDeleteData,
sendRemoveAllQueries,
sendClearArray,
sendClearMutationCache,
status,
}: ConnectionStatusProps) {
if (!connected) {
return (
<div className="h-full flex-1 flex items-center justify-center">
<SetupMessage variant={variant} />
</div>
);
switch (status.kind) {
case "connected":
return (
<PageLayout variant={variant} queries={queries} mutations={mutations} />
);
case "loading":
return (
<Centered>
<SyncStatusMessage
title="Loading the query cache…"
detail={`Received ${String(status.received)} of ${String(status.total)} parts.`}
/>
</Centered>
);
case "failed":
return (
<Centered>
<SyncStatusMessage
alert
title="Could not load the query cache"
detail="The page's cache did not reach the DevTools in one piece, so none of it is shown. The reason is logged in the console; reopening the DevTools tries again."
/>
</Centered>
);
case "disconnected":
return (
<div className="h-full flex-1 flex items-center justify-center">
<SetupMessage variant={variant} />
</div>
);
}

return (
<PageLayout
variant={variant}
queries={queries}
mutations={mutations}
sendAction={sendAction}
sendSetData={sendSetData}
sendDeleteData={sendDeleteData}
sendRemoveAllQueries={sendRemoveAllQueries}
sendClearArray={sendClearArray}
sendClearMutationCache={sendClearMutationCache}
/>
);
}
69 changes: 69 additions & 0 deletions components/DetailHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ArrowLeft } from "lucide-react";
import type { ReactNode } from "react";
import type { StatusTheme } from "@/utils/status-theme";

interface DetailHeaderProps {
readonly title: string;
/** Caption above the key, e.g. `Query key:`. */
readonly keyLabel: string;
/** Pre-formatted key text shown in the key box. */
readonly keyText: string;
readonly status: string;
readonly theme: StatusTheme;
readonly onBack?: () => void;
/** Meta lines shown under the key box (observers, timestamps). */
readonly children?: ReactNode;
}

/**
* Top of a detail view: optional back button, sticky title, key box with a
* status badge, and meta lines. Renders as a fragment so its sticky parts
* stick within the caller's scroll container.
*/
export function DetailHeader({
title,
keyLabel,
keyText,
status,
theme,
onBack,
children,
}: DetailHeaderProps) {
return (
<>
{onBack && (
<button
type="button"
onClick={onBack}
className="sticky top-0 bg-white z-11 dark:bg-gray-900 flex items-center gap-1 px-3 py-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
>
<ArrowLeft className="size-4" />
Back
</button>
)}

<h2 className="sticky bg-white dark:bg-gray-900 px-4 pt-3 pb-1 top-0 z-10 text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2">
{title}
</h2>

<div className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 px-4 py-1">
<div className="border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 rounded-lg p-3">
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400">
{keyLabel}
</span>
<span
className={`px-2 py-0.5 rounded-full text-xs font-medium capitalize border ${theme.bg} ${theme.text} ${theme.border}`}
>
{status}
</span>
</div>
<pre className="text-sm text-gray-900 dark:text-gray-100 whitespace-pre-wrap break-all font-mono">
{keyText}
</pre>
</div>
{children}
</div>
</>
);
}
Loading
Loading