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
@@ -0,0 +1,33 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { readTextFile } from "@tauri-apps/plugin-fs";
import { ImportDialog } from "./ImportDialog";
vi.mock("@tauri-apps/plugin-dialog", () => ({ open: vi.fn().mockResolvedValue("/tmp/f.csv") }));
vi.mock("@tauri-apps/plugin-fs", () => ({ readTextFile: vi.fn().mockResolvedValue("a,b\n1,2\n3,4") }));
describe("ImportDialog", () => {
it("parses CSV and stages a bulk_insert change", async () => {
const addChange = vi.fn();
render(<ImportDialog open schema="public" table="t" columns={["a", "b"]} onStage={addChange} onClose={() => {}} />);
fireEvent.click(screen.getByRole("button", { name: /choose file/i }));
await waitFor(() => expect(screen.getByText(/preview/i)).toBeInTheDocument());
fireEvent.click(screen.getByRole("button", { name: /stage import/i }));
await waitFor(() => {
expect(addChange).toHaveBeenCalledWith(expect.objectContaining({
type: "bulk_insert", schema: "public", table: "t",
columns: ["a", "b"],
}));
expect(addChange.mock.calls[0][0].rows.length).toBe(2);
});
});
it("rejects files over the row cap", async () => {
vi.mocked(readTextFile).mockResolvedValue("a\n" + "1\n".repeat(100_001));
const onStage = vi.fn();
render(<ImportDialog open schema="public" table="t" columns={["a"]} onStage={onStage} onClose={() => {}} />);
fireEvent.click(screen.getByRole("button", { name: /choose file/i }));
await waitFor(() => expect(screen.getByText(/limit/i)).toBeInTheDocument());
expect(onStage).not.toHaveBeenCalled();
});
});