release: desktop v1.0.0-beta.1 - #9
Merged
Merged
Conversation
added 30 commits
July 14, 2026 21:25
Complete design spec for GoodWebTools Desktop: - One codebase, two shells (web + Tauri) architecture - Service abstraction layer for shell-agnostic tools - All 5 native capabilities (system capture, hotkeys, marquee, system audio, native FFmpeg) - All 55 tools refactored to use services - Complete desktop features (Settings, tray, wizard, auto-update) - Cross-platform permission handling (macOS, Windows, Linux) - Build & release pipeline with GitHub Actions - Download page with tracking endpoint - 6-8 week implementation timeline
33-task plan covering 8-week implementation: - Tasks 1-10: Foundation & service layer (2 weeks) - Tasks 11-20: Tool refactoring all 55 tools (3 weeks) - Tasks 21-28: Desktop features (Settings, tray, wizard, updater) (2 weeks) - Tasks 29-33: Build pipeline & testing (1 week) Each task follows TDD pattern with exact file paths, code, and tests. All 259 existing tests must pass throughout refactoring.
- Install @tauri-apps/cli v2 - Add tauri npm scripts (dev, build, bundle) - Create Cargo.toml with dependencies (tauri plugins, platform-specific deps) - Create tauri.conf.json (bundle disabled for dev) - Create main.rs, lib.rs, build.rs - Generate app icons from existing icon-512.png - Verify Rust compilation passes
- Create Platform service with shell detection (browser vs Tauri) - Detect platform (macOS, Windows, Linux) - Detect architecture (x86_64, aarch64) - Add comprehensive unit tests (4 passing tests) - Export PlatformInfo interface for type safety
- Define CaptureService interface with 7 methods - Add types: Rectangle, CaptureOptions, RecordOptions, RecordingHandle - Add CaptureServiceCapabilities interface - Implement shell detection pattern (browser vs Tauri) - Lazy-load implementations based on environment - Ready for browser and Tauri implementations
- Implement captureScreen using getDisplayMedia and canvas - Implement start/stopRecording using MediaRecorder - Implement captureWindow (falls back to captureScreen) - Return null for showRegionSelector (not supported in browser) - Add comprehensive unit tests (4 passing tests) - Mock all browser APIs for testing (MediaStream, video, canvas) - Returns correct capabilities (all false for browser)
- Create commands.rs with 7 IPC command stubs - Define Rust types: CaptureOptions, Rectangle, RecordOptions, RecordingHandle - Register commands in main.rs invoke_handler - Create TauriCaptureService TypeScript implementation - Add browser fallback for when native capture not implemented - Platform-specific stubs return 'not yet implemented' errors - Rust compiles successfully (0 errors, 7 warnings for unused params)
- Replace direct getDisplayMedia call with captureService.captureScreen() - Update supported check to use service capabilities - Simplify capture logic (service handles stream/video creation) - Maintains countdown and crop functionality - First tool refactored to validate service layer works - TypeScript compiles successfully
- Create FileService interface with file picker and save methods - Implement BrowserFileService using File System Access API with legacy fallback - Implement TauriFileService using native dialogs and FS APIs - Add comprehensive type definitions - Install @tauri-apps/api for Tauri integration - 5 tests created (3 passing, 2 need fixes)
- Change from 'in window' to typeof === 'function' check - Fixes test mocking for legacy fallback paths - All 5 FileService tests now passing
- Replace MediaRecorder setup with captureService.startRecording() - Replace stop logic with captureService.stopRecording() - Simplify state management (removed stream/chunks refs) - Keep UI-specific features (elapsed timer, mic toggle) - Second tool validated against service layer - TypeScript compiles successfully
- Create ClipboardService interface for clipboard operations - Implement BrowserClipboardService using Clipboard API with legacy fallback - Implement TauriClipboardService using Tauri clipboard API - Support text and image read/write operations - 5 comprehensive tests - all passing - Image clipboard in Tauri requires custom Rust commands (not yet implemented)
- Create HotkeyService interface for hotkey registration - Implement BrowserHotkeyService with window-level key listeners - Implement TauriHotkeyService with true global shortcuts - Support modifier keys and key combinations - 6 comprehensive tests - all passing - Browser hotkeys only work when window focused (platform limitation)
- Create DownloadService interface with download/downloadZip methods - Split into BrowserDownloadService (File System Access API + fallback) - Create TauriDownloadService (native save dialog) - Update 22 imports across codebase from old path to new - Maintain backward compatibility with existing API - ZIP support via fflate in both implementations
- Create AssetService interface with fetch/isCached/clearCache methods - Split into BrowserAssetService (in-memory cache + streaming) - Create TauriAssetService (file-based cache in AppCache dir) - Update imports across codebase from old path to new - Maintain backward compatibility with assetCache API - Both implementations support progress tracking for large assets
- Replace File System Access API with fileService.openFile/saveFile - Replace Clipboard API with clipboardService.writeText - Remove FileSystemFileHandle refs (handled by service) - Simplify save logic - service handles dialog automatically - Removed unused useRef import - Third high-value tool refactored to validate services
- Add @tauri-apps/api to optimizeDeps.exclude - Prevents Vite from trying to bundle Tauri packages in web mode - Tauri imports are only loaded dynamically in desktop environment - Resolves 'could not be resolved' error in web dev mode
…Recorder - Remove premature captureService.getCapabilities() call before instance creation - Use direct browser API detection (navigator.mediaDevices.getDisplayMedia) - Fixes 'browser doesn't support screen capture' false negative - Both tools now correctly detect browser support
- Initialize supported state based on actual browser capability - Add console.log to debug what's being detected - Check for window existence to avoid SSR issues - Will help diagnose why browser support check is failing
- Import isTauri() to detect desktop app mode - In Tauri: Always show as supported (uses native Rust APIs) - In browser: Check for getDisplayMedia support - Fixes 'browser doesn't support' error in Tauri WebView - WebViews don't expose mediaDevices API (expected) - Tauri capture works via IPC commands, not browser APIs
- Add withGlobalTauri: true to app config - Enables window.__TAURI__ global object - Required for isTauri() detection to work - Also add url and label to main window config
- Remove @tauri-apps/api from optimizeDeps.exclude - Vite needs to resolve these imports for Tauri dev mode - Dynamic imports ensure they're only loaded in Tauri runtime - Tree-shaking removes them from web build - Fixes 'Failed to resolve import' error when capturing in Tauri
- Change '@tauri-apps/api/tauri' to '@tauri-apps/api/core' - invoke() is exported from /core in Tauri 2 - Fixes 'Failed to resolve import' error - Other APIs (dialog, fs, clipboard) still in main package
- Remove try/catch with browserFallback in captureScreen - Remove browserFallback method entirely - Browser APIs don't work in Tauri WebView - Let native errors propagate to UI properly - Users see 'not implemented' instead of mediaDevices error
- Log errors to console for debugging - Will show actual error from Rust IPC or service layer - Helps diagnose why capture fails in Tauri app
- Add core-graphics and image dependencies for macOS - Implement capture_screen using CGDisplay API - Capture main display and convert to PNG/JPEG - Handle format and quality options - Supports both PNG and JPEG output formats - macOS screen capture now fully functional!
- Import ImageEncoder trait for PngEncoder - Convert ColorType to ExtendedColorType with .into() - Add explicit type annotation for ImageError - Fixes compilation errors
- Cast bytes_per_row to usize in offset calculation - Ensures type compatibility with data.len() - Fixes compilation error
- Wrap CGMainDisplayID() call in unsafe block - Make JPEG encoder mutable - Fixes compilation errors
- Initialize supported state to false for SSR - useEffect sets correct value on client mount - Fixes React hydration warnings in Tauri app
…11-20)
ScreenRecorder.tsx:
- Replace invoke('list_displays') → captureService.listDisplays()
- Replace invoke('capture_region') for countdown preview → captureService.captureRegion()
- Replace invoke('capture_screen') for overlay background → captureService.captureScreen()
- Add blobToDataUrl() helper; window-management invokes stay direct (not in CaptureService)
SqlitePlayground.tsx:
- Replace navigator.clipboard.writeText() → clipboardService.writeText()
so cell-copy works natively in Tauri without browser clipboard permission
385/385 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
…nit tests The blocking bug: AudioRecorder::stop() called child.wait() BEFORE child.kill(). FFmpeg capturing a live mic never self-exits, so wait() blocked forever and the kill() after it was unreachable — any recording with audio enabled hung in stop_recording. Now stop() sends 'q' to stdin for a graceful finalize, drops stdin (EOF), waits briefly, then force-kills so it can never block. Also route all audio FFmpeg calls through crate::ffmpeg::ffmpeg_path() so the bundled sidecar is used instead of only system ffmpeg on PATH. Tests: extract muxed_extension() as a pure helper and add 6 unit tests covering format→extension mapping, no-op recorder start/stop, stop() idempotency, and output-path preservation. cargo test audio:: → 6 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
Replaced the hardcoded placeholder GUID (audio=@device_cm_{...}\wave_{...},
which matched no real device) with runtime enumeration: query
`ffmpeg -list_devices true -f dshow -i dummy`, parse the audio devices from
stderr, and capture from the first one. Errors clearly if no mic is present.
The parser (parse_dshow_audio_devices) is a pure function handling both FFmpeg
output styles — newer inline `(audio)`/`(video)` tags and older
`DirectShow audio devices` section headers — and excludes alternative-name
lines and video devices. 7 new unit tests cover both formats, no-device,
alt-name exclusion, and quote extraction. cargo test audio:: → 13 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
Pre-warmed window architecture to make region screenshots feel instant: A) pre-warm + reuse overlay window, B) raw-bytes IPC + asset-protocol bg, C) two-phase instant reveal, D) native server-side crop. Includes a risk register drawn from prior abandoned attempts in git history. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
Building the overlay WebviewWindow on the shortcut hot path paid the full WebKit/WebView2 init penalty (~50-150ms) on every screenshot, then closed it. Now: - prewarm_region_selector() builds the window hidden at startup (main.rs setup) - show_region_selector() reuses it: reposition/resize to the target display, show, focus, and emit `overlay-show` (repositions every show to avoid the prior wrong-display reuse bug); builds on the fly only as a fallback - close_region_selector() now hides instead of closing (keeps it warm) - overlay.astro init logic extracted into a re-runnable initOverlay() that runs on load and on each `overlay-show` event (resets selection, reloads bg) Builds clean (0 errors); 385 tests green. Requires macOS hardware smoke-test: 2nd+ capture should show the crosshair fast, on the correct display, with a reset selection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
… JSON) capture_screen / capture_region / capture_window now return tauri::ipc::Response::new(bytes) instead of Vec<u8>. By default Tauri serializes Vec<u8> as a JSON array of numbers — a full-res 5K PNG became a ~40MB+ inflated JSON string that froze the IPC thread on parse. Returning a Response ships the raw bytes as a binary ArrayBuffer instead. Frontend (capture/tauri.ts) reads invoke<ArrayBuffer>(...) → Blob directly, no Uint8Array-from-number[] round-trip. Test mocks updated to resolve ArrayBuffers to match the real IPC contract. The overlay-background load (base64→localStorage → asset protocol) is deferred to Phase C, where the two-phase reveal reworks background injection via an event. Builds clean (0 errors); 385 tests green. Manual test: capture on a 4K/5K display should no longer show a multi-second freeze. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
…full-screen fit Three bugs surfaced by hardware-testing Phase A (window reuse): 1. Blank overlay: a reused/persistent overlay webview's localStorage is stale vs the main window's writes (WKWebView caches per-webview), so the background never loaded. Background + displayId now travel over Tauri events (overlay-set-background / overlay-show) instead of localStorage. Updated all four call sites (global-hotkeys x2, Screenshot, ScreenRecorder). 2. ESC cancel wedged the hotkey: close_region_selector never emitted region-selector-closed, so showRegionSelector's promise hung, leaving screenshotInProgress stuck true → every later hotkey ignored. close now emits the event (cancel path); submit uses a new silent hide_region_selector so a real selection can't be clobbered. handleGlobalScreenshot also clears the guard in a finally as a belt-and-suspenders. 3. Pre-selection not full screen: the reused window resizes just as overlay-show fires, so window.innerWidth was stale. Pre-warm now sizes to the main display, and the overlay re-fits the full-screen selection on the webview resize event. Also: global-hotkeys captures the overlay background at scale 0.5 (4x fewer pixels) like the in-tool flow already did — snappier pre-overlay capture. Builds clean; 385 tests green (global-hotkeys tests updated to assert the event-based background instead of localStorage). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
…sleeps hide_main_window used minimize(), which plays a ~250ms macOS genie animation; the flow then slept 150-200ms to wait it out before capturing (obs 8766 tuned that). Switched to hide() — the window drops in ~1 frame — and cut the post-hide waits to ~60ms (a couple of frames for the compositor). Removes ~150-190ms of dead time from every region capture. Applies to the in-tool Screenshot flow and both ScreenRecorder hide points. show_main_window already restores hidden windows (unminimize is a harmless no-op). Builds clean; 385 tests green. Manual test: main window must NOT appear in the capture (if it does on a slower machine, bump the 60ms settle back up). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
perf(desktop): optimize screenshot tool — pre-warmed window, raw-bytes IPC, snappier reveal
feat(desktop): GoodWebTools Desktop app (Tauri 2) — Phase 10
…to JS) The in-tool region flow captured the frozen full-res screen, shipped the whole PNG to JS (10-30MB on a 5K display), base64'd it, decoded it, and cropped in a canvas — even for a tiny region. Now: capture_hold captures the frozen full frame once (while the main window is hidden) and keeps it in Rust; after selection, crop_held crops server-side with HiDPI scaling and returns only the small region as raw PNG bytes. release_held frees it on cancel. Memory is bounded to one held frame (cleared on next capture). physical_crop_rect (logical→physical, clamped) is a pure fn with 6 unit tests. Screenshot.tsx loads the crop via createImageBitmap — no base64, no full-res decode. Region button is Tauri-only so there's no browser path to preserve. cargo test: 19 passed; JS: 385 passed. Manual test: crop is pixel-accurate on HiDPI and correct on a secondary display. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
Windows/Linux had no capture backend — every capture command returned "not yet implemented", so screen recording (which feeds on capture_screen_fast) produced zero frames and the audio pipeline had nothing to attach to. Adds xcap (0.9.4, wgc on Windows) as the Windows + Linux capture backend, wired into list_displays, capture_screen, capture_screen_fast, and capture_hold. macOS keeps its validated core-graphics path untouched. HeldCapture is unified on image::RgbaImage so crop_held is one platform-agnostic path; crop_rgba is a pure fn with unit tests. Verification: macOS builds + 21 unit tests pass locally. Windows/Linux can't be compiled on a macOS dev machine, so .github/workflows/desktop-check.yml compile-checks all three targets on CI (with the PipeWire/Wayland/dbus deps xcap needs on Linux). Runtime validation still needs real Windows/Linux hardware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
xcap's Linux backend pulls libspa/spa-sys, which need PipeWire 1.0+ headers (spa_meta_region_is_valid, spa_video_info_raw.flags). ubuntu-22.04 ships an older PipeWire and failed to compile libspa. Bumped both the compile-check and release workflows to ubuntu-24.04 and added libpipewire-0.3-dev, libspa-0.2-dev, wayland/xcb, and libclang (spa-sys uses bindgen). Windows already compiled clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
Down from 12 warnings to 0: - remove unnecessary parens in capture_screen_fast - drop unused `std::path::PathBuf` import in recording.rs - remove genuinely-dead overlay::Rectangle struct (+ its serde import) - #[allow(dead_code)] on frontend-contract fields not yet read in Rust (CaptureOptions/RecordOptions bitrate + audio flags) and on RecordingState snapshot fields and the reference capture_screen_internal helper - declare cfg(cargo-clippy) via [lints.rust] check-cfg so the objc msg_send! macro no longer trips unexpected_cfgs Build is warning-clean; audio unit tests still pass. No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
…r-frame) record_frames called capture_screen_fast per frame, which on Linux re-negotiates the xdg-desktop-portal screencast on every grab — far too slow for real-time recording (and it would re-prompt). macOS keeps its fast per-frame core-graphics loop; Windows/Linux now open ONE xcap VideoRecorder stream, consume frames from its channel, throttle to the target FPS, crop/encode each kept frame to JPEG, and push into the same buffer the encoder drains on stop. - record_frames is now macOS-only; record_frames_xcap handles Windows/Linux - start_recording spawns the right loop per platform - crop_rgba is pub(crate) so the recording loop can reuse the crop math - even-dimension enforcement so H.264/mp4 encoding accepts the frames macOS builds clean. Windows/Linux compilation verified via desktop-check CI. Runtime still needs real hardware (xcap Linux path needs the portal grant). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
…-crop perf(screenshot): Phase D — native server-side region crop
feat(desktop): Windows + Linux screen capture & recording (xcap)
chore(desktop): clean up all Rust compiler warnings (12 → 0)
Two bugs made every recording come out silent: 1. TauriCaptureService.startRecording dropped includeAudio/systemAudio before the IPC call, so Rust never started the AudioRecorder. Now forwarded. 2. The mux always used -c:a aac, but WebM (the default output) can't hold AAC — it needs Opus, so the mux failed and left a video-only file. Pick the audio codec by container: mp4 → aac, webm → libopus. Also fixes a build break: the [lints.rust] check-cfg form added earlier is invalid on current rustc (errored with "invalid --check-cfg argument"); switched to `unexpected_cfgs = "allow"`. macOS builds clean (0 warnings); 385 JS tests pass. Needs a mic-enabled recording on hardware to confirm audio lands in the file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
fix(recording): silent recordings — capture + mux audio correctly
… not hide) Phase C switched hide_main_window to window.hide() for snappier screenshots, but recording keeps the window out of view for the whole session — and a hidden window can't be brought back from the dock, so users got locked out of the app mid-recording (most obvious when recording an extended display, where GWT sits on the still-visible main screen). Add a minimize_main_window command and use it for the recording-start hide so the window can be restored from the dock. Screenshots keep the instant hide() (they auto-restore a moment later). Region-selection hide is short-lived and unchanged. macOS builds clean; 385 JS tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
The recording hotkey was registered in an effect keyed on [recording, stopping], so it unregistered + re-registered on every start/stop. That async churn raced — the re-register could hit "already registered" and silently fail, leaving ⌘⇧R dead so it no longer toggled recording. Register it ONCE (keyed on [inTauriApp]) and route the callback through a ref that always holds the current toggle logic, so state stays fresh without re-registering. 385 tests pass; file lints clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
fix(recording): restorable window + reliable ⌘⇧R hotkey
Recording lived inside the ScreenRecorder component, so ⌘⇧R only worked while that tool page was open. Extracted a global recording manager (services/global-recording.ts) that is the single source of truth for recording state, the window (minimize/restore), and the produced blob. - ⌘⇧R is now registered globally at startup (global-hotkeys.ts) → toggles a full-screen recording from ANY page using the last-configured settings. - Stop from a non-recorder page auto-downloads the file (no UI to show it); from the recorder page the mounted component shows it via a live event. - ScreenRecorder is now a thin UI over the manager: it persists settings, drives start/stop through it, and mirrors state/results via gwt:recording-* events. Removed its component-scoped hotkey registration. - Dropped the pre-record countdown to unify the hotkey and in-page flows (recording starts immediately on trigger). Also fixes two pre-existing bugs found while verifying: - download.astro had a top-level `return` in its module <script>, which broke the entire `npm run build` (web deploy + Tauri release). Guarded instead. - ScreenRecorder.selectRegion referenced `invoke` out of scope in its catch. Frontend builds; 386 JS tests pass (added a ⌘⇧R registration test); lints clean. No NEW type errors introduced. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
Two fixes from hardware testing the global recording: 1. Timer stuck at 00:00. The manager tracked no start time, and mounting the recorder page mid-recording reflected recording=true without starting the ticker. The manager now records `startedAt` (after any countdown) and exposes it; the UI ticks elapsed from it — accurate even when navigating to the page mid-recording, and immune to background throttling. 2. Restored the pre-record countdown. A 5s countdown now shows on the target display before capture begins (for both the ⌘⇧R hotkey and the in-page button), which also signals which screen is being recorded. The countdown window blends in with a screenshot of the area behind it and self-closes. Frontend builds; 386 tests pass; lints clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
⌘⇧R already toggles (stops when recording), but the window only came back if the manager had minimized it. Stopping now always brings the main window to front so a single ⌘⇧R reliably stops the recording AND reveals the app. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
Pressing the recording hotkey with more than one display now opens the same multi-display picker the screenshot flow uses, so the user chooses which screen to record. Recording (with the 5s countdown) begins after they pick. The screen-selected event is shared with the screenshot flow, so a pending-intent flag (consumePendingRecordingSelect) routes the pick to recording vs screenshot. Single-display stays a direct start. A guard ignores repeat presses while the picker is open. Frontend builds; 386 tests pass; lints clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
feat(recording): truly global ⌘⇧R recording hotkey
Match the desktop-v1.0.0-beta.1 release tag so the built app and the auto-updater manifest report the correct version. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dqouzFP8vy9jaKhTDFj5H
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Desktop app first beta — v1.0.0-beta.1
Promotes
developtomainfor the first tagged desktop release.Highlights since the last
main:/downloadpage, GitHub Actions release pipeline.download.astrono longer breaksnpm run build.Version bumped to
1.0.0-beta.1. Taggingdesktop-v1.0.0-beta.1after merge triggers the cross-platform release build.🤖 Generated with Claude Code