Skip to content

Commit d2cb70d

Browse files
committed
fix: guard recording source discovery
1 parent 138a4e2 commit d2cb70d

7 files changed

Lines changed: 190 additions & 22 deletions

File tree

docs/snow-shot-alignment-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Pin、截图历史、二维码、可选 OCR/翻译、滚动截图和 FFmpeg 录
3939
| 历史 | 缩略图、相对时间、关键词搜索、来源筛选(含 Selection、Scrolling、Full screen 和 Pinned)、展开、单项动作、批量删除和键盘导航选择已具备;300 条真实记录默认仅渲染 5 条预览,重启截图与内存复核通过 | 暂无未覆盖的 P1 工作流差距;继续保留长期增长观察 | P1(完成) |
4040
| 设置主页 | 分组页面、深浅主题、状态反馈和面向 Capture/Library/Record/App 的任务导航已具备;深浅主题语义前景色在背景和面板上均由自动测试保证至少 4.5:1 对比度;深色/浅色 980x760、浅色 520x640 和深浅色最小 420x420 的 Release 原生截图复核通过;设置探针现在可按零基显示器索引定位目标 DPI | 150%/200% 缩放下的最终实机复核仍需完成 | P1 |
4141
| 保存 | PNG/JPEG/WebP 导出、快速保存、项目 sidecar、默认目录和命名配置已具备;设置页的 `Check folder` 与新目录选择都会先验证当前目录能创建、同步并清理私有探针文件;首选历史目录失效时,选择、全屏和 Pin 快速保存会回退到托管目录并同步历史索引 | 不同 Windows profile 下的目录权限和命名仍需手工复核 | P2 |
42-
| 高级能力 | OCR、翻译、滚动截图和录屏、环境检查及失败恢复已具备;OCR 保留 `FLASH_SHOT_TESSERACT` 覆盖,同时自动识别 Windows 常见 Tesseract 安装路径;录屏停止请求现在有独立 `Stopping` 状态,收尾期间禁用重复停止/暂停/切换目标;设置页的 `Test service` 只在用户显式点击且配置 HTTPS 端点时发送固定短语,并仅显示返回长度;翻译边界兼容服务常见的 `translation``translatedText` 响应字段;`recognition-acceptance` 默认只读报告 OCR/翻译依赖,显式 `--ocr-image``--translation-text` 才会执行真实链路并仅写入长度元数据;生产录屏后端的显示器、窗口、区域与暂停/恢复/停止已由 `recording-acceptance` 验收探针生成 MP4 并通过 `ffprobe` | 翻译真实端点与 UI 成功态仍需验收,完整录屏 UI 手工矩阵仍需执行 | P2 |
42+
| 高级能力 | OCR、翻译、滚动截图和录屏、环境检查及失败恢复已具备;OCR 保留 `FLASH_SHOT_TESSERACT` 覆盖,同时自动识别 Windows 常见 Tesseract 安装路径;录屏停止请求现在有独立 `Stopping` 状态,收尾期间禁用重复停止/暂停/切换目标;显示器与音频源枚举有 generation 守卫,设置页在枚举期间锁定冲突录屏动作;设置页的 `Test service` 只在用户显式点击且配置 HTTPS 端点时发送固定短语,并仅显示返回长度;翻译边界兼容服务常见的 `translation``translatedText` 响应字段;`recognition-acceptance` 默认只读报告 OCR/翻译依赖,显式 `--ocr-image``--translation-text` 才会执行真实链路并仅写入长度元数据;生产录屏后端的显示器、窗口、区域与暂停/恢复/停止已由 `recording-acceptance` 验收探针生成 MP4 并通过 `ffprobe` | 翻译真实端点与 UI 成功态仍需验收,完整录屏 UI 手工矩阵仍需执行 | P2 |
4343

4444
## 4. 实施阶段
4545

docs/windows-manual-acceptance.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ cargo run --release --bin settings-ui-acceptance -- light 520 640 target/ui-acce
151151
| 2026-08-10 | `current-ocr-ui-single-100` | 当前单屏 2560x1440、DPI 96 环境中,从选区工具栏的 `More -> OCR` 启动真实 Tesseract OCR;显式提供本机 `C:\Program Files\Tesseract-OCR\tesseract.exe` 后,菜单显示 `Recognized text` 结果预览,状态显示 `Text recognized locally`,点击 `Copy text` 后剪贴板实际得到 674 个字符。首次未继承用户环境变量时,选区保持不变并显示 `Retry OCR`,随后重启并重试成功。 | 待执行 | 截图位于 `target/ui-acceptance/manual-ocr-more-menu.png``target/ui-acceptance/manual-ocr-result.png``target/ui-acceptance/manual-ocr-result-configured.png`。本次证明本地 OCR、失败保留选区、重试和结果复制链路;翻译服务、双屏/混合 DPI 与完整录屏矩阵仍待执行。 |
152152
| 2026-08-10 | `current-save-folder-preflight` | 快速保存目录预检新增 4 项库测试,验证可写目录在创建、同步和删除私有探针文件后保持为空,并拒绝非目录路径;用户通过文件夹选择器切换历史根目录前也会先运行相同预检;Release 原生深色/浅色 Library 520x640 截图复核通过。 | 待执行 | `Check folder` 位于当前快速保存路径与文件夹选择之间,空闲时可点击、检测中会禁用以避免重复请求;截图为 `target/ui-acceptance/settings-p2-storage-check-520x640.png``target/ui-acceptance/settings-p2-storage-check-light-520x640.png`。不同 Windows profile 的真实权限矩阵仍待执行。 |
153153
| 2026-08-10 | `current-recording-stop-state` | 录屏停止请求新增独立 `Stopping` 状态;停止收尾期间 Record 页显示 `Stopping...`、隐藏 Pause、禁用重复停止和 Display/Audio/Check support 切换,收到 Finished/Failed 后才恢复空闲。状态文案、冲突优先级、录屏模块 27 项测试、全目标测试和严格 Clippy 通过。 | 待执行 | Release Record 页深色/浅色 520x640 截图位于 `target/ui-acceptance/recording-stop-state-dark-520x640.png``target/ui-acceptance/recording-stop-state-light-520x640.png`;截图复核空闲布局,停止态由状态回归测试覆盖。完整 FFmpeg UI 手工矩阵仍待执行。 |
154+
| 2026-08-10 | `current-recording-discovery-guard` | 录屏显示器/音频源枚举现在绑定 workflow generation;枚举期间 Record 页锁定互相冲突的录屏动作,过期结果不会覆盖新会话或正在录制的状态;相关回归测试、全量测试、严格 Clippy 和 Release 设置页截图均通过。 | 待执行 | `target/ui-acceptance/recording-discovery-lock-520x640.png` 的 Record 页面在 100% DPI 下无文字截断或控件重叠;枚举忙状态与过期结果由自动测试覆盖。双屏、混合 DPI 和完整录屏 UI 手工矩阵仍待执行。 |
154155
| 2026-08-10 | `current-translation-service-test` | 设置页翻译操作改为显式 `Test service`:未配置端点时保持本地、可操作提示;配置 HTTPS 端点后才发送固定 `Flash Shot` 探测短语,成功只显示返回字符数,失败保留端点检查恢复提示。聚焦状态格式测试通过,Release 设置页截图复核通过。 | 待执行 | 截图位于 `target/ui-acceptance/settings-p2-translation-service-520x640.png`。当前机器未配置翻译端点,因此真实成功响应仍需在用户服务环境执行;探测不会发送截图原文。 |
155156
| 2026-08-10 | `e393f5d` | 当前 Release `recording-acceptance` 串行完成显示器、区域、窗口三种目标录制;三份 MP4 均由探针内部及 FFprobe 校验为 H.264,暂停/恢复事件均观察到,最终进度帧分别为 32、29、36,录制结束后未残留 FFmpeg、FFprobe 或验收窗口进程。 | 待执行 | 报告位于 `target/ui-acceptance/recording-p2-current3-display.json``recording-p2-current3-region.json``recording-p2-current3-window.json`;显示器输出为 2560x1440、2.53 秒,区域输出为 640x360、2.4 秒,窗口输出为 520x640、2.8 秒。窗口截图 `target/ui-acceptance/recording-p2-current3-window-settings.png` 复核 Record 导航、Display/Audio、Check support、Record display 和状态栏无截断或重叠;完整应用内录屏 UI、双屏和混合 DPI 手工矩阵仍待执行。 |
156157

src/app/view.rs

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ struct RecordingViewState {
2727
active: bool,
2828
starting: bool,
2929
stopping: bool,
30+
display_discovery_in_flight: bool,
31+
audio_discovery_in_flight: bool,
3032
paused: bool,
3133
progress: crate::recording::RecordingProgress,
3234
}
@@ -43,6 +45,8 @@ impl gpui::Render for FlashShotApp {
4345
active: self.recording_control.is_some(),
4446
starting: self.recording_start_in_flight,
4547
stopping: self.recording_stopping,
48+
display_discovery_in_flight: self.recording_display_discovery_in_flight,
49+
audio_discovery_in_flight: self.recording_audio_discovery_in_flight,
4650
paused: self.recording_paused,
4751
progress: self.recording_progress,
4852
};
@@ -796,22 +800,34 @@ fn recording_settings(
796800
audio: &str,
797801
app: gpui::Entity<FlashShotApp>,
798802
) -> gpui::Div {
803+
let source_discovery_busy = recording_source_discovery_busy(state);
804+
let settings_idle =
805+
!state.active && !state.starting && !state.stopping && !source_discovery_busy;
806+
let recording_toggle_enabled = !state.starting && !state.stopping && !source_discovery_busy;
799807
settings_section("Recording", colors)
800808
.child(settings_row("Display", colors).child(settings_button(
801809
"settings-recording-display",
802-
display,
810+
if state.display_discovery_in_flight {
811+
"Discovering..."
812+
} else {
813+
display
814+
},
803815
colors,
804-
!state.active && !state.starting && !state.stopping,
816+
settings_idle,
805817
{
806818
let app = app.clone();
807819
move |_, _, cx| app.update(cx, |this, cx| this.cycle_recording_display(cx))
808820
},
809821
)))
810822
.child(settings_row("Audio", colors).child(settings_button(
811823
"settings-recording-audio",
812-
audio,
824+
if state.audio_discovery_in_flight {
825+
"Discovering..."
826+
} else {
827+
audio
828+
},
813829
colors,
814-
!state.active && !state.starting && !state.stopping,
830+
settings_idle,
815831
{
816832
let app = app.clone();
817833
move |_, _, cx| app.update(cx, |this, cx| this.cycle_recording_audio(cx))
@@ -827,25 +843,17 @@ fn recording_settings(
827843
"settings-check-recording-support",
828844
"Check support",
829845
colors,
830-
!state.active && !state.starting && !state.stopping,
846+
settings_idle,
831847
{
832848
let app = app.clone();
833849
move |_, _, cx| app.update(cx, |this, cx| this.check_recording_support(cx))
834850
},
835851
))
836852
.child(settings_button(
837853
"settings-record-display",
838-
if state.starting {
839-
"Preparing..."
840-
} else if state.stopping {
841-
"Stopping..."
842-
} else if state.active {
843-
"Stop recording"
844-
} else {
845-
"Record display"
846-
},
854+
recording_toggle_label(state),
847855
colors,
848-
!state.starting && !state.stopping,
856+
recording_toggle_enabled,
849857
{
850858
let app = app.clone();
851859
move |_, _, cx| app.update(cx, |this, cx| this.toggle_display_recording(cx))
@@ -886,6 +894,26 @@ fn recording_status_visible(state: RecordingViewState) -> bool {
886894
state.starting || state.active || state.stopping
887895
}
888896

897+
/// Reports whether display or audio discovery is still changing the next recording input.
898+
fn recording_source_discovery_busy(state: RecordingViewState) -> bool {
899+
state.display_discovery_in_flight || state.audio_discovery_in_flight
900+
}
901+
902+
/// Gives the record command a truthful label while source discovery temporarily owns the action.
903+
fn recording_toggle_label(state: RecordingViewState) -> &'static str {
904+
if state.starting {
905+
"Preparing..."
906+
} else if state.stopping {
907+
"Stopping..."
908+
} else if recording_source_discovery_busy(state) {
909+
"Discovering..."
910+
} else if state.active {
911+
"Stop recording"
912+
} else {
913+
"Record display"
914+
}
915+
}
916+
889917
/// Summarizes recording lifecycle and FFmpeg progress in the settings page while a capture runs.
890918
fn recording_progress_label(
891919
recording_active: bool,
@@ -2088,9 +2116,10 @@ mod tests {
20882116
RecordingViewState, capture_command_label, capture_shortcut_summary,
20892117
history_clear_confirmation_label, history_entry_label, history_entry_matches,
20902118
history_result_summary, history_retention_label, history_visibility_label,
2091-
recording_progress_label, recording_status_visible, relative_timestamp_label,
2092-
settings_navigation_items, settings_page_copy, settings_page_intro, settings_path_label,
2093-
status_indicator_color, uses_compact_settings_navigation, visible_history_entries,
2119+
recording_progress_label, recording_source_discovery_busy, recording_status_visible,
2120+
recording_toggle_label, relative_timestamp_label, settings_navigation_items,
2121+
settings_page_copy, settings_page_intro, settings_path_label, status_indicator_color,
2122+
uses_compact_settings_navigation, visible_history_entries,
20942123
};
20952124
use crate::app::{HistoryClearScope, HistoryFilter, SettingsSection};
20962125
use crate::history::{HistoryEntry, HistorySource};
@@ -2262,18 +2291,37 @@ mod tests {
22622291
active: false,
22632292
starting: false,
22642293
stopping: true,
2294+
display_discovery_in_flight: false,
2295+
audio_discovery_in_flight: false,
22652296
paused: false,
22662297
progress: RecordingProgress::default(),
22672298
}));
22682299
assert!(!recording_status_visible(RecordingViewState {
22692300
active: false,
22702301
starting: false,
22712302
stopping: false,
2303+
display_discovery_in_flight: false,
2304+
audio_discovery_in_flight: false,
22722305
paused: false,
22732306
progress: RecordingProgress::default(),
22742307
}));
22752308
}
22762309

2310+
#[test]
2311+
fn recording_controls_explain_discovery_busy_state() {
2312+
let state = RecordingViewState {
2313+
active: false,
2314+
starting: false,
2315+
stopping: false,
2316+
display_discovery_in_flight: true,
2317+
audio_discovery_in_flight: false,
2318+
paused: false,
2319+
progress: RecordingProgress::default(),
2320+
};
2321+
assert!(recording_source_discovery_busy(state));
2322+
assert_eq!(recording_toggle_label(state), "Discovering...");
2323+
}
2324+
22772325
#[test]
22782326
fn settings_navigation_compacts_before_the_content_column_becomes_too_narrow() {
22792327
assert!(uses_compact_settings_navigation(639.0));

src/app/workflow.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ pub(super) use recognition::recognition_start_conflict_status;
2323
#[cfg(test)]
2424
use recording::{
2525
format_recording_progress, format_recording_stopping, next_recording_audio_selection,
26-
next_recording_display_selection, recording_start_conflict_status,
26+
next_recording_display_selection, recording_discovery_conflict_status,
27+
recording_discovery_result_is_applicable, recording_start_conflict_status,
2728
recording_start_failure_status, recording_support_status, recording_target_label,
2829
};
2930
pub(super) use recording::{recording_audio_selection_label, recording_display_selection_label};

src/app/workflow/capture.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,8 @@ impl FlashShotApp {
498498
};
499499
self.close_capture_overlays(cx);
500500
self.close_manual_scroll_window(cx);
501+
self.recording_audio_discovery_in_flight = false;
502+
self.recording_display_discovery_in_flight = false;
501503
self.return_to_background();
502504
cx.notify();
503505
}
@@ -533,6 +535,8 @@ impl FlashShotApp {
533535
self.recording_start_in_flight = false;
534536
self.recording_stopping = false;
535537
self.recording_paused = false;
538+
self.recording_audio_discovery_in_flight = false;
539+
self.recording_display_discovery_in_flight = false;
536540
// GPUI has already removed native windows before invoking on_app_quit.
537541
// Keeping the handles untouched avoids issuing late operations on closed HWNDs.
538542
log::info!(target: "flash_shot::lifecycle", "capture_workflow_shutdown");

0 commit comments

Comments
 (0)