From 32a7b852ecf025067187764ba8e0f9ff8c15b2ab Mon Sep 17 00:00:00 2001 From: "Adrian Alfred C. Bonpin" Date: Sat, 8 Aug 2026 00:09:36 +0800 Subject: [PATCH] v0.7.8: MySQL/SQLite backup-sync, Excel export, query cancel, settings import/export, Windows title-bar fix, SQLite table editor (#16) * [P1-T1] feat(backup): MySQL/SQLite backup models + db_type on SyncOptions (Task 1.1) * [P1-T2] feat: enable tools(mysql,sqlite) + tableManagement(sqlite) + add fflate (Task 1.2) * [P1-T3] feat(cancel): CancelHandle enum + CancelRegistry (Task 1.3) * [P2-T1] feat(export): hand-rolled XLSX writer with inline-string cells (Task 2.1) * [P2-T2] feat(backup): SQLite .dump/restore/sync core, fail-closed virtual tables (Task 2.2) * [P2-T3] feat(backup): MySQL dump/restore/sync arg builders + tool resolution (Task 2.3) * [P2-T4] feat(settings): pure import validator + SettingsExport envelope + Store.apply_settings (Task 2.4) * [P2-T5] feat(table-editor): SQLite create/diff/rebuild SQL generation, fail-closed AUTOINCREMENT (Task 2.5) * [P3-T1] feat(commands): MySQL/SQLite backup + settings export/import commands + wrappers (Task 3.1) * [P3-T2] feat(cancel): capture cancel primitives at connect; cancel_query command; SQLite interrupt test (Task 3.2) * [P3-T3] feat(table-editor): SQLite object-change dispatch + execute_change Ddl/RebuildTable (Task 3.3) * [P4-T1] feat(tools): DB-aware backup/restore/sync pages (Task 4.1) * [P4-T2] feat(export): xlsx export in grid toolbar + overflow menu (Task 4.2) * [P4-T3] feat(query): cancel button wired to cancelQuery (Task 4.3) * [P4-T4] feat(settings): export/import buttons + validation gate (Task 4.4) * [P4-T5] fix(ui): gate macOS overlay drag strip to macOS only (Task 4.5) * [P4-T6] feat(table-editor): SQLite Create/Edit Table mode (Task 4.6) * [P5-T1] chore: bump 0.7.7 -> 0.7.8 + README/AGENTS/ROADMAP status (Task 5.1) * [P5-T2] build(release): bundle mariadb-dump + mariadb client (system-first fallback) (Task 5.2) * fix(cancel): propagate cancellations past wrapped->raw fallback (SQLite/PG/MySQL) + MySQL CONNECTION_ID cast * fix(export): Excel export from overflow menu did nothing + add export success/error toasts * fix(export): tree kebab export fetches table data when rows not loaded * docs(readme): surface v0.7.8 features (MySQL/SQLite backup-sync, Excel export, query cancel, SQLite table editor, settings import/export) --- .github/workflows/release.yml | 108 +++ AGENTS.md | 13 +- README.md | 55 +- ROADMAP.md | 12 +- bun.lock | 3 + package.json | 3 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/resources/mysql_tools/README.md | 13 + src-tauri/src/cancel.rs | 164 +++++ src-tauri/src/commands/backup.rs | 621 ++++++++++++++++++ src-tauri/src/commands/backup.test.rs | 114 ++++ src-tauri/src/commands/db_viewer.rs | 55 +- src-tauri/src/commands/objects.rs | 382 +++++++---- src-tauri/src/commands/objects.test.rs | 49 +- src-tauri/src/commands/query.rs | 261 +++++++- src-tauri/src/commands/settings.rs | 90 +++ src-tauri/src/db/object_ddl.rs | 152 +++++ src-tauri/src/lib.rs | 16 + src-tauri/src/models/backup.rs | 125 ++++ src-tauri/src/models/settings.rs | 7 + src-tauri/src/store/mod.rs | 15 + src-tauri/tauri.conf.json | 4 +- src/App.tsx | 4 +- src/components/db-viewer/BackupPage.test.tsx | 54 ++ src/components/db-viewer/BackupPage.tsx | 492 ++++++++++---- src/components/db-viewer/DbViewerScreen.tsx | 18 +- .../db-viewer/DbViewerSidebar.test.tsx | 8 +- src/components/db-viewer/RestorePage.test.tsx | 67 ++ src/components/db-viewer/RestorePage.tsx | 363 ++++++---- src/components/db-viewer/SyncDialog.tsx | 1 + src/components/db-viewer/SyncPage.test.tsx | 62 ++ src/components/db-viewer/SyncPage.tsx | 279 +++++--- .../db-viewer/TableControls.test.tsx | 26 + src/components/db-viewer/TableControls.tsx | 18 +- .../db-viewer/TableOverflowMenu.test.tsx | 36 + .../db-viewer/TableOverflowMenu.tsx | 44 +- .../db-viewer/objects/TableForm.test.tsx | 115 ++++ .../db-viewer/objects/TableForm.tsx | 51 +- src/components/editor/QueryToolbar.test.tsx | 26 + src/components/editor/QueryToolbar.tsx | 24 +- .../settings/GeneralSettingsTab.test.tsx | 166 +++++ .../settings/GeneralSettingsTab.tsx | 79 +++ src/lib/bundle-config.test.ts | 6 +- src/lib/commands.test.ts | 9 + src/lib/commands.ts | 44 +- src/lib/dbCapabilities.test.ts | 36 +- src/lib/dbCapabilities.ts | 4 +- src/lib/docs-coverage.test.ts | 19 +- src/lib/exportData.test.ts | 22 + src/lib/exportData.ts | 12 + src/lib/platform.test.ts | 23 + src/lib/platform.ts | 9 + src/lib/settingsImport.test.ts | 39 ++ src/lib/settingsImport.ts | 55 ++ src/lib/types.ts | 44 ++ src/lib/version.test.ts | 4 +- src/lib/xlsx.test.ts | 52 ++ src/lib/xlsx.ts | 63 ++ 59 files changed, 4068 insertions(+), 572 deletions(-) create mode 100644 src-tauri/resources/mysql_tools/README.md create mode 100644 src-tauri/src/cancel.rs create mode 100644 src/components/db-viewer/RestorePage.test.tsx create mode 100644 src/components/db-viewer/SyncPage.test.tsx create mode 100644 src/components/settings/GeneralSettingsTab.test.tsx create mode 100644 src/lib/platform.test.ts create mode 100644 src/lib/platform.ts create mode 100644 src/lib/settingsImport.test.ts create mode 100644 src/lib/settingsImport.ts create mode 100644 src/lib/xlsx.test.ts create mode 100644 src/lib/xlsx.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d30311b..d61af24 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -167,6 +167,114 @@ jobs: "$OUT/${b}${BIN_EXT}" --version >/dev/null 2>&1 || { echo "$b failed to run from resource dir"; exit 1; } done + - name: Build MariaDB client tools (bundled) + shell: bash + run: | + set -euo pipefail + MARIADB_VER="11.4.5" + OUT="${GITHUB_WORKSPACE}/src-tauri/resources/mysql_tools" + mkdir -p "$OUT" + case "${{ matrix.platform }}" in + ubuntu-22.04) + sudo apt-get update -y + sudo apt-get install -y cmake build-essential libssl-dev libzstd-dev pkg-config + git clone --depth 1 --branch "mariadb-${MARIADB_VER}" https://github.com/MariaDB/server.git /tmp/mariadb-server + cd /tmp/mariadb-server + # Client-only build: wolfSSL + zlib are vendored, so the bundled + # clients need no system OpenSSL on the user's machine. + cmake -DCMAKE_BUILD_TYPE=Release \ + -DWITHOUT_SERVER=ON \ + -DWITHOUT_TOKUDB=1 \ + -DWITHOUT_ROCKSDB=1 \ + -DWITHOUT_MROONGA=1 \ + -DWITHOUT_SPIDER=1 \ + -DWITHOUT_SEQUENCE=1 \ + -DWITH_UNIT_TESTS=OFF \ + -DWITH_SSL=bundled \ + -DWITH_ZLIB=bundled . + make -j"$(nproc)" mariadb-dump mariadb + # Clients build into client/ (stable across MariaDB 10.x/11.x); + # fall back to a broad search if a future version moves them. + for b in mariadb-dump mariadb; do + src=$(find client -maxdepth 1 -type f -name "$b" -print -quit 2>/dev/null || true) + test -n "$src" || src=$(find . -type f -name "$b" -print -quit || true) + test -n "$src" || { echo "build produced no $b binary"; exit 1; } + cp "$src" "$OUT"/ + done + # Print the clients' shared-library deps for the first CI run. + # TODO(RELEASE): if the --version smoke check below fails with a + # shared-library error, the clients linked a shared libmariadb/ + # libedit — copy it into "$OUT" and patchelf --set-rpath '$ORIGIN' + # exactly like the pg_tools step does for libpq. + ldd "$OUT"/mariadb-dump || true + ;; + macos-latest|macos-15-intel) + git clone --depth 1 --branch "mariadb-${MARIADB_VER}" https://github.com/MariaDB/server.git /tmp/mariadb-server + cd /tmp/mariadb-server + if [ "$(uname -m)" = arm64 ]; then + SSL_DIR="/opt/homebrew/opt/openssl" + else + SSL_DIR="/usr/local/opt/openssl" + fi + [ -d "$SSL_DIR" ] || brew install openssl + cmake -DCMAKE_BUILD_TYPE=Release \ + -DWITHOUT_SERVER=ON \ + -DWITHOUT_TOKUDB=1 \ + -DWITHOUT_ROCKSDB=1 \ + -DWITHOUT_MROONGA=1 \ + -DWITHOUT_SPIDER=1 \ + -DWITHOUT_SEQUENCE=1 \ + -DWITH_UNIT_TESTS=OFF \ + -DWITH_SSL="$SSL_DIR" \ + -DWITH_ZLIB=bundled . + make -j"$(sysctl -n hw.ncpu)" mariadb-dump mariadb + for b in mariadb-dump mariadb; do + src=$(find client -maxdepth 1 -type f -name "$b" -print -quit 2>/dev/null || true) + test -n "$src" || src=$(find . -type f -name "$b" -print -quit || true) + test -n "$src" || { echo "build produced no $b binary"; exit 1; } + cp "$src" "$OUT"/ + done + # TODO(RELEASE): if the --version smoke check below fails with a + # dyld error, rewrite the lib dependency to @loader_path like the + # pg_tools step does for libpq (see also the Linux shared-lib note). + otool -L "$OUT"/mariadb-dump || true + ;; + windows-latest) + URL="https://archive.mariadb.org/mariadb-${MARIADB_VER}/winx64-packages/mariadb-${MARIADB_VER}-winx64.zip" + curl -fsSL "$URL" -o /tmp/mariadb.zip + # TODO(RELEASE): pin the sha256 printed on the first CI run, then + # uncomment the check so later releases verify the download. + # EXPECTED="" + # echo "$EXPECTED /tmp/mariadb.zip" | sha256sum -c - + sha256sum /tmp/mariadb.zip + unzip -o /tmp/mariadb.zip -d /tmp/mariadb + WINROOT="/tmp/mariadb/mariadb-${MARIADB_VER}-winx64" + cp "$WINROOT"/bin/mariadb-dump.exe "$WINROOT"/bin/mariadb.exe "$OUT"/ + # The clients' only runtime dependency is the client library + # (lib/libmariadb.dll); the winx64 package statically links + # wolfSSL, so there are no OpenSSL DLLs to bundle. bin/*.dll is + # just the embedded server.dll, which the client tools don't need. + cp "$WINROOT"/lib/libmariadb.dll "$OUT"/ + ;; + esac + (cd "$OUT" && sha256sum * | tee checksums.txt) + # Resolve the per-platform binary suffix WITHOUT a command + # substitution (see the pg_tools step for why). + if [ "${{ matrix.platform }}" = windows-latest ]; then + BIN_EXT=".exe" + else + BIN_EXT="" + fi + for b in mariadb-dump mariadb; do + f="$OUT/${b}${BIN_EXT}" + test -f "$f" || { echo "missing $f"; exit 1; } + done + # Sanity: every tool must run (loader path is correct) — this catches + # a missing shared library before we ship a broken bundle. + for b in mariadb-dump mariadb; do + "$OUT/${b}${BIN_EXT}" --version >/dev/null 2>&1 || { echo "$b failed to run from resource dir"; exit 1; } + done + - name: Build and upload to GitHub Release uses: tauri-apps/tauri-action@v0 env: diff --git a/AGENTS.md b/AGENTS.md index 1764fbe..0ac162b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -166,7 +166,7 @@ Cut a release by tagging the **`prod`** branch once the PR is merged — `git ta **Before tagging**, keep everything in sync: - Version number across `package.json`, `src-tauri/Cargo.toml`, and `src-tauri/tauri.conf.json` - `src/lib/version.test.ts` and `src/lib/docs-coverage.test.ts` if they assert the version -- **README download links are static (versioned)** — both download tables (top **Download** section + **Which file should I download?**) link directly to the release-tag assets (`releases/download/v0.7.7/`). tauri-action uses default versioned asset names (`Gridline__aarch64.dmg`, `Gridline--1.x86_64.rpm`, etc.) — update BOTH tables to the new names on every release (see the MAINTENANCE comment in README.md). +- **README download links are static (versioned)** — both download tables (top **Download** section + **Which file should I download?**) link directly to the release-tag assets (`releases/download/v0.7.8/`). tauri-action uses default versioned asset names (`Gridline__aarch64.dmg`, `Gridline--1.x86_64.rpm`, etc.) — update BOTH tables to the new names on every release (see the MAINTENANCE comment in README.md). - **Bundled pg tools:** `tauri.conf.json` `bundle.resources` lists `resources/pg_tools/*`; the `release.yml` matrix builds/downloads + checksum-verifies the static binaries before the Tauri build step. ### Adding a Tauri Command @@ -270,7 +270,8 @@ Planned work is prioritized in the [Project Roadmap](./ROADMAP.md) (source of tr | Column show/hide | ✅ | Toggle visibility per column | | Column resize (drag handle) | ✅ | Double-click to auto-fit | | Row selection (checkboxes + select all) | ✅ | Bulk copy (JSON/CSV/SQL) and delete | -| Export toolbar (JSON, CSV, SQL, Markdown) | ✅ | Client-side Blob download of visible rows | +| Export toolbar (JSON, CSV, SQL, Markdown, Excel (.xlsx)) | ✅ | Client-side Blob download of visible rows; Excel (.xlsx) via client-side workbook generation (v0.7.8) | +| Excel (.xlsx) export | ✅ | Client-side .xlsx export of visible rows alongside JSON/CSV/SQL/Markdown (v0.7.8) | | Auto-refresh timer | ✅ | Configurable interval in settings | | Changes queue (INSERT, UPDATE, DELETE, bulk_insert, empty_table, drop_table) | ✅ | Stage → **Commit All**. Tab bar **Changes** button (amber border + count badge when pending) toggles a **popover** anchored to it: header with **Visual/SQL** toggle (cards showing op badge + table + description + per-change **Revert**, or a generated-SQL preview via `buildChangeSql`), footer **Clear All** + **Commit All (N)** with **⌘S/Ctrl+S** shortcut. Committed cards show a green ✓ (failed ✗); committing `drop_table` auto-closes open tabs of that table | | Auto schema-tree refresh | ✅ | Tree auto-refreshes after a successful schema-modifying query run (`CREATE`/`DROP`/`ALTER`/`TRUNCATE` via `isSchemaModifyingQuery`) and after committing schema-modifying queue changes — `drop_table`, schema-modifying `ddl` (e.g. `CREATE TABLE`), and `rebuild_table` — no manual refresh needed | @@ -296,6 +297,7 @@ Planned work is prioritized in the [Project Roadmap](./ROADMAP.md) (source of tr | Table options | ✅ | Tablespace picker (non-system, from `pg_tablespace`) + row-level security toggle in the Options section | | Roles & grants management | ✅ | Objects → Roles: list non-system roles (attributes incl. connection limit — `rolconnlimit::int8` cast fix), role detail with attribute grid + memberships + privilege explorer; create/edit/drop roles staged through the queue | | Role privilege explorer | ✅ | Per-role privileges grouped by object class (tables/sequences/routines/schemas/databases) with collapsible sections — collapsed shows first 5 + fade, expand shows all, chevron hidden when ≤5; GRANT/REVOKE composer (object class + schema + name, WITH GRANT OPTION); aclexplode-based queries (`role_sequence_grants` removed in PG 15, `schema_privileges` never existed) | +| SQLite table editor (Create/Edit Table) | ✅ | Visual Create Table / Edit Table extended to SQLite: SQLite-aware type mapping (`INTEGER PRIMARY KEY AUTOINCREMENT` instead of `serial`, `TEXT`/`REAL`/`BLOB`) and ALTER TABLE limits respected (v0.7.8) | ### Object Explorer (non-table objects) | Feature | Status | Details | @@ -329,6 +331,7 @@ Planned work is prioritized in the [Project Roadmap](./ROADMAP.md) (source of tr | Query history / recent queries | ✅ | v5 `query_history` table + v6 `favorite` column; consecutive-identical dedup + retention pruning (500/connection); `get_query_history`/`clear_query_history`/`set_history_favorite` commands; **QueryHistoryDropdown** in the query toolbar (load / run / favorite / clear, lazy fetch, loading + error states) | | SQL autocomplete (keywords, tables, columns) | ✅ | Completion provider in `src/lib/monacoSetup.ts` backed by `src/lib/sqlCompletion.ts` (pure, unit-tested): keywords (~60) + table names from the active schema; typing `table.` or `schema.table.` suggests that table's columns (introspected via `get_schema_graph`, cached per schema in memory, `incomplete: true` warm-up on first use) | | Multiple result sets | ❌ | | +| Cancel long-running queries | ✅ | Per-connection cancel from the query toolbar (`pg_cancel_backend` / MySQL `KILL` / SQLite interruption) instead of waiting or killing the app (v0.7.8) | | Saved queries (named, organized) | ✅ | v6 `queries` table (nullable `connection_id` for global queries, `folder` field, `ON DELETE CASCADE`); `save_query`/`get_saved_queries`/`update_saved_query`/`delete_saved_query` commands with validation (name ≤200, folder ≤100, text ≤1MB); **SaveQueryDialog** (name + folder, empty-name guard); managed in the Queries view Saved tab | | Query favorites / pinning | ✅ | Star toggle per history entry via `set_history_favorite`; favorites-only filter in the Queries view History tab | | Queries view | ✅ | Two-pane layout following Explorer: left sidebar (Explorer-styled header — Queries title, History/Saved dropdown, favorites/clear/search icons, animated search) scoped to the **current connection**, right side reuses the shared tabbed query workspace (TabBar + toolbar + editor + results); clicking a history/saved row loads it into the editor | @@ -344,7 +347,8 @@ Planned work is prioritized in the [Project Roadmap](./ROADMAP.md) (source of tr | DB-to-DB sync | ✅ | In-page view: source/target connection pickers, schema dropdown, pipe-based pg_dump → pg_restore. **pg_restore side passes `--clean --if-exists`**, so sync works into a non-empty target (UI already requires destructive-overwrite confirmation). Core logic in headless-testable `run_db_sync` | | Unified Tools view | ✅ | Backup / Restore / DB Sync merged into a single **Tools** nav item; operation-switcher dropdown in the view toolbar, existing forms rendered below | | Bundled PostgreSQL client tools | ✅ | Static pg_dump/pg_restore/psql shipped as Tauri resources; system-first, bundled-fallback resolution via resource_dir (v0.7.5) | -| SQLite .dump | ❌ | | +| SQLite .dump (backup/restore) | ✅ | Backup a SQLite database to a portable SQL dump and restore it back, matching the pg_dump UX (v0.7.8) | +| Backup / Restore / Sync for MySQL & SQLite | ✅ | MySQL backup/restore via mysqldump (system-first); DB-to-DB sync extended beyond PostgreSQL (v0.7.8) | | Table structure export (DDL) | ❌ | | ### Settings @@ -352,6 +356,7 @@ Planned work is prioritized in the [Project Roadmap](./ROADMAP.md) (source of tr | :--- | :---: | :--- | | Settings screen (redesigned) | ✅ | DB-viewer-styled shell: icon+text sidebar (Back on top, accent background), header shows the active tab, border-sharp sections with gap-spaced rows (no cards), Back returns to the view it was opened from (push/pop in `uiStore`) | | Theme (dark/light/system) | ✅ | Applied live via a `.light` class on the document root (dark-first base palette); "system" follows the OS via `matchMedia` and live-updates; native window chrome synced through Tauri `setTheme`/`setBackgroundColor` with a macOS **Overlay** titlebar (in-flow drag strip) | +| Windows/Linux title bar fix | ✅ | macOS-only overlay drag strip (`h-7` in `App.tsx`) gated to macOS; Windows/Linux use the native title bar for dragging (v0.7.8) | | Font size | ✅ | rem scale via `data-font-size` on the root (`small`/`medium`/`large`) | | Accent color | ✅ | 10-preset circle palette in General → Appearance; applied via `--color-accent` on the root; hover/muted shades derive from it via `color-mix` | | Default folder for new connections | ✅ | Honored on startup — Home opens into `default_folder_id` unless the user has already navigated | @@ -364,7 +369,7 @@ Planned work is prioritized in the [Project Roadmap](./ROADMAP.md) (source of tr | More keyboard shortcuts | ❌ | Only 2 configurable actions | | Editor settings | ✅ | Five options wired to the settings store + live Monaco `updateOptions` | | SSH key management | ❌ | Only path inputs, no key file reading | -| Settings export/import | ❌ | | +| Settings export/import | ✅ | Export/import settings (theme, accent, editor options, page sizes, defaults) as a JSON file (v0.7.8) | ### Demo & Onboarding | Feature | Status | Details | diff --git a/README.md b/README.md index e08e84c..9b279c8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

A lightweight, open-source database GUI for PostgreSQL, MySQL, SQLite, and Redis.
- Unlimited connections, tabs, and saved queries — with first-class pg_dump, pg_restore, and DB-to-DB sync. + Unlimited connections, tabs, and saved queries — with first-class backup, restore, and DB-to-DB sync for PostgreSQL, MySQL, and SQLite.

@@ -30,21 +30,21 @@ ## Download -Grab the installer for your OS from the [latest release](https://github.com/AdrianBonpin/gridline/releases/latest) — the links below point at the current release (**v0.7.7**): +Grab the installer for your OS from the [latest release](https://github.com/AdrianBonpin/gridline/releases/latest) — the links below point at the current release (**v0.7.8**): | OS | Architecture | Download | | :--------------------------- | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------- | -| **macOS** | Apple Silicon (M1/M2/M3/M4…) | [Gridline_0.7.7_aarch64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.7/Gridline_0.7.7_aarch64.dmg) | -| **macOS** | Intel | [Gridline_0.7.7_x64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.7/Gridline_0.7.7_x64.dmg) | -| **Windows** | x64 | [Gridline_0.7.7_x64-setup.exe](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.7/Gridline_0.7.7_x64-setup.exe) | -| **Debian / Ubuntu** | amd64 | [Gridline_0.7.7_amd64.deb](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.7/Gridline_0.7.7_amd64.deb) | -| **Fedora / RHEL / openSUSE** | x86_64 | [Gridline-0.7.7-1.x86_64.rpm](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.7/Gridline-0.7.7-1.x86_64.rpm) | -| **Other Linux** | amd64 | [Gridline_0.7.7_amd64.AppImage](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.7/Gridline_0.7.7_amd64.AppImage) | +| **macOS** | Apple Silicon (M1/M2/M3/M4…) | [Gridline_0.7.8_aarch64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.8/Gridline_0.7.8_aarch64.dmg) | +| **macOS** | Intel | [Gridline_0.7.8_x64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.8/Gridline_0.7.8_x64.dmg) | +| **Windows** | x64 | [Gridline_0.7.8_x64-setup.exe](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.8/Gridline_0.7.8_x64-setup.exe) | +| **Debian / Ubuntu** | amd64 | [Gridline_0.7.8_amd64.deb](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.8/Gridline_0.7.8_amd64.deb) | +| **Fedora / RHEL / openSUSE** | x86_64 | [Gridline-0.7.8-1.x86_64.rpm](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.8/Gridline-0.7.8-1.x86_64.rpm) | +| **Other Linux** | amd64 | [Gridline_0.7.8_amd64.AppImage](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.8/Gridline_0.7.8_amd64.AppImage) | > Not sure if your Mac is Intel or Apple Silicon? See [Which file should I download?](#which-file-should-i-download) below. All installers are **unsigned** — see the [notes](#which-file-should-i-download) on first-launch warnings.