feat: Query history + saved queries (#5)

* fix: restore frontend test baseline (vitest jsdom env + tsc + mock fixes)

* feat: v6 migration — favorite column + queries table (Task 1)

* feat: extend TS types for favorites + saved queries (Task 2)

* feat: dedup consecutive + prune to 500 in insert_query_history (Task 3)

* feat: favorite column threading + set_history_favorite command (Task 4)

* feat: saved queries store CRUD (Task 5)

* feat: saved query commands + registration (Task 6)

* feat: queryStore — Zustand cache for history + saved queries (Task 7)

* feat: QueryHistoryDropdown — toolbar history dropdown (Task 8)

* feat: SaveQueryDialog — save query modal (Task 9)

* feat: QueryToolbar — add History + Save icons (Task 10)

* feat: QueriesPanel — History + Saved tabs (Task 11)

* feat: wire Queries view + toolbar to panel (Task 12)

* fix: global scope wrappers, empty/spinner states, cache invalidation (Task 13)

* feat: Queries view — history sidebar + tabbed query workspace (PR feedback)

* fix: toolbar action order + view-specific empty state (PR feedback)

* fix: view-specific empty state icon (PR feedback)

* fix: portal Tooltip to body so it is never clipped by overflow containers (PR feedback)

* feat: Queries sidebar — Explorer-style header + per-connection scoping (PR feedback)

* fix: move History/Saved dropdown to right side of Queries header (PR feedback)

* fix: History/Saved dropdown on its own row in Queries header (PR feedback)

* fix: Queries header order (search above dropdown) + sidebar width matches Explorer (PR feedback)

* fix: Queries header spacing — tight rows, pb-3 on container (PR feedback)

* fix: Queries header spacing — space-y-2 on container, no mb on title row (PR feedback)

* feat: history/saved rows click-to-load, remove sub-buttons (PR feedback)

* feat: merge Functions/Triggers/Sequences/Enums/Extensions into single Objects view (PR feedback)

* fix: Schema Visualizer nav icon — node graph glyph (PR feedback)

* fix: Objects view — type dropdown replaces static header label (PR feedback)

* fix: Objects empty state — remove bg circle, larger icon (PR feedback)

* fix: center icon in Objects empty state (PR feedback)

* feat: merge Backup/Restore/DB Sync into single Tools view (PR feedback)

* docs: update AGENTS.md + README for query history/saved queries, merged Objects + Tools views
This commit is contained in:
2026-08-01 17:43:40 +08:00
committed by GitHub
parent 80962d7d11
commit 7db66f1160
33 changed files with 3069 additions and 192 deletions
+28 -11
View File
@@ -1,4 +1,4 @@
import { useEffect, useState, useMemo, useCallback, useRef } from "react";
import { useEffect, useState, useMemo, useCallback, useRef, cloneElement } from "react";
import {
ChevronRight,
FunctionSquare,
@@ -29,7 +29,6 @@ export type ObjectType =
| "extensions";
interface ObjectExplorerPageProps {
type: ObjectType;
connectionId: string;
}
@@ -41,6 +40,10 @@ const TYPE_LABELS: Record<ObjectType, string> = {
extensions: "Extensions",
};
const OBJECT_TYPE_OPTIONS = (Object.keys(TYPE_LABELS) as ObjectType[]).map(
(t) => ({ value: t, label: TYPE_LABELS[t] }),
);
const SINGULAR_LABELS: Record<ObjectType, string> = {
functions: "function",
triggers: "trigger",
@@ -805,10 +808,8 @@ function renderDetail(type: ObjectType, item: AnyObject) {
}
}
export function ObjectExplorerPage({
type,
connectionId,
}: ObjectExplorerPageProps) {
export function ObjectExplorerPage({ connectionId }: ObjectExplorerPageProps) {
const [type, setType] = useState<ObjectType>("functions");
const [panelWidth, setPanelWidth] = useState(280);
const panelResizeRef = useRef<{ startX: number; startW: number } | null>(
null,
@@ -943,6 +944,18 @@ export function ObjectExplorerPage({
const label = TYPE_LABELS[type];
const singular = SINGULAR_LABELS[type];
// Switching object type: reset selection/search, clear the stale list so
// the loading state renders (no flash of the previous type's objects), and
// reset the last-fetched-schema marker so the fetch effect re-runs.
const handleTypeChange = (next: ObjectType) => {
setType(next);
setSearchQuery("");
setSelectedItem(null);
setItems(null);
setLoading(true);
lastSchemaRef.current = undefined;
};
return (
<div className="flex flex-1 min-h-0 overflow-hidden">
{/* Left panel: toolbar + object list */}
@@ -952,9 +965,13 @@ export function ObjectExplorerPage({
>
<div className="p-3 border-b border-border space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-semibold text-text">
{label}
</span>
<SelectDropdown
value={type}
onChange={(v) => handleTypeChange(v as ObjectType)}
options={OBJECT_TYPE_OPTIONS}
variant="ghost"
aria-label="Object type"
/>
<div className="flex items-center gap-1">
<button
aria-label="Refresh"
@@ -1144,8 +1161,8 @@ export function ObjectExplorerPage({
) : (
<div className="flex items-center justify-center h-full text-text-muted">
<div className="text-center space-y-2">
<div className="w-12 h-12 mx-auto rounded-full bg-surface flex items-center justify-center">
{icon}
<div className="flex justify-center">
{cloneElement(icon as React.ReactElement<{ size?: number }>, { size: 20 })}
</div>
<p className="text-sm">
Select a {singular} to view details