Merge pull request #1460 from lalomalvi:codex/missing-font-prompt-hardening-pr

This commit is contained in:
Hakan Seven committed 2026-09-24 15:01:55 +03:00
commit 9cf9184d9a
7 files changed
+114 -15

No files matched your search

+2
View File
@@ -426,8 +426,10 @@ impl OpenCADStudio {
pub(super) fn control_state(&self) -> Value {
let tab = &self.tabs[self.active_tab];
let command = tab.active_cmd.as_deref().map(active_command_metadata);
let missing_fonts = crate::io::font_repo::missing_shx_fonts(&tab.scene.document);
json!({"ok":true,"protocol":1,"session_id":session_id(),"version":env!("OCS_APP_VERSION"),
"mode":if self.main_window.is_some(){"gui"}else{"headless"},"enabled":self.control.enabled,
"read_only":self.read_only,"missing_fonts":missing_fonts,
"document_id":tab.id,"revision":tab.edit_revision,"geometry_revision":tab.scene.geometry_epoch,"camera_revision":tab.scene.camera_generation,
"hand_seed":format!("{:X}",tab.scene.document.header.handle_seed.max(tab.scene.document.next_handle())),
"plugins":plugin_ids(),
+12
View File
@@ -1258,6 +1258,15 @@ pub(super) struct OpenCADStudio {
/// Missing SHX fonts of the last opened drawing, offered for download
/// from the community repository (see `crate::io::font_repo`).
missing_fonts: Option<Vec<String>>,
/// Drawing that produced `missing_fonts`; the active tab may change while
/// a download is in flight.
missing_fonts_path: Option<PathBuf>,
/// Prevent duplicate download tasks and keep modal contents visible while
/// the background request is running.
missing_fonts_downloading: bool,
/// Fonts skipped or unavailable during this process lifetime. Reopening a
/// drawing must not repeatedly nag for the same unresolved file.
suppressed_missing_fonts: rustc_hash::FxHashSet<String>,
/// Drawings handed to us by other launches while `opening` was busy.
/// `opening` is a single slot that a second `OpenPathPicked` would
/// overwrite, and `on_file_opened` drops any result arriving once it is
@@ -4178,6 +4187,9 @@ impl OpenCADStudio {
open_job_serial: 0,
recovery_report: None,
missing_fonts: None,
missing_fonts_path: None,
missing_fonts_downloading: false,
suppressed_missing_fonts: rustc_hash::FxHashSet::default(),
pending_opens: std::collections::VecDeque::new(),
active_interaction_index: None,
queued_interaction_indices: std::collections::VecDeque::new(),
+12 -2
View File
@@ -1911,12 +1911,22 @@ impl OpenCADStudio {
// fetch them from the community repository before the user
// studies garbled substitute text (unless recovery already
// owns the modal slot).
let missing = crate::io::font_repo::missing_shx_fonts(
let mut missing = crate::io::font_repo::missing_shx_fonts(
&self.tabs[i].scene.document,
);
if !missing.is_empty() && self.check_missing_fonts {
missing.retain(|name| {
!self
.suppressed_missing_fonts
.contains(&crate::io::font_repo::font_key(name))
});
// Read-only/MCP evaluation sessions must remain non-blocking:
// report missing fonts through control state, but never offer
// a network/download mutation from a read-only launch.
if !missing.is_empty() && self.check_missing_fonts && !self.read_only {
self.font_source_input = self.font_source_url.clone();
self.missing_fonts = Some(missing);
self.missing_fonts_path = self.tabs[i].current_path.clone();
self.missing_fonts_downloading = false;
self.active_modal = Some(crate::app::ModalKind::MissingFonts);
}
}
+57 -7
View File
@@ -1180,13 +1180,20 @@ impl OpenCADStudio {
Task::none()
}
Message::MissingFontsDownload => {
if self.missing_fonts_downloading {
return Task::none();
}
// Remember the source across sessions — and across drawings.
let source = self.font_source_input.trim().to_string();
if self.font_source_url != source {
self.font_source_url = source.clone();
self.save_config();
}
let fonts = self.missing_fonts.take().unwrap_or_default();
let fonts = self.missing_fonts.clone().unwrap_or_default();
if fonts.is_empty() {
return Task::none();
}
self.missing_fonts_downloading = true;
Task::perform(
async move {
let source = crate::io::font_repo::FontSource::from_url(&source);
@@ -1196,31 +1203,66 @@ impl OpenCADStudio {
)
}
Message::MissingFontsDismiss => {
self.missing_fonts = None;
if self.missing_fonts_downloading {
return Task::none();
}
for name in self.missing_fonts.take().unwrap_or_default() {
self.suppressed_missing_fonts
.insert(crate::io::font_repo::font_key(&name));
}
self.missing_fonts_path = None;
self.close_active_modal();
Task::none()
}
Message::MissingFontsResult(result) => {
self.missing_fonts = None;
self.close_active_modal();
self.missing_fonts_downloading = false;
match result {
Ok(pairs) if pairs.is_empty() => {
let unavailable = self.missing_fonts.take().unwrap_or_default();
for name in &unavailable {
self.suppressed_missing_fonts
.insert(crate::io::font_repo::font_key(name));
}
self.missing_fonts_path = None;
self.close_active_modal();
self.command_line.push_error(crate::t!(
"None of the missing fonts are in the community repository yet. Contribute them at github.com/huaninstratech/OpenCADStudio/tree/main/fonts."
"None of the requested fonts are available from the selected source. Substitute fonts remain active; this notice will not repeat during this session."
).as_ref());
}
Ok(pairs) => {
let requested = self.missing_fonts.take().unwrap_or_default();
let downloaded: rustc_hash::FxHashSet<String> = pairs
.iter()
.map(|(name, _)| crate::io::font_repo::font_key(name))
.collect();
let unavailable: Vec<String> = requested
.into_iter()
.filter(|name| {
!downloaded.contains(&crate::io::font_repo::font_key(name))
})
.collect();
for name in &unavailable {
self.suppressed_missing_fonts
.insert(crate::io::font_repo::font_key(name));
}
for (name, path) in &pairs {
self.command_line.push_output(crate::tf!(
"FONT Downloaded {name} → {path}",
path = path.display()
).as_ref());
}
if !unavailable.is_empty() {
self.command_line.push_info(crate::tf!(
"FONT Not available from the selected source: {fonts}. Substitute fonts remain active.",
fonts = unavailable.join(", ")
).as_ref());
}
// The downloaded files change glyph resolution for the
// whole drawing — reload it through the standard open
// pipeline so every wire is rebuilt with the real fonts.
let i = self.active_tab;
if let Some(path) = self.tabs[i].current_path.clone() {
let path = self.missing_fonts_path.take();
self.close_active_modal();
if let Some(path) = path {
return Task::done(Message::OpenExternal(path));
}
self.command_line.push_info(crate::t!(
@@ -1228,6 +1270,8 @@ impl OpenCADStudio {
).as_ref());
}
Err(e) => {
// Keep the prompt open: the user can correct a custom
// source or retry a transient network failure.
self.command_line.push_error(crate::tf!("Font download failed: {e}").as_ref());
}
}
@@ -8043,6 +8087,12 @@ impl OpenCADStudio {
if self.active_modal == Some(super::ModalKind::RecoveryPrompt) {
return self.update(Message::RecoveryDecline);
}
// The title-bar X, Escape, and MCP `close_modal` action mean
// the same thing as Skip for this prompt. Record that choice
// so reopening the drawing does not immediately nag again.
if self.active_modal == Some(super::ModalKind::MissingFonts) {
return self.update(Message::MissingFontsDismiss);
}
// The Options window's × and Esc behave like its Close button.
if self.active_modal == Some(super::ModalKind::Options) {
return self.update(Message::OptionsClose);
+1
View File
@@ -1675,6 +1675,7 @@ impl OpenCADStudio {
crate::ui::window::missing_fonts::view_window(
fonts,
&font_source,
self.missing_fonts_downloading,
flow,
)
})