4 Commits
Author SHA1 Message Date
adrianbonpin 6d1bbb0fa3 feat: copy connection URL from connection card menu (#22)
Release / publish (, ubuntu-22.04) (push) Failing after 20m6s
Release / publish (, windows-latest) (push) Canceled after 0s
Release / publish (--target aarch64-apple-darwin, macos-latest) (push) Canceled after 0s
Release / publish (--target x86_64-apple-darwin, macos-15-intel) (push) Canceled after 0s
Add 'Copy connection URL' (with password, fetched from the OS keychain on
demand) and 'Copy connection URL (no password)' to the connection card kebab
menu. Builds postgresql://, mysql://, sqlite://, redis:// strings with
percent-encoded credentials and the PG sslmode appended. Bump to v0.7.10.
2026-08-13 14:33:54 +08:00
adrianbonpin df3e3957a2 docs: add feature request + improvement issue templates, auto-assign new issues (#21)
- Add 2-feature_request.md and 3-improvement.md templates (chooser now
  lists Bug report, Feature request, Improvement in order)
- Improve bug report template: cross-template pointer, Linux log hint,
  Related issues/PRs section, demo-DB reproduction hint
- Auto-assign every newly opened issue to the repo owner:
  assignees in template front matter + a workflows/auto-assign.yml
  (covers issues opened without a template)
- Add ISSUE_TEMPLATE/config.yml with Roadmap/README contact links
- Update README Contributing section
2026-08-08 17:16:16 +08:00
adrianbonpin 5bc31a14a6 docs: use sudo for the quarantine-removal command everywhere (#20)
The bundle contains read-only files (e.g. the bundled OpenSSL dylibs), so a
sudo-less 'xattr -dr com.apple.quarantine' fails with Permission denied for
some users. Standardize on 'sudo xattr -dr com.apple.quarantine' in the
README, ROADMAP, the release.yml SIGNING STATUS comment, and the release
notes; AGENTS.md now requires release notes to carry the macOS first-launch
instructions until a Developer ID cert is secured.
2026-08-08 14:04:25 +08:00
adrianbonpin 38de9b4da1 docs: call out macOS first-launch caveat at the top Download section (#19)
Users at the top table previously saw no mention of the unsigned-app
warnings; the details lived ~200 lines down under Getting Started. Add a
callout right under the Download table linking to the macOS first-launch
instructions (right-click Open / xattr workaround).
2026-08-08 13:32:05 +08:00
19 changed files with 409 additions and 45 deletions
@@ -3,12 +3,15 @@ name: Bug report
about: Report a bug or unexpected behavior in Gridline so we can fix it
title: "[Bug]: "
labels: ["bug"]
assignees: ''
assignees: ['AdrianBonpin']
---
<!--
Thank you for reporting a bug! A complete report is the fastest way to get it fixed.
Please fill in every section you can — the environment table alone often closes issues.
Is this a request for something new instead? Use the
Feature request or Improvement templates — those get triaged faster.
-->
## Before you submit
@@ -58,7 +61,9 @@ assignees: ''
<!--
Paste the error text, console output, or a crash report here.
On macOS: Console.app → Gridline. On Windows: Event Viewer → Application.
- macOS: Console.app → Gridline
- Windows: Event Viewer → Application
- Linux: run Gridline from a terminal and capture stdout/stderr (or check the app-data dir, e.g. ~/.local/share/…)
Remove anything that looks like a host/user/password/token before pasting.
-->
@@ -76,6 +81,10 @@ assignees: ''
<!-- Anything that gets you unstuck today (e.g. "works when I connect without SSH", "restarting the app fixes it"). Even a partial workaround helps others. -->
## Related issues / PRs
<!-- Links to related issues, PRs, or roadmap items. -->
## Additional context
<!-- Anything else: does it reproduce with a fresh connection or only one specific database? Did it work in an earlier version? Frequency (always / sometimes)? -->
<!-- Anything else: does it reproduce with a fresh connection or only one specific database? Did it work in an earlier version? Frequency (always / sometimes)? Does it happen on the demo SQLite database? -->
@@ -0,0 +1,61 @@
---
name: Feature request
about: Suggest a new feature or capability for Gridline
title: "[Feature]: "
labels: ["enhancement"]
assignees: ['AdrianBonpin']
---
<!--
Thank you for suggesting a feature! 🎉
Before you submit, check that it isn't already shipped or planned:
- README feature list: https://github.com/AdrianBonpin/gridline#features
- Roadmap: https://github.com/AdrianBonpin/gridline/blob/prod/ROADMAP.md
-->
## Before you submit
- [ ] I searched [existing issues](https://github.com/AdrianBonpin/gridline/issues?q=is%3Aissue) and this isn't a duplicate
- [ ] I checked the [Roadmap](https://github.com/AdrianBonpin/gridline/blob/prod/ROADMAP.md) — it isn't already planned
- [ ] I checked the [README feature list](https://github.com/AdrianBonpin/gridline#features) — it isn't already shipped
- [ ] I will **not** paste secrets — redact any credentials as `<REDACTED>`
## Problem
<!-- What can't you do today? Describe the workflow that's blocked or painful. The problem matters more than the solution you have in mind. -->
## Proposed solution
<!-- How should it work? High-level description. Consider where it lives in the UI (home screen, DB viewer, object explorer, query editor, tools). Mockups welcome — see below. -->
## Example scenario
<!-- A concrete walkthrough: "I want to compare row counts across three production schemas, so I open … and …" -->
## Alternatives considered
<!-- What you do today instead: other tools, manual SQL, workarounds. This helps us judge how much it's really needed. -->
## Scope
<!-- Rough size so we can prioritize. -->
- [ ] Small — UI polish / single command
- [ ] Medium — a new view or feature area
- [ ] Large — new DB type, new driver, multi-week effort
## Mockups / screenshots
<!-- Drag & drop sketches or screenshots; ASCII art is fine. If referencing how another tool does it, name the tool (TablePlus, Beekeeper Studio, DB Pro, …). -->
## Related
<!-- Roadmap items, related issues/PRs, competitor references, docs links. -->
## Priority
<!-- Optional: how important is this to you? -->
- [ ] Nice to have
- [ ] Important — would save me time every day
- [ ] Critical — blocks my workflow without it
+40
View File
@@ -0,0 +1,40 @@
---
name: Improvement
about: Suggest a refinement or UX polish for an existing Gridline feature
title: "[Improvement]: "
labels: ["enhancement"]
assignees: ['AdrianBonpin']
---
<!--
For refinements to something that already exists — not brand-new features
(use the Feature request template for those) and not bugs (use Bug report).
Examples: a confusing label, a clunky interaction, a missing keyboard shortcut,
faster scroll, better default, accessibility fix.
-->
## Before you submit
- [ ] I searched [existing issues](https://github.com/AdrianBonpin/gridline/issues?q=is%3Aissue) and this isn't a duplicate
- [ ] This targets an **existing** feature (not a new one)
- [ ] I will **not** paste secrets — redact any credentials as `<REDACTED>`
## What's lacking today?
<!-- The pain point in the current behavior. Point at the exact screen/flow. -->
## Suggested change
<!-- The refinement. What should it look like / behave like? Specific beats vague. -->
## Why it matters
<!-- Who benefits and how (time saved, fewer misclicks, consistency, accessibility, …). -->
## Screenshots / recordings
<!-- Current behavior, plus any mock of the desired behavior. -->
## Related
<!-- Links to related issues/PRs. -->
+10
View File
@@ -0,0 +1,10 @@
# Template chooser settings — see
# https://docs.github.com/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository
blank_issues_enabled: true
contact_links:
- name: Gridline Roadmap
url: https://github.com/AdrianBonpin/gridline/blob/prod/ROADMAP.md
about: Check what's already planned before requesting a feature
- name: README — feature list
url: https://github.com/AdrianBonpin/gridline#features
about: Confirm a feature isn't already shipped
+25
View File
@@ -0,0 +1,25 @@
name: Auto-assign new issues
# Assigns every newly opened issue to the repository owner so nothing
# lands in the inbox untriaged. The `issues` event only fires in the base
# repository (not forks) and this job never checks out code — it only
# talks to the GitHub API, so there is no fork/pwn-request exposure.
on:
issues:
types: [opened]
permissions:
issues: write
jobs:
auto-assign:
name: Assign issue to repo owner
runs-on: ubuntu-latest
steps:
- name: Assign to ${{ github.repository_owner }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_URL: ${{ github.event.issue.html_url }}
OWNER: ${{ github.repository_owner }}
run: gh issue edit "$ISSUE_URL" --add-assignee "$OWNER"
+1 -1
View File
@@ -11,7 +11,7 @@ name: Release
# (bundle.macOS.signingIdentity: "-", hardenedRuntime: false). That replaces
# the linker-only signature that made macOS report the app as "damaged" and
# refuse Finder launches — users now get the standard "Open Anyway" prompt
# (or run `xattr -dr com.apple.quarantine` for the damaged-error case, see
# (or run `sudo xattr -dr com.apple.quarantine` for the damaged-error case, see
# README). Still NOT Developer-ID signed / notarized (no certs yet).
# tauri-action automatically signs + notarizes when the signing secrets are
# present, so the moment we add APPLE_CERTIFICATE / APPLE_API_KEY /
+2 -1
View File
@@ -168,6 +168,7 @@ Cut a release by tagging the **`prod`** branch once the PR is merged — `git ta
- `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.9/<file>`). tauri-action uses default versioned asset names (`Gridline_<ver>_aarch64.dmg`, `Gridline-<ver>-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.
- **Release notes must include the macOS first-launch instructions** — until a Developer ID signing certificate is secured, releases are ad-hoc signed only (not notarized), so every macOS user hits a Gatekeeper prompt. The draft release body must include: right-click → **Open** → **Open** (or System Settings → Privacy & Security → **Open Anyway**) for "developer cannot be verified", and `sudo xattr -dr com.apple.quarantine /Applications/Gridline.app` (with the re-run-after-every-upgrade note) for "damaged and can't be opened".
### Adding a Tauri Command
@@ -247,7 +248,7 @@ Planned work is prioritized in the [Project Roadmap](./ROADMAP.md) (source of tr
| Move-to-folder bulk action | ✅ | Selection toolbar → Move to Folder dialog (folder picker, move confirmed via dialog) |
| Favorites / Recent connections | ✅ | Star toggle in the connection card ⋮ menu (persisted `favorite` flag); Recent connections row (top 8 via `getRecentConnections`) |
| Connection status indicator on cards | ✅ | Kebab menu → Test connection with inline idle/checking/online/offline result, on-demand via keychain + `testConnection`. **Reports real `server_version` + `latency_ms`** (PG/MySQL/SQLite queries + connect timing in the Rust backend); shows `Online · 16.4 · 42ms` or the error, re-check debounced 2s |
| Connection card actions menu (⋮) | ✅ | Kebab dropdown: Favorite toggle, Test connection (inline status), Manage submenu (Edit… / Duplicate / Delete…) |
| Connection card actions menu (⋮) | ✅ | Kebab dropdown: Favorite toggle, Test connection (inline status), **Copy connection URL** (with password) / **Copy connection URL (no password)** (builds a `postgresql://`/`mysql://`/`sqlite://`/`redis://` string from the saved connection, percent-encoded, sslmode appended for PG; password fetched from keychain on demand), Manage submenu (Edit… / Duplicate / Delete…) |
### Database Viewer
| Feature | Status | Details |
+24 -22
View File
@@ -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.9**):
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.10**):
| OS | Architecture | Download |
| :--------------------------- | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------- |
| **macOS** | Apple Silicon (M1/M2/M3/M4…) | [Gridline_0.7.9_aarch64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline_0.7.9_aarch64.dmg) |
| **macOS** | Intel | [Gridline_0.7.9_x64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline_0.7.9_x64.dmg) |
| **Windows** | x64 | [Gridline_0.7.9_x64-setup.exe](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline_0.7.9_x64-setup.exe) |
| **Debian / Ubuntu** | amd64 | [Gridline_0.7.9_amd64.deb](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline_0.7.9_amd64.deb) |
| **Fedora / RHEL / openSUSE** | x86_64 | [Gridline-0.7.9-1.x86_64.rpm](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline-0.7.9-1.x86_64.rpm) |
| **Other Linux** | amd64 | [Gridline_0.7.9_amd64.AppImage](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline_0.7.9_amd64.AppImage) |
| **macOS** | Apple Silicon (M1/M2/M3/M4…) | [Gridline_0.7.10_aarch64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline_0.7.10_aarch64.dmg) |
| **macOS** | Intel | [Gridline_0.7.10_x64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline_0.7.10_x64.dmg) |
| **Windows** | x64 | [Gridline_0.7.10_x64-setup.exe](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline_0.7.10_x64-setup.exe) |
| **Debian / Ubuntu** | amd64 | [Gridline_0.7.10_amd64.deb](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline_0.7.10_amd64.deb) |
| **Fedora / RHEL / openSUSE** | x86_64 | [Gridline-0.7.10-1.x86_64.rpm](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline-0.7.10-1.x86_64.rpm) |
| **Other Linux** | amd64 | [Gridline_0.7.10_amd64.AppImage](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline_0.7.10_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.
> **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.9
MAINTENANCE: These links are STATIC (versioned) — they point at the v0.7.10
release assets, not at a moving "latest" target. On every new release,
update BOTH tables here (Download + Which file should I download?) to the
new version's asset names, which are tauri-action's default naming:
@@ -89,6 +89,8 @@ Gridline is built for developers and small teams who manage multiple database en
<details>
<summary>View the full changelog</summary>
- **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.8** — **SQLite `.dump` backup/restore** plus **backup / restore / sync for MySQL & SQLite** (mysqldump-based, system-first); **Excel (.xlsx) export** in the grid toolbar alongside CSV/JSON/SQL/Markdown; **cancel long-running queries** per connection; **settings export/import** (JSON); **Windows/Linux title bar fix** (macOS-only overlay drag strip); **SQLite table editor** (Create/Edit Table with SQLite-aware type mapping); version bump.
@@ -301,10 +303,10 @@ Gridline is currently distributed **unsigned** — it doesn't pay for code-signi
- **macOS — "Gridline is damaged and can't be opened":** remove the quarantine flag macOS attaches to downloaded apps, then launch normally:
```bash
xattr -dr com.apple.quarantine /Applications/Gridline.app
sudo xattr -dr com.apple.quarantine /Applications/Gridline.app
```
You need to re-run this after **every upgrade** (each freshly-downloaded copy gets re-quarantined).
(`sudo` is required — some files inside the bundle are read-only, so removing the flag needs admin rights.) Re-run after **every upgrade** — each freshly-downloaded copy gets re-quarantined.
- **Windows:** on the SmartScreen prompt, click **More info** → **Run anyway**.
- **Linux:** no warning — install and run normally.
@@ -312,16 +314,16 @@ Code signing **will be added in the future** (Apple Developer Program + a Window
#### 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.9**):
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.10**):
| Your system | Download this | Notes |
| :------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------- |
| macOS **Apple Silicon** (M1/M2/M3/M4…) | [Gridline_0.7.9_aarch64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline_0.7.9_aarch64.dmg) | `aarch64` = Apple's own chip |
| macOS **Intel** | [Gridline_0.7.9_x64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline_0.7.9_x64.dmg) | `x64` = Intel/AMD |
| **Windows** (most PCs) | [Gridline_0.7.9_x64-setup.exe](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline_0.7.9_x64-setup.exe) | The `.msi` is an alternate installer (for enterprises/IT admins) |
| **Debian / Ubuntu** | [Gridline_0.7.9_amd64.deb](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline_0.7.9_amd64.deb) | Install: `sudo apt install ./Gridline_0.7.9_amd64.deb` |
| **Fedora / RHEL / openSUSE** | [Gridline-0.7.9-1.x86_64.rpm](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline-0.7.9-1.x86_64.rpm) | Install: `sudo dnf install Gridline-0.7.9-1.x86_64.rpm` |
| **Any other Linux** | [Gridline_0.7.9_amd64.AppImage](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.9/Gridline_0.7.9_amd64.AppImage) | Works on every distro: `chmod +x` the file, then double-click it |
| macOS **Apple Silicon** (M1/M2/M3/M4…) | [Gridline_0.7.10_aarch64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline_0.7.10_aarch64.dmg) | `aarch64` = Apple's own chip |
| macOS **Intel** | [Gridline_0.7.10_x64.dmg](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline_0.7.10_x64.dmg) | `x64` = Intel/AMD |
| **Windows** (most PCs) | [Gridline_0.7.10_x64-setup.exe](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline_0.7.10_x64-setup.exe) | The `.msi` is an alternate installer (for enterprises/IT admins) |
| **Debian / Ubuntu** | [Gridline_0.7.10_amd64.deb](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline_0.7.10_amd64.deb) | Install: `sudo apt install ./Gridline_0.7.10_amd64.deb` |
| **Fedora / RHEL / openSUSE** | [Gridline-0.7.10-1.x86_64.rpm](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline-0.7.10-1.x86_64.rpm) | Install: `sudo dnf install Gridline-0.7.10-1.x86_64.rpm` |
| **Any other Linux** | [Gridline_0.7.10_amd64.AppImage](https://github.com/AdrianBonpin/gridline/releases/download/v0.7.10/Gridline_0.7.10_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.
@@ -331,8 +333,8 @@ Cutting a release is one command — CI builds everything. **Releases are cut fr
```bash
git checkout prod && git pull
git tag v0.7.9
git push origin v0.7.9
git tag v0.7.10
git push origin v0.7.10
```
GitHub Actions (`.github/workflows/release.yml`) builds installers for **Apple Silicon, Intel Macs, Windows, and Linux**, then opens a **draft release** on the [Releases](https://github.com/adrianbonpin/gridline/releases) page — review it and hit **Publish release**.
@@ -416,7 +418,7 @@ gridline/
## Roadmap
The full plan — next-up (v0.7.9), queue, and shipped history — lives in **[ROADMAP.md](./ROADMAP.md)**.
The full plan — next-up (v0.7.10), queue, and shipped history — lives in **[ROADMAP.md](./ROADMAP.md)**.
Highlights of what's next:
@@ -452,7 +454,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.
- Report a bug via the **[issue template](.github/ISSUE_TEMPLATE/bug_report.md)** — it walks you through environment details (OS, Gridline version, DB type/version, connection method) so we can reproduce issues quickly. Opening a [new issue](https://github.com/AdrianBonpin/gridline/issues/new) pre-fills the template automatically.
- 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.
- Submit a pull request. Keep Tauri commands thin, type IPC boundaries explicitly, and follow the existing Rust/React conventions.
---
+6 -1
View File
@@ -6,10 +6,15 @@ This file is the **source of truth** for what Gridline is building. [AGENTS.md](
---
## ✅ Shipped (0.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** 0.7.9 → **0.7.10**.
## ✅ Shipped (0.7.9)
- **macOS Finder launch fix** — the local store opens under the OS app-data directory instead of a cwd-relative `gridline.db` (Finder/LaunchServices launches run with cwd `/`, so the old path made the app silently exit with a Rust panic before the UI started).
- **macOS ad-hoc code signing** — bundles are signed at build time (`bundle.macOS.signingIdentity "-"`, hardened runtime off), replacing the Xcode linker-only signature that macOS treated as unsigned ("damaged and can't be opened", silent Finder refusal). Users still get a one-time Gatekeeper prompt; README documents the `xattr -dr com.apple.quarantine` workaround.
- **macOS ad-hoc code signing** — bundles are signed at build time (`bundle.macOS.signingIdentity "-"`, hardened runtime off), replacing the Xcode linker-only signature that macOS treated as unsigned ("damaged and can't be opened", silent Finder refusal). Users still get a one-time Gatekeeper prompt; README documents the `sudo xattr -dr com.apple.quarantine` workaround.
- **Vendored OpenSSL for `ssh2`** — the release binary no longer links an absolute Homebrew `/opt/homebrew/.../libssl.3.dylib` path (dyld aborted on machines without it).
- **Version bump** 0.7.8 → **0.7.9**.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "gridline",
"private": true,
"version": "0.7.9",
"version": "0.7.10",
"description": "An open-source, high-performance database GUI client for PostgreSQL and beyond",
"type": "module",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "gridline"
version = "0.7.9"
version = "0.7.10"
description = "An open-source, high-performance database GUI client for PostgreSQL and beyond"
authors = ["you"]
edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Gridline",
"version": "0.7.9",
"version": "0.7.10",
"identifier": "com.adrianbonpin.gridline",
"build": {
"beforeDevCommand": "bun run dev",
@@ -200,4 +200,41 @@ describe("ConnectionCardMenu", () => {
await userEvent.keyboard("{Escape}");
expect(screen.queryByText("Manage")).not.toBeInTheDocument();
});
it("Copy connection URL fetches the password and copies the full URL", async () => {
vi.mocked(commands.getConnectionPassword).mockResolvedValue("s3cret");
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, "clipboard", {
value: { writeText },
configurable: true,
});
renderMenu();
await openMenu();
await userEvent.click(screen.getByText("Copy connection URL"));
expect(commands.getConnectionPassword).toHaveBeenCalledWith("c1");
expect(writeText).toHaveBeenCalledWith(
"postgresql://:s3cret@prod.example.com:5432",
);
expect(screen.queryByText("Manage")).not.toBeInTheDocument();
});
it("Copy connection URL (no password) does not fetch the password", async () => {
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, "clipboard", {
value: { writeText },
configurable: true,
});
renderMenu();
await openMenu();
await userEvent.click(screen.getByText("Copy connection URL (no password)"));
expect(commands.getConnectionPassword).not.toHaveBeenCalled();
expect(writeText).toHaveBeenCalledWith(
"postgresql://prod.example.com:5432",
);
expect(screen.queryByText("Manage")).not.toBeInTheDocument();
});
});
@@ -5,6 +5,7 @@ import {
CircleCheck,
CircleX,
Copy,
EyeOff,
Loader2,
MoreVertical,
Pencil,
@@ -14,6 +15,7 @@ import {
import type { Connection } from "../../lib/types";
import { useConnectionStore } from "../../stores/connectionStore";
import { buildConfigFromConnection } from "./ConnectionCard";
import { buildConnectionUrl } from "../../lib/connectionString";
import { useConnectionStatus } from "./useConnectionStatus";
interface ConnectionCardMenuProps {
@@ -82,6 +84,19 @@ export function ConnectionCardMenu({
setManageOpen(false);
};
const copyUrl = async (withPassword: boolean) => {
let password: string | null = null;
if (withPassword) {
password = await useConnectionStore
.getState()
.getConnectionPassword(connection.id)
.catch(() => null);
}
const url = buildConnectionUrl(connection, password);
await navigator.clipboard.writeText(url).catch(() => {});
close();
};
const statusLabel =
state === "checking"
? "Testing…"
@@ -169,6 +184,28 @@ export function ConnectionCardMenu({
</span>
</button>
<button
type="button"
onClick={() => {
void copyUrl(true);
}}
className={menuItemClass}
>
<Copy size={14} className="text-text-muted" />
<span className="truncate">Copy connection URL</span>
</button>
<button
type="button"
onClick={() => {
void copyUrl(false);
}}
className={menuItemClass}
>
<EyeOff size={14} className="text-text-muted" />
<span className="truncate">Copy connection URL (no password)</span>
</button>
<button
type="button"
onClick={() => setManageOpen((m) => !m)}
+3 -3
View File
@@ -1,11 +1,11 @@
import { describe, it, expect } from "vitest";
import tauriConf from "../../src-tauri/tauri.conf.json";
describe("tauri bundle config (v0.7.9)", () => {
describe("tauri bundle config (v0.7.10)", () => {
it("declares bundled pg_tools resources", () => {
expect(tauriConf.bundle.resources).toContain("resources/pg_tools/*");
});
it("version is 0.7.9", () => {
expect(tauriConf.version).toBe("0.7.9");
it("version is 0.7.10", () => {
expect(tauriConf.version).toBe("0.7.10");
});
});
+92 -1
View File
@@ -1,5 +1,24 @@
import { describe, it, expect } from "vitest";
import { parseConnectionString, looksLikeConnectionString, detectProviderFromHost } from "./connectionString";
import { parseConnectionString, looksLikeConnectionString, detectProviderFromHost, buildConnectionUrl } from "./connectionString";
import type { Connection } from "./types";
function makeConn(overrides: Partial<Connection> = {}): Connection {
return {
id: "c1",
name: "Test",
db_type: "postgresql",
host: "localhost",
port: 5432,
username: "user",
folder_id: null,
keychain_ref: null,
tag_ids: [],
favorite: false,
created_at: "",
updated_at: "",
...overrides,
};
}
describe("parseConnectionString", () => {
it("parses a PostgreSQL URL", () => {
@@ -150,3 +169,75 @@ describe("detectProviderFromHost", () => {
expect(detectProviderFromHost("prod.example.com")).toBeNull();
});
});
describe("buildConnectionUrl", () => {
it("builds a PostgreSQL URL with password and sslmode", () => {
const url = buildConnectionUrl(
makeConn({ db_type: "postgresql", host: "prod.example.com", port: 5432, username: "alice", database: "app", ssl_mode: "require" }),
"s3cret",
);
expect(url).toBe("postgresql://alice:s3cret@prod.example.com:5432/app?sslmode=require");
});
it("omits password when null (no-password variant)", () => {
const url = buildConnectionUrl(
makeConn({ db_type: "postgresql", host: "prod.example.com", port: 5432, username: "alice", database: "app" }),
null,
);
expect(url).toBe("postgresql://alice@prod.example.com:5432/app");
});
it("omits sslmode when disable or unset", () => {
expect(
buildConnectionUrl(makeConn({ ssl_mode: "disable" }), null),
).toBe("postgresql://user@localhost:5432");
expect(
buildConnectionUrl(makeConn({ ssl_mode: undefined }), null),
).toBe("postgresql://user@localhost:5432");
});
it("percent-encodes special characters in user/password/database", () => {
const url = buildConnectionUrl(
makeConn({ username: "a b", database: "my db" }),
"p@ss w:rd",
);
expect(url).toBe("postgresql://a%20b:p%40ss%20w%3Ard@localhost:5432/my%20db");
});
it("builds a MySQL URL", () => {
const url = buildConnectionUrl(
makeConn({ db_type: "mysql", host: "127.0.0.1", port: 3306, username: "root", database: "app" }),
"pw",
);
expect(url).toBe("mysql://root:pw@127.0.0.1:3306/app");
});
it("builds a Redis URL with password but no username", () => {
const url = buildConnectionUrl(
makeConn({ db_type: "redis", host: "localhost", port: 6379, username: null, database: "0" }),
"pw",
);
expect(url).toBe("redis://:pw@localhost:6379/0");
});
it("builds a SQLite URL from the host file path", () => {
const url = buildConnectionUrl(
makeConn({ db_type: "sqlite", host: "/Users/me/data.db", port: null, username: null, database: null }),
null,
);
expect(url).toBe("sqlite:///Users/me/data.db");
});
it("round-trips through parseConnectionString", () => {
const conn = makeConn({ db_type: "postgresql", host: "localhost", port: 5432, username: "user", database: "mydb" });
const url = buildConnectionUrl(conn, "pass");
expect(parseConnectionString(url)).toEqual({
db_type: "postgresql",
host: "localhost",
port: 5432,
username: "user",
password: "pass",
database: "mydb",
});
});
});
+47 -1
View File
@@ -1,4 +1,4 @@
import type { DbType } from "./types";
import type { Connection, DbType } from "./types";
export interface ParsedConnectionString {
db_type: DbType;
@@ -89,3 +89,49 @@ export function detectProviderFromHost(host: string): "supabase" | "neon" | null
if (h.endsWith(".neon.tech")) return "neon";
return null;
}
/**
* Build a connection URL string from a saved connection, for copying into
* env vars / other tools. `password` is the decrypted keychain value (or
* `null` to omit it — e.g. the "no password" copy variant).
*
* Formats per DB type:
* - postgresql: `postgresql://user:pass@host:port/db?sslmode=...`
* - mysql: `mysql://user:pass@host:port/db`
* - sqlite: `sqlite:///abs/path` (host stores the file path)
* - redis: `redis://user:pass@host:port/db`
*
* Username/password/database are percent-encoded; the ssl_mode is appended as
* a query param for PostgreSQL when set to anything other than `disable`.
*/
export function buildConnectionUrl(conn: Connection, password: string | null): string {
const enc = (s: string) => encodeURIComponent(s);
const port = conn.port ? `:${conn.port}` : "";
const user = conn.username ? enc(conn.username) : "";
const auth = password
? user
? `${user}:${enc(password)}@`
: `:${enc(password)}@`
: user
? `${user}@`
: "";
const db = conn.database ? `/${enc(conn.database)}` : "";
switch (conn.db_type) {
case "postgresql": {
const ssl =
conn.ssl_mode && conn.ssl_mode !== "disable"
? `?sslmode=${encodeURIComponent(conn.ssl_mode)}`
: "";
return `postgresql://${auth}${conn.host}${port}${db}${ssl}`;
}
case "mysql":
return `mysql://${auth}${conn.host}${port}${db}`;
case "sqlite":
// host stores the file path; `sqlite:///abs/path` round-trips through
// parseConnectionString (pathname → host).
return `sqlite://${conn.host}`;
case "redis":
return `redis://${auth}${conn.host}${port}${db}`;
}
}
+7 -7
View File
@@ -4,7 +4,7 @@ import { describe, it, expect } from "vitest";
import agents from "../../AGENTS.md?raw";
import readme from "../../README.md?raw";
describe("v0.7.9 docs coverage", () => {
describe("v0.7.10 docs coverage", () => {
it("AGENTS.md marks inline cell editing complete", () => {
expect(agents).toContain("Inline cell editing");
expect(agents).toMatch(/Inline cell editing \| ✅/);
@@ -24,8 +24,8 @@ describe("v0.7.9 docs coverage", () => {
expect(agents).toMatch(/Connection status indicator on cards \| ✅/);
expect(agents).toMatch(/Move-to-folder bulk action \| ✅/);
});
it("README declares v0.7.9", () => {
expect(readme).toContain("0.7.9");
it("README declares v0.7.10", () => {
expect(readme).toContain("0.7.10");
});
it("AGENTS.md marks schema CRUD complete", () => {
expect(agents).toMatch(/Schema CRUD \| ✅/);
@@ -64,9 +64,9 @@ describe("v0.7.9 docs coverage", () => {
expect(agents).toMatch(/Cancel long-running queries \| ✅/);
expect(agents).toMatch(/Settings export\/import \| ✅/);
});
it("README links to v0.7.9 assets in both download tables", () => {
expect(readme).toContain("releases/download/v0.7.9/");
expect(readme).toContain("Gridline_0.7.9_aarch64.dmg");
expect(readme).toContain("Gridline-0.7.9-1.x86_64.rpm");
it("README links to v0.7.10 assets in both download tables", () => {
expect(readme).toContain("releases/download/v0.7.10/");
expect(readme).toContain("Gridline_0.7.10_aarch64.dmg");
expect(readme).toContain("Gridline-0.7.10-1.x86_64.rpm");
});
});
+2 -2
View File
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
import pkg from "../../package.json";
describe("version", () => {
it("declares v0.7.9 across the app shell", () => {
expect(pkg.version).toBe("0.7.9");
it("declares v0.7.10 across the app shell", () => {
expect(pkg.version).toBe("0.7.10");
});
});