Cap Desktop -> GPUI - #2133
Conversation
Phase 0 of the gpui rewrite: a standalone crate that opens the main recording window shell. No tauri, no webview. Kept out of the root cargo workspace on purpose. The gpui tree pulls its own git revisions of the wgpu/font stacks, and the root workspace carries `[patch.crates-io]` entries (vendored wgpu-hal, tao) that exist only for the Tauri app. A nested workspace leaves the root Cargo.lock, the `cargo hakari` workspace-hack and CI untouched. gpui is pinned to the wingleeio/zed fork at 5d1f83d, which carries the Porter-Duff OVER fix for destination alpha on transparent windows that the rounded main-window shell depends on. Device enumeration reuses cap-camera, scap-targets and the same cpal fork the recorder uses, so device identities stay byte-identical to what cap-recording expects. rust-toolchain.toml pins stable here rather than the root's 1.88.0: gpui's tree needs 1.89+ (smol_str 0.3.6, cosmic-text 0.19). The nearest toolchain file wins, so the Tauri app is unaffected. Dev profile builds dependencies at opt-level 2 and the app crate at 0, matching zed/comet -- gpui is unusable with unoptimized deps.
gpui resolves `.font_family("Geist")` against the platform text system, so
the faces have to be added before the first window renders or every label
silently falls back to the system UI font.
Only four files are embedded, so `include_bytes!` covers it -- zed reaches
for `rust_embed` because it walks icons/themes/sounds/prompts as well. The
`AssetSource` impl is still worth having: gpui's `svg()` element and image
loader resolve their paths through it.
Cameras, microphones, displays and windows, through the same crates the recorder uses. The identities handed back are the ones cap-recording already expects -- camera device_id, microphone name, DisplayId, WindowId -- rather than a new identity space that would need translating later. Microphone listing mirrors MicrophoneFeed::list_with_settings: default input device first so it heads the list, then the rest, deduped by name. Window listing mirrors the picker path of screen_capture::list_windows (include_accessory_panels: false), which collapses to a non-empty title, not owned by the Window Server, and window level 0. The level check is the one that matters -- a raw Window::list() on this machine returns 33 entries, 19 of which are menu-bar extras (Control Centre alone contributes seven `Item-0` windows), leaving 14 real application windows. It is duplicated rather than imported because cap-recording drags in ffmpeg and the entire encode stack, which this app has no other reason to build. Enumeration blocks on AVFoundation and the window server, so it is documented as background-executor work, never to be called from render.
The whole window drawn in gpui: custom header with traffic lights, logo, mode pill, the four capture-target tiles and the three device rows. Metrics are transcribed from the Tauri implementation rather than eyeballed, and the Tailwind class each one comes from is quoted next to it, so `pl-3` and `gap-2.5` stay checkable against the original. Colours are the resolved Radix values with the dark-mode overrides from theme.css applied -- six of the dark grays and gray-11 deviate from stock Radix, so regenerating the palette from a Radix crate would silently change it. Two things could not be drawn the obvious way. gpui rasterises an SVG and keeps only its alpha, tinting the result with one colour, so the logo mark's three concentric circles would have collapsed into a solid block: the mark is rebuilt from divs and only the genuinely single-colour wordmark goes through `svg()`, which also makes it swap between the app's light and dark logo variants for free. And the window drag region is scoped to the header spacer alone, not the header root -- a mouse-down handler on the root starts a window drag for every press in the header, which eats the button clicks before they are delivered. The expanded state is the real 600x660 layout, not a scaled compact one: section headings appear, the target tiles turn horizontal and gain descriptions, and the control rhythm goes 8px -> 10px. Verified at both sizes, in light and dark. Device enumeration runs on the background executor and reports 3 cameras, 3 microphones, 1 display and 14 windows on this machine; the rows render the result but selecting a device is not wired up yet.
Clicking the camera or microphone row now opens a picker, and picking a device updates the row and its pill. Like the Tauri app, this is not a popup: the panel takes over the whole window body and offers a Back button. Rows carry real subtitles rather than placeholder text. Cameras report their highest-resolution advertised format via `CameraInfo::formats()`, ordered by pixel count then frame rate to match the web UI's `bestFormat`; microphones report the config they would actually be opened with, from cpal's `default_input_config`. On this machine that reads 1920x1080 @ 30fps for the Camo camera and 44.1kHz Mono for the built-in microphone. Enumerating all of it still takes 0.27s, so it stays on the background executor. Selection uses `--blue-500`, which is a Cap-specific token and a different colour from `blue-9` used by the pills -- worth keeping straight, since both are "the blue one". The search field that sits beside Back in the Tauri panel is not here: it needs real text input, which is its own piece of gpui plumbing. System audio has no device to choose, so its row is a plain toggle.
Groups 1-5 and 7 of the punch list, plus two window-lifecycle bugs the
screenshots turned up. They land together because generalising the panel
state underpins the mode-info panel, the target pickers and the search
field, and splitting that refactor from its three callers would have meant
three commits that do not build.
Fidelity:
- The logo is the real `logo-full.svg` again, through `img()` rather than
`svg()`. The two take different paths in gpui -- `svg()` keeps only the
alpha and tints it one colour, which flattened the badge, the blue rings
and the wordmark into a silhouette, so it had been rebuilt out of divs.
`img()` rasterises through resvg and keeps the colour, at 2x, so a 103px
source covers the 184 device pixels a 92px lockup needs. Dark mode picks
`logo-full-dark.svg`, as the app does.
- The plan badge ("Personal") sits next to it. Only the free variant is
drawn: which of Pro/Commercial applies comes from the license query, and
claiming a plan with no license plumbing would be worse than showing none.
- The help button is lucide `circle-help`, not the chat bubble.
- The mode pill gains its info dot, hanging off the top-left corner.
Panels. The Display and Window chevrons now open two-column card grids of
real targets: displays with their refresh rate, windows with their owner and
`1920x1080 @ 60 Hz` from real bounds. The mode info panel carries
`ModeInfoPanel`'s copy, which is deliberately not `MODE_BUTTONS`' copy -- the
hover cards and the panel describe the modes differently and the app ships
both. All three share one panel chrome with the device pickers.
Search is hand-rolled; gpui has no stock text input. Focus is tracked so the
panel keeps receiving keys, `key_char` supplies the typed character so dead
keys and option-layouts work, and the caret is a 1px div. There is no
selection, no cursor movement and no blink -- enough for a filter and
nothing more. Escape clears, then closes.
Recents appears when expanded, header and empty state only; thumbnails need
the recordings library. Its copy is the real "Your latest captures will
appear here.", not the shorter string in the brief.
How to run it, what is real, and a table of every deviation from the Tauri app with the reason. Several of those reasons are non-obvious enough to be worth writing down -- notably that `WindowKind::Floating` is not the way to get always-on-top, because it allocates an NSPanel and a panel hides itself when the application deactivates. The roadmap lists all fourteen app windows with their real sizes from windows.rs, so the size of what is left is legible: the main window is one of fourteen, and the editor alone is larger than everything done so far. Also records the gpui trap that cost the most time today -- touching the window from inside `open_window`'s builder fails silently, twice over -- and the per-window `screencapture -l` recipe, since a plain full-screen capture returns only the desktop without Screen Recording permission.
180ms with an ease-out cubic, matching `resizeMainWindow`. The task is held on the view so that assigning a new one drops the old: a second toggle mid-animation cancels the first, rather than both driving `resize` and leaving the window at some interpolated size. Verified by sampling the window's bounds while it runs -- it interpolates and lands exactly on 600x660. Smoothness could not be verified here: while the app is inactive its foreground run loop is barely pumped, so the 8ms timer between steps takes closer to a second and the resize arrives as two or three jumps. That is the same starvation that stops an inactive window repainting at all, not something in the animation. Does not re-clamp into the monitor work area afterwards, which the Tauri version does; noted in the README.
`logo-wordmark.svg` was cut out of the logo for the div-rebuilt mark, which `img()` replaced; `support.svg` was the chat bubble standing in for the help button before lucide `circle-help`. Both were still embedded. The existing test only checked that every icon referenced is embedded. It now checks the reverse as well, since a dead asset is invisible -- it costs binary size and nothing else notices.
cap-recording as a path dep (studio + instant actors, kameo feeds), the root's vendored wgpu-hal patch mirrored, ort pinned to the root lockfile's rc.12 (rc.13 renames CoreMLExecutionProvider out from under camera-effects). Projects land in the same recordings library as the Tauri app and are finalized the way the CLI finalizes: remux for studio, output.mp4 + meta pair for instant.
Idle/Starting/Recording/Stopping on the entity, driven over gpui_tokio. The recording takeover matches index.tsx 3510-3529 (gray-1/80 wash, red stop button); a TEMP blue start footer stands in for the target-select overlay that owns starting in the real app. CAP_GPUI_AUTO_RECORD drives the same code paths for the end-to-end check.
…s bar The platform module reaches the NSWindow behind a gpui window through raw-window-handle and applies what gpui does not expose: window level, Spaces collection behavior, shadow. The main window gets its level-100 all-Spaces treatment from windows.rs; the 320x150 controls bar is a non-activating WindowKind::PopUp panel at the level the Tauri bar actually runs at (CGWindowLevelForKey(10) = 8 -- the constant windows.rs names kCGMaximumWindowLevelKey is really the modal-panel key, reproduced verbatim for parity). The recording lifecycle moves off MainWindow into a RecordingSession entity both windows observe. Starting opens the bar first and hides the main window, passing the bar's window number to the actors as a capture exclusion; stop, delete, restart, and failed starts tear it back down. Pause/resume produce real segment boundaries. A microphone that enumerates but fails to open degrades to a no-mic recording instead of failing the start.
occlusionState reports visible windows with an undocumented 0x2000 bit on macOS 26 and no longer sets NSWindowOcclusionStateVisible (0x2), which is the only bit gpui's display-link gate checks -- so no window ever received frame callbacks after its first paint. The bar timer sat frozen on Starting through whole recordings and every prior refresh-tick workaround was painting nothing. Shim occlusionState on gpui's own window classes (GPUIWindow/GPUIPanel) to OR the documented bit back in whenever AppKit reports any visibility, and re-fire the occlusion handler from apply_panel_behavior so the link starts. Drop when the gpui pin understands the macOS 26 bit.
Selecting a camera spins up an app-scoped CameraFeed and opens the preview bubble immediately, the Tauri model; the mic feed is app-scoped too, feeding live levels to the picker rows and the recording bar, whose mute button now flips the recording-scoped payload-zeroing mute in instant mode. Recordings lock the running feeds instead of spawning their own (per-recording spawn kept as fallback), and the bubble is excluded from studio captures but included in instant ones, mirroring filter_for_instant_mode. Frames go VideoToolbox 420v->BGRA into gpui RenderImages -- the surface() element hard-asserts 420f on this rev and ignores rounded clipping, and the default bubble is a circle. Chrome state (size/shape/blur) persists to gpui-state.json; round/square/full shapes, S/L presets, hover toolbar, corner resize, and drag-to-move all match CameraPreviewChrome.tsx; mirroring is present-but-disabled (no flip transform in this gpui rev).
One transparent non-activating panel per display at the Tauri-verbatim level 7 (CGWindowLevelForKey(10) - 1), translated from target-select-overlay.tsx: the display variant's hover wash and monitor art, the window variant's cursor-following highlight with click-to-pin and app icons, the area variant's draw/move/resize crop with eight handles and min-size validation, and the camera-only variant. The overlays own Start Recording: starting closes them, opens the bar, hides the main window, and excludes the overlay windows from capture. An app-scoped TargetSelect entity polls the cursor's display and topmost window on the background executor (the Tauri app's 50ms tokio loop, at 80ms) and repaints every overlay explicitly, since none of them is the active window. Two platform findings shipped alongside: gpui windows all carry NSTitledWindowMask, so AppKit's constrainFrameRect: pushes a display-covering window below the menu bar - a shim on GPUIWindow and GPUIPanel returns the rect unchanged, the same override tao gives the Tauri app; and setFrame:/orderFrontRegardless synchronously re-enter gpui's window callbacks, so placement runs from a spawned task instead of inside a window update (inside one it logs 'RefCell already borrowed' and drops the callback). Verified: all four variants screenshotted; overlay probed at CG layer 7 covering 0,0 1512x982; a seeded 800x500 area crop recorded a 1600x1000 display track (ffprobe); CAP_GPUI_AUTO_OVERLAY / CAP_GPUI_AUTO_AREA document the harness.
…ct overlays Target-select row done (capture-area marked superseded - nothing in the Tauri frontend opens the standalone window), the TEMP start footer and 'Area does not arm' deviations removed, overlay deviations added (focused-key Escape, start-only cluster, bubble over inline preview), and two new gpui traps: AppKit window mutations inside a gpui update, and the titled-window menu-bar constraint.
The recording start/stop flows called orderOut:/makeKeyAndOrderFront: inside window updates, which re-enters gpui's window callbacks and logged 'RefCell already borrowed' three times per stop, dropping the callbacks. Same fix shape as the overlay placement: grab the retained NSWindow inside the update, do the AppKit call from a spawned task. A full record-stop-reshow cycle now logs zero borrow errors.
…window
The main window now sits on the same native material the Tauri app
gives its chrome windows via applyMacOSWindowMaterial("panel"): an
NSGlassEffectView on macOS 26+ (runtime class lookup, style regular,
radius 16, the SystemManaged main-window path -- no always-active SPI
pinning), an NSVisualEffectView windowBackground/BehindWindow fallback
below that, and on both paths the content view's layer clipped to a
16px continuous-corner squircle. gpui's contentView is a plain AppKit
container with the Metal view as a subview, so the material drops in
NSWindowBelow it exactly as the Tauri recipe assumes. Installed from a
spawned task on the retained NSWindow -- subview insertion re-enters
gpui's window callbacks, so it cannot run inside an update.
What the shell paints over the material is transcribed from theme.css's
--macos-settings-* tokens into theme::MaterialTokens (glass tint
rgba(255,255,255,0.55) light / rgba(17,17,17,0.88) dark, control fills,
selection, text), with the CSS rule quoted next to each value. The
body remaps are liquid-glass-only, which is the CSS's own asymmetry:
under vibrancy only the shell and header change.
No occlusion SPI, no CGS, no setState:/setActive: probing -- the
shipping app's WindowServer wedge is the recorded reason.
CAP_GPUI_AUTO_EXPAND=1 drives the expand animation for verification.
Verified: NSGlassEffectView path confirmed by log on macOS 26;
translucency screenshotted light+dark, compact+expanded (glass tracks
the resize via the autoresizing mask); full record cycle finalizes with
zero borrow errors; 12 tests green.
A real second chrome window: 782x775, resizable to the 780x560 minimum
(gpui's window_min_size covers setContentMinSize:, and TitlebarOptions
appears_transparent + traffic_light_position covers the (22,22) lights
-- no new AppKit needed), on the settings material at radius 26 with
the settings-surface tokens (sidebar/content/card/fill/hover/muted)
added to theme::MaterialTokens from their --macos-settings-* rules.
The sidebar carries all twelve pages from settings.tsx's settingsItems
(ungated there, ungated here); General is built in full -- theme tiles,
app toggles, the Pro rows, quality, recording behaviours, countdown,
storage path, project-name template, excluded windows, update channel,
self-host, telemetry -- each row writing its real store key.
Writes go through store::set_store_setting: a read-modify-write on the
raw JSON of the tauri-plugin-store file ('store', no extension) that
replaces exactly one key and preserves everything else -- auth,
presets, migration flags, the settings no page renders. A corrupt
store is refused, never replaced; writes are temp-file + rename.
Typed reads are field-by-field with per-field defaults so one unknown
value cannot blank the page. recordings_dir now reads through the same
module, so CAP_GPUI_TAURI_STORE redirects everything at once.
The main window's header gear opens settings and hides the main window
(the showWindow({Settings}) + hide() pair from new-main); closing
settings brings the main window back.
Verified: material log LiquidGlass radius=26; window screenshot at
782x775 with sidebar + full General page; single-key write against a
byte-copy of the real store diffs to exactly one line with all 12
sections and 40 general_settings keys preserved (md5-proven the real
store was never touched); record cycle finalizes with zero borrow
errors; 23 tests green.
Mode select: the real 580x340 mode-select route -- three fixed cards,
opaque bg-gray-1 (it is an opaque slab in the shipping app too), native
traffic lights at AppKit's default position, selection through the same
set_mode path the main window's pill uses, main window hidden while it
is up. One faithfulness call went the other way from the implementing
agent's draft: shipping new-main passes onInfoClick into Mode.tsx, so
the mode dot opens the in-body info panel there -- and now here too;
nothing in the shipping frontend ever reaches showWindow("ModeSelect"),
so the standalone window is harness-only (CAP_GPUI_AUTO_MODE_SELECT).
Teleprompter: 560x320 resizable to 420x220, native level 101
(TELEPROMPTER_PANEL_LEVEL = MAIN_PANEL_LEVEL + 1) on all Spaces, the
teleprompter material at radius 22, traffic lights at (14,14). Typed
script with the centered layout, fade vignette and cue markers;
word-count-driven auto-scroll porting calculate_playback_speed /
advance_playback_position from teleprompter-utils.ts with their tests;
wpm 60-350, window alpha 45-100 through NSWindow alphaValue, font
22-52; settings popover with cue markers and (inert, no flip transform
in this gpui rev) mirror. State persists under the store's teleprompter
key through set_store_setting with a 250ms debounce. While a recording
runs the window is excluded from capture and content-protected
(sharingType none), restored after -- the recording-gated behavior
apply_content_protection ships.
Deviations documented: append-only script editor (no selection or
click-to-position), wash-not-mask vignette, inert mirror.
Verified: level=101 probed, material LiquidGlass radius=22, alpha 0.92
applied, capture exclusion + sharing=0 logged during a finalized record
cycle with zero borrow errors; script persistence round-trip against a
store copy diffs to the one teleprompter key with the real store
md5-untouched; 31 tests green.
…ridge library.rs transcribes list_recordings / list_screenshots faithfully: every known recordings folder (active, default, previousRecordingsPaths, deduped by canonical path), recording-meta.json through cap-project's own loader, the shipping asymmetries kept (screenshots filter on .cap and sort by their PNG's ctime; recordings do neither), both lists capped at 9 before and after the merge. Thumbnails are the pre-baked bundle files (screenshots/display.jpg, or the screenshot's PNG), decoded and downscaled on the background executor into RenderImages with the replaced frame evicted from the atlas. cap-recording never writes the thumbnail, so a recording made here showed the icon fallback forever -- including in the shipping app. The first-frame JPEG generator (create_screenshot, lib.rs:2582) is ported; post-finalize now writes it for studio and instant recordings both. The display path is re-read from disk after the remux: until remux_if_needed runs the meta points at the fragmented segment directory, after it at display.mp4 (the same ordering that makes the Tauri app use updated_studio_meta). The camera bubble's blur toggle now bridges into the finished studio project's camera.backgroundBlur.mode (recording.rs:3889 in the Tauri app), read at finalize for the live-state semantics handle_recording_finish has. Blur is never baked into the track by either app -- this field is what makes the project open blurred in the editor. The write is the store discipline applied to project-config: raw-JSON read-modify-write of exactly one nested key, parse failure refused, temp + rename. Verified end-to-end in one run: record -> finalize -> thumbnail on disk -> blur bridged with all 16 config keys and the timeline intact -> library rescan -> the new card renders its real thumbnail; zero borrow errors; 38 tests green.
…eadlessly cap-rendering, cap-editor and cap-export join the workspace as path deps. The three feared blockers from the editor recon all dissolved under evidence: - wgpu skew: none exists and none could. cap-rendering (wgpu 25.0.2 on the vendored wgpu-hal) was already in this graph via cap-recording, and gpui_macos is Metal-direct -- the wgpu 29 in the lock is reached only through gpui's linux/web backends and resolves to nothing on the host (cargo tree -i prints empty). No duplicate wgpu or naga. - image: ^0.25.1 (gpui) and ^0.25.2 (cap crates) unify at 0.25.10 on one node, so gpui frames and cap frames stay the same type; all camera/library image paths unchanged. - workspace-hack: no new route to tauri-utils; tauri proper still absent from the lock. tests/editor_frame0.rs proves the stack end to end: EditorInstance (clean of tauri types -- the websocket glue lives in the Tauri app's editor_window.rs) opened on a copy of a real studio .cap with a headless audio output, one instruction pushed onto preview_tx (seek_to renders nothing -- the digest trap, now confirmed in source), frame 0 taken off the frame callback, un-padded from the 256-byte row stride and written as a 1080x702 PNG with size/layout/content asserts. A bundle this app recorded itself round-trips through the editor stack at the same dimensions. Opening a project is not read-only (EditorInstance::new writes project-config.json back in two places) -- the smoke works on a copy, and the editor window unit inherits that fact. Also inherited: ProjectRecordingsMeta::new panics rather than errors on a corrupt display track, so the window needs a pre-flight probe. cargo check +17.8s, target 18G -> 19G, 38+1 tests green, record cycle unaffected.
…ames editor_window.rs: 1275x800 (min the same), native traffic lights at (20,32), one window per .cap path with re-focus on a second open (EditorWindowIds, as a registry Vec). Opaque, no material, faithfully: /editor is a sibling of (window-chrome), applyMacOSWindowMaterial has exactly two call sites and neither is it, and CapWindowId::Editor is absent from is_transparent(). Project load runs EditorInstance on the background executor behind a preflight that reproduces every Err EditorInstance::new can return and then runs the panicking ProjectRecordingsMeta::new under catch_unwind -- both arms proven with deliberately corrupted bundle copies, the app alive and showing the in-window error state each time. Opening a project writes project-config.json back, exactly as the shipping app does. The main window hides on open and restores when the last editor closes (windows.rs:1930 + new-main:2925 + lib.rs:5788). Frames: frame_cb -> bounded flume channel -> background un-pad of the 256-byte stride + R/B swap -> RenderImage, previous frame evicted from the atlas per replacement; the first picture arrives by pushing the playhead onto preview_tx (seek_to renders nothing). Letterboxing is Player.tsx:566-601 verbatim, painted via canvas bounds into the fitted rect only. Shell regions pixel-measured against the capture: header 56, sidebar rail 64, sidebar column 416 with the 8px gutter, resize handle 16, timeline 260, clip track 52 -- every one the transcribed metric. Recents cards now open the editor; CAP_GPUI_AUTO_EDITOR drives it for the harness. Record -> edit round trip proven on a bundle this app recorded. Header/transport/sidebar controls render inert where their features do not exist yet (documented); the full 1:1 build-out continues in E2+. 49 unit + 1 integration tests green; record cycle zero borrow errors.
…, measured 60fps
The on_state_change seam is real now: playhead positions land in an
AtomicU32 poked through a one-slot channel from the cap-playback OS
thread, drained on the main thread with burst coalescing. Transport is
desired-state -- {playing, seek, seek_gen} behind a mutex with a single
driver task applying stop/seek/start in the source's order, which is
what makes seek-while-playing coalesce exactly as the Tauri frontend
does (Timeline/index.tsx:829-909).
Behaviors transcribed with evidence, including the surprises: at end
of media the UI stops 0.1s before the engine's own end and parks the
playhead (Player.tsx:156-210); prev/next are jump-to-start/end, not
frame steps (Player.tsx:370-405); container presses seek on mouseup to
the mousedown position while only the ruler surface scrub-drags
(TL/index.tsx:1155-1169 vs 873-957); Space toggles; playback starts at
(60, 1248x702). Ruler scrubbing includes the 10px snap-to-zero.
Performance, measured on a 15.9s recording: 59.9fps sustained, 949
frames, 0 dropped, engine skips 0, convert 0.93ms/frame (5.6% of the
60fps budget) -- the zero-copy upstream change is not needed for
playback. The one real finding: at opt-level 0 the same convert is
30.1ms and the pump drops 45% of frames, so the dev profile now
compiles the app crate at opt-level 2 (+7s build) to show the editor's
true speed.
Reliability: 45-cycle play/pause/seek torture, 45/45 clean, one
dropped frame total, RSS plateaus at 1.1GB (engine frame cache, not a
leak; paused editor sits at 345MB); close mid-playback leaves no
cap-playback thread 1.2s later and a reopened editor plays at full
rate; verified with real CGEvent input (Accessibility is granted on
this machine) alongside the env hooks.
Also corrected E1's README claim about lazy audio: EditorInstance
prewarms the cpal stream at load (editor_instance.rs:305).
56 unit + 1 integration tests green; record cycle zero borrow errors.
src/ui/, 13 modules: Button/IconButton (8 variants x 4 sizes), Toggle, Slider (one maths module replacing both formula families it found -- proven equivalent at 10,001 points per range), SegmentedControl (the superset of all four Tauri idioms), Menu with the full keyboard contract (open-at-pointer, arrow-walk with wrap, Enter commits, Escape and click-away dismiss), Select, TextField, Card/Popover/SettingRow/ Section surfaces incl. liquid-glass, Collapsible with real measured- height animation, TabRail, CircularProgress (one, replacing three), KbdChip, Tooltip. Theming is named constructors per surface, mirroring exactly the three attribute-selector remaps theme.css actually has. Settings, teleprompter, main and editor windows retrofitted; mode select is genuinely bespoke and stays local. Net -882/+446 across the window files. The regression bar: before/after captures for every window pair are pixel-identical (0 differing pixels; the two apparent diffs traced to a parked-cursor hover and store ordering, not code). Menu and slider contracts proven with real CGEvent input, including the store write landing (doNothing -> reopenRecordingWindow; defaultZoomAmount 3.9). Every harness flow re-run clean; playback still 59.9fps / 0 dropped / 867us convert. One intentional behavior delta, documented: settings text fields now treat modifier+Backspace/Escape as edits, matching the main window's field. 93 unit + 1 integration tests green (+37).
…zoom and pan editor_timeline.rs: the whole timeline model and rendering at fidelity. The transform is the Tauri model exactly (zoom = visible seconds, position = left-edge second, clamp order matters on short projects, originPercentage capped not floored, the once-only 80px on-mount fit), and every zoom gesture anchors where the source anchors -- playbackTime for keys/glyphs/slider, previewTime ?? playbackTime for ctrl+wheel (context.ts:1389-1487, Player.tsx:256-465, TL/index.tsx:1190-1205). gpui's scroll delta is the DOM's opposite sign; pinch maps through Chromium's ctrl+wheel synthesis. CGEvent-verified to four decimals. All nine track types render from real data and were verified visually on a fixture carrying every one: clip (with in-clip markings, hold bands, and REAL waveforms -- the peak extraction from src-tauri audio.rs:42-73 transcribed and unit-tested, the createWaveformPath curve through PathBuilder), captions, keyboard, text, mask, audio with fade envelopes, zoom segments with the hover ghost, 3D, and scene. Colors carry their theme.css quotes; the hover ghost playhead shows previewTime in the clock without rendering a frame, as shipped. Also fixed an E2 measurement error: the timeline content column is 1111px at 1275 (the scroll body carries pr-1), not 1115. Full 11-row timeline costs 0.3 fps: 59.6 vs 59.9 on the same media, zero dropped, segments outside the viewport never built. 45-cycle torture clean. Two shipping findings recorded: border-green-7 and border-sky-7 are dead classes (caption/keyboard selection changes nothing on screen in the Tauri app), and the Tauri editor has no noninteractive route to open a project, so comparison was against the TSX metrics. 120 unit + 1 integration tests green (+27).
|
🚨 Contributor flagged. Click here for more info: Superagent Dashboard |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
|
hey @greptile-apps please review |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
Adds a native Cap GPUI Version (
apps/desktop-gpui) alongside the existing Tauri desktop. Same Cap media stack, no webview. The UI is drawn by GPUI.There was already an Electron experiment in #2119. The plan is to ship Cap GPUI Version as an optional toggle in the existing Cap desktop so people can trial it without replacing Tauri.
On the same machine and protocol as that Electron comparison, Cap GPUI Version is substantially faster and lighter than both Tauri and Electron:
Test plan
cd apps/desktop-gpui && cargo runopens the native recording windowapps/desktopis unchanged and still the default shipping appGreptile Summary
The PR adds a native GPUI desktop application alongside the existing Tauri app and extends the shared recording, playback, rendering, encoding, and export stack.
Confidence Score: 4/5
The PR should not merge until the outstanding upload retry path reliably persists the completed video ID or stops before returning a retryable thumbnail error.
A video can finish uploading, the sharing-metadata save can fail and be ignored, and a subsequent thumbnail failure can return control without a durable video ID; the next export then selects initial-upload mode and creates another server video.
Files Needing Attention: apps/desktop-gpui/src/upload.rs, apps/desktop-gpui/src/editor_export.rs
Important Files Changed
Reviews (6): Last reviewed commit: "fix(desktop-gpui): persist sharing meta ..." | Re-trigger Greptile
Context used: