Commit graph

72 commits

Author SHA1 Message Date
le king fu
e314bbe1e3 fix: remove handle_auth_callback from invoke_handler
All checks were successful
PR Check / rust (push) Successful in 17m12s
PR Check / frontend (push) Successful in 2m12s
PR Check / rust (pull_request) Successful in 16m56s
PR Check / frontend (pull_request) Successful in 2m14s
The auth callback is handled exclusively via the deep-link handler in
lib.rs — exposing it as a JS-invocable command is unnecessary attack
surface. The frontend listens for auth-callback-success/error events
instead.

Plaintext token storage documented as known limitation (see #66).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:35:10 -04:00
le king fu
60b995394e fix: tighten CSP img-src, show initials instead of external avatar
Some checks are pending
PR Check / rust (push) Waiting to run
PR Check / frontend (push) Waiting to run
PR Check / rust (pull_request) Successful in 17m9s
PR Check / frontend (pull_request) Successful in 2m15s
Privacy-first: remove 'https:' from img-src CSP directive to prevent
IP leaks via external avatar URLs (Google/Gravatar). AccountCard now
shows user initials instead of loading a remote image.

Also remove .keys-temp/ from .gitignore (not relevant to this PR).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:24:52 -04:00
le king fu
4e92882724 fix: restrict last_check file perms + add useAuth to architecture docs
Some checks are pending
PR Check / rust (push) Waiting to run
PR Check / frontend (push) Waiting to run
PR Check / rust (pull_request) Successful in 17m24s
PR Check / frontend (pull_request) Successful in 2m14s
- Use write_restricted() for auth/last_check file (consistent 0600)
- Add useAuth hook to the hooks table in docs/architecture.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:14:31 -04:00
le king fu
ca3005bc0e fix: use write_restricted for account.json (0600 perms)
Some checks are pending
PR Check / rust (push) Waiting to run
PR Check / frontend (push) Waiting to run
PR Check / rust (pull_request) Successful in 17m0s
PR Check / frontend (pull_request) Successful in 2m12s
account.json contains PII and subscription_status — apply the same
restricted file permissions as tokens.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:04:05 -04:00
le king fu
9e26ad58d1 fix: use base64 crate, restrict token file perms, safer chrono_now
Some checks are pending
PR Check / rust (push) Waiting to run
PR Check / frontend (push) Waiting to run
PR Check / rust (pull_request) Successful in 17m32s
PR Check / frontend (pull_request) Successful in 2m15s
- Replace hand-rolled base64 encoder with base64::URL_SAFE_NO_PAD crate
- Set 0600 permissions on tokens.json via write_restricted() helper (Unix)
- Replace chrono_now() .unwrap() with .unwrap_or_default()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:58:10 -04:00
le king fu
be5f6a55c5 fix: URL-decode auth code + replace Mutex unwrap with map_err
Some checks failed
PR Check / rust (push) Has been cancelled
PR Check / frontend (push) Has been cancelled
PR Check / rust (pull_request) Successful in 17m21s
PR Check / frontend (pull_request) Successful in 2m21s
- extract_auth_code now URL-decodes the code parameter to handle
  percent-encoded characters from the OAuth provider
- Replace Mutex::lock().unwrap() with .lock().map_err() in start_oauth
  and handle_auth_callback to avoid panics on poisoned mutex

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:43:18 -04:00
le king fu
b53a902f11 feat: Maximus Account OAuth2 PKCE + machine activation + subscription check (#51, #53)
All checks were successful
PR Check / rust (push) Successful in 16m34s
PR Check / frontend (push) Successful in 2m14s
PR Check / rust (pull_request) Successful in 16m31s
PR Check / frontend (pull_request) Successful in 2m13s
- Add auth_commands.rs: OAuth2 PKCE flow (start_oauth, handle_auth_callback,
  refresh_auth_token, get_account_info, check_subscription_status, logout)
- Add deep-link handler in lib.rs for simpl-resultat://auth/callback
- Add AccountCard.tsx + useAuth hook + authService.ts
- Add machine activation commands (activate, deactivate, list, get_activation_status)
- Extend LicenseCard with machine management UI
- get_edition() now checks account subscription for Premium detection
- Daily subscription status check (refresh token if last check > 24h)
- Configure CSP for API/auth endpoints
- Configure tauri-plugin-deep-link for desktop
- Update i18n (FR/EN), changelogs, and architecture docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:18:51 -04:00
le king fu
2e9df1c0b9 fix(rust): pass raw public key bytes to DecodingKey::from_ed_der
All checks were successful
PR Check / rust (push) Successful in 15m54s
PR Check / frontend (push) Successful in 2m15s
PR Check / rust (pull_request) Successful in 16m7s
PR Check / frontend (pull_request) Successful in 2m15s
Previous test refactor wrapped both keys in their respective DER
envelopes. CI surfaced the asymmetry: jsonwebtoken's two from_ed_der
constructors expect different inputs.

- EncodingKey::from_ed_der → PKCS#8 v1 wrapped (ring's
  Ed25519KeyPair::from_pkcs8 path). The 16-byte prefix + 32-byte seed
  blob is correct.
- DecodingKey::from_ed_der → raw 32-byte public key. Internally it
  becomes ring's UnparsedPublicKey::new(&ED25519, key_bytes), which
  takes the bare bytes, NOT a SubjectPublicKeyInfo wrapper.

The test was building an SPKI DER for the public key, so verification
saw a malformed key and failed every signature with InvalidSignature
(`accepts_well_formed_base_license` and `activation_token_matches_machine`).

Drop the SPKI helper, pass `signing_key.verifying_key().to_bytes()`
straight into DecodingKey::from_ed_der. Inline doc-comment captures
the asymmetry so the next person doesn't fall in the same hole.
2026-04-09 11:12:10 -04:00
le king fu
69e136cab0 fix(rust): use DER-built keys in license tests, drop ed25519-dalek pem feature
Some checks failed
PR Check / rust (push) Failing after 10m20s
PR Check / frontend (push) Successful in 2m15s
PR Check / rust (pull_request) Failing after 9m30s
PR Check / frontend (pull_request) Successful in 2m7s
cargo CI flagged: `unresolved import ed25519_dalek::pkcs8::LineEnding`. The
`LineEnding` re-export path varies between pkcs8/spki/der versions, so the
test code that called `to_pkcs8_pem(LineEnding::LF)` won't compile against
the dependency tree we get with ed25519-dalek 2.2 + pkcs8 0.10.

Fix:
- Drop the `pem` feature from the ed25519-dalek dev-dependency.
- In tests, build the PKCS#8 v1 PrivateKeyInfo and SubjectPublicKeyInfo
  DER blobs manually from the raw 32-byte Ed25519 seed/public key. The
  Ed25519 layout is fixed (16-byte prefix + 32-byte key) so this is short
  and stable.
- Pass the resulting DER bytes to `EncodingKey::from_ed_der` /
  `DecodingKey::from_ed_der`.

Refactor:
- Extract `strict_validation()` and `embedded_decoding_key()` helpers so
  the validation config (mandatory exp/iat for CWE-613) lives in one
  place and production callers all share the same DecodingKey constructor.
- `validate_with_key` and `validate_activation_with_key` now take a
  `&DecodingKey` instead of raw PEM bytes; production builds the key
  once via `embedded_decoding_key()`.
- New canary test `embedded_public_key_pem_parses` fails fast if the
  embedded PEM constant ever becomes malformed.
2026-04-09 10:59:12 -04:00
le king fu
99fef19a6b feat: add license validation and entitlements (Rust) (#46)
Some checks failed
PR Check / rust (push) Failing after 5m50s
PR Check / frontend (push) Successful in 2m9s
PR Check / rust (pull_request) Failing after 6m1s
PR Check / frontend (pull_request) Successful in 2m12s
Introduces the offline license infrastructure for the Base/Premium editions.

- jsonwebtoken (EdDSA) verifies license JWTs against an embedded Ed25519
  public key. The exp claim is mandatory (CWE-613) and is enforced via
  Validation::set_required_spec_claims.
- Activation tokens (server-issued, machine-bound) prevent license.key
  copying between machines. Storage is wired up; the actual issuance flow
  ships with Issue #49.
- get_edition() fails closed to "free" when the license is missing,
  invalid, expired, or activated for a different machine.
- New commands/entitlements module centralizes feature → tier mapping so
  Issue #48 (and any future gate) reads from a single source of truth.
- machine-uid provides the cross-platform machine identifier; OS reinstall
  invalidates the activation token by design.
- Tests cover happy path, expiry, wrong-key signature, malformed JWT,
  unknown edition, and machine_id matching for activation tokens.

The embedded PUBLIC_KEY_PEM is the RFC 8410 §10.3 test vector, clearly
labelled as a development placeholder; replacing it with the production
public key is a release-time task.
2026-04-09 10:02:02 -04:00
le king fu
198897cbba chore: release v0.6.7
All checks were successful
Release / build-and-release (push) Successful in 22m49s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:16:35 -04:00
le king fu
003f456203 chore: bump version to 0.6.6
Some checks failed
Release / build-and-release (push) Has been cancelled
Includes fixes #34, #37, #39: budget prev year actuals, changelog sync via Vite, inline buildPrevYearTotalMap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 17:29:04 -04:00
52faa017f3 chore: release v0.6.5
All checks were successful
Release / build-and-release (push) Successful in 27m1s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 20:14:52 -04:00
376ca4b477 chore: release v0.6.4
All checks were successful
Release / build-and-release (push) Successful in 27m16s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:30:37 -04:00
le king fu
32bcd27a5a Bump version to 0.6.3 — Dashboard revamp, formatting and category fixes
All checks were successful
Release / build-and-release (push) Successful in 26m11s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 09:18:31 -05:00
le king fu
420506b074 Bump version to 0.6.2 — Section subtotals and category detail fix
All checks were successful
Release / build-and-release (push) Successful in 30m13s
Add per-section subtotals (expenses, income, transfers) to budget table
and budget vs actual report. Fix category detail panel visibility when
scrolling through long category lists.

Closes #11, closes #12

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:22:36 -05:00
le king fu
ec38cd5669 Bump version to 0.6.1 — Bilingual changelog and UX fixes
All checks were successful
Release / build-and-release (push) Successful in 27m22s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:00:37 -05:00
le king fu
0a5b7bce10 Bump version to 0.6.0 — Reports enhancements and comment visibility fix
All checks were successful
Release / build-and-release (push) Successful in 26m12s
- Add table/chart toggle for Trends, By Category, and Over Time reports
- Add "Show amounts" toggle to display values on chart elements
- Add filter panel with category checkboxes and source dropdown
- Add source filter at SQL level for all chart report queries
- Add sticky headers on Dynamic Report and Budget vs Actual tables
- Add interactive hover: dimmed non-hovered bars, filtered tooltip, legend hover
- Fix comment icon color to match split indicator (orange) (#7)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:01:13 -05:00
le king fu
079ddfb0e7 Bump version to 0.5.2
All checks were successful
Release / build-and-release (push) Successful in 26m18s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:28:20 -05:00
le king fu
55fbb1ae92 Bump version to 0.5.1
Some checks failed
Release / build-and-release (push) Failing after 26m40s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 16:39:28 -05:00
le king fu
08c54b1f75 Fix migration repair: update checksums instead of deleting records
Some checks are pending
Release / build-and-release (push) Waiting to run
The previous approach deleted migration records to force re-application,
but this is dangerous for migration 2 which DELETEs all categories and
keywords before re-inserting them, wiping user customizations.

Now computes the expected SHA-384 checksum (matching sqlx) and updates
the stored checksum in _sqlx_migrations, so the migration is recognized
as already applied without being re-run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 16:27:02 -05:00
le king fu
1b49871ea0 Release v0.5.0 — open source under GPL-3.0
Some checks failed
Release / build-and-release (push) Failing after 26m7s
Bump version to 0.5.0 and update CHANGELOG with all unreleased changes
including error handling, log viewer, report improvements, and GPL-3.0
license addition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:17:44 -05:00
le king fu
f5bf4e720b Add GPL-3.0 license
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:10:43 -05:00
le king fu
15d626cbbb Fix migration checksum mismatch on startup
Add repair_migrations Tauri command that deletes stale migration 1
checksum from _sqlx_migrations before Database.load(). Migration 1
is idempotent (CREATE IF NOT EXISTS) so re-applying is safe.
Fixes "migration 1 was previously applied but has been modified".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 09:17:35 -05:00
le king fu
4328c2f929 Bump version to 0.4.7
Some checks failed
Release / build-and-release (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 08:24:40 -05:00
le king fu
6f84964689 Bump version to 0.4.6
Some checks failed
Release / build-and-release (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 21:27:47 -05:00
le king fu
3896a1ac1a Bump version to 0.4.5
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 19:11:36 -05:00
le king fu
9ab8d3d7df Fix glibc compat: build in Ubuntu 22.04 container (glibc 2.35)
Some checks failed
Release / build-and-release (push) Failing after 27s
The Forgejo runner's default image uses glibc 2.39, which produces
binaries incompatible with Pop!_OS / Ubuntu 22.04 (glibc 2.35).
Build inside container: ubuntu:22.04 with Node.js and Rust installed.
Bump to v0.4.4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 21:46:31 -05:00
le king fu
3302d79c38 Fix updater: use Forgejo package registry for stable latest.json URL
Some checks failed
Release / build-and-release (push) Has been cancelled
- Forgejo has no /releases/latest/download/ route (GitHub-specific)
- Upload latest.json to generic package registry for a stable endpoint
- Fix Linux signature collection: use .AppImage.sig (not .tar.gz.sig)
- Collect all platform signatures (.deb.sig, .rpm.sig, .AppImage.sig)
- Bump to v0.4.3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 21:10:50 -05:00
le king fu
b97a80d8b9 Release v0.4.2: Windows cross-compile via cargo-xwin + updater trampoline
Some checks failed
Release / build-and-release (push) Failing after 15m4s
- Add Windows cross-compile (cargo-xwin) to Forgejo CI workflow
- Add libsqlite3-sys bundled for cross-compile compatibility
- Switch updater endpoint from GitHub to self-hosted Forgejo
- Collect Windows NSIS assets and include windows-x86_64 in latest.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 20:23:25 -05:00
le king fu
21c4c73a62 Bump version to v0.4.1
Some checks failed
Release / build-and-release (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 21:57:13 -05:00
le king fu
d2a0ee65b3 Fix app stuck on spinner after v0.4.0 update (GH #9)
Restore seed_categories.sql to its original content so the migration 2
checksum matches existing databases. Move the level-3 insurance
subcategories (310-312) into a new migration 7 using INSERT OR IGNORE.
Add .catch() on connectActiveProfile() to surface DB errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 21:54:42 -05:00
le king fu
7b9ab383bc Regenerate signing keys with password
Some checks failed
Release / build-and-release (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 20:57:07 -05:00
le king fu
640caf2617 Add Forgejo CI/CD workflow and sync versions
Some checks failed
Release / build-linux (push) Failing after 6s
Release / build-windows (push) Has been cancelled
Release / release (push) Has been cancelled
- Add .forgejo/workflows/release.yml for Forgejo Actions
- Update signing pubkey for new key pair
- Sync Cargo.toml version to 0.4.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 07:14:05 -05:00
le king fu
d735fb4bd6 chore: bump version to 0.4.0
Some checks failed
Release / build-windows (push) Has been cancelled
Release / build-linux (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:47 -05:00
le king fu
a04813ced2 feat: add 3rd level of category hierarchy
Support up to 3 levels of categories (e.g., Dépenses récurrentes →
Assurances → Assurance-auto) while keeping SQL JOINs bounded and
existing 2-level branches fully compatible.

Changes across 14 files:
- Types: add "level3" pivot field, depth property on budget row types
- Reports: grandparent JOIN for 3-level resolution in dynamic reports
- Categories: depth validation (max 3), auto is_inputable management,
  recursive tree operations, 3-level drag-drop with subtree validation
- Budget: 3-level grouping with intermediate subtotals, leaf-only
  aggregation, depth-based indentation (pl-8/pl-14)
- Seed data: Assurances split into Assurance-auto/habitation/vie
- i18n: level3 translations for FR and EN

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:54:05 -05:00
le king fu
db337b3285 chore: bump version to 0.3.11
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:42:13 -05:00
le king fu
945985c969 chore: bump version to 0.3.10
Some checks failed
Release / build-windows (push) Has been cancelled
Release / build-linux (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:21:32 -05:00
le king fu
df742af8ef chore: bump version to 0.3.9
Some checks failed
Release / build-windows (push) Has been cancelled
Release / build-linux (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 08:29:04 -05:00
le king fu
3f4e1516a3 chore: bump version to 0.3.8
Some checks failed
Release / build-windows (push) Has been cancelled
Release / build-linux (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:31:35 -05:00
le king fu
d1fa8e0200 chore: bump version to 0.3.7
Some checks failed
Release / build-windows (push) Has been cancelled
Release / build-linux (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:56:37 -05:00
le king fu
72fa483e45 fix: remove MSI bundle to prevent updater install path conflict
Users who installed via MSI (Program Files) received NSIS updates
(LocalAppData), causing the app to revert to the old version on restart.
Serialized platform builds to prevent latest.json race condition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:54:29 -05:00
le king fu
367644e38e fix: change Windows updater installMode to basicUi
Some checks failed
Release / build (ubuntu-22.04) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
Passive mode prevented NSIS installer from requesting UAC elevation,
causing updates to silently fail and roll back to the last installed version.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:12:10 -05:00
Le-King-Fu
731610cf3c fix: improve split indicator visibility and adjustments layout
Some checks failed
Release / build (ubuntu-22.04) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
- Change split icon color to orange-500 in transactions table for
  better contrast in both dark and light modes
- Show split transactions at the top of the adjustments left panel
  when there are no manual adjustments (instead of below empty state)
- Add a divider between manual adjustments and splits when both exist

Bumps version to 0.3.5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 02:01:15 +00:00
Le-King-Fu
b190df4eae feat: show transaction splits on Adjustments page + fix CSV auto-detect
Some checks failed
Release / build (ubuntu-22.04) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
Add a "Répartitions" section below manual adjustments listing all
split transactions. Clicking a split opens the existing modal to
view, edit, or delete it.

Fix CSV auto-detect failing on files with preamble lines (e.g.
Mastercard CSVs with metadata header). Three fixes:
- Delimiter detection uses mode of column counts instead of first-line
- Detect and skip preamble rows before header/data detection
- Exclude date-like columns from amount candidates and prefer columns
  with decimal values when picking the amount column

Bumps version to 0.3.4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 01:41:08 +00:00
Le-King-Fu
0b8d469699 fix: remove accent from productName for Linux .deb compatibility
Some checks failed
Release / build (ubuntu-22.04) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
dpkg rejects non-ASCII characters in package names. The window title
remains "Simpl'Résultat" as it is set separately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 00:53:54 +00:00
Le-King-Fu
4b2b45bf6f chore: bump version to 0.3.2
Some checks failed
Release / build (ubuntu-22.04) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 00:34:06 +00:00
Le-King-Fu
c7baf85cbb chore: bump version to 0.3.1
Some checks failed
Release / build (windows-latest) (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 23:31:34 +00:00
Le-King-Fu
20cae64f60 feat: add multiple profiles with separate databases and optional PIN (v0.3.0)
Some checks failed
Release / build (windows-latest) (push) Has been cancelled
Each profile gets its own SQLite database file for complete data isolation.
Profile selection screen at launch, sidebar switcher for quick switching,
and optional 4-6 digit PIN for privacy. Existing database becomes the
default profile with seamless upgrade.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 12:54:09 +00:00
Le-King-Fu
0831663bbd feat: add user guide page with print/PDF support
Some checks failed
Release / build (windows-latest) (push) Has been cancelled
Add DocsPage with full user guide content, TOC sidebar with scroll spy,
and a print button that opens the OS print dialog for PDF export.
Print styles hide sidebars and remove layout constraints for clean output.
Link to user guide added on Settings page.

Bump version to 0.2.12.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 12:19:29 +00:00