refactor: convert to bun-workspaces monorepo (desktop/ + www/)

- Move the Tauri app (React frontend + Rust backend) into desktop/ via
  git mv — configs unchanged (relative paths: tauri.conf.json, vite.config.ts)
- Root package.json becomes a private bun workspace container with
  orchestration scripts; desktop package renamed gridline-desktop
- Scaffold www/ with Astro 7 + Tailwind v4 (hand-wired vite plugin,
  static output, ready for Dokploy)
- Update release.yml for the new layout: projectPath: desktop,
  desktop/src-tauri resource paths, rust-cache workspace path
- Update README + AGENTS.md structure trees and dev commands
- Verified: vitest (1074), cargo test (354), desktop build, tauri dev
  (window launches), tauri build (dmg + app bundles), Astro build
This commit is contained in:
2026-08-16 19:31:53 +08:00
parent d752642b4d
commit 6854d889c5
400 changed files with 857 additions and 149 deletions
+130
View File
@@ -0,0 +1,130 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import App from "./App";
import type { Settings } from "./lib/types";
import { useConnectionStore } from "./stores/connectionStore";
import { useSettingsStore } from "./stores/settingsStore";
import { useUiStore } from "./stores/uiStore";
vi.mock("./lib/commands", () => ({
getConnections: vi.fn().mockResolvedValue([]),
getFolders: vi.fn().mockResolvedValue([]),
getTags: vi.fn().mockResolvedValue([]),
getSettings: vi.fn().mockResolvedValue({
confirm_before_delete: true,
default_folder_id: null,
theme: "dark",
font_size: "medium",
default_ports: { postgresql: 5432, mysql: 3306, redis: 6379, sqlite: null },
tag_order: null,
table_refresh_rate: 30,
table_page_size: 50,
shortcuts: {},
accent_color: "#2563EB",
editor_font_size: 13,
editor_font_family: "Space Mono",
editor_word_wrap: "off",
editor_minimap: false,
editor_tab_size: 4,
} satisfies Settings),
testConnection: vi.fn().mockResolvedValue({ ok: true }),
}));
beforeEach(() => {
useConnectionStore.setState({
connections: [],
folders: [],
tags: [],
loading: false,
error: null,
});
useSettingsStore.setState({ settings: null, loading: false, error: null });
useUiStore.setState({ activeView: "home" });
useUiStore.setState({ activeFolderId: null });
vi.clearAllMocks();
});
describe("App", () => {
it("renders home view on mount", async () => {
render(<App />);
expect(await screen.findByPlaceholderText(/search connections/i)).toBeInTheDocument();
});
it("shows empty state when no connections", async () => {
render(<App />);
expect(await screen.findByText(/no connections/i)).toBeInTheDocument();
});
it("loads data on mount", async () => {
render(<App />);
await screen.findByPlaceholderText(/search connections/i);
expect(useConnectionStore.getState().loading).toBe(false);
});
it("renders settings page when activeView is settings", async () => {
useUiStore.setState({ activeView: "settings" });
render(<App />);
expect(screen.getByRole("heading", { name: /general/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /back/i })).toBeInTheDocument();
});
it("renders new connection form when activeView is new-connection", async () => {
useUiStore.setState({ activeView: "new-connection" });
render(<App />);
expect(await screen.findByText("Save Connection")).toBeInTheDocument();
});
it("shows error banner when connectionStore has error", async () => {
const { getConnections } = await import("./lib/commands");
vi.mocked(getConnections).mockRejectedValueOnce(new Error("Storage error"));
useConnectionStore.setState({ connections: [], loading: false, error: null });
render(<App />);
expect(await screen.findByText(/storage error/i)).toBeInTheDocument();
});
it("sets the active folder from default_folder_id on startup", async () => {
const { getFolders, getSettings } = await import("./lib/commands");
vi.mocked(getFolders).mockResolvedValueOnce([
{
id: "folder-1",
name: "Projects",
parent_id: null,
tag_ids: [],
created_at: "",
updated_at: "",
},
]);
// Resolve settings only after folders have loaded so the default-folder
// effect doesn't race HomeScreen's "reset missing folder" effect.
let resolveSettings!: (value: Settings) => void;
vi.mocked(getSettings).mockImplementationOnce(
() =>
new Promise<Settings>((resolve) => {
resolveSettings = resolve;
}),
);
useUiStore.setState({ activeFolderId: null });
render(<App />);
await screen.findByPlaceholderText(/search connections/i);
resolveSettings({
confirm_before_delete: true,
default_folder_id: "folder-1",
theme: "dark",
font_size: "medium",
default_ports: { postgresql: 5432, mysql: 3306, redis: 6379, sqlite: null },
tag_order: null,
table_refresh_rate: 30,
table_page_size: 50,
shortcuts: {},
accent_color: "#2563EB",
editor_font_size: 13,
editor_font_family: "Space Mono",
editor_word_wrap: "off",
editor_minimap: false,
editor_tab_size: 4,
});
await waitFor(() => {
expect(useUiStore.getState().activeFolderId).toBe("folder-1");
});
});
});