v0.7.0: New Connection screen revamp + full MySQL DB viewer (#10)

* docs: correct competitor comparison for DB Pro, Beekeeper, TablePlus

Research-verified the 'Why Gridline vs the alternatives' claims against
vendor docs, pricing pages, GitHub, and release notes (May 2026):

- DB Pro is an Electron app (founder-confirmed), not native; add
  TablePlus column to the comparison table
- Fix wrong cells: DB Pro has query/dashboard folders + table tags and
  CSV/JSON export on the free tier; object-explorer depth corrected for
  DB Pro (tables/views/indexes/enums) and Beekeeper
  (tables/views/routines/triggers)
- Reframe differentiators: unlimited-everything framing dropped for
  Beekeeper (free tier is already unlimited on tabs/connections/queries);
  keep DB-to-DB sync as the genuinely unique feature
- Add a dated 'Competitor reality check' section to AGENTS.md so future
  edits don't re-assert inaccurate claims

* docs: add project roadmap, link it from README and AGENTS

New ROADMAP.md is the source of truth for planned work, reflecting the
in-flight v0.7.0 connection-screen-revamp spec (new-connection flow,
full MySQL DB viewer, capability gating, Supabase/Neon presets,
SQLite path mode, tag overflow scroll, styling sweep).

Next-up scope: PostgreSQL object management CRUD with companion
features (schema CRUD, global object search, copy-as-DDL, object
dependencies) and an admin follow-up (users/roles/grants,
VACUUM/ANALYZE/REINDEX). MySQL Objects view explicitly deferred.

Queue: Redis browsing, MariaDB/TimescaleDB, PlanetScale/Turso,
query workbench upgrades (multiple result sets, query cancel,
result streaming, visual query builder), schema/data tooling,
SQLite .dump, schema diff, more export formats.

Planned: BYOK AI, website & docs, rolling UI/UX polish (incl.
onboarding tour, settings import/export, SSH key management).

README roadmap section now links to ROADMAP.md; AGENTS.md Related
Documents + Implementation Status reference it and the v0.7.0 spec.

* docs: release notes reference prod as the production branch

The repo's production branch is prod (feature branches merge back to
prod), not main. Update the release-cut instructions in the README and
the trigger comment in release.yml.

* docs: add robust bug report issue template

Structured .github/ISSUE_TEMPLATE/bug_report.md covering environment
(OS, Gridline version, install type, DB type/version, hosted provider,
connection method incl. SSH/TLS/socket), steps to reproduce, expected vs
actual, screenshots, logs, impact, and workarounds — plus a duplicate
checklist and secrets-redaction note. Referenced from the README
Contributing section.

* docs: drop in-flight branch mention from roadmap; remove unused starter assets

- ROADMAP.md no longer references the in-flight feature branch/spec
  (removed at the end anyway when the branch PRs into prod)
- Remove unused Vite/Tauri starter SVGs from public/ (no favicon or
  asset references anywhere in the app)

* test: fix stale README comparison-table regex in docs-coverage (5-col table)

* feat: shared INPUT_ROUNDING constant + bump to v0.7.0 (Task 1.1)

* fix: map SQLite file path to host field + provider host detection (Task 1.2)

* test: bump version expectation to 0.7.0 (Task 1.1 follow-up)

* feat: db capability matrix for DB viewer gating (Task 1.3)

* feat: provider tab definitions, Supabase/Neon icons + setup guides (Task 1.4)

* feat(rust): MySQL SQL builders + identifier quoting (Task 2.1)

* chore(rust): sync Cargo.lock to gridline 0.7.0

* feat(rust): MySQL db_connect (SSL + SSH tunnel) + pool variant (Tasks 2.2-2.3)

* feat(rust): MySQL execute_query with wrapped pagination + raw fallback (Task 2.4)

* feat(rust): MySQL introspection + changes-queue editing + DDL (Task 2.5)

* fix(ui): show table toolbar immediately while tab is still loading first data

* feat: gate DB viewer sidebar nav by db capabilities (Task 3.1)

* feat: guard DB viewer views by capability + Redis unsupported state (Task 3.2)

* feat(ui): 2-column provider tab grid (Task 4.1)

* feat(ui): collapsible Supabase/Neon setup guide (Task 4.2)

* feat(ui): SQLite file-path input with Browse (Task 4.3)

* feat(ui): connection metadata row (label + tags/env/folder) (Task 4.4)

* feat(ui): rework GeneralTab (URI + OR + manual) + reduce Detailed form tabs (Task 4.5)

* feat(ui): NewConnectionScreen two-stage flow; remove SimpleConnectionForm (Task 5.1)

* feat(ui): scroll connection-card tag row past 3 tags (Task 5.2)

* style: sweep form controls from rounded-full to rounded-lg (Task 5.3)

* feat(ui): EditConnectionModal parity + managed-preset SSL hint (Task 5.4)

* fix(rust): decode MySQL VARBINARY metadata columns (information_schema/SHOW) as strings

* test: full suite green for v0.7.0 connection revamp (Task 5.5)

* feat(ui): schema dropdown + tables tree loading state while schema tree fetches

* docs: update AGENTS/README/ROADMAP for v0.7.0 (connection revamp, MySQL viewer, gating)
This commit is contained in:
2026-08-04 21:13:20 +08:00
committed by GitHub
parent 002d8345ae
commit 1195d2c3f9
67 changed files with 3150 additions and 649 deletions
@@ -8,7 +8,9 @@ import {
} from "./DbViewerScreen";
import { useDbViewerStore } from "../../stores/dbViewerStore";
import { useUiStore } from "../../stores/uiStore";
import { useConnectionStore } from "../../stores/connectionStore";
import * as commands from "../../lib/commands";
import type { Connection } from "../../lib/types";
vi.mock("../../hooks/useDbConnection", () => ({
useDbConnection: (_connectionId: string) => ({
@@ -92,6 +94,7 @@ const mockQueryResult = {
describe("DbViewerScreen", () => {
beforeEach(() => {
useDbViewerStore.getState().reset();
useConnectionStore.setState({ connections: [] });
useDbViewerStore.setState({
databases: ["mydb"],
schemas: ["public"],
@@ -596,6 +599,46 @@ describe("DbViewerScreen", () => {
);
});
it("shows the table toolbar while a table tab is still loading its first data", () => {
useDbViewerStore.setState({
tabs: [
{
id: "tab-loading",
schema: "public",
table: "users",
page: 1,
pageSize: 50,
loading: true,
error: null,
data: null, // first fetch still in flight
filterRules: [],
sortRules: [],
hiddenColumns: [],
smartSortApplied: false,
tabType: "table",
},
],
activeTabId: "tab-loading",
});
render(
<DbViewerScreen
connectionId="c1"
onHome={() => {}}
onSettings={() => {}}
/>,
);
// Toolbar must be visible immediately while data is still loading,
// so the user sees the loading state instead of an empty pane.
expect(
screen.getByLabelText(/refresh table/i),
).toBeInTheDocument();
expect(
screen.getByLabelText(/column filters/i),
).toBeInTheDocument();
});
it("disables Insert Row for a materialized-view tab", async () => {
useDbViewerStore.setState({
tables: [
@@ -821,4 +864,88 @@ describe("DbViewerScreen", () => {
const cols = [{ name: "id", data_type: "integer" }];
expect(pickDisplayColumn(cols, "id")).toBe("id");
});
it("shows a Redis unsupported state when a redis connection is active", () => {
useConnectionStore.setState({
connections: [
{
id: "redis-1",
name: "Redis",
db_type: "redis",
host: "localhost",
port: 6379,
username: null,
folder_id: null,
keychain_ref: null,
tag_ids: [],
favorite: false,
created_at: "2024-01-01T00:00:00Z",
updated_at: "2024-01-01T00:00:00Z",
} as Connection,
],
});
render(
<DbViewerScreen
connectionId="redis-1"
onHome={() => {}}
onSettings={() => {}}
/>,
);
expect(
screen.getByText(/redis browsing isn't supported/i),
).toBeInTheDocument();
});
it("guards the Objects view for MySQL (capability false)", () => {
useConnectionStore.setState({
connections: [
{
id: "c1",
name: "Postgres",
db_type: "postgresql",
host: "localhost",
port: 5432,
username: "user",
folder_id: null,
keychain_ref: null,
tag_ids: [],
favorite: false,
created_at: "2024-01-01T00:00:00Z",
updated_at: "2024-01-01T00:00:00Z",
} as Connection,
{
id: "mysql-1",
name: "MySQL",
db_type: "mysql",
host: "localhost",
port: 3306,
username: "user",
folder_id: null,
keychain_ref: null,
tag_ids: [],
favorite: false,
created_at: "2024-01-01T00:00:00Z",
updated_at: "2024-01-01T00:00:00Z",
} as Connection,
],
});
const { rerender } = render(
<DbViewerScreen
connectionId="c1"
onHome={() => {}}
onSettings={() => {}}
/>,
);
fireEvent.click(screen.getByLabelText(/objects/i));
rerender(
<DbViewerScreen
connectionId="mysql-1"
onHome={() => {}}
onSettings={() => {}}
/>,
);
expect(
screen.getByText(/objects is unsupported for mysql/i),
).toBeInTheDocument();
});
});