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
1 change: 1 addition & 0 deletions .opencode/ops/cargo-audit-fix-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Each phase lives on a sub-branch off `fix/204-cargo-audit-advisories` to isolate
## Test Plan

After each phase:

- `cargo build --workspace`
- `cargo test --workspace`
- `cargo clippy --workspace -- -D warnings`
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ RUST_LOG=debug

| Technology | Purpose |
| ---------------------------------------------- | ------------------------------------------------------ |
| [Vitest](https://vitest.dev/) | Frontend unit tests (1488 tests) |
| [Vitest](https://vitest.dev/) | Frontend unit tests (1488 tests) |
| [cargo test](https://doc.rust-lang.org/cargo/) | Rust integration tests against Docker MySQL (45 tests) |
| [Docker](https://www.docker.com/) | MySQL 8, MySQL 5.7, MariaDB 11 test containers |

Expand All @@ -316,7 +316,6 @@ sqlpilot/
│ │ ├── history/ # QueryHistory
│ │ ├── import/ # ImportDialog (CSV + SQL modes)
│ │ ├── layout/ # AppLayout, Sidebar, MainPanel, Toolbar, StatusBar
│ │ ├── querybuilder/ # QueryBuilder (visual SQL builder)
│ │ ├── routine/ # RoutineViewer (procedure/function executor)
│ │ └── schema/ # TableStructure (columns, indexes, DDL)
│ ├── hooks/ # 7 custom hooks (context menu, keyboard, theme, schema cache, grid editing, query execution, click handler)
Expand Down
10 changes: 10 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ pre-commit:
unit-tests:
glob: "*.{ts,tsx,rs}"
run: npm run test:unit -- --reporter=dot
lint:
glob: "*.{ts,tsx,js,jsx}"
run: npm run lint
type-check:
glob: "*.{ts,tsx}"
run: npm run type-check
dprint:
glob: "*.{ts,tsx,js,jsx,json,yml,yaml,md,toml}"
run: npx dprint check --list-different
cargo-fmt:
root: "src-tauri/"
glob: "*.rs"
run: cargo fmt --check
cargo-clippy:
root: "src-tauri/"
glob: "*.rs"
run: cargo clippy --all-targets --all-features -- -D warnings
lockfile-check:
glob: "package.json"
run: |
Expand Down
8 changes: 0 additions & 8 deletions src-tauri/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,6 @@ pub fn build_menu(app: &tauri::AppHandle) -> tauri::Result<Menu<tauri::Wry>> {
true,
Some("F5"),
)?)
.separator()
.item(&MenuItem::with_id(
app,
"query-builder",
"Visual Query Builder",
true,
None::<&str>,
)?)
.item(&MenuItem::with_id(
app,
"compare-schemas",
Expand Down
10 changes: 2 additions & 8 deletions src/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,8 @@ export function AppLayout() {
// inline custom menu (Windows/Linux via DOM CustomEvent)
useEffect(() => {
const handleAction = (action: string) => {
const { selectedConnectionId, activeConnections, disconnect } = useConnectionStore.getState();
const { addTab, addAdminTab, addCompareTab, addQueryBuilderTab, editorInstance } = useEditorStore.getState();
const selectedConn = activeConnections.find((c) => c.id === selectedConnectionId);
const { selectedConnectionId, disconnect } = useConnectionStore.getState();
const { addTab, addAdminTab, addCompareTab, editorInstance } = useEditorStore.getState();

switch (action) {
case "new-query":
Expand Down Expand Up @@ -142,11 +141,6 @@ export function AppLayout() {
case "refresh-schema":
useSchemaCache.getState().refreshSchema();
break;
case "query-builder":
if (selectedConnectionId && selectedConn?.database) {
addQueryBuilderTab(selectedConnectionId, selectedConn.database);
}
break;
case "compare-schemas":
addCompareTab();
break;
Expand Down
9 changes: 0 additions & 9 deletions src/components/layout/MainPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { QueryToolbar } from "../editor/QueryToolbar";
import { SQLEditor } from "../editor/SQLEditor";
import { ExplainPanel } from "../explain/ExplainPanel";
import { ResultsGrid } from "../grid/ResultsGrid";
import { QueryBuilder } from "../querybuilder/QueryBuilder";
import { RoutineViewer } from "../routine/RoutineViewer";

export function MainPanel() {
Expand All @@ -25,7 +24,6 @@ export function MainPanel() {
const isCompare = activeTab?.type === "compare";
const isRoutine = activeTab?.type === "routine";
const isDesigner = activeTab?.type === "designer";
const isQueryBuilder = activeTab?.type === "querybuilder";

return (
<div className="flex h-full flex-col min-h-0 bg-[var(--color-bg-primary)]">
Expand All @@ -42,13 +40,6 @@ export function MainPanel() {
tableName={activeTab.tableName}
/>
)
: isQueryBuilder && activeTab?.connectionId && activeTab?.database
? (
<QueryBuilder
connectionId={activeTab.connectionId}
database={activeTab.database}
/>
)
: isRoutine && activeTab?.connectionId && activeTab?.database && activeTab?.routineName
&& activeTab?.routineType
? (
Expand Down
2 changes: 0 additions & 2 deletions src/components/layout/MenuBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ const MENUS: MenuDef[] = [
label: "Database",
items: [
{ type: "item", id: "refresh-schema", label: "Refresh Schema", shortcut: "Ctrl+Shift+R" },
{ type: "separator" },
{ type: "item", id: "query-builder", label: "Visual Query Builder" },
{ type: "item", id: "compare-schemas", label: "Compare Schemas" },
{ type: "separator" },
{ type: "item", id: "admin-tools", label: "Admin Tools" },
Expand Down
16 changes: 0 additions & 16 deletions src/components/layout/TitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
ArrowLeftRight,
HardDriveDownload,
HardDriveUpload,
LayoutGrid,
Minus,
Monitor,
Moon,
Expand Down Expand Up @@ -56,7 +55,6 @@ export function TitleBar(
const lastClickTime = useRef(0);

const selectedConnectionId = useConnectionStore((s) => s.selectedConnectionId);
const activeConnections = useConnectionStore((s) => s.activeConnections);
const theme = useThemeStore((s) => s.theme);
const setTheme = useThemeStore((s) => s.setTheme);
const ThemeIcon = themeIcons[theme];
Expand All @@ -66,11 +64,6 @@ export function TitleBar(
useEditorStore.getState().addAdminTab(selectedConnectionId);
};
const handleOpenCompare = () => useEditorStore.getState().addCompareTab();
const handleOpenQueryBuilder = () => {
if (!selectedConnectionId) return;
const conn = activeConnections.find((c) => c.id === selectedConnectionId);
if (conn?.database) useEditorStore.getState().addQueryBuilderTab(selectedConnectionId, conn.database);
};
const cycleTheme = () => {
const idx = themeOrder.indexOf(theme);
setTheme(themeOrder[(idx + 1) % themeOrder.length]);
Expand Down Expand Up @@ -169,15 +162,6 @@ export function TitleBar(

{/* Toolbar buttons */}
<div className="flex items-center gap-0.5 px-1" onContextMenu={(e) => e.stopPropagation()}>
<button
onClick={handleOpenQueryBuilder}
disabled={!selectedConnectionId}
title="Visual Query Builder"
className={toolBtn(!selectedConnectionId)}
>
<LayoutGrid className="h-3.5 w-3.5" />
<span>Visual Builder</span>
</button>
<button onClick={handleOpenCompare} title="Compare Schemas" className={toolBtn()}>
<ArrowLeftRight className="h-3.5 w-3.5" />
<span>Compare</span>
Expand Down
19 changes: 0 additions & 19 deletions src/components/layout/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
ArrowLeftRight,
HardDriveDownload,
HardDriveUpload,
LayoutGrid,
Monitor,
Moon,
Sparkles,
Expand Down Expand Up @@ -31,7 +30,6 @@ export function Toolbar(
{ onShowImport, onShowBackup, onShowRestore, onToggleAI, aiPanelOpen, aiEnabled }: ToolbarProps,
) {
const selectedConnectionId = useConnectionStore((s) => s.selectedConnectionId);
const activeConnections = useConnectionStore((s) => s.activeConnections);
const theme = useThemeStore((s) => s.theme);
const setTheme = useThemeStore((s) => s.setTheme);

Expand All @@ -44,14 +42,6 @@ export function Toolbar(
useEditorStore.getState().addCompareTab();
};

const handleOpenQueryBuilder = () => {
if (!selectedConnectionId) return;
const conn = activeConnections.find((c) => c.id === selectedConnectionId);
const database = conn?.database ?? "";
if (!database) return;
useEditorStore.getState().addQueryBuilderTab(selectedConnectionId, database);
};

const cycleTheme = () => {
const idx = themeOrder.indexOf(theme);
setTheme(themeOrder[(idx + 1) % themeOrder.length]);
Expand All @@ -62,15 +52,6 @@ export function Toolbar(
return (
<div className="flex h-10 items-center border-b border-[var(--color-border)] bg-[var(--color-bg-secondary)] px-3">
<div className="flex-1" />
<button
onClick={handleOpenQueryBuilder}
disabled={!selectedConnectionId}
title="Visual Query Builder"
className="flex items-center gap-1 rounded px-2 py-1 text-xs text-[var(--color-text-muted)] hover:bg-[var(--color-bg-tertiary)] hover:text-[var(--color-text-primary)] transition-colors disabled:opacity-40 disabled:cursor-not-allowed mr-1"
>
<LayoutGrid className="h-3.5 w-3.5" />
<span>Visual Builder</span>
</button>
<button
onClick={handleOpenCompare}
title="Compare Schemas"
Expand Down
38 changes: 0 additions & 38 deletions src/components/layout/__tests__/AppLayout.browser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ let editorState = {
addTab: vi.fn(() => "tab-1"),
addAdminTab: vi.fn(),
addCompareTab: vi.fn(),
addQueryBuilderTab: vi.fn(),
editorInstance: null as any,
};

Expand Down Expand Up @@ -219,7 +218,6 @@ describe("AppLayout (browser)", () => {
addTab: vi.fn(() => "tab-1"),
addAdminTab: vi.fn(),
addCompareTab: vi.fn(),
addQueryBuilderTab: vi.fn(),
editorInstance: null,
};
aiState = {
Expand Down Expand Up @@ -663,29 +661,6 @@ describe("AppLayout (browser)", () => {
expect(refreshSpy).toHaveBeenCalled();
});

// ─── Menu action: query-builder when connected with database ───
it("handles query-builder menu action when connected with database", async () => {
connectionState.selectedConnectionId = "conn-1";
connectionState.activeConnections = [
{ id: "conn-1", profile_id: "p1", host: "localhost", port: 3306, database: "testdb" },
];
await renderApp();
await act(async () => {
window.dispatchEvent(new CustomEvent("menu-action", { detail: "query-builder" }));
});
expect(editorState.addQueryBuilderTab).toHaveBeenCalledWith("conn-1", "testdb");
});

// ─── Menu action: query-builder when not connected ───
it("handles query-builder menu action when not connected (no-op)", async () => {
connectionState.selectedConnectionId = null;
await renderApp();
await act(async () => {
window.dispatchEvent(new CustomEvent("menu-action", { detail: "query-builder" }));
});
expect(editorState.addQueryBuilderTab).not.toHaveBeenCalled();
});

// ─── Menu action: compare-schemas ───
it("handles compare-schemas menu action", async () => {
await renderApp();
Expand Down Expand Up @@ -762,19 +737,6 @@ describe("AppLayout (browser)", () => {
expect(dialog).not.toBeInTheDocument();
});

// ─── query-builder without database in active connection ───
it("handles query-builder when connected but no database set (no-op)", async () => {
connectionState.selectedConnectionId = "conn-1";
connectionState.activeConnections = [
{ id: "conn-1", profile_id: "p1", host: "localhost", port: 3306 },
];
await renderApp();
await act(async () => {
window.dispatchEvent(new CustomEvent("menu-action", { detail: "query-builder" }));
});
expect(editorState.addQueryBuilderTab).not.toHaveBeenCalled();
});

// ─── undo/redo without editorInstance (graceful no-op) ───
it("handles undo when no editorInstance (graceful no-op)", async () => {
editorState.editorInstance = null;
Expand Down
2 changes: 0 additions & 2 deletions src/components/layout/__tests__/AppLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ let editorState = {
addTab: vi.fn(() => "tab-1"),
addAdminTab: vi.fn(),
addCompareTab: vi.fn(),
addQueryBuilderTab: vi.fn(),
editorInstance: null as any,
};

Expand Down Expand Up @@ -232,7 +231,6 @@ describe("AppLayout", () => {
addTab: vi.fn(() => "tab-1"),
addAdminTab: vi.fn(),
addCompareTab: vi.fn(),
addQueryBuilderTab: vi.fn(),
editorInstance: null,
};
aiState = {
Expand Down
20 changes: 0 additions & 20 deletions src/components/layout/__tests__/MainPanel.browser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ vi.mock("../../compare/SchemaCompare", () => ({
vi.mock("../../designer/TableDesigner", () => ({
TableDesigner: () => <div data-testid="table-designer" />,
}));
vi.mock("../../querybuilder/QueryBuilder", () => ({
QueryBuilder: () => <div data-testid="query-builder" />,
}));
vi.mock("../../routine/RoutineViewer", () => ({
RoutineViewer: () => <div data-testid="routine-viewer" />,
}));
Expand Down Expand Up @@ -160,23 +157,6 @@ describe("MainPanel", () => {
expect(screen.getByTestId("query-toolbar")).toBeInTheDocument();
});

it("renders QueryBuilder when active tab type is querybuilder", () => {
mockEditorState({
tabs: [{
id: "tab-1",
title: "QB",
content: "",
type: "querybuilder",
connectionId: "conn-1",
database: "testdb",
isDirty: false,
}],
activeTabId: "tab-1",
});
render(<MainPanel />);
expect(screen.getByTestId("query-builder")).toBeInTheDocument();
});

it("renders RoutineViewer when active tab type is routine", () => {
mockEditorState({
tabs: [{
Expand Down
4 changes: 0 additions & 4 deletions src/components/layout/__tests__/MainPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ vi.mock(
"../../designer/TableDesigner",
() => ({ TableDesigner: vi.fn(() => <div data-testid="table-designer">TableDesigner</div>) }),
);
vi.mock(
"../../querybuilder/QueryBuilder",
() => ({ QueryBuilder: vi.fn(() => <div data-testid="query-builder">QueryBuilder</div>) }),
);

vi.mock("react-resizable-panels", () => ({
Group: vi.fn(({ children }: { children: React.ReactNode }) => <div data-testid="panel-group">{children}</div>),
Expand Down
17 changes: 0 additions & 17 deletions src/components/layout/__tests__/TitleBar.browser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,11 @@ import { TitleBar } from "../TitleBar";

const mockAddAdminTab = vi.fn();
const mockAddCompareTab = vi.fn();
const mockAddQueryBuilderTab = vi.fn();

function mockEditorStore() {
const getStateMock = vi.fn(() => ({
addAdminTab: mockAddAdminTab,
addCompareTab: mockAddCompareTab,
addQueryBuilderTab: mockAddQueryBuilderTab,
}));
(useEditorStore as any).getState = getStateMock;
vi.mocked(useEditorStore).mockReturnValue({} as any);
Expand Down Expand Up @@ -151,7 +149,6 @@ describe("TitleBar", () => {

it("renders all toolbar buttons when connected", () => {
render(<TitleBar aiEnabled={true} onToggleAI={vi.fn()} />);
expect(screen.getByText("Visual Builder")).toBeInTheDocument();
expect(screen.getByText("Compare")).toBeInTheDocument();
expect(screen.getByText("Admin")).toBeInTheDocument();
expect(screen.getByText("Import")).toBeInTheDocument();
Expand Down Expand Up @@ -282,7 +279,6 @@ describe("TitleBar", () => {
it("disables connection-dependent buttons when no connection", () => {
mockConnectionStore({ selectedConnectionId: null, activeConnections: [] });
render(<TitleBar />);
expect(screen.getByText("Visual Builder").closest("button")).toBeDisabled();
expect(screen.getByText("Admin").closest("button")).toBeDisabled();
expect(screen.getByText("Import").closest("button")).toBeDisabled();
expect(screen.getByText("Backup").closest("button")).toBeDisabled();
Expand Down Expand Up @@ -314,19 +310,6 @@ describe("TitleBar", () => {
expect(mockAddAdminTab).not.toHaveBeenCalled();
});

it("calls addQueryBuilderTab when Visual Builder is clicked", () => {
render(<TitleBar />);
fireEvent.click(screen.getByText("Visual Builder"));
expect(mockAddQueryBuilderTab).toHaveBeenCalledWith("conn-1", "testdb");
});

it("does not call addQueryBuilderTab when no connection", () => {
mockConnectionStore({ selectedConnectionId: null, activeConnections: [] });
render(<TitleBar />);
fireEvent.click(screen.getByText("Visual Builder"));
expect(mockAddQueryBuilderTab).not.toHaveBeenCalled();
});

it("calls onShowImport when Import button is clicked", () => {
const onShowImport = vi.fn();
render(<TitleBar onShowImport={onShowImport} />);
Expand Down
Loading
Loading