From f8321d926fa33dc3ba57d43719e903e16eae452c Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Tue, 1 Sep 2026 05:13:31 +0800 Subject: [PATCH 1/8] fix: refresh recent connections once connections are loaded The Recent strip was computed from get().connections which is still empty while loadAll() is in flight on app startup, so cached recent IDs got filtered out and the strip could appear empty/flaky until a remount. loadAll now refreshes recents after connections populate. --- desktop/src/stores/connectionStore.test.ts | 27 ++++++++++++++++++++++ desktop/src/stores/connectionStore.ts | 3 +++ 2 files changed, 30 insertions(+) diff --git a/desktop/src/stores/connectionStore.test.ts b/desktop/src/stores/connectionStore.test.ts index e134110..f0cfc0d 100644 --- a/desktop/src/stores/connectionStore.test.ts +++ b/desktop/src/stores/connectionStore.test.ts @@ -225,6 +225,33 @@ describe("favorites / recents / move-selection", () => { expect(commands.recordRecentConnection).toHaveBeenCalledWith("c1"); }); + it("loadAll refreshes recents once connections are known (startup race fix)", async () => { + const conn = makeConn({ id: "c1", name: "Prod" }); + vi.spyOn(commands, "getConnections").mockResolvedValue([conn]); + vi.spyOn(commands, "getFolders").mockResolvedValue([]); + vi.spyOn(commands, "getTags").mockResolvedValue([]); + vi.spyOn(commands, "getRecentConnections").mockResolvedValue([ + { connection_id: "c1", opened_at: "2026-08-02T00:00:00Z" }, + ]); + + // Simulate the app-shell mount: loadAll fires and populates connections. + await useConnectionStore.getState().loadAll(); + + // The recent strip must now show the connection that was opened previously. + expect(commands.getRecentConnections).toHaveBeenCalledWith(8); + expect(useConnectionStore.getState().recent).toEqual([conn]); + }); + + it("loadRecent maps cached recent ids against the current connection list", async () => { + const conn = makeConn({ id: "c1", name: "Prod" }); + useConnectionStore.setState({ connections: [conn] }); + vi.spyOn(commands, "getRecentConnections").mockResolvedValue([ + { connection_id: "c1", opened_at: "2026-08-02T00:00:00Z" }, + ]); + await useConnectionStore.getState().loadRecent(); + expect(useConnectionStore.getState().recent).toEqual([conn]); + }); + describe("duplicateConnection", () => { it("copies fields with a '(copy)' name and favorite false", async () => { const src = makeConn({ id: "c1", name: "Prod", folder_id: "f1", tag_ids: ["t1"], favorite: true }); diff --git a/desktop/src/stores/connectionStore.ts b/desktop/src/stores/connectionStore.ts index 04e28b7..3e856c2 100644 --- a/desktop/src/stores/connectionStore.ts +++ b/desktop/src/stores/connectionStore.ts @@ -46,6 +46,9 @@ export const useConnectionStore = create((set, get) => ({ set((s) => ({ connections: [...s.connections].sort((a, b) => Number(b.favorite) - Number(a.favorite)) })); // Also load tag order get().loadTagOrder(); + // Refresh recents once connections are known so the recent strip is not + // computed against an empty list (avoids a startup race with loadRecent). + get().loadRecent().catch(() => {}); } catch (e) { set({ loading: false, error: e instanceof Error ? e.message : String(e) }); } -- 2.54.0 From 46e0dc2cee80a2dfa6c31d8704f0ba1788341823 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Tue, 1 Sep 2026 05:17:47 +0800 Subject: [PATCH 2/8] fix: bind edited PG cell values via text wire format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frontend commits edited cell values as plain strings (and PK locators as numbers/booleans), but tokio-postgres' String binds only text-ish types and i64 only int8, so editing integer/boolean/uuid/jsonb/… columns failed with 'cannot convert between the Rust type and the Postgres type'. Introduce PgText — a ToSql impl that accepts any Postgres type and writes the value as UTF-8 in text wire format so the server parses it into the column's own type. NULL binds as SQL NULL. Adds the bytes crate and unit tests. --- desktop/src-tauri/Cargo.lock | 3 +- desktop/src-tauri/Cargo.toml | 1 + desktop/src-tauri/src/commands/db_viewer.rs | 157 +++++++++++++++++--- 3 files changed, 143 insertions(+), 18 deletions(-) diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index b7190d4..aee3a62 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -1783,8 +1783,9 @@ dependencies = [ [[package]] name = "gridline" -version = "0.7.10" +version = "0.7.11" dependencies = [ + "bytes", "chrono", "deadpool-postgres", "indexmap 2.14.0", diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 974c4f2..098e192 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -31,6 +31,7 @@ tauri-plugin-dialog = "2" tauri-plugin-fs = "2" tokio = { version = "1", features = ["full"] } tokio-postgres = { version = "0.7", features = ["with-chrono-0_4", "with-uuid-1", "with-serde_json-1"] } +bytes = "1" sqlx = { version = "0.8", features = ["runtime-tokio", "mysql", "tls-rustls", "json"] } redis = { version = "0.27", features = ["tokio-comp"] } # async-ssh2 is not available at v0.4; using synchronous ssh2 via tokio::task::spawn_blocking per plan's fallback diff --git a/desktop/src-tauri/src/commands/db_viewer.rs b/desktop/src-tauri/src/commands/db_viewer.rs index 7ae09c5..fc976ea 100644 --- a/desktop/src-tauri/src/commands/db_viewer.rs +++ b/desktop/src-tauri/src/commands/db_viewer.rs @@ -8,10 +8,12 @@ use crate::models::db_viewer::{ Change, ColumnInfo, ConstraintInfo, EnumInfo, ExtensionInfo, FunctionInfo, IndexInfo, QueryResult, SequenceInfo, TableInfo, TriggerInfo, }; +use bytes::BytesMut; use std::collections::HashMap; +use std::error::Error; use sqlx::Row; use tauri::State; -use tokio_postgres::types::ToSql; +use tokio_postgres::types::{Format, IsNull, ToSql, Type}; // --------------------------------------------------------------------------- // Helper functions @@ -585,26 +587,75 @@ pub fn build_pg_insert_sql( ) } +/// A value bound to a PostgreSQL parameter in TEXT wire format, accepting any +/// parameter type. +/// +/// The frontend ships edited values as plain strings (and PK locators as +/// numbers/booleans), but binding a native `String` fails for every non-text +/// column type — `String::accepts` only matches `varchar`/`text`/`bpchar`/ +/// `name` — with `error serializing parameter N: cannot convert between the +/// Rust type ... and the Postgres type ...`. Binding a native `i64` is also +/// wrong because `i64::accepts` only matches `int8`. +/// +/// `PgText` accepts *any* parameter type and writes the value as UTF-8 text in +/// text wire format, so PostgreSQL itself parses the literal into the target +/// column type. This works uniformly for `integer`, `bigint`, `numeric`, +/// `boolean`, `uuid`, `json`/`jsonb`, timestamps, enums, etc. — exactly what +/// PostgreSQL does for an `''` in a query. +#[derive(Debug, Clone)] +struct PgText(String); + +impl From<&str> for PgText { + fn from(s: &str) -> Self { + PgText(s.to_string()) + } +} + +impl ToSql for PgText { + fn to_sql( + &self, + _ty: &Type, + out: &mut BytesMut, + ) -> Result> { + out.extend_from_slice(self.0.as_bytes()); + Ok(IsNull::No) + } + + fn accepts(_ty: &Type) -> bool { + true + } + + fn encode_format(&self, _ty: &Type) -> Format { + Format::Text + } + + fn to_sql_checked( + &self, + ty: &Type, + out: &mut BytesMut, + ) -> Result> { + self.to_sql(ty, out) + } +} + /// Convert a JSON value into a boxed PostgreSQL-bindable value. /// -/// Maps common JSON types to `tokio_postgres::types::ToSql` implementors. -/// Unknown/complex types are stringified as a fallback. +/// Every non-null value is bound as a `PgText` (text wire format) so the server +/// parses it into the target column type; SQL `NULL` is bound as `None`. This +/// is deliberately type-agnostic — the caller already knows the column type +/// through the SQL context (e.g. `SET "col" = $1` lets PostgreSQL infer `$1` +/// as `col`'s type). fn pg_box_value(v: &serde_json::Value) -> Box { - match v { - serde_json::Value::Null => Box::new(Option::::None), - serde_json::Value::Bool(b) => Box::new(*b), - serde_json::Value::Number(n) => { - if let Some(i) = n.as_i64() { - Box::new(i) - } else if let Some(f) = n.as_f64() { - Box::new(f) - } else { - Box::new(n.to_string()) - } - } - serde_json::Value::String(s) => Box::new(s.clone()), - other => Box::new(other.to_string()), + if v.is_null() { + return Box::new(Option::::None); } + let text = match v { + serde_json::Value::String(s) => s.clone(), + serde_json::Value::Bool(b) => b.to_string(), + serde_json::Value::Number(n) => n.to_string(), + other => other.to_string(), + }; + Box::new(PgText::from(text.as_str())) } /// Convert a JSON value into a `rusqlite::types::Value` for SQLite binding. @@ -3320,6 +3371,78 @@ mod tests { ); } + // ----------------------------------------------------------------------- + // PgText: universal text-format parameter binding (serialization fix) + // ----------------------------------------------------------------------- + + #[test] + fn pg_text_accepts_any_postgres_type() { + // String::accepts only matches text-ish types, so a plain String can't + // bind to an integer/boolean/uuid/jsonb column. PgText must accept all + // of these (the server parses the text into the column's own type). + for ty in [ + Type::TEXT, + Type::VARCHAR, + Type::INT4, + Type::INT8, + Type::FLOAT8, + Type::NUMERIC, + Type::BOOL, + Type::UUID, + Type::JSON, + Type::JSONB, + Type::TIMESTAMP, + Type::TIMESTAMPTZ, + Type::DATE, + Type::TIME, + ] { + assert!( + PgText::accepts(&ty), + "PgText must accept {:?}", + ty + ); + } + // Sanity: a native String must NOT accept a non-text type (proving the + // root cause this fix addresses). + assert!(!<&str as ToSql>::accepts(&Type::INT4)); + } + + #[test] + fn pg_text_serializes_as_utf8_text() { + use bytes::BytesMut; + use tokio_postgres::types::IsNull; + + let val = pg_box_value(&serde_json::json!("42")); + let mut out = BytesMut::new(); + let isnull = val.to_sql_checked(&Type::INT4, &mut out).unwrap(); + assert!(matches!(isnull, IsNull::No)); + assert_eq!(&out[..], b"42"); + // Text wire format: the bytes are raw UTF-8, not binary-encoded int. + assert!(matches!(val.encode_format(&Type::INT4), Format::Text)); + } + + #[test] + fn pg_text_boxes_null_to_sql_null() { + use bytes::BytesMut; + use tokio_postgres::types::IsNull; + + let val = pg_box_value(&serde_json::Value::Null); + let mut out = BytesMut::new(); + assert!(matches!( + val.to_sql_checked(&Type::TEXT, &mut out).unwrap(), + IsNull::Yes + )); + assert!(out.is_empty()); + } + + #[test] + fn pg_text_stringifies_booleans_and_numbers() { + let b = pg_box_value(&serde_json::json!(true)); + assert!(matches!(b.encode_format(&Type::BOOL), Format::Text)); + let n = pg_box_value(&serde_json::json!(5)); + assert!(matches!(n.encode_format(&Type::INT4), Format::Text)); + } + async fn fresh_pool_manager() -> tokio::sync::Mutex { tokio::sync::Mutex::new(ConnectionPoolManager::new()) } -- 2.54.0 From 4ac738e6e911c7a7316e92a67859fd62f40ebef2 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Tue, 1 Sep 2026 06:26:07 +0800 Subject: [PATCH 3/8] ci(windows): load MSVC dev environment before building rustc resolves link.exe via PATH for the windows-msvc target, but Git for Windows ships GNU coreutils' link.exe which shadows MSVC's linker, breaking every build-script link. Add ilammy/msvc-dev-cmd@v1 before the tauri build so PATH/LIB/INCLUDE point at the real MSVC toolchain (and it fails fast if MSVC is missing instead of at link time). --- .gitea/workflows/release.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index a232f7c..cc6e3a9 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -212,6 +212,16 @@ jobs: cp "$WINROOT"/bin/mariadb-dump.exe "$WINROOT"/bin/mariadb.exe "$OUT"/ cp "$WINROOT"/lib/libmariadb.dll "$OUT"/ + # rustc resolves `link.exe` via PATH for the windows-msvc target, but Git + # for Windows ships GNU coreutils with its own `link.exe` that shadows + # MSVC's linker (`link: extra operand ...`). Load the MSVC dev + # environment so PATH/LIB/INCLUDE point at the real MSVC toolchain. + # Fails fast here (instead of at link time) if the toolchain is missing. + - name: Set up MSVC build environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + - name: Build installers shell: bash run: bun run --filter gridline-desktop tauri build -- 2.54.0 From 927798417a971c18ea52d0de78af8f29b6e21434 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Tue, 1 Sep 2026 08:25:20 +0800 Subject: [PATCH 4/8] ci(windows): use locally-cached DB tool zips when present archive.mariadb.org / get.enterprisedb.com downloads are extremely slow from the self-hosted Windows runner (30-40+ min for the MariaDB zip). Check for a pre-downloaded copy at C:\ci\cache\ first and copy it, falling back to the network download only when absent. --- .gitea/workflows/release.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index cc6e3a9..13cb0f2 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -189,7 +189,13 @@ jobs: OUT="${GITHUB_WORKSPACE}/desktop/src-tauri/resources/pg_tools" mkdir -p "$OUT" URL="https://get.enterprisedb.com/postgresql/postgresql-${PG_VER}-1-windows-x64-binaries.zip" - curl -fsSL "$URL" -o /tmp/pg.zip + CACHE="/c/ci/cache/postgresql-${PG_VER}-1-windows-x64-binaries.zip" + if [ -f "$CACHE" ]; then + echo "Using cached PG zip: $CACHE" + cp "$CACHE" /tmp/pg.zip + else + curl -fsSL "$URL" -o /tmp/pg.zip + fi EXPECTED="3508d8f085bc3980f38211a82e3f31e5fcae9952105d3dc2f8be67b64a822baa" echo "$EXPECTED /tmp/pg.zip" | sha256sum -c - sha256sum /tmp/pg.zip @@ -205,7 +211,13 @@ jobs: OUT="${GITHUB_WORKSPACE}/desktop/src-tauri/resources/mysql_tools" mkdir -p "$OUT" URL="https://archive.mariadb.org/mariadb-${MARIADB_VER}/winx64-packages/mariadb-${MARIADB_VER}-winx64.zip" - curl -fsSL "$URL" -o /tmp/mariadb.zip + CACHE="/c/ci/cache/mariadb-${MARIADB_VER}-winx64.zip" + if [ -f "$CACHE" ]; then + echo "Using cached MariaDB zip: $CACHE" + cp "$CACHE" /tmp/mariadb.zip + else + curl -fsSL "$URL" -o /tmp/mariadb.zip + fi sha256sum /tmp/mariadb.zip unzip -o /tmp/mariadb.zip -d /tmp/mariadb WINROOT="/tmp/mariadb/mariadb-${MARIADB_VER}-winx64" -- 2.54.0 From 042d05245e3d336925d02ee0ed59edd0e167285c Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Tue, 1 Sep 2026 08:42:02 +0800 Subject: [PATCH 5/8] ci(windows): build inside the MSVC developer shell msvc-dev-cmd sets the env but rustc still resolved link.exe to Git's GNU coreutils link (C:\Program Files\Git\usr\bin\link.exe), so build-script linking failed with 'extra operand'. Run the tauri build in a cmd shell that sources vcvars64.bat so PATH/LIB/INCLUDE point at the real MSVC toolchain in the same process. --- .gitea/workflows/release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 13cb0f2..9e94866 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -235,8 +235,10 @@ jobs: arch: x64 - name: Build installers - shell: bash - run: bun run --filter gridline-desktop tauri build + shell: cmd + run: | + call "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat" + bun run --filter gridline-desktop tauri build - name: Create release and upload Windows assets shell: bash -- 2.54.0 From 064e935a607b8f3f740c176ac275c8cdddd5576c Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Tue, 1 Sep 2026 11:18:06 +0800 Subject: [PATCH 6/8] ci: use python3-or-python fallback in release upload steps Windows installs Python as 'python' (python3 is a Store alias), so the upload step's python3 JSON parsing failed on the Windows runner. Fall back to 'python' when python3 is absent. --- .gitea/workflows/release.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 9e94866..458e18e 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -137,16 +137,17 @@ jobs: set -euo pipefail API="${GITEA_SERVER}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}" AUTH="Authorization: token ${GITHUB_TOKEN}" + PY="python3"; command -v python3 >/dev/null 2>&1 || PY="python" # Ensure the release exists (idempotent — both jobs may call this). REL_ID=$(curl -fsS -H "$AUTH" "${API}/releases/tags/${TAG}" \ - | python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) + | $PY -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) if [ -z "$REL_ID" ]; then BODY="Gridline ${TAG#v} — Linux and Windows installers." REL_ID=$(curl -fsS -X POST -H "$AUTH" -H "Content-Type: application/json" \ -d "{\"tag_name\":\"${TAG}\",\"name\":\"Gridline ${TAG#v}\",\"body\":\"${BODY}\",\"draft\":true}" \ "${API}/releases" \ - | python3 -c "import sys,json;print(json.load(sys.stdin)['id'])") + | $PY -c "import sys,json;print(json.load(sys.stdin)['id'])") fi BUNDLE="${GITHUB_WORKSPACE}/desktop/src-tauri/target/release/bundle" @@ -249,15 +250,16 @@ jobs: set -euo pipefail API="${GITEA_SERVER}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}" AUTH="Authorization: token ${GITHUB_TOKEN}" + PY="python3"; command -v python3 >/dev/null 2>&1 || PY="python" REL_ID=$(curl -fsS -H "$AUTH" "${API}/releases/tags/${TAG}" \ - | python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) + | $PY -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) if [ -z "$REL_ID" ]; then BODY="Gridline ${TAG#v} — Linux and Windows installers." REL_ID=$(curl -fsS -X POST -H "$AUTH" -H "Content-Type: application/json" \ -d "{\"tag_name\":\"${TAG}\",\"name\":\"Gridline ${TAG#v}\",\"body\":\"${BODY}\",\"draft\":true}" \ "${API}/releases" \ - | python3 -c "import sys,json;print(json.load(sys.stdin)['id'])") + | $PY -c "import sys,json;print(json.load(sys.stdin)['id'])") fi BUNDLE="${GITHUB_WORKSPACE}/desktop/src-tauri/target/release/bundle" -- 2.54.0 From 71735cc31e0e8fcdda89a088e9260d0183fee48b Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Tue, 1 Sep 2026 11:41:57 +0800 Subject: [PATCH 7/8] ci: extract release id with grep instead of python The Windows runner's act_runner service env still lacks python/python3, so the release-upload step failed to parse JSON. Use grep to pull the release id, removing the Python dependency from both upload steps. --- .gitea/workflows/release.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 458e18e..a8c01bb 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -137,17 +137,16 @@ jobs: set -euo pipefail API="${GITEA_SERVER}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}" AUTH="Authorization: token ${GITHUB_TOKEN}" - PY="python3"; command -v python3 >/dev/null 2>&1 || PY="python" # Ensure the release exists (idempotent — both jobs may call this). REL_ID=$(curl -fsS -H "$AUTH" "${API}/releases/tags/${TAG}" \ - | $PY -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) + | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true) if [ -z "$REL_ID" ]; then BODY="Gridline ${TAG#v} — Linux and Windows installers." REL_ID=$(curl -fsS -X POST -H "$AUTH" -H "Content-Type: application/json" \ -d "{\"tag_name\":\"${TAG}\",\"name\":\"Gridline ${TAG#v}\",\"body\":\"${BODY}\",\"draft\":true}" \ "${API}/releases" \ - | $PY -c "import sys,json;print(json.load(sys.stdin)['id'])") + | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) fi BUNDLE="${GITHUB_WORKSPACE}/desktop/src-tauri/target/release/bundle" @@ -250,16 +249,15 @@ jobs: set -euo pipefail API="${GITEA_SERVER}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}" AUTH="Authorization: token ${GITHUB_TOKEN}" - PY="python3"; command -v python3 >/dev/null 2>&1 || PY="python" REL_ID=$(curl -fsS -H "$AUTH" "${API}/releases/tags/${TAG}" \ - | $PY -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) + | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true) if [ -z "$REL_ID" ]; then BODY="Gridline ${TAG#v} — Linux and Windows installers." REL_ID=$(curl -fsS -X POST -H "$AUTH" -H "Content-Type: application/json" \ -d "{\"tag_name\":\"${TAG}\",\"name\":\"Gridline ${TAG#v}\",\"body\":\"${BODY}\",\"draft\":true}" \ "${API}/releases" \ - | $PY -c "import sys,json;print(json.load(sys.stdin)['id'])") + | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) fi BUNDLE="${GITHUB_WORKSPACE}/desktop/src-tauri/target/release/bundle" -- 2.54.0 From 6d1b589f3f253743bc7d7fa5a9f3e645a8a0d557 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Tue, 1 Sep 2026 12:22:20 +0800 Subject: [PATCH 8/8] chore: bump version to 0.7.12 across the app + docs/website Sync version in desktop/package.json, Cargo.toml, tauri.conf.json, the Astro landing page, version/bundle-config/docs-coverage tests, and both README download tables. Add v0.7.12 changelog entry. --- README.md | 36 ++++++++++++++------------- desktop/package.json | 2 +- desktop/src-tauri/Cargo.toml | 2 +- desktop/src-tauri/tauri.conf.json | 2 +- desktop/src/lib/bundle-config.test.ts | 6 ++--- desktop/src/lib/docs-coverage.test.ts | 14 +++++------ desktop/src/lib/version.test.ts | 4 +-- www/src/pages/index.astro | 2 +- 8 files changed, 35 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 3c9d8ad..5d0f279 100644 --- a/README.md +++ b/README.md @@ -30,16 +30,16 @@ ## Download -Grab the installer for your OS from the [latest release](https://git.ranio.xyz/adrianbonpin/gridline/releases/latest) — the links below point at the current release (**v0.7.11**): +Grab the installer for your OS from the [latest release](https://git.ranio.xyz/adrianbonpin/gridline/releases/latest) — the links below point at the current release (**v0.7.12**): | OS | Architecture | Download | | :--------------------------- | :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | -| **macOS** | Apple Silicon (M1/M2/M3/M4…) | [Gridline_0.7.11_aarch64.dmg](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_aarch64.dmg) | -| **macOS** | Intel | [Gridline_0.7.11_x64.dmg](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_x64.dmg) | -| **Windows** | x64 | [Gridline_0.7.11_x64-setup.exe](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_x64-setup.exe) | -| **Debian / Ubuntu** | amd64 | [Gridline_0.7.11_amd64.deb](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_amd64.deb) | -| **Fedora / RHEL / openSUSE** | x86_64 | [Gridline-0.7.11-1.x86_64.rpm](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline-0.7.11-1.x86_64.rpm) | -| **Other Linux** | amd64 | [Gridline_0.7.11_amd64.AppImage](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_amd64.AppImage) | +| **macOS** | Apple Silicon (M1/M2/M3/M4…) | [Gridline_0.7.12_aarch64.dmg](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.12/Gridline_0.7.12_aarch64.dmg) | +| **macOS** | Intel | [Gridline_0.7.12_x64.dmg](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.12/Gridline_0.7.12_x64.dmg) | +| **Windows** | x64 | [Gridline_0.7.12_x64-setup.exe](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.12/Gridline_0.7.12_x64-setup.exe) | +| **Debian / Ubuntu** | amd64 | [Gridline_0.7.12_amd64.deb](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.12/Gridline_0.7.12_amd64.deb) | +| **Fedora / RHEL / openSUSE** | x86_64 | [Gridline-0.7.12-1.x86_64.rpm](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.12/Gridline-0.7.12-1.x86_64.rpm) | +| **Other Linux** | amd64 | [Gridline_0.7.12_amd64.AppImage](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.12/Gridline_0.7.12_amd64.AppImage) | **macOS via Homebrew** (recommended for Mac users): @@ -54,7 +54,7 @@ The cask strips the macOS quarantine flag automatically during install, so **no > **macOS first launch:** macOS may say *"Gridline is damaged and can't be opened"* or *"the developer cannot be verified"* — this is expected; the app isn't Developer-ID signed/notarized yet. See the [macOS first-launch instructions](#installers-are-unsigned-for-now) (right-click → Open, or the one-time `xattr` fix). Not sure if your Mac is Intel or Apple Silicon? See [Which file should I download?](#which-file-should-i-download) below.