4 Commits
Author SHA1 Message Date
adrianbonpin 341354ecb9 ci: use ASCII release body so Windows create succeeds (#8)
Release / linux (push) Successful in 18m41s
Release / windows (push) Successful in 19m4s
2026-09-01 05:06:03 +00:00
adrianbonpin 37e6638854 ci: make release-creation race-safe across parallel jobs (#7)
Release / windows (push) Failing after 17m35s
Release / linux (push) Successful in 20m23s
2026-09-01 04:44:42 +00:00
adrianbonpin 7bd4e8d05f fix: PG cell-edit serialization + recent-connections startup race (#6)
Release / windows (push) Failing after 18m10s
Release / linux (push) Successful in 19m10s
2026-09-01 04:23:07 +00:00
adrianbonpin 7a66b70c26 fix(www): Gridline favicon + Gitea links (#4) 2026-08-21 01:10:07 +00:00
18 changed files with 308 additions and 92 deletions
+46 -10
View File
@@ -140,13 +140,19 @@ jobs:
# Ensure the release exists (idempotent — both jobs may call this). # Ensure the release exists (idempotent — both jobs may call this).
REL_ID=$(curl -fsS -H "$AUTH" "${API}/releases/tags/${TAG}" \ 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) | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true)
if [ -z "$REL_ID" ]; then if [ -z "$REL_ID" ]; then
BODY="Gridline ${TAG#v} — Linux and Windows installers." BODY="Gridline ${TAG#v} - Linux and Windows installers."
REL_ID=$(curl -fsS -X POST -H "$AUTH" -H "Content-Type: application/json" \ 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}" \ -d "{\"tag_name\":\"${TAG}\",\"name\":\"Gridline ${TAG#v}\",\"body\":\"${BODY}\",\"draft\":true}" \
"${API}/releases" \ "${API}/releases" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])") | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true)
fi
if [ -z "$REL_ID" ]; then
# A parallel job may have created the release between our GET and
# POST (create returns 422). Re-fetch by tag and upload to it.
REL_ID=$(curl -fsS -H "$AUTH" "${API}/releases/tags/${TAG}" \
| grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true)
fi fi
BUNDLE="${GITHUB_WORKSPACE}/desktop/src-tauri/target/release/bundle" BUNDLE="${GITHUB_WORKSPACE}/desktop/src-tauri/target/release/bundle"
@@ -189,7 +195,13 @@ jobs:
OUT="${GITHUB_WORKSPACE}/desktop/src-tauri/resources/pg_tools" OUT="${GITHUB_WORKSPACE}/desktop/src-tauri/resources/pg_tools"
mkdir -p "$OUT" mkdir -p "$OUT"
URL="https://get.enterprisedb.com/postgresql/postgresql-${PG_VER}-1-windows-x64-binaries.zip" 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" EXPECTED="3508d8f085bc3980f38211a82e3f31e5fcae9952105d3dc2f8be67b64a822baa"
echo "$EXPECTED /tmp/pg.zip" | sha256sum -c - echo "$EXPECTED /tmp/pg.zip" | sha256sum -c -
sha256sum /tmp/pg.zip sha256sum /tmp/pg.zip
@@ -205,16 +217,34 @@ jobs:
OUT="${GITHUB_WORKSPACE}/desktop/src-tauri/resources/mysql_tools" OUT="${GITHUB_WORKSPACE}/desktop/src-tauri/resources/mysql_tools"
mkdir -p "$OUT" mkdir -p "$OUT"
URL="https://archive.mariadb.org/mariadb-${MARIADB_VER}/winx64-packages/mariadb-${MARIADB_VER}-winx64.zip" 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 sha256sum /tmp/mariadb.zip
unzip -o /tmp/mariadb.zip -d /tmp/mariadb unzip -o /tmp/mariadb.zip -d /tmp/mariadb
WINROOT="/tmp/mariadb/mariadb-${MARIADB_VER}-winx64" WINROOT="/tmp/mariadb/mariadb-${MARIADB_VER}-winx64"
cp "$WINROOT"/bin/mariadb-dump.exe "$WINROOT"/bin/mariadb.exe "$OUT"/ cp "$WINROOT"/bin/mariadb-dump.exe "$WINROOT"/bin/mariadb.exe "$OUT"/
cp "$WINROOT"/lib/libmariadb.dll "$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 - name: Build installers
shell: bash shell: cmd
run: bun run --filter gridline-desktop tauri build 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 - name: Create release and upload Windows assets
shell: bash shell: bash
@@ -227,13 +257,19 @@ jobs:
AUTH="Authorization: token ${GITHUB_TOKEN}" AUTH="Authorization: token ${GITHUB_TOKEN}"
REL_ID=$(curl -fsS -H "$AUTH" "${API}/releases/tags/${TAG}" \ 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) | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true)
if [ -z "$REL_ID" ]; then if [ -z "$REL_ID" ]; then
BODY="Gridline ${TAG#v} — Linux and Windows installers." BODY="Gridline ${TAG#v} - Linux and Windows installers."
REL_ID=$(curl -fsS -X POST -H "$AUTH" -H "Content-Type: application/json" \ 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}" \ -d "{\"tag_name\":\"${TAG}\",\"name\":\"Gridline ${TAG#v}\",\"body\":\"${BODY}\",\"draft\":true}" \
"${API}/releases" \ "${API}/releases" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])") | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true)
fi
if [ -z "$REL_ID" ]; then
# A parallel job may have created the release between our GET and
# POST (create returns 422). Re-fetch by tag and upload to it.
REL_ID=$(curl -fsS -H "$AUTH" "${API}/releases/tags/${TAG}" \
| grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true)
fi fi
BUNDLE="${GITHUB_WORKSPACE}/desktop/src-tauri/target/release/bundle" BUNDLE="${GITHUB_WORKSPACE}/desktop/src-tauri/target/release/bundle"
+26 -24
View File
@@ -18,28 +18,28 @@
<a href="https://react.dev/"><img src="https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB" alt="React" /></a> <a href="https://react.dev/"><img src="https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB" alt="React" /></a>
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" /></a> <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" /></a>
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-D22128.svg?style=for-the-badge" alt="Apache 2.0 License" /></a> <a href="./LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-D22128.svg?style=for-the-badge" alt="Apache 2.0 License" /></a>
<a href="https://github.com/adrianbonpin/gridline/stargazers"><img src="https://img.shields.io/github/stars/adrianbonpin/gridline?style=for-the-badge&logo=github&label=Stars" alt="GitHub stars" /></a> <a href="https://git.ranio.xyz/adrianbonpin/gridline"><img src="https://img.shields.io/badge/Open_Source-Gitea-4183C4.svg?style=for-the-badge" alt="Open source on Gitea" /></a>
</p> </p>
<p> <p>
<a href="https://github.com/adrianbonpin/gridline/releases"><img src="https://img.shields.io/badge/Download_Latest_Release-2ea44f?style=for-the-badge&logo=github&logoColor=white" alt="Download Latest" /></a> <a href="https://git.ranio.xyz/adrianbonpin/gridline/releases"><img src="https://img.shields.io/badge/Download_Latest_Release-2ea44f?style=for-the-badge" alt="Download Latest" /></a>
<a href="https://github.com/adrianbonpin/gridline/issues/new"><img src="https://img.shields.io/badge/Open_an_Issue-%23E4405F.svg?style=for-the-badge&logo=github&logoColor=white" alt="Open an Issue" /></a> <a href="https://git.ranio.xyz/adrianbonpin/gridline/issues/new"><img src="https://img.shields.io/badge/Open_an_Issue-%23E4405F.svg?style=for-the-badge" alt="Open an Issue" /></a>
</p> </p>
--- ---
## Download ## 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 | | 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** | 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.11_x64.dmg](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_x64.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.11_x64-setup.exe](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_x64-setup.exe) | | **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.11_amd64.deb](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_amd64.deb) | | **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.11-1.x86_64.rpm](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline-0.7.11-1.x86_64.rpm) | | **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.11_amd64.AppImage](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_amd64.AppImage) | | **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): **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. > **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.
<!-- <!--
MAINTENANCE: These links are STATIC (versioned) — they point at the v0.7.11 MAINTENANCE: These links are STATIC (versioned) — they point at the v0.7.12
release assets, not at a moving "latest" target. On every new release, release assets, not at a moving "latest" target. On every new release,
update BOTH tables here (Download + Which file should I download?) to the update BOTH tables here (Download + Which file should I download?) to the
new version's asset names, which are tauri-action's default naming: new version's asset names, which are tauri-action's default naming:
@@ -99,6 +99,8 @@ Gridline is built for developers and small teams who manage multiple database en
<details> <details>
<summary>View the full changelog</summary> <summary>View the full changelog</summary>
- **v0.7.12** — **Reliable cell editing for non-text columns**: editing a PostgreSQL cell whose type isn't `text` (integers, booleans, UUIDs, `json`/`jsonb`, timestamps, enums, numerics, …) previously failed with a Rust serialization error; edited values are now sent to PostgreSQL in text wire format so the server parses them into the column's own type. **Recent connections fix**: the Recent strip no longer renders empty on first launch when the connections list hadn't loaded yet. **Full CI release pipeline for Windows**: self-hosted Windows runner builds the `.exe`/`.msi` installers (bundled `pg_dump`/`pg_restore`/`psql` + MariaDB clients) and uploads them to the release alongside Linux; version bump.
- **v0.7.10** — **Copy connection URL**: from a connection card's ⋮ menu, copy the connection string for use in env vars / other tools — **Copy connection URL** (includes the password, fetched from the OS keychain on demand) and **Copy connection URL (no password)** (safe to share). Builds `postgresql://` / `mysql://` / `sqlite://` / `redis://` strings with percent-encoded credentials and the PG `sslmode` appended; version bump. - **v0.7.10** — **Copy connection URL**: from a connection card's ⋮ menu, copy the connection string for use in env vars / other tools — **Copy connection URL** (includes the password, fetched from the OS keychain on demand) and **Copy connection URL (no password)** (safe to share). Builds `postgresql://` / `mysql://` / `sqlite://` / `redis://` strings with percent-encoded credentials and the PG `sslmode` appended; version bump.
- **v0.7.9** — **macOS install/launch fixes**: the app's local store now opens under the OS app-data directory (a cwd-relative `gridline.db` made Finder-launched copies silently exit with a Rust panic before the UI started); macOS bundles are **ad-hoc signed at build time** (`signingIdentity "-"`), replacing the Xcode linker-only signature that macOS treated as unsigned ("damaged and can't be opened", silent Finder refusals) — users now get a standard one-time Gatekeeper prompt (see the [install notes](#installers-are-unsigned-for-now), incl. the `xattr` workaround); **OpenSSL is vendored** into the `ssh2` build so the release no longer depends on a Homebrew OpenSSL path; version bump. - **v0.7.9** — **macOS install/launch fixes**: the app's local store now opens under the OS app-data directory (a cwd-relative `gridline.db` made Finder-launched copies silently exit with a Rust panic before the UI started); macOS bundles are **ad-hoc signed at build time** (`signingIdentity "-"`), replacing the Xcode linker-only signature that macOS treated as unsigned ("damaged and can't be opened", silent Finder refusals) — users now get a standard one-time Gatekeeper prompt (see the [install notes](#installers-are-unsigned-for-now), incl. the `xattr` workaround); **OpenSSL is vendored** into the `ssh2` build so the release no longer depends on a Homebrew OpenSSL path; version bump.
@@ -301,9 +303,9 @@ _Notes: DB Pro is an Electron app (launched Nov 2025) whose marketing copy overc
### Download a release ### Download a release
Pre-built installers for macOS, Windows, and Linux are published on the [Releases](https://github.com/adrianbonpin/gridline/releases) page. Pre-built installers for macOS, Windows, and Linux are published on the [Releases](https://git.ranio.xyz/adrianbonpin/gridline/releases) page.
> ⚠️ Gridline is under active development. Expect rough edges and please [open issues](https://github.com/adrianbonpin/gridline/issues/new) when you hit them. > ⚠️ Gridline is under active development. Expect rough edges and please [open issues](https://git.ranio.xyz/adrianbonpin/gridline/issues/new) when you hit them.
#### Installers are unsigned (for now) #### Installers are unsigned (for now)
@@ -326,16 +328,16 @@ Code signing **will be added in the future** (Apple Developer Program + a Window
#### Which file should I download? #### Which file should I download?
Each release contains **one file per platform** — you only need the one that matches your computer. The links below point at the current release (**v0.7.11**): Each release contains **one file per platform** — you only need the one that matches your computer. The links below point at the current release (**v0.7.12**):
| Your system | Download this | Notes | | Your system | Download this | Notes |
| :------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------- | | :------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------- |
| 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) | `aarch64` = Apple's own chip | | 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) | `aarch64` = Apple's own chip |
| 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) | `x64` = Intel/AMD | | 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) | `x64` = Intel/AMD |
| **Windows** (most PCs) | [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) | The `.msi` is an alternate installer (for enterprises/IT admins) | | **Windows** (most PCs) | [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) | The `.msi` is an alternate installer (for enterprises/IT admins) |
| **Debian / Ubuntu** | [Gridline_0.7.11_amd64.deb](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_amd64.deb) | Install: `sudo apt install ./Gridline_0.7.11_amd64.deb` | | **Debian / Ubuntu** | [Gridline_0.7.12_amd64.deb](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.12/Gridline_0.7.12_amd64.deb) | Install: `sudo apt install ./Gridline_0.7.12_amd64.deb` |
| **Fedora / RHEL / openSUSE** | [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) | Install: `sudo dnf install Gridline-0.7.11-1.x86_64.rpm` | | **Fedora / RHEL / openSUSE** | [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) | Install: `sudo dnf install Gridline-0.7.12-1.x86_64.rpm` |
| **Any other Linux** | [Gridline_0.7.11_amd64.AppImage](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.11/Gridline_0.7.11_amd64.AppImage) | Works on every distro: `chmod +x` the file, then double-click it | | **Any other Linux** | [Gridline_0.7.12_amd64.AppImage](https://git.ranio.xyz/adrianbonpin/gridline/releases/download/v0.7.12/Gridline_0.7.12_amd64.AppImage) | Works on every distro: `chmod +x` the file, then double-click it |
**Not sure if your Mac is Intel or Apple Silicon?** Click the **Apple menu** → **About This Mac**. If it shows "Apple M1/M2/M3/M4…" download the `aarch64` file; if it shows an Intel chip, download `x64`. Downloading the wrong one won't run. **Not sure if your Mac is Intel or Apple Silicon?** Click the **Apple menu** → **About This Mac**. If it shows "Apple M1/M2/M3/M4…" download the `aarch64` file; if it shows an Intel chip, download `x64`. Downloading the wrong one won't run.
@@ -345,8 +347,8 @@ Cutting a release is one command — CI builds everything. **Releases are cut fr
```bash ```bash
git checkout prod && git pull git checkout prod && git pull
git tag v0.7.11 git tag v0.7.12
git push origin v0.7.11 git push origin v0.7.12
``` ```
Gitea Actions (`.gitea/workflows/release.yml`) builds the **Linux and Windows** installers on the self-hosted runners, then opens a **draft release** on the [Releases](https://git.ranio.xyz/adrianbonpin/gridline/releases) page. **macOS is built manually** on the dev Mac and its DMGs uploaded to the same release via `scripts/release-mac.sh`. Review the draft and hit **Publish release**. Gitea Actions (`.gitea/workflows/release.yml`) builds the **Linux and Windows** installers on the self-hosted runners, then opens a **draft release** on the [Releases](https://git.ranio.xyz/adrianbonpin/gridline/releases) page. **macOS is built manually** on the dev Mac and its DMGs uploaded to the same release via `scripts/release-mac.sh`. Review the draft and hit **Publish release**.
@@ -357,7 +359,7 @@ Before tagging, make sure the version number is in sync across `desktop/package.
```bash ```bash
# 1. Clone the repository # 1. Clone the repository
git clone https://github.com/adrianbonpin/gridline.git git clone https://git.ranio.xyz/adrianbonpin/gridline.git
cd gridline cd gridline
# 2. Install all workspace dependencies # 2. Install all workspace dependencies
@@ -473,7 +475,7 @@ Highlights of what's next:
Contributions, bug reports, and feature ideas are welcome. Gridline is Apache 2.0-licensed and intentionally stays open — no paywalled tiers, no bundled proprietary services. Contributions, bug reports, and feature ideas are welcome. Gridline is Apache 2.0-licensed and intentionally stays open — no paywalled tiers, no bundled proprietary services.
- Open an issue via the **[template chooser](https://github.com/AdrianBonpin/gridline/issues/new)** — pick **Bug report** (with an environment table so we can reproduce issues quickly), **Feature request**, or **Improvement** (UX polish for existing features). New issues are auto-assigned to the maintainer. - Open an issue via the **[template chooser](https://git.ranio.xyz/adrianbonpin/gridline/issues/new)** — pick **Bug report** (with an environment table so we can reproduce issues quickly), **Feature request**, or **Improvement** (UX polish for existing features). New issues are auto-assigned to the maintainer.
- Submit a pull request. Keep Tauri commands thin, type IPC boundaries explicitly, and follow the existing Rust/React conventions. - Submit a pull request. Keep Tauri commands thin, type IPC boundaries explicitly, and follow the existing Rust/React conventions.
--- ---
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "gridline-desktop", "name": "gridline-desktop",
"private": true, "private": true,
"version": "0.7.11", "version": "0.7.12",
"description": "An open-source, high-performance database GUI client for PostgreSQL and beyond", "description": "An open-source, high-performance database GUI client for PostgreSQL and beyond",
"type": "module", "type": "module",
"scripts": { "scripts": {
+2 -1
View File
@@ -1783,8 +1783,9 @@ dependencies = [
[[package]] [[package]]
name = "gridline" name = "gridline"
version = "0.7.10" version = "0.7.11"
dependencies = [ dependencies = [
"bytes",
"chrono", "chrono",
"deadpool-postgres", "deadpool-postgres",
"indexmap 2.14.0", "indexmap 2.14.0",
+2 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "gridline" name = "gridline"
version = "0.7.11" version = "0.7.12"
description = "An open-source, high-performance database GUI client for PostgreSQL and beyond" description = "An open-source, high-performance database GUI client for PostgreSQL and beyond"
authors = ["you"] authors = ["you"]
edition = "2021" edition = "2021"
@@ -31,6 +31,7 @@ tauri-plugin-dialog = "2"
tauri-plugin-fs = "2" tauri-plugin-fs = "2"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-postgres = { version = "0.7", features = ["with-chrono-0_4", "with-uuid-1", "with-serde_json-1"] } 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"] } sqlx = { version = "0.8", features = ["runtime-tokio", "mysql", "tls-rustls", "json"] }
redis = { version = "0.27", features = ["tokio-comp"] } 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 # async-ssh2 is not available at v0.4; using synchronous ssh2 via tokio::task::spawn_blocking per plan's fallback
+140 -17
View File
@@ -8,10 +8,12 @@ use crate::models::db_viewer::{
Change, ColumnInfo, ConstraintInfo, EnumInfo, ExtensionInfo, FunctionInfo, IndexInfo, Change, ColumnInfo, ConstraintInfo, EnumInfo, ExtensionInfo, FunctionInfo, IndexInfo,
QueryResult, SequenceInfo, TableInfo, TriggerInfo, QueryResult, SequenceInfo, TableInfo, TriggerInfo,
}; };
use bytes::BytesMut;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error;
use sqlx::Row; use sqlx::Row;
use tauri::State; use tauri::State;
use tokio_postgres::types::ToSql; use tokio_postgres::types::{Format, IsNull, ToSql, Type};
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Helper functions // 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 `'<literal>'` 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<IsNull, Box<dyn Error + Sync + Send>> {
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<IsNull, Box<dyn Error + Sync + Send>> {
self.to_sql(ty, out)
}
}
/// Convert a JSON value into a boxed PostgreSQL-bindable value. /// Convert a JSON value into a boxed PostgreSQL-bindable value.
/// ///
/// Maps common JSON types to `tokio_postgres::types::ToSql` implementors. /// Every non-null value is bound as a `PgText` (text wire format) so the server
/// Unknown/complex types are stringified as a fallback. /// 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<dyn ToSql + Send + Sync> { fn pg_box_value(v: &serde_json::Value) -> Box<dyn ToSql + Send + Sync> {
match v { if v.is_null() {
serde_json::Value::Null => Box::new(Option::<String>::None), return Box::new(Option::<PgText>::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()),
} }
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. /// 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<ConnectionPoolManager> { async fn fresh_pool_manager() -> tokio::sync::Mutex<ConnectionPoolManager> {
tokio::sync::Mutex::new(ConnectionPoolManager::new()) tokio::sync::Mutex::new(ConnectionPoolManager::new())
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Gridline", "productName": "Gridline",
"version": "0.7.11", "version": "0.7.12",
"identifier": "com.adrianbonpin.gridline", "identifier": "com.adrianbonpin.gridline",
"build": { "build": {
"beforeDevCommand": "bun run dev", "beforeDevCommand": "bun run dev",
+3 -3
View File
@@ -1,11 +1,11 @@
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import tauriConf from "../../src-tauri/tauri.conf.json"; import tauriConf from "../../src-tauri/tauri.conf.json";
describe("tauri bundle config (v0.7.11)", () => { describe("tauri bundle config (v0.7.12)", () => {
it("declares bundled pg_tools resources", () => { it("declares bundled pg_tools resources", () => {
expect(tauriConf.bundle.resources).toContain("resources/pg_tools/*"); expect(tauriConf.bundle.resources).toContain("resources/pg_tools/*");
}); });
it("version is 0.7.11", () => { it("version is 0.7.12", () => {
expect(tauriConf.version).toBe("0.7.11"); expect(tauriConf.version).toBe("0.7.12");
}); });
}); });
+7 -7
View File
@@ -4,7 +4,7 @@ import { describe, it, expect } from "vitest";
import agents from "../../../AGENTS.md?raw"; import agents from "../../../AGENTS.md?raw";
import readme from "../../../README.md?raw"; import readme from "../../../README.md?raw";
describe("v0.7.11 docs coverage", () => { describe("v0.7.12 docs coverage", () => {
it("AGENTS.md marks inline cell editing complete", () => { it("AGENTS.md marks inline cell editing complete", () => {
expect(agents).toContain("Inline cell editing"); expect(agents).toContain("Inline cell editing");
expect(agents).toMatch(/Inline cell editing \| ✅/); expect(agents).toMatch(/Inline cell editing \| ✅/);
@@ -24,8 +24,8 @@ describe("v0.7.11 docs coverage", () => {
expect(agents).toMatch(/Connection status indicator on cards \| ✅/); expect(agents).toMatch(/Connection status indicator on cards \| ✅/);
expect(agents).toMatch(/Move-to-folder bulk action \| ✅/); expect(agents).toMatch(/Move-to-folder bulk action \| ✅/);
}); });
it("README declares v0.7.11", () => { it("README declares v0.7.12", () => {
expect(readme).toContain("0.7.11"); expect(readme).toContain("0.7.12");
}); });
it("AGENTS.md marks schema CRUD complete", () => { it("AGENTS.md marks schema CRUD complete", () => {
expect(agents).toMatch(/Schema CRUD \| ✅/); expect(agents).toMatch(/Schema CRUD \| ✅/);
@@ -64,9 +64,9 @@ describe("v0.7.11 docs coverage", () => {
expect(agents).toMatch(/Cancel long-running queries \| ✅/); expect(agents).toMatch(/Cancel long-running queries \| ✅/);
expect(agents).toMatch(/Settings export\/import \| ✅/); expect(agents).toMatch(/Settings export\/import \| ✅/);
}); });
it("README links to v0.7.11 assets in both download tables", () => { it("README links to v0.7.12 assets in both download tables", () => {
expect(readme).toContain("releases/download/v0.7.11/"); expect(readme).toContain("releases/download/v0.7.12/");
expect(readme).toContain("Gridline_0.7.11_aarch64.dmg"); expect(readme).toContain("Gridline_0.7.12_aarch64.dmg");
expect(readme).toContain("Gridline-0.7.11-1.x86_64.rpm"); expect(readme).toContain("Gridline-0.7.12-1.x86_64.rpm");
}); });
}); });
+2 -2
View File
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
import pkg from "../../package.json"; import pkg from "../../package.json";
describe("version", () => { describe("version", () => {
it("declares v0.7.11 across the app shell", () => { it("declares v0.7.12 across the app shell", () => {
expect(pkg.version).toBe("0.7.11"); expect(pkg.version).toBe("0.7.12");
}); });
}); });
@@ -225,6 +225,33 @@ describe("favorites / recents / move-selection", () => {
expect(commands.recordRecentConnection).toHaveBeenCalledWith("c1"); 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", () => { describe("duplicateConnection", () => {
it("copies fields with a '(copy)' name and favorite false", async () => { 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 }); const src = makeConn({ id: "c1", name: "Prod", folder_id: "f1", tag_ids: ["t1"], favorite: true });
+3
View File
@@ -46,6 +46,9 @@ export const useConnectionStore = create<ConnectionState>((set, get) => ({
set((s) => ({ connections: [...s.connections].sort((a, b) => Number(b.favorite) - Number(a.favorite)) })); set((s) => ({ connections: [...s.connections].sort((a, b) => Number(b.favorite) - Number(a.favorite)) }));
// Also load tag order // Also load tag order
get().loadTagOrder(); 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) { } catch (e) {
set({ loading: false, error: e instanceof Error ? e.message : String(e) }); set({ loading: false, error: e instanceof Error ? e.message : String(e) });
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

After

Width:  |  Height:  |  Size: 1.5 KiB

+31 -8
View File
@@ -1,9 +1,32 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128"> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" /> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<style> <svg width="100%" height="100%" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
path { fill: #000; } <g transform="matrix(1.464129,0,0,1.464129,-67.349927,-273.792094)">
@media (prefers-color-scheme: dark) { <path d="M729,357.75L729,699.25C729,793.489 652.489,870 558.25,870L216.75,870C122.511,870 46,793.489 46,699.25L46,357.75C46,263.511 122.511,187 216.75,187L558.25,187C652.489,187 729,263.511 729,357.75Z" style="fill:url(#_Linear1);stroke:black;stroke-width:0.68px;"/>
path { fill: #FFF; } </g>
} <g id="Icon">
</style> <g transform="matrix(1.186992,0,0,1.186992,-93.495935,-96.300813)">
<path d="M325,351C325,305.743 403.415,269 500,269C596.585,269 675,305.743 675,351C675,365.931 666.465,379.936 651.557,392C640.124,401.252 624.943,409.364 606.99,415.876C577.4,426.609 540.28,433 500,433C459.711,433 422.583,426.607 392.99,415.869C375.046,409.358 359.872,401.249 348.443,392C333.535,379.936 325,365.931 325,351Z" style="fill:white;stroke:black;stroke-width:14.04px;"/>
</g>
<g transform="matrix(1.186992,0,0,1.186992,-93.495935,-96.300813)">
<path d="M325,433L325,351C325,365.931 333.535,379.936 348.443,392C359.872,401.249 375.046,409.358 392.99,415.869L392.99,497.869C375.046,491.358 359.872,483.249 348.443,474C333.535,461.936 325,447.931 325,433Z" style="fill:white;stroke:black;stroke-width:14.04px;"/>
</g>
<path d="M626.996,592.007C591.874,604.748 547.812,612.333 500,612.333L500,515C547.812,515 591.874,507.414 626.996,494.674C648.307,486.944 666.326,477.316 679.897,466.333C697.592,452.013 707.724,435.39 707.724,417.667L707.724,515C707.724,532.723 697.592,549.347 679.897,563.667C666.326,574.649 648.307,584.277 626.996,592.007Z" style="fill:white;stroke:black;stroke-width:16.67px;"/>
<g transform="matrix(1.186992,0,0,1.186992,-93.495935,-96.300813)">
<path d="M325,515L325,433C325,447.931 333.535,461.936 348.443,474C359.872,483.249 375.046,491.358 392.99,497.869L392.99,579.869C375.046,573.358 359.872,565.249 348.443,556C333.535,543.936 325,529.931 325,515Z" style="fill:white;stroke:black;stroke-width:14.04px;"/>
</g>
<g transform="matrix(1.186992,0,0,1.186992,-93.495935,-96.300813)">
<path d="M325,597L325,515C325,529.931 333.535,543.936 348.443,556C359.872,565.249 375.046,573.358 392.99,579.869L392.99,661.869C375.046,655.358 359.872,647.249 348.443,638C333.535,625.936 325,611.931 325,597Z" style="fill:white;stroke:black;stroke-width:14.04px;"/>
</g>
<g transform="matrix(1.186992,0,0,1.186992,-93.495935,-96.300813)">
<path d="M675,515L675,597C675,611.931 666.465,625.936 651.557,638C640.124,647.252 624.943,655.364 606.99,661.876L606.99,579.876C624.943,573.364 640.124,565.252 651.557,556C666.465,543.936 675,529.931 675,515Z" style="fill:white;stroke:black;stroke-width:14.04px;"/>
</g>
<g transform="matrix(1.186992,0,0,1.186992,-93.495935,-96.300813)">
<path d="M675,597L675,679C675,705.383 648.352,728.872 606.99,743.876L606.99,661.876C624.943,655.364 640.124,647.252 651.557,638C666.465,625.936 675,611.931 675,597Z" style="fill:white;stroke:black;stroke-width:14.04px;"/>
</g>
<path d="M372.98,786.665C323.898,768.856 292.276,740.978 292.276,709.667L292.276,612.333C292.276,630.057 302.408,646.68 320.103,661C333.669,671.979 351.68,681.603 372.98,689.332C408.107,702.078 452.177,709.667 500,709.667C547.812,709.667 591.874,702.081 626.996,689.341L626.996,786.674C591.874,799.414 547.812,807 500,807C452.177,807 408.107,799.411 372.98,786.665Z" style="fill:white;stroke:black;stroke-width:16.67px;"/>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(683,683,-683,683,46,187)"><stop offset="0" style="stop-color:rgb(108,108,108);stop-opacity:1"/><stop offset="0.17" style="stop-color:rgb(15,15,15);stop-opacity:1"/><stop offset="0.5" style="stop-color:black;stop-opacity:1"/><stop offset="0.83" style="stop-color:rgb(15,15,15);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(108,108,108);stop-opacity:1"/></linearGradient>
</defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 4.4 KiB

+4 -4
View File
@@ -98,7 +98,7 @@ const isProd = import.meta.env.PROD;
</a> </a>
<div class="hidden md:flex items-center gap-6 text-sm"> <div class="hidden md:flex items-center gap-6 text-sm">
<a href="/#features" class="nav-link">Features</a> <a href="/#features" class="nav-link">Features</a>
<a href="https://github.com/AdrianBonpin/gridline" target="_blank" rel="noopener" class="nav-link">GitHub</a> <a href="https://git.ranio.xyz/adrianbonpin/gridline" target="_blank" rel="noopener" class="nav-link">Gitea</a>
<a href="/#download" data-download data-download-label class="nav-link">Download</a> <a href="/#download" data-download data-download-label class="nav-link">Download</a>
<button id="theme-toggle" class="p-2 rounded-full hover:bg-[var(--surface-elevated)] transition-colors" aria-label="Toggle theme"> <button id="theme-toggle" class="p-2 rounded-full hover:bg-[var(--surface-elevated)] transition-colors" aria-label="Toggle theme">
<span class="theme-icon-sun hidden">☀</span> <span class="theme-icon-sun hidden">☀</span>
@@ -133,9 +133,9 @@ const isProd = import.meta.env.PROD;
<div> <div>
<h4 class="font-semibold text-sm mb-4">Project</h4> <h4 class="font-semibold text-sm mb-4">Project</h4>
<ul class="space-y-2 text-sm text-[var(--text-muted)]"> <ul class="space-y-2 text-sm text-[var(--text-muted)]">
<li><a href="https://github.com/AdrianBonpin/gridline" target="_blank" rel="noopener" class="footer-link">GitHub</a></li> <li><a href="https://git.ranio.xyz/adrianbonpin/gridline" target="_blank" rel="noopener" class="footer-link">Gitea</a></li>
<li><a href="https://github.com/AdrianBonpin/gridline/issues" target="_blank" rel="noopener" class="footer-link">Issues</a></li> <li><a href="https://git.ranio.xyz/adrianbonpin/gridline/issues" target="_blank" rel="noopener" class="footer-link">Issues</a></li>
<li><a href="https://github.com/AdrianBonpin/gridline/blob/prod/ROADMAP.md" target="_blank" rel="noopener" class="footer-link">Roadmap</a></li> <li><a href="https://git.ranio.xyz/adrianbonpin/gridline/src/branch/prod/ROADMAP.md" target="_blank" rel="noopener" class="footer-link">Roadmap</a></li>
</ul> </ul>
</div> </div>
<div> <div>
+3 -3
View File
@@ -19,7 +19,7 @@ describe("seo builders", () => {
applicationCategory: "DeveloperApplication", applicationCategory: "DeveloperApplication",
offersPrice: 0, offersPrice: 0,
offersCurrency: "USD", offersCurrency: "USD",
downloadUrl: "https://github.com/AdrianBonpin/gridline/releases", downloadUrl: "https://git.ranio.xyz/adrianbonpin/gridline/releases",
}); });
expect(ld["@type"]).toBe("SoftwareApplication"); expect(ld["@type"]).toBe("SoftwareApplication");
expect(ld.name).toBe("Gridline"); expect(ld.name).toBe("Gridline");
@@ -42,10 +42,10 @@ describe("seo builders", () => {
name: "Gridline", name: "Gridline",
url: "https://getgridline.app/", url: "https://getgridline.app/",
logo: "https://getgridline.app/gridline-icon.svg", logo: "https://getgridline.app/gridline-icon.svg",
sameAs: ["https://github.com/AdrianBonpin/gridline"], sameAs: ["https://git.ranio.xyz/adrianbonpin/gridline"],
}); });
expect(ld["@type"]).toBe("Organization"); expect(ld["@type"]).toBe("Organization");
expect(ld.sameAs).toContain("https://github.com/AdrianBonpin/gridline"); expect(ld.sameAs).toContain("https://git.ranio.xyz/adrianbonpin/gridline");
}); });
it("buildWebSiteJsonLd includes SearchAction", () => { it("buildWebSiteJsonLd includes SearchAction", () => {
+2 -2
View File
@@ -38,8 +38,8 @@ const jsonLd = [
<p class="mt-8 text-sm text-[var(--text-muted)]"> <p class="mt-8 text-sm text-[var(--text-muted)]">
Full release history on{" "} Full release history on{" "}
<a href="https://github.com/AdrianBonpin/gridline/releases" target="_blank" rel="noopener" class="text-[var(--primary)] hover:text-[var(--primary-hover)]"> <a href="https://git.ranio.xyz/adrianbonpin/gridline/releases" target="_blank" rel="noopener" class="text-[var(--primary)] hover:text-[var(--primary-hover)]">
GitHub Releases Gitea Releases
</a>. </a>.
</p> </p>
</div> </div>
+8 -8
View File
@@ -13,7 +13,7 @@ import queryEditor from "../assets/screenshots/query-editor.png";
import erDiagram from "../assets/screenshots/er-diagram.png"; import erDiagram from "../assets/screenshots/er-diagram.png";
import home from "../assets/screenshots/home.png"; import home from "../assets/screenshots/home.png";
import dataGrid from "../assets/screenshots/data-grid.png"; import dataGrid from "../assets/screenshots/data-grid.png";
const version = "0.7.11"; const version = "0.7.12";
const site = "https://getgridline.app"; const site = "https://getgridline.app";
const jsonLd = [ const jsonLd = [
buildSoftwareApplicationJsonLd({ buildSoftwareApplicationJsonLd({
@@ -26,7 +26,7 @@ const jsonLd = [
applicationCategory: "DeveloperApplication", applicationCategory: "DeveloperApplication",
offersPrice: 0, offersPrice: 0,
offersCurrency: "USD", offersCurrency: "USD",
downloadUrl: "https://github.com/AdrianBonpin/gridline/releases", downloadUrl: "https://git.ranio.xyz/adrianbonpin/gridline/releases",
}), }),
buildFaqJsonLd(faqItems), buildFaqJsonLd(faqItems),
buildOrganizationJsonLd({ buildOrganizationJsonLd({
@@ -34,7 +34,7 @@ const jsonLd = [
url: `${site}/`, url: `${site}/`,
logo: `${site}/gridline-icon.svg`, logo: `${site}/gridline-icon.svg`,
sameAs: [ sameAs: [
"https://github.com/AdrianBonpin/gridline", "https://git.ranio.xyz/adrianbonpin/gridline",
"https://x.com/AdrianBonpin", "https://x.com/AdrianBonpin",
], ],
}), }),
@@ -59,11 +59,11 @@ const jsonLd = [
Query, explore, sync, and edit PostgreSQL, MySQL, SQLite, and Redis — with no paywalls, no tab limits, no account required. Query, explore, sync, and edit PostgreSQL, MySQL, SQLite, and Redis — with no paywalls, no tab limits, no account required.
</p> </p>
<div class="mt-8 flex flex-wrap items-center justify-center gap-3"> <div class="mt-8 flex flex-wrap items-center justify-center gap-3">
<a id="download" href="https://github.com/AdrianBonpin/gridline/releases" class="primary-btn" data-download> <a id="download" href="https://git.ranio.xyz/adrianbonpin/gridline/releases" class="primary-btn" data-download>
Download Download
</a> </a>
<a href="https://github.com/AdrianBonpin/gridline" target="_blank" rel="noopener" class="ghost-btn"> <a href="https://git.ranio.xyz/adrianbonpin/gridline" target="_blank" rel="noopener" class="ghost-btn">
View on GitHub View on Gitea
</a> </a>
</div> </div>
@@ -174,8 +174,8 @@ const jsonLd = [
<p class="text-[var(--text-muted)] mb-8 max-w-md mx-auto"> <p class="text-[var(--text-muted)] mb-8 max-w-md mx-auto">
Gridline is Apache 2.0 and developed openly. Contributions, issues, and feature requests are welcome. Gridline is Apache 2.0 and developed openly. Contributions, issues, and feature requests are welcome.
</p> </p>
<a href="https://github.com/AdrianBonpin/gridline" target="_blank" rel="noopener" class="ghost-btn"> <a href="https://git.ranio.xyz/adrianbonpin/gridline" target="_blank" rel="noopener" class="ghost-btn">
Star on GitHub Star on Gitea
</a> </a>
</div> </div>
</section> </section>