Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
workspaces: ./src-tauri -> target

# Named packages, NOT --workspace: that would pull in the Tauri app crate
# and with it WebKitGTK on Linux / WebView2 on Windows. These nine are
# and with it WebKitGTK on Linux / WebView2 on Windows. These ten are
# split out (CLAUDE.md §4) precisely so their gates run without a webview.
#
# keystore's tests all run against its in-memory double. The real
Expand All @@ -94,7 +94,10 @@ jobs:
#
# Windows matters here too: fsatomic is the §3.1 atomic-save gate and the
# one crate doing real filesystem syscalls, where replace-over-existing
# differs between rename(2) and MoveFileEx.
# differs between rename(2) and MoveFileEx. fileops is the second such
# crate and needs Windows even more specifically: the names it rejects
# (reserved devices, trailing dots) and the case-only rename it must allow
# are *Windows* behaviours, and the Linux leg alone cannot see either.
- name: Test logic crates
run: >
cargo test
Expand All @@ -107,6 +110,7 @@ jobs:
-p snapshots
-p mergemd
-p keystore
-p fileops

# `--all` covers workspace *members*, and the vendored glib is excluded
# from the workspace (§2) — so unlike clippy, this never touches upstream
Expand Down
65 changes: 64 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,46 @@ jobs:
args: ""

runs-on: ${{ matrix.platform }}

# Job level, not step level, so a step's `if:` can read it. A step cannot
# reliably test an env var it declares itself.
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}

steps:
- uses: actions/checkout@v4

# Fail in five seconds with an explanation rather than twenty minutes into
# a Rust build. `createUpdaterArtifacts` is on in tauri.conf.json, and the
# bundler refuses to produce an unsigned update — so a missing key is a
# configuration error, not a build error, and should read like one.
#
# This is deliberately a hard stop rather than a "skip the updater and
# carry on": silently cutting another release with no update path is
# exactly how v1.0.0 stranded every copy of itself.
- name: Check the updater signing key is configured
shell: bash
env:
KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if [ -z "$KEY" ]; then
echo "::error::TAURI_SIGNING_PRIVATE_KEY is not set. Update artifacts cannot be signed."
echo "::error::Generate a keypair with 'pnpm tauri signer generate', add the private key"
echo "::error::as this repository secret, and paste the public key into"
echo "::error::src-tauri/tauri.conf.json under plugins.updater.pubkey."
echo "::error::See docs/RELEASE-SIGNING.md."
exit 1
fi
if ! grep -q '"pubkey": *"[^"]' src-tauri/tauri.conf.json; then
echo "::error::plugins.updater.pubkey is empty in src-tauri/tauri.conf.json."
echo "::error::Installed builds verify updates against it, so shipping it empty"
echo "::error::produces a release that can never update itself."
echo "::error::See docs/RELEASE-SIGNING.md."
exit 1
fi

- name: Install Linux webview dependencies
if: matrix.platform == 'ubuntu-22.04'
run: |
Expand Down Expand Up @@ -58,9 +95,29 @@ jobs:
- name: Install frontend dependencies
run: pnpm install --frozen-lockfile

# Windows Authenticode via Azure Trusted Signing, and *only* when the
# account exists. The signCommand lives in an overlay config applied here
# rather than in tauri.conf.json, so a fork — or a local `pnpm tauri build`
# — still produces a working unsigned installer instead of failing on a
# missing tool. Without this, every contributor would need an Azure
# subscription to build the app at all.
- name: Set up Windows code signing
if: matrix.platform == 'windows-latest' && env.AZURE_CLIENT_ID != ''
shell: bash
run: |
cargo install trusted-signing-cli --locked
echo "SIGNING_CONFIG=--config src-tauri/tauri.signing.conf.json" >> "$GITHUB_ENV"

- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Signs the update artifacts (minisign). Not the same thing as
# Authenticode above: this proves an update came from us, that one
# stops SmartScreen warning about the installer.
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# The AZURE_* trio that trusted-signing-cli reads comes from the job's
# env above, so it is already in scope here.
with:
tagName: ${{ github.ref_name }}
releaseName: "Toril ${{ github.ref_name }}"
Expand All @@ -81,4 +138,10 @@ jobs:
# pinned to `true` through the alpha/beta series, which would have published
# v1.0.0 itself as a prerelease labelled "early alpha".
prerelease: ${{ contains(github.ref_name, '-') }}
args: ${{ matrix.args }}
# Publishes `latest.json` beside the installers — the static manifest
# the in-app updater fetches. Without it the plugin has nothing to read
# and every check reports "up to date" forever.
includeUpdaterJson: true
# `SIGNING_CONFIG` is set only on Windows and only when the Azure
# secrets exist; it expands to nothing otherwise.
args: ${{ matrix.args }} ${{ env.SIGNING_CONFIG }}
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ dist-ssr
/toril-harness-*.png
/sweep-*.png

# Signing keys — a backstop, not the intended home. The updater's private key
# belongs outside the repository entirely (docs/RELEASE-SIGNING.md); losing it
# strands every installed copy, and committing it lets anyone ship an "update"
# that Toril would trust and install.
*.key
*.key.pub
/.tauri/

# Editor / OS
.DS_Store
.idea/
Expand Down
64 changes: 64 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,70 @@ GitHub Release notes plus the commits that shipped in it.

## [Unreleased]

### Added
- **Manage your notes from the files pane.** New note, new folder, rename and delete,
from a right-click on any file or folder — or the `+` button beside the folder name.
Until now Toril could open and save notes but not *organize* them: renaming a note
meant leaving for the file manager and coming back.

**Deleting moves the note to a `.trash` folder inside your workspace, and offers you
Undo.** Nothing is unlinked, so a delete is recoverable long after the message has
gone — and because it is recoverable, Toril does not interrupt you to confirm it. The
one thing trash cannot bring back is a buffer you never saved, so that is the one case
that stops and asks.

**A rename takes the note's version history with it**, and moves any open tab —
including every tab inside a renamed folder — to the new path. Renaming does not
rewrite a single byte of the file.

Names are checked before anything touches disk, against the rules Windows actually
enforces: no `< > : " / \ | ? *`, nothing ending in a space or a dot, and none of the
reserved device names (`CON`, `NUL`, `COM1`…) that appear to work and then behave like
hardware. Renaming `notes.md` to `Notes.md` — a change of case only — works. Nothing
can be created or renamed outside the folder you opened, and no operation will ever
overwrite a file that is already there.

- **Toril can update itself.** `v1.0.0` had no update path at all, so every copy was
stranded on the version it was installed with — the only way forward was to notice a
new release and download the installer by hand. Toril now checks for a newer build
once a day at launch, and whenever you ask via **Help → Check for Updates…**.

**It tells you; it never installs behind your back.** You choose when to download, and
a restart is refused while anything is unsaved — the update is already on disk by then
and applies the next time you start, so waiting costs nothing. Automatic checks can be
turned off in **View → Check for Updates on Launch**.

The check is a plain request for a static file. Nothing about you, your vault or your
session is sent with it, and there is no telemetry in Toril.

- **The window remembers where it was.** Size, position and maximized state come back
the way you left them.

- **Zoom the writing surface** with `Ctrl` and `+` / `-` / `0`. It scales the text and
the measure, not the tab bar — your display scaling already handles the whole UI, and
a bigger tab bar is not what anyone wants at 11pm.

- **File → Open Recent** lists the last ten notes you opened. An entry that no longer
resolves removes itself rather than failing twice.

- **Ctrl-click a link** to open it in your browser. Only web and email links are handed
to the system — a note can come from anywhere, and the rest of what a URL can name is
not something an editor should hand to your operating system on a click.

- **Drop notes on the window to open them.** `.md`, `.markdown`, `.html` and `.htm`;
anything else in the same drop is skipped and counted.

- **A real welcome note on first run**, and a blank page on every later launch with
nothing to restore — the two used to be the same two-line stub. With no folder open,
the files pane now offers to open one instead of only saying that none is.

### Notes
- Updates are cryptographically signed, and an installed Toril refuses one that does not
verify. Setting that up is a one-time step for whoever cuts releases — see
`docs/RELEASE-SIGNING.md`.
- Windows installers are still unsigned, so SmartScreen still warns on first run. The
wiring for Azure Trusted Signing is in place but inert until an account exists.

## [v1.0.0] — 2026-08-17

**Toril leaves beta.** Same promise as always: your notes are plain `.md` (and
Expand Down
Loading
Loading