diff --git a/.github/workflows/redeploy-site-on-release.yml b/.github/workflows/redeploy-site-on-release.yml
index 50700f45..6d805906 100644
--- a/.github/workflows/redeploy-site-on-release.yml
+++ b/.github/workflows/redeploy-site-on-release.yml
@@ -1,24 +1,24 @@
name: Redeploy site on release publish
-# The releases page (on the `site` branch) is rendered at build time, so it only
-# reflects releases that existed when it was last built. `release` events fire
-# only for workflows on the default branch, so this lives on `main` and simply
-# triggers the site's own deploy workflow over on the `site` branch.
+# The releases page is rendered at build time, so it only reflects releases that
+# existed when it was last built. `release` events fire only for workflows on the
+# default branch, so this lives on `main`.
+#
+# The site is built by Cloudflare Workers Builds, which triggers on a commit to
+# `site` — and publishing a release makes no commit anywhere. Without this the
+# site simply goes stale, with nothing failing to say so.
on:
release:
types: [published]
workflow_dispatch:
-permissions:
- actions: write # needed to dispatch another workflow
-
jobs:
trigger:
runs-on: ubuntu-latest
steps:
- - name: Trigger site deploy
- # --repo is required: this job has no checkout, so gh can't infer the
- # repository from a local git remote.
- run: gh workflow run deploy-site.yml --ref site --repo "${{ github.repository }}"
+ - name: Trigger the Cloudflare build
+ # -f, or curl exits 0 on a 404 and this job stays green having done
+ # nothing — which is the failure this workflow exists to prevent.
+ run: curl -fsS -X POST "$HOOK"
env:
- GH_TOKEN: ${{ github.token }}
+ HOOK: ${{ secrets.CLOUDFLARE_DEPLOY_HOOK }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c4674435..8f4beb49 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -19,6 +19,10 @@ env:
# the repo. Absent (any local build): the Google Sheets export reports it isn't configured.
QRATE_GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
QRATE_GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
+ # Sent to the credential endpoint. It ships inside the binary, so it turns away casual scraping
+ # and nothing more — see docs/site-oauth-handoff.md. Absent: the endpoint answers 401 and the
+ # app carries on with the pair above.
+ QRATE_GOOGLE_CONFIG_TOKEN: ${{ secrets.GOOGLE_CONFIG_TOKEN }}
jobs:
# ---------------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index e8b9910d..d2a1bf70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,7 @@ GEMINI.md
# The Islandora plugin is its own repository, checked out here so a dev build loads it.
plugins/islandora/
+
+# The OAuth client JSON Google hands you. Named here because the download lands with this shape
+# and a stray copy in the repo root is the easiest way to publish it by accident.
+client_secret_*.json
diff --git a/Cargo.lock b/Cargo.lock
index a289439e..ca8701dc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -23,7 +23,7 @@ dependencies = [
"atspi-common",
"phf 0.13.1",
"serde",
- "zvariant",
+ "zvariant 5.10.0",
]
[[package]]
@@ -75,7 +75,7 @@ dependencies = [
"futures-lite",
"futures-util",
"serde",
- "zbus",
+ "zbus 5.14.0",
]
[[package]]
@@ -216,6 +216,7 @@ dependencies = [
"gpui-component",
"gpui-component-assets",
"gpui_platform",
+ "keyring",
"log",
"os_info",
"plugin-api",
@@ -313,7 +314,7 @@ dependencies = [
"futures-util",
"getrandom 0.4.2",
"serde",
- "zbus",
+ "zbus 5.14.0",
]
[[package]]
@@ -512,11 +513,11 @@ dependencies = [
"enumflags2",
"serde",
"static_assertions",
- "zbus",
+ "zbus 5.14.0",
"zbus-lockstep",
"zbus-lockstep-macros",
- "zbus_names",
- "zvariant",
+ "zbus_names 4.3.1",
+ "zvariant 5.10.0",
]
[[package]]
@@ -527,7 +528,7 @@ checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc"
dependencies = [
"atspi-common",
"serde",
- "zbus",
+ "zbus 5.14.0",
]
[[package]]
@@ -1522,6 +1523,35 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
+[[package]]
+name = "dbus"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e"
+dependencies = [
+ "libc",
+ "libdbus-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "dbus-secret-service"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6"
+dependencies = [
+ "aes",
+ "block-padding",
+ "cbc",
+ "dbus",
+ "fastrand",
+ "hkdf",
+ "num",
+ "once_cell",
+ "sha2",
+ "zeroize",
+]
+
[[package]]
name = "deflate64"
version = "0.1.11"
@@ -3498,6 +3528,24 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "keyring"
+version = "3.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
+dependencies = [
+ "byteorder",
+ "dbus-secret-service",
+ "linux-keyutils",
+ "log",
+ "secret-service",
+ "security-framework 2.11.1",
+ "security-framework 3.7.0",
+ "windows-sys 0.60.2",
+ "zbus 4.4.0",
+ "zeroize",
+]
+
[[package]]
name = "khronos-egl"
version = "6.0.0"
@@ -3603,6 +3651,15 @@ version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
+[[package]]
+name = "libdbus-sys"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
+dependencies = [
+ "pkg-config",
+]
+
[[package]]
name = "libfuzzer-sys"
version = "0.4.12"
@@ -3670,6 +3727,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c7b0a3383c2a1002d11349c92c85a666a5fb679e96c79d782cf0dbe557fd6ee"
+[[package]]
+name = "linux-keyutils"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590"
+dependencies = [
+ "bitflags 2.11.0",
+ "libc",
+]
+
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
@@ -4117,7 +4184,7 @@ dependencies = [
"openssl-probe",
"openssl-sys",
"schannel",
- "security-framework",
+ "security-framework 3.7.0",
"security-framework-sys",
"tempfile",
]
@@ -4157,6 +4224,19 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.11.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
[[package]]
name = "nix"
version = "0.31.2"
@@ -4860,10 +4940,10 @@ dependencies = [
"serde_bytes",
"sha2",
"subtle",
- "zbus",
- "zbus_macros",
+ "zbus 5.14.0",
+ "zbus_macros 5.14.0",
"zeroize",
- "zvariant",
+ "zvariant 5.10.0",
]
[[package]]
@@ -4951,7 +5031,7 @@ checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b"
dependencies = [
"android_system_properties",
"log",
- "nix",
+ "nix 0.31.2",
"objc2 0.6.4",
"objc2-foundation 0.3.2",
"objc2-ui-kit",
@@ -6256,7 +6336,7 @@ dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
- "security-framework",
+ "security-framework 3.7.0",
]
[[package]]
@@ -6425,6 +6505,38 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+[[package]]
+name = "secret-service"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4"
+dependencies = [
+ "aes",
+ "cbc",
+ "futures-util",
+ "generic-array",
+ "hkdf",
+ "num",
+ "once_cell",
+ "rand 0.8.5",
+ "serde",
+ "sha2",
+ "zbus 4.4.0",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.11.0",
+ "core-foundation 0.9.4",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
[[package]]
name = "security-framework"
version = "3.7.0"
@@ -9014,6 +9126,15 @@ dependencies = [
"windows-targets 0.52.6",
]
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
[[package]]
name = "windows-sys"
version = "0.61.2"
@@ -9409,6 +9530,16 @@ dependencies = [
"x11",
]
+[[package]]
+name = "xdg-home"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "xml-rs"
version = "0.8.28"
@@ -9487,6 +9618,44 @@ dependencies = [
"synstructure",
]
+[[package]]
+name = "zbus"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-fs",
+ "async-io",
+ "async-lock",
+ "async-process",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "enumflags2",
+ "event-listener",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "hex",
+ "nix 0.29.0",
+ "ordered-stream",
+ "rand 0.8.5",
+ "serde",
+ "serde_repr",
+ "sha1",
+ "static_assertions",
+ "tracing",
+ "uds_windows",
+ "windows-sys 0.52.0",
+ "xdg-home",
+ "zbus_macros 4.4.0",
+ "zbus_names 3.0.0",
+ "zvariant 4.2.0",
+]
+
[[package]]
name = "zbus"
version = "5.14.0"
@@ -9517,9 +9686,9 @@ dependencies = [
"uuid",
"windows-sys 0.61.2",
"winnow",
- "zbus_macros",
- "zbus_names",
- "zvariant",
+ "zbus_macros 5.14.0",
+ "zbus_names 4.3.1",
+ "zvariant 5.10.0",
]
[[package]]
@@ -9529,7 +9698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863"
dependencies = [
"zbus_xml",
- "zvariant",
+ "zvariant 5.10.0",
]
[[package]]
@@ -9543,7 +9712,20 @@ dependencies = [
"syn",
"zbus-lockstep",
"zbus_xml",
- "zvariant",
+ "zvariant 5.10.0",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "zvariant_utils 2.1.0",
]
[[package]]
@@ -9556,9 +9738,20 @@ dependencies = [
"proc-macro2",
"quote",
"syn",
- "zbus_names",
- "zvariant",
- "zvariant_utils",
+ "zbus_names 4.3.1",
+ "zvariant 5.10.0",
+ "zvariant_utils 3.3.0",
+]
+
+[[package]]
+name = "zbus_names"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
+dependencies = [
+ "serde",
+ "static_assertions",
+ "zvariant 4.2.0",
]
[[package]]
@@ -9569,7 +9762,7 @@ checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f"
dependencies = [
"serde",
"winnow",
- "zvariant",
+ "zvariant 5.10.0",
]
[[package]]
@@ -9580,8 +9773,8 @@ checksum = "441a0064125265655bccc3a6af6bef56814d9277ac83fce48b1cd7e160b80eac"
dependencies = [
"quick-xml 0.38.4",
"serde",
- "zbus_names",
- "zvariant",
+ "zbus_names 4.3.1",
+ "zvariant 5.10.0",
]
[[package]]
@@ -9932,6 +10125,19 @@ dependencies = [
"zune-core 0.5.1",
]
+[[package]]
+name = "zvariant"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
+dependencies = [
+ "endi",
+ "enumflags2",
+ "serde",
+ "static_assertions",
+ "zvariant_derive 4.2.0",
+]
+
[[package]]
name = "zvariant"
version = "5.10.0"
@@ -9943,8 +10149,21 @@ dependencies = [
"serde",
"serde_bytes",
"winnow",
- "zvariant_derive",
- "zvariant_utils",
+ "zvariant_derive 5.10.0",
+ "zvariant_utils 3.3.0",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "zvariant_utils 2.1.0",
]
[[package]]
@@ -9957,7 +10176,18 @@ dependencies = [
"proc-macro2",
"quote",
"syn",
- "zvariant_utils",
+ "zvariant_utils 3.3.0",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml
index 10c23433..ae6223b2 100644
--- a/crates/app/Cargo.toml
+++ b/crates/app/Cargo.toml
@@ -20,6 +20,15 @@ schemars.workspace = true
# The agent bridge's request/response types; the transport lives here because only the
# composition root links both the contract and the live table.
ai = { path = "../ai" }
+# Where the Google refresh token lives; it never expires, so it doesn't go in the settings DB.
+# The Linux features are the pure-Rust ones — the `sync`/`openssl` alternatives link libdbus.
+keyring = { version = "3", features = [
+ "apple-native",
+ "windows-native",
+ "linux-native-async-persistent",
+ "async-io",
+ "crypto-rust",
+] }
window-wrapper = { path = "../window-wrapper" }
project-wizard = { path = "../project-wizard" }
checks = { path = "../checks" }
diff --git a/crates/app/src/app_menus.rs b/crates/app/src/app_menus.rs
index 5858468c..74dbbb18 100644
--- a/crates/app/src/app_menus.rs
+++ b/crates/app/src/app_menus.rs
@@ -2,7 +2,7 @@ use gpui::*;
use window_wrapper::OpenBrowser;
use crate::actions::{NewProject, Save, ToggleBottomDock, ToggleLeftDock, ToggleRightDock};
-use crate::export::{EXPORT_FORMATS, Export, ExportFormat};
+use crate::export::{EXPORT_FORMATS, Export};
use crate::theming::{SwitchTheme, theme_choices};
// The Edit menu's items act on the grid, so they're the grid's actions — `table` declares and
@@ -69,14 +69,18 @@ fn app_menus(cx: &gpui::App) -> Vec