-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Keyboard and captions tracks #1615
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
richiemcilroy
merged 82 commits into
main
from
cursor/keyboard-and-captions-tracks-8d45
Mar 25, 2026
Merged
Changes from all commits
Commits
Show all changes
82 commits
Select commit
Hold shift + click to select a range
1c0b0cb
feat: add keyboard event types and word grouping algorithm to cap-pro…
cursoragent 86640c2
feat: add keyboard field to MultipleSegment recording metadata
cursoragent 03019a1
feat: add caption/keyboard track segments, keyboard settings, and bac…
cursoragent 73b5d07
feat: record keyboard presses alongside cursor in studio recording
cursoragent 0f6dadb
feat: add keyboard events to RenderSegment, SegmentMedia, and export …
cursoragent 5b121ee
feat: add keyboard overlay rendering layer with fade and character bu…
cursoragent 17e9327
feat: add caption and keyboard track types to editor context and time…
cursoragent 78763c2
feat: add CaptionsTrack and KeyboardTrack timeline components with fu…
cursoragent c0f843b
feat: add KeyboardTab sidebar, per-segment caption overrides, and key…
cursoragent 5a43fb1
feat: add generate_keyboard_segments Tauri command for keyboard track…
cursoragent 83348f2
chore: format Rust code with cargo fmt
cursoragent 783d887
fix: adjust caption and keyboard segments when clip timescale changes
cursoragent 2388f2d
Merge branch 'main' into cursor/keyboard-and-captions-tracks-8d45
richiemcilroy b59adc8
fix(recording): update Meta keycode to LMeta
richiemcilroy 5d85c7a
feat(project): add keyboard path fallback resolution for segments
richiemcilroy 6349804
feat(project): add keyboard and caption segment fields to structs
richiemcilroy 9dedbbf
feat(rendering): add recording_time field to ProjectUniforms
richiemcilroy 6f5a42c
refactor(rendering): simplify caption layer to use timeline segments
richiemcilroy d59897d
refactor(rendering): simplify keyboard layer fade with per-segment ov…
richiemcilroy 13ea9d5
chore: update auto-generated tauri bindings
richiemcilroy 41b04c4
chore: update auto-generated icon imports
richiemcilroy d27b706
feat(editor): add badge prop to Field component
richiemcilroy 2f78e95
feat(editor): migrate CaptionsTab to timeline-based caption segments
richiemcilroy 72192cb
feat(editor): add keyboard segment generation and redesign settings UI
richiemcilroy c9cd085
feat(editor): add keyboard segment selection and config panel
richiemcilroy 315cafe
style(editor): update caption track icon and empty state text
richiemcilroy 572802f
style(editor): update keyboard track segment colors to gray
richiemcilroy b15209b
Merge branch 'main' into cursor/keyboard-and-captions-tracks-8d45
richiemcilroy 0b40e55
chore(biome): extend formatter ignores and relax CSS lint rules
richiemcilroy f5b55a8
chore(vendor/tao): allow dead_code on macOS-only APIs
richiemcilroy cebf249
refactor(storybook): type Storybook package path helper as string
richiemcilroy a1f9b6f
refactor(web-domain): tighten optional schema generic constraints
richiemcilroy ed219f1
fix(database): guard session token id with optional chaining
richiemcilroy 0cd47c0
chore(ui-solid): declare lucide chevron icon auto-imports
richiemcilroy 0338478
fix(discord-bot): validate GitHub workflow token claims
richiemcilroy eb4cdc6
chore(media-server): tighten tests and drop unused imports
richiemcilroy ba966d4
test(web): prefer optional chaining in schema unit tests
richiemcilroy a4332b8
fix(web): remove documentation search autofocus
richiemcilroy 452e298
style(web): remove unnecessary important from prose overrides
richiemcilroy 858d4c1
refactor(web): harden docs headings and release metadata parsing
richiemcilroy 958ac83
refactor(web): prefix unused transcribe workflow helpers
richiemcilroy a4021a2
refactor(web): silence unused translated transcript variable
richiemcilroy 31b4459
feat(project): add binary keyboard events and update styling defaults
richiemcilroy 051d785
chore(editor): add caption and keyboard segments to playback benchmar…
richiemcilroy cebe8ce
fix(enc-avfoundation): satisfy clippy in mp4 encoder tests
richiemcilroy a7bb64e
refactor(recording): simplify benchmark runner string formatting
richiemcilroy c0a631c
refactor(recording): use async muxer setup in pipeline tests
richiemcilroy c769272
fix(recording): ignore duplicate camera feed sender registrations
richiemcilroy 6bb6b50
feat(recording): improve recovery inspection and keyboard capture pip…
richiemcilroy b3367a2
feat(rendering): stack keyboard overlay around active captions
richiemcilroy 9aa3b80
refactor(rendering): simplify cursor decimation unit test
richiemcilroy b0ef6db
feat(desktop): extend general settings for hints and keyboard capture
richiemcilroy ce240b5
feat(desktop): pass transcription hints into whisper initial prompt
richiemcilroy 919cd95
feat(desktop): wire inspect recovery, remux, and keyboard capture toggle
richiemcilroy fa938a6
fix(desktop): restore camera window safely and widen settings layout
richiemcilroy aeffebc
feat(desktop): add shared general settings helpers
richiemcilroy 37de84b
test(desktop): cover transcription hint normalization
richiemcilroy aaf75b7
refactor(desktop): read general settings types from shared helper
richiemcilroy 08ffa66
feat(desktop): add editor caption utilities and text style controls
richiemcilroy 4e5ffd4
feat(desktop): add transcription settings page and route
richiemcilroy b708c68
feat(desktop): surface studio recording toggles in general settings
richiemcilroy fb97d3e
refactor(desktop): tighten settings typing and external actions
richiemcilroy ad5d36d
refactor(desktop): harden window chrome and client mount guards
richiemcilroy d3e9107
refactor(desktop): tighten shared utility typings
richiemcilroy e5972ce
refactor(desktop): remove unsafe assertions in chrome and overlay routes
richiemcilroy d543352
feat(desktop): add captions and keyboard data to editor timeline
richiemcilroy 403f39c
feat(desktop): expand captions and keyboard editor side panels
richiemcilroy 40a71bc
feat(desktop): integrate new tracks into editor shell and playback
richiemcilroy 472ac37
bits
richiemcilroy 0e0d11e
refactor(desktop): generic composeEventHandlers for keyboard inputs
richiemcilroy 251d421
refactor(desktop): simplify Tauri event listener payload typing
richiemcilroy a12c215
fix(editor): read hovered mask time once when previewing segments
richiemcilroy 79e1948
fix(editor): preserve section markers with a single adjacent boundary
richiemcilroy f9d7150
fix(editor): initialize keyboard track when importing captions
richiemcilroy 154a18e
refactor(editor): narrow ComingSoonTooltip prop types
richiemcilroy 804b79d
fix(web): skip doc headings when regex captures are missing
richiemcilroy 10df2da
fix(recording): label space key for keyboard event capture
richiemcilroy 405c3e4
fix(recording): build keyboard recovery paths without unwrap
richiemcilroy b6c1de1
fix(project): skip empty keyboard segments on group flush
richiemcilroy 3cf1eff
test(project): cover backspace-to-empty keyboard segment regression
richiemcilroy 4d90f02
fix(editor): generate unique IDs for split keyboard and caption segments
richiemcilroy f515eae
fix(editor): defer caption track enable until generation succeeds
richiemcilroy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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
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
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
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
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2123,6 +2123,51 @@ async fn generate_zoom_segments_from_clicks( | |||||||||||||||||||||||||||||||||||
| Ok(zoom_segments) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| #[tauri::command] | ||||||||||||||||||||||||||||||||||||
| #[specta::specta] | ||||||||||||||||||||||||||||||||||||
| #[instrument(skip(editor_instance))] | ||||||||||||||||||||||||||||||||||||
| async fn generate_keyboard_segments( | ||||||||||||||||||||||||||||||||||||
| editor_instance: WindowEditorInstance, | ||||||||||||||||||||||||||||||||||||
| grouping_threshold_ms: f64, | ||||||||||||||||||||||||||||||||||||
| linger_duration_ms: f64, | ||||||||||||||||||||||||||||||||||||
| show_modifiers: bool, | ||||||||||||||||||||||||||||||||||||
| show_special_keys: bool, | ||||||||||||||||||||||||||||||||||||
| ) -> Result<Vec<cap_project::KeyboardTrackSegment>, String> { | ||||||||||||||||||||||||||||||||||||
| let meta = editor_instance.meta(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| let RecordingMetaInner::Studio(studio_meta) = &meta.inner else { | ||||||||||||||||||||||||||||||||||||
| return Ok(vec![]); | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| let segments = match studio_meta.as_ref() { | ||||||||||||||||||||||||||||||||||||
| StudioRecordingMeta::MultipleSegments { inner, .. } => &inner.segments, | ||||||||||||||||||||||||||||||||||||
| _ => return Ok(vec![]), | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| let mut all_events = cap_project::KeyboardEvents { presses: vec![] }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| for segment in segments { | ||||||||||||||||||||||||||||||||||||
| let events = segment.keyboard_events(meta); | ||||||||||||||||||||||||||||||||||||
| all_events.presses.extend(events.presses); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| all_events.presses.sort_by(|a, b| { | ||||||||||||||||||||||||||||||||||||
| a.time_ms | ||||||||||||||||||||||||||||||||||||
| .partial_cmp(&b.time_ms) | ||||||||||||||||||||||||||||||||||||
| .unwrap_or(std::cmp::Ordering::Equal) | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| let grouped = cap_project::group_key_events( | ||||||||||||||||||||||||||||||||||||
| &all_events, | ||||||||||||||||||||||||||||||||||||
| grouping_threshold_ms, | ||||||||||||||||||||||||||||||||||||
| linger_duration_ms, | ||||||||||||||||||||||||||||||||||||
| show_modifiers, | ||||||||||||||||||||||||||||||||||||
| show_special_keys, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2160
to
+2166
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor robustness: if these come from the UI as floats, clamping to non-negative avoids
Suggested change
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| Ok(grouped) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| #[tauri::command] | ||||||||||||||||||||||||||||||||||||
| #[specta::specta] | ||||||||||||||||||||||||||||||||||||
| #[instrument] | ||||||||||||||||||||||||||||||||||||
|
|
@@ -3105,6 +3150,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { | |||||||||||||||||||||||||||||||||||
| set_project_config, | ||||||||||||||||||||||||||||||||||||
| update_project_config_in_memory, | ||||||||||||||||||||||||||||||||||||
| generate_zoom_segments_from_clicks, | ||||||||||||||||||||||||||||||||||||
| generate_keyboard_segments, | ||||||||||||||||||||||||||||||||||||
| permissions::open_permission_settings, | ||||||||||||||||||||||||||||||||||||
| permissions::do_permissions_check, | ||||||||||||||||||||||||||||||||||||
| permissions::request_permission, | ||||||||||||||||||||||||||||||||||||
|
|
@@ -3673,13 +3719,14 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { | |||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||
| CapWindowId::TargetSelectOverlay { .. } | ||||||||||||||||||||||||||||||||||||
| | CapWindowId::Main | ||||||||||||||||||||||||||||||||||||
| | CapWindowId::Camera | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| let _ = window.show(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| restore_camera_window(app); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| #[cfg(target_os = "windows")] | ||||||||||||||||||||||||||||||||||||
| if !has_open_editor_window(app) { | ||||||||||||||||||||||||||||||||||||
| reopen_main_window(app); | ||||||||||||||||||||||||||||||||||||
|
|
@@ -3694,12 +3741,12 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { | |||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||
| CapWindowId::TargetSelectOverlay { .. } | ||||||||||||||||||||||||||||||||||||
| | CapWindowId::Main | ||||||||||||||||||||||||||||||||||||
| | CapWindowId::Camera | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| let _ = window.show(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| restore_camera_window(app); | ||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| CapWindowId::TargetSelectOverlay { display_id } => { | ||||||||||||||||||||||||||||||||||||
|
|
@@ -3901,9 +3948,25 @@ fn restore_main_windows_if_no_editors(app: &AppHandle) { | |||||||||||||||||||||||||||||||||||
| if let Some(main) = CapWindowId::Main.get(app) { | ||||||||||||||||||||||||||||||||||||
| let _ = main.show(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| if let Some(camera) = CapWindowId::Camera.get(app) { | ||||||||||||||||||||||||||||||||||||
| let _ = camera.show(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| restore_camera_window(app); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| fn restore_camera_window(app: &AppHandle) { | ||||||||||||||||||||||||||||||||||||
| let should_restore_camera = app | ||||||||||||||||||||||||||||||||||||
| .state::<ArcLock<App>>() | ||||||||||||||||||||||||||||||||||||
| .try_read() | ||||||||||||||||||||||||||||||||||||
| .map(|state| state.selected_camera_id.is_some()) | ||||||||||||||||||||||||||||||||||||
| .unwrap_or(false); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if should_restore_camera { | ||||||||||||||||||||||||||||||||||||
| let app = app.clone(); | ||||||||||||||||||||||||||||||||||||
| tokio::spawn(async move { | ||||||||||||||||||||||||||||||||||||
| let operation_lock = app.state::<CameraWindowOperationLock>(); | ||||||||||||||||||||||||||||||||||||
| let _operation_guard = operation_lock.lock().await; | ||||||||||||||||||||||||||||||||||||
| let _ = ShowCapWindow::Camera { centered: false }.show(&app).await; | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
partial_cmp+unwrap_or(Equal)can hide NaNs and makes ordering less explicit. Since this isf64,total_cmpis a nice drop-in here.