107 Commits
Author SHA1 Message Date
aiongg 08679d5a65 Integration: cli: run the inline --cmd=ID form 2026-10-24 09:11:44 +00:00
aiongg fd07262983 Merge PR #760: Bounce the whole session when CLI --bounce has no range 2026-10-24 06:17:41 +00:00
aiongg 9719f955a4 Integration: #552 allow --dither and --no-dither in convert's options 2026-10-24 06:17:33 +00:00
aiongg 98aa5dd1b5 Merge PR #555: convert: dither only when the output loses resolution (#548)
apps/soundcraft-cli/src/main.rs: kept main's OsStr::to_str spelling and the PR's dither lines.
2026-10-24 06:17:33 +00:00
aiongg 9a5ab203d2 Merge PR #552: cli: reject unknown options and missing option values 2026-10-24 06:17:18 +00:00
aiongg 038c4c0ca7 Merge PR #636: Import audio off the UI thread (#597)
crates/engine/src/io.rs: kept the PR's decode_import_bytes split; main's OsStr::to_str spelling.
crates/engine/src/cmd/file.rs: kept the PR's take_import path; main's OsStr::to_str spelling.
2026-10-24 06:16:25 +00:00
Brandon Thomas 567172f530 Merge pull request #979 from marcoarnulfo/feat/repeatable-automation-roots
MCP: --automation-read-root and --automation-write-root may each be given more than once
2026-10-24 01:01:28 -04:00
Brandon Thomas a50fdf9cc9 Merge remote-tracking branch 'origin/main' into m/947 2026-10-24 00:53:01 -04:00
Brandon Thomas b8b9a90c6f Merge remote-tracking branch 'origin/main' into m/979 2026-10-24 00:50:13 -04:00
Dylan M. Taylor 64669e9a7c chore: resolve SonarCloud maintainability issues (#1003)
* chore: resolve SonarCloud maintainability issues

* fix(packaging): extract verify_sha256 helper in package.sh

* style: apply rustfmt formatting

* fix(video): ensure prefix is i32 in cabac_mb
2026-10-23 22:33:59 -04:00
aiongg e8abd44712 CI: one test binary per crate, nextest, line tables only (#985)
* Build: line tables only for workspace crates, no debug info for dependencies

Full debug info made most of the dev and test target size and link time. Line tables keep file and line numbers in backtraces; dependency frames lose them.

* Tests: build each crate's integration tests as one binary

Each tests/*.rs file was its own test binary: 98 binaries, each linking the crate and its dependencies. The files are now modules of tests/it/main.rs, one binary per crate, which cuts link time and target size. Filter by module: `cargo test -p <crate> --test it <module>::`.

Binaries with their own main (harness = false) in clap-host and vst3-host stay separate, as do the ui-egui tests that switch the process-wide Light/Dark palette and recovery.rs, whose autosave locks leak into child processes other tests start.

* Xtask: run tests with nextest, doctests apart, and no examples in the test build

cargo xtask ci runs the tests with cargo nextest when it is installed, else cargo test with a notice, and doctests with cargo test --doc. The test build leaves out examples (--lib --bins --tests); clippy --all-targets still compiles them. Test binaries with harness = false run under cargo test: their own main cannot answer nextest's --list. clippy and the wasm check keep going past a failing crate. cargo xtask test runs the test step alone.
2026-10-23 14:00:38 -04:00
Marco Arnulfo d01abd0e4e MCP: --automation-read-root and --automation-write-root may each be given more than once (fixes #874) 2026-10-23 18:25:05 +02:00
Dylan M. Taylor d47abe484d Use chrono instead of hand-rolled UTC conversion (#844)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the civil_from_days copy behind the desktop app's log timestamp with
chrono as a workspace dependency. The output format is unchanged and pinned by
the existing test. AGENTS.md now says time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic logging on a clock outside chrono's range

chrono's From<SystemTime> unwraps internally, so a clock past year 262143
panicked inside the logger. timestamp now reads the duration since the epoch
and goes through the fallible DateTime::from_timestamp, reading an
out-of-range clock as the epoch, exactly as the pre-epoch clamp already did.
The year is written with {:04} rather than %Y, which would prefix a '+' past
year 9999. Tests cover the far-future and year-10000 cases.

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* time: preserve old out-of-range log timestamp (no epoch fallback)

`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any clock
past chrono's year range to 1970-01-01, changing output the old hand-rolled
`civil_from_days` converted exactly. Restore the old conversion domain with a
cycle-based helper that routes the calendar through chrono (the calendar
repeats every 400 years = 146_097 days, so month/day/time come from chrono and
only the year is carried in i128, keeping every i64 exact). Keep the pre-1970
epoch clamp; u64 seconds past i64::MAX saturate rather than falling back.

Pinned old outputs in the test (replacing the u64::MAX/2 -> epoch assertion):
  u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z
  10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z

* Don't build a year-318857 SystemTime unchecked in the logging test

Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-23 11:35:06 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 52471b37e9 chore(deps): bump the cargo-dependencies group across 1 directory with 11 updates (#767)
Bumps the cargo-dependencies group with 11 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.151` | `1.0.154` |
| [proptest](https://github.com/proptest-rs/proptest) | `1.11.0` | `1.12.0` |
| [symphonia](https://github.com/pdeljanov/Symphonia) | `0.5.5` | `0.6.1` |
| [zip](https://github.com/zip-rs/zip2) | `9.0.1` | `9.0.5` |
| [cpal](https://github.com/RustAudio/cpal) | `0.16.0` | `0.18.2` |
| [egui](https://github.com/emilk/egui) | `0.36.2` | `0.37.0` |
| [eframe](https://github.com/emilk/egui) | `0.36.2` | `0.37.0` |
| [egui_kittest](https://github.com/emilk/egui) | `0.36.2` | `0.37.0` |
| [libloading](https://github.com/nagisa/rust_libloading) | `0.8.9` | `0.9.0` |
| [muda](https://github.com/tauri-apps/muda) | `0.19.3` | `0.21.2` |
| [winresource](https://github.com/BenjaminRi/winresource) | `0.1.31` | `0.1.34` |



Updates `serde_json` from 1.0.151 to 1.0.154
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.151...v1.0.154)

Updates `proptest` from 1.11.0 to 1.12.0
- [Release notes](https://github.com/proptest-rs/proptest/releases)
- [Changelog](https://github.com/proptest-rs/proptest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/proptest-rs/proptest/compare/v1.11.0...v1.12.0)

Updates `symphonia` from 0.5.5 to 0.6.1
- [Release notes](https://github.com/pdeljanov/Symphonia/releases)
- [Commits](https://github.com/pdeljanov/Symphonia/compare/v0.5.5...v0.6.1)

Updates `zip` from 9.0.1 to 9.0.5
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v9.0.1...v9.0.5)

Updates `cpal` from 0.16.0 to 0.18.2
- [Release notes](https://github.com/RustAudio/cpal/releases)
- [Changelog](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustAudio/cpal/compare/v0.16.0...v0.18.2)

Updates `egui` from 0.36.2 to 0.37.0
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.36.2...0.37.0)

Updates `eframe` from 0.36.2 to 0.37.0
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.36.2...0.37.0)

Updates `egui_kittest` from 0.36.2 to 0.37.0
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.36.2...0.37.0)

Updates `libloading` from 0.8.9 to 0.9.0
- [Commits](https://github.com/nagisa/rust_libloading/compare/0.8.9...0.9.0)

Updates `muda` from 0.19.3 to 0.21.2
- [Release notes](https://github.com/tauri-apps/muda/releases)
- [Changelog](https://github.com/tauri-apps/muda/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/tauri-apps/muda/compare/muda-v0.19.3...muda-v0.21.2)

Updates `winresource` from 0.1.31 to 0.1.34
- [Commits](https://github.com/BenjaminRi/winresource/commits)

---
updated-dependencies:
- dependency-name: cpal
  dependency-version: 0.18.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: eframe
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: egui
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: egui_kittest
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: libloading
  dependency-version: 0.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: muda
  dependency-version: 0.21.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: proptest
  dependency-version: 1.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.154
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: symphonia
  dependency-version: 0.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: winresource
  dependency-version: 0.1.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: zip
  dependency-version: 9.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-22 23:36:57 -04:00
Marco Arnulfo 9049eb53c5 Save Session: a bare file name is checked as the working directory against the write root (fixes #869) 2026-10-23 02:08:38 +02:00
Paul G 8198d6e35a Merge remote-tracking branch 'upstream/main' into fix/597-import-off-ui-thread
# Conflicts:
#	crates/ui-egui/src/dialogs.rs
2026-10-21 04:14:28 -07:00
Gert Pellin c8f8fc45eb convert: dither only when the output loses resolution (#548)
soundcraft-cli convert always passed dither: true, so a same-depth or
wider conversion still added ±1 LSB TPDF noise and digital silence came
out as noise. It now dithers only when writing integer samples that
cannot hold the source exactly: fewer bits than the source, a float or
lossy source, or samples changed by --rate/--normalize. 32-bit float
output never dithers. --dither and --no-dither override the choice,
like the Bounce option in #551.
2026-10-19 16:34:26 +02:00
Brandon Thomas 8385c22616 Merge pull request #646 from marcoarnulfo/fix/web-open-dirty-session
Ask before a session opened in the browser replaces a modified one
2026-10-19 02:54:53 -04:00
NetterSilverandClaude Haiku 5.5 a72e46e7b6 Bounce the whole session when CLI --bounce has no range
Fixes #745

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:04:41 +02:00
Marco Arnulfo 29c014deef Ask before a session opened in the browser replaces a modified one (fixes #432)
Picking a session with Open Session or dropping one on the page in the
browser replaced the document as soon as its bytes were read, without the
Save Changes question the native Open goes through, so unsaved edits and
their undo history were lost. The bytes now wait behind that question,
asked when the read finishes: Don't Save opens them, Save saves first and
then opens them, Cancel drops them and leaves the document alone.
2026-10-16 02:01:15 +02:00
Paul GandClaude Opus 5.5 05fe56ec1a Import audio off the UI thread (#597)
Decode, resample and build peaks on a worker; file.import_audio then only places the clip. CLI/MCP/control unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:21:28 -07:00
Hamza f8b2f8ce2e cli: reject unknown options and missing option values
`convert`, `run` and `script` read options with `arg_value`, which matches a
flag exactly and takes the argument after it. A misspelling, an `--option=value`
form or an option with no value therefore read as absent: the command ran
without what was asked for and still exited 0.

    convert IN OUT --rate=44100   wrote IN's 48000 Hz unchanged
    convert IN OUT --bitdepth 16  wrote the default 24-bit
    convert IN OUT --bogus        wrote the file, said nothing
    run --cmd session.new --bounce=OUT   bounced nothing

Check what each subcommand accepts and report an unknown option or a missing
value with a non-zero exit, and read `--option=value` as the value it looks
like. The documented space-separated form is unchanged.
2026-10-14 16:59:15 +05:00
Justin JohnandClaude Opus 5.5 194cf8c83d Portable mode: portable.txt beside the executable keeps settings in SoundCraftData (#268)
With the marker present, ui.json, Autosave, Presets and logs go to
<exe dir>/SoundCraftData instead of the user profile. SOUNDCRAFT_CONFIG_DIR
still wins, and a data folder that can't be written falls back to the
profile with a warning in the log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:04:26 +05:30
Brandon Thomas c2f052715d Merge main into #319 2026-10-13 06:16:54 -04:00
Brandon Thomas 00faedcbd0 Merge main into #334 2026-10-13 06:09:17 -04:00
Brandon Thomas 5686d61f45 Merge main into #390 2026-10-13 06:04:00 -04:00
Brandon Thomas 5051b4a45f Merge main into #393 2026-10-13 05:12:31 -04:00
Brandon Thomas 5d98ddfa69 Merge main into #215 2026-10-13 05:03:15 -04:00
Brandon Thomas d4b7300a1e Merge main into #300 2026-10-13 04:56:48 -04:00
Brandon Thomas 46399aa729 Merge main into #368 2026-10-13 04:50:04 -04:00
Brandon Thomas 5495378d7a Merge pull request #309 from isaac-levine/mcp-automation-roots
MCP: confine file access with --automation-read-root and --automation-write-root
2026-10-13 04:43:44 -04:00
Brandon ThomasandClaude Opus 5.5 74b3278a19 control server: cap request lines at about 86 MiB, not 256 MiB
256 MiB per line times 16 connections let a peer make the app buffer about
4 GiB. 16 MiB would break a legitimate request: mix.insert_state carries a
plugin's state as base64 inline, and the engine accepts states of up to
b64::MAX_DECODED_BYTES (64 MiB), about 85.3 MiB of base64. Nothing else sent
to the app is that large (screenshots are replies, which this doesn't bound).
So the cap is that base64 length plus 1 MiB for the JSON around it, derived
from the same constant so the two stay in step; a compile-time check keeps it
under 96 MiB, about 1.5 GiB across all 16 connections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:37:04 -04:00
Brandon Thomas 7c061de737 Merge main into #261
# Conflicts:
#	ROADMAP.md
2026-10-13 04:30:18 -04:00
Brandon ThomasandClaude Opus 5.5 5183bc4dba rustfmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:22:21 -04:00
grovebit 8f8884ec05 Refuse to replace the session while bouncing
A bounce keeps its own snapshot of the session, decoded audio included.
The progress window blocks the mouse, but shortcuts, the macOS menu bar,
dropped files and the control channel still reach SoundApp::run, so a
second session could be opened beside the one bouncing; two large ones
could exhaust memory, which aborts the app.

New, Open, Close and Revert now fail with "Finish or cancel the bounce
first" while a bounce runs, and so do sessions opened or dropped in the
browser.
2026-10-12 23:47:24 +01:00
grovebit 5fff2e8c78 Keep working when the interface hits a bug
Nothing caught a panic outside a command: one in the interface's frame
code ended the process in that frame (exit code 101), losing every
unsaved change, and the log got a single line with no backtrace.

Each frame's logic and drawing now run guarded. A panic there autosaves
the session (once per change, so a bug that repeats every frame doesn't
flood the disk), says so in the status line and lets the app carry on.
If the app goes down after all, the autosave is offered at the next
launch. The panic hook now logs a backtrace.
2026-10-12 23:32:47 +01:00
grovebit b5ea465274 Autosave each session to its own folder and offer unsaved work at launch
Autosave wrote every session called "Untitled" to the same
Autosave/Untitled folder and reused audio files there by source id, so
a second untitled session, or a take recorded after an undo, could be
autosaved with another take's audio. Its first run also copied all of
the session's audio again. Nothing ever offered an autosave back
(latest_autosave had no callers), so after a crash the work could only
be found by digging in the settings folder.

Each open session now autosaves to a folder of its own, locked while
the session is open. Audio already saved with the session is referenced
where it is; audio that exists only in memory is copied once per
recording or render. Saving, closing the session, opening another and
quitting with SoundCraft's Quit delete the folder. A crash, or an exit
that skips Quit with unsaved changes, leaves it behind, after one last
autosave when SoundCraft gets the chance. At the next launch,
SoundCraft offers each kept folder, newest first, to recover or
discard. Recovered work opens as an unsaved version of the session it
came from, so Save writes it back there; unsaved changes in the session
it replaces are kept the same way. Folders older than their session
file are dropped.
2026-10-12 23:32:47 +01:00
woodgrain816andClaude Opus 5.5 1a49fc70fb File › Open Recent: the last 10 sessions, newest first
Pro Tools has File › Open Recent; SoundCraft had no way back to a session but
browsing for it. The list (UiState.recent_sessions) records the engine's
session path whenever it changes, so opening or saving by any route (menu,
control channel, MCP, a dropped file) is covered by one rule.

- Under File › Open; each entry is "name — folder", full path on hover.
- A session that has moved or been deleted is greyed out "(missing)" instead
  of failing on click; existence is re-checked at most every 3 s while the menu
  is open, since a network share costs a round trip per file.
- Opening goes through the dialog layer, so unsaved changes are asked about.
- Clear Recent empties it.
- The desktop host saves ui.json as soon as the list changes, not only on a
  clean exit: the list matters most after a crash.
- An older ui.json without the field still loads (serde default).

The macOS system menu bar (native_menu) does not show it yet; the egui menu
bar on Windows and Linux does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:31:22 -05:00
Limestudio90andClaude Opus 5.5 a1e9660e60 Audio device settings: system, devices, sample rate, buffer size; opt-in ASIO
Setup > Playback Engine now chooses the audio system (cpal host), output
and input device, device sample rate and buffer size, kept per machine
with the UI preferences and applied by reopening the devices (refused
while recording; the transport position is kept). ui.audio_device does
the same for agents and lists every system with its devices, channels,
rates and buffer range.

Player and recorder open the chosen device, fall back to the defaults
for anything unavailable (unplugged device, unsupported rate), retry
with the device's default buffer when the chosen one is refused, and
convert 32/16-bit integer devices from and to the f32 mix through
scratch buffers allocated up front (no allocation in the callbacks).

ASIO is behind an opt-in 'asio' feature (playback and app): building it
downloads Steinberg's ASIO SDK (proprietary licence or GPLv3) and needs
LLVM, so it stays off by default. Verified on Windows with an SSL 2+:
WASAPI output/input switching and buffer 256, ASIO duplex at 128.

Docs: G-01 shrunk, hardware-parity rows, roadmap log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:22:41 +02:00
Isaac Levine a9fa027528 CLI: add --automation-read-root and --automation-write-root to mcp 2026-10-12 10:50:05 -04:00
Scott Campbell 7d8c185d13 Add a sampler UI: sample strip, drag and drop, instrument sliders
Built-in instruments now open a window with their parameters (driven by
mix.instrument_param). The Sampler's window adds a waveform with draggable
start and loop markers, Load Sample and Clear. Dropping an audio file on an
instrument track or a Sampler window loads it; in the browser the dropped
bytes are read and loaded through engine::io::load_sampler_bytes as one undo
step.
2026-10-12 10:39:45 -04:00
MarekSpodBiedry dabcb4840a Open .m4b audiobook files like .m4a
M4B is the same MP4/AAC container as M4A, and the decoder already reads it,
but the Open dialog, the web file picker and the file browser hid .m4b files.
Add m4b to those lists and to the extension fallback.
2026-10-12 16:27:57 +02:00
grovebit eb5bf699c7 Bounce Mix: Browse for the file, and start in the session folder
From #186: the Bounce dialog had no Browse button, so the file path had
to be typed, and it always proposed the home folder.

- A Browse… button opens the system save panel with an Audio filter
  (WAV, AIFF, FLAC). The chosen file goes in the field, and its
  extension sets the Format.
- A saved session bounces into its own folder by default; unsaved
  sessions still start in the home folder.
- The desktop save panel now picks its filter by purpose (bounce or
  session) and opens in the proposed file's folder when it exists, so
  Save As starts there too instead of wherever the panel was last.
- The file field is 420pt wide, like the other path prompts.
2026-10-12 15:13:25 +01:00
NetterSilver 9f1a62470d Reject invalid --rate and --bit-depth in convert (#256)
convert ignored a --rate that did not parse and mapped any unknown
--bit-depth to 24-bit, then reported success. Both options are now
validated before the input is read; an invalid value prints which
option was wrong and exits non-zero without writing output. Omitted
options keep their defaults.
2026-10-12 08:54:26 +02:00
Brandon Thomas 525f00b719 Merge pull request #157 from grovebit/unsaved-changes
Ask before discarding unsaved changes; Save opens Save As for new sessions
2026-10-11 20:59:57 -04:00
Brandon Thomas 48c3f65e12 Merge pull request #160 from grovebit/menu-fixes
Clip > Rename opens its dialog; Undo and Redo name their step; the Window menu's keys work
2026-10-11 20:59:52 -04:00
Brandon Thomas 6754f04a05 Merge pull request #192 from grovebit/idle-repaint
Let the desktop app sleep when idle or covered
2026-10-11 20:59:48 -04:00
Brandon ThomasandClaude Opus 5.5 d37386721a Merge origin/main into unsaved-changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:42:42 -04:00
Brandon ThomasandClaude Opus 5.5 1c52ad1c6e Merge origin/main into idle-repaint
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:42:34 -04:00
Brandon ThomasandClaude Opus 5.5 e7e4a19cfa Merge origin/main into fix/web-files
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:42:26 -04:00