diff --git a/ROADMAP.md b/ROADMAP.md index 01b9dbe814..74d9241e1c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -219,6 +219,8 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md ## Progress log +- **2026-11-01 (M14, #5572):** Preferences ▸ Documents and view sets how many recent files to keep, from 0 to 50 with 12 by default. At 0 Home has no Recent section and File ▸ Open Recent is disabled. The list is cut when Preferences closes, so the number can pass through 0 on its way to another. + - 2026-11-01: PR #2155 further review preserves inherited Form text state and soft-mask chronology during Arrange, composes crop-group moves in the proper scope, and uses affine/press-relative image resize plus a shared rotation pivot. No-op script buttons preserve hidden source text; paste coordinates, visible selection ownership, and Snapshot/Export permissions have dedicated regressions. Candidate only; estimates unchanged. - 2026-11-01: PR #2155 latest follow-up handles explicit image-paste events without release-time guesses, refreshes the font-size field when a selection becomes mixed, and specifies mixed-script normalization with exact Undo restoration. Candidate only; estimates unchanged. diff --git a/apps/pdfcraft/src/main.rs b/apps/pdfcraft/src/main.rs index d2a75c5941..5bc5c5c372 100644 --- a/apps/pdfcraft/src/main.rs +++ b/apps/pdfcraft/src/main.rs @@ -10,7 +10,7 @@ //! --theme light|dark|system --language auto| --mode all|read|edit|convert|sign --tool --left open|closed //! --organize on --fields on --dialog properties|shortcuts|about --palette --home on //! --cover on|off --default-layout single|continuous|two-up|two-page --default-continuous on|off --default-zoom fit-width|fit-page| --always-default-view on|off -//! --page-gaps on|off --default-page-gaps on|off +//! --page-gaps on|off --default-page-gaps on|off --recent-files 0..50 //! --combine-view grid|list --combine-zoom 60..200` //! //! `--new-window` opens a window of its own. Without it, on Windows, a launch that only names files diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index abfecba94f..619f406cbf 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -118,6 +118,7 @@ impl PdfCraftApp { all_tools_query: String::new(), all_tools_tab: (None, false), recent: Vec::new(), + recent_limit: crate::RECENT_FILES_DEFAULT, reopen_last_session: false, restore_last_view: true, last_views: Vec::new(), diff --git a/crates/ui-egui/src/app/control.rs b/crates/ui-egui/src/app/control.rs index 85fdae38af..26e20f8df5 100644 --- a/crates/ui-egui/src/app/control.rs +++ b/crates/ui-egui/src/app/control.rs @@ -170,6 +170,12 @@ impl PdfCraftApp { _ => return Err("default-page-gaps must be on or off".into()), }; } + ("recent-files", _) => { + let max = crate::RECENT_FILES_MAX; + let limit = value.parse().ok().filter(|n| *n <= max).ok_or_else(|| format!("recent-files must be between 0 and {max}"))?; + self.recent_limit = limit; + self.recent.truncate(limit); + } ("cover", Some(v)) => { let on = match value { "on" => true, diff --git a/crates/ui-egui/src/app/files.rs b/crates/ui-egui/src/app/files.rs index 5ef110d6aa..29e6978461 100644 --- a/crates/ui-egui/src/app/files.rs +++ b/crates/ui-egui/src/app/files.rs @@ -333,7 +333,7 @@ impl PdfCraftApp { macos_association::repair(std::path::Path::new(&p)); self.recent.retain(|r| r.path != p); self.recent.insert(0, RecentFile { name: name.to_string(), path: p, pages, size }); - self.recent.truncate(12); + self.recent.truncate(self.recent_limit); } // What the form's scripts said while it opened (messages, errors) shows now, not with // the next edit. diff --git a/crates/ui-egui/src/app/prefs.rs b/crates/ui-egui/src/app/prefs.rs index 7d3fd6b948..c2a7de31dc 100644 --- a/crates/ui-egui/src/app/prefs.rs +++ b/crates/ui-egui/src/app/prefs.rs @@ -78,7 +78,7 @@ impl PdfCraftApp { let mut v = serde_json::json!({ "side_panels": self.side_panels.to_json(self.left_open), "docking_right": self.right, - "recent": self.recent, + "recent": self.recent.iter().take(self.recent_limit).collect::>(), "reopen_last_session": self.reopen_last_session, "restore_last_view": self.restore_last_view, "last_views": self.last_views_to_save(), @@ -139,6 +139,7 @@ impl PdfCraftApp { }); // Set apart: one more key inside the macro above passes serde_json's recursion limit. v["default_page_gaps"] = serde_json::Value::Bool(self.view_defaults.show_page_gaps); + v["recent_limit"] = serde_json::json!(self.recent_limit); v["fill_text_color"] = serde_json::json!(self.fill_text_color); v["redact_prefs"] = self.redact_defaults.to_json(); v["pinned_tools"] = serde_json::json!(self.pinned_tools); @@ -181,6 +182,11 @@ impl PdfCraftApp { let r: Vec = r.into_iter().filter(|f| std::path::Path::new(&f.path).exists()).collect(); self.recent = r; } + // Nonsense keeps the limit it had; settings from before the limit have none. + if let Some(limit) = v["recent_limit"].as_u64() { + self.recent_limit = usize::try_from(limit).map_or(crate::RECENT_FILES_MAX, |n| n.min(crate::RECENT_FILES_MAX)); + } + self.recent.truncate(self.recent_limit); self.restore_layout_prefs(&v); self.restore_behavior_prefs(&v); self.restore_view_prefs(&v); diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 6786ac6b0c..aec79cf4f5 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -1931,6 +1931,8 @@ fn finish_close(app: &mut PdfCraftApp, dialog: Dialog, modal_closed: bool, req: // Protect / Remove security replace the Properties dialog. let replaces = req.link_command.is_some_and(|c| c.starts_with("protect.")) || req.open_revision.is_some(); if modal_closed || req.close || replaces { + // Only now: the number in Preferences can pass through 0 on its way to the one wanted. + app.recent.truncate(app.recent_limit); app.dialog = None; app.props_draft = None; app.view_draft = None; diff --git a/crates/ui-egui/src/home.rs b/crates/ui-egui/src/home.rs index 957fe661fd..4f5ac9deba 100644 --- a/crates/ui-egui/src/home.rs +++ b/crates/ui-egui/src/home.rs @@ -186,25 +186,28 @@ pub fn show(app: &mut PdfCraftApp, ui: &mut egui::Ui) { crate::folders_ui::section(app, ui); } - ui.add_space(26.0); let mut clear = false; - ui.horizontal(|ui| { - ui.label(egui::RichText::new(tl!("Recent")).font(theme::semibold(17.0))); - if !app.recent.is_empty() { - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let resp = widgets::ghost_button(ui, "trash-2", tl!("Clear")); - resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, tl!("Clear Recent Files"))); - clear = resp.on_hover_text(tl!("Clear Recent Files")).clicked(); - }); + // Preferences can turn the list off (#5572). + if app.recent_limit > 0 { + ui.add_space(26.0); + ui.horizontal(|ui| { + ui.label(egui::RichText::new(tl!("Recent")).font(theme::semibold(17.0))); + if !app.recent.is_empty() { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let resp = widgets::ghost_button(ui, "trash-2", tl!("Clear")); + resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, tl!("Clear Recent Files"))); + clear = resp.on_hover_text(tl!("Clear Recent Files")).clicked(); + }); + } + }); + ui.add_space(8.0); + if app.recent.is_empty() { + ui.label(egui::RichText::new(tl!("Files you open in PdfCraft appear here. Drop a PDF anywhere to open it.")).color(t.text_muted)); } - }); - ui.add_space(8.0); - if app.recent.is_empty() { - ui.label(egui::RichText::new(tl!("Files you open in PdfCraft appear here. Drop a PDF anywhere to open it.")).color(t.text_muted)); } let mut open = None; let mut remove = None; - for r in &app.recent { + for r in app.recent.iter().take(app.recent_limit) { let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 46.0), Sense::click()); resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, &r.name)); let press = theme::Press::track(ui, &resp); diff --git a/crates/ui-egui/src/i18n/ar.tsv b/crates/ui-egui/src/i18n/ar.tsv index babaa33002..1b08929307 100644 --- a/crates/ui-egui/src/i18n/ar.tsv +++ b/crates/ui-egui/src/i18n/ar.tsv @@ -20,6 +20,10 @@ # reorders each line with UAX #9, so preordering here would reverse right-to-left text twice. # Spaces and a colon at either end of an entry stay at that end, next to what the code appends. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu القائمة File ملف Edit تحرير diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 48af0db09a..e269febf18 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -7,6 +7,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) stay in Latin letters; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + About За програмата Contributors Сътрудници Models Модели diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 54901bb5a4..7ff0f83fee 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -9,6 +9,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menú File Fitxer Edit Edita diff --git a/crates/ui-egui/src/i18n/ckb.tsv b/crates/ui-egui/src/i18n/ckb.tsv index 7138dc2c1f..3b6a7671ed 100644 --- a/crates/ui-egui/src/i18n/ckb.tsv +++ b/crates/ui-egui/src/i18n/ckb.tsv @@ -20,6 +20,10 @@ # reorders each line with UAX #9, so preordering here would reverse right-to-left text twice. # Spaces and a colon at either end of an entry stay at that end, next to what the code appends. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu پێڕست File فایل Edit دەستکاریکردن diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 51a569f4fc..22f6e3e056 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -19,6 +19,10 @@ # commands is kept. Messages with a count but no `@plural` entry use count-neutral wording # ("Vybráno stránek: {n}"), because Czech has three plural forms. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Nabídka File Soubor Rotation: Otočení: diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index e8e1f8b555..2c9ce7ae36 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -9,6 +9,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menü File Datei Edit Bearbeiten diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 4d9bd63fb0..d83bf4aef7 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -2,6 +2,11 @@ # Partial, clean-room initial contribution. Entries not yet translated fall back to English. # Translate from English meanings; retain placeholders and trailing ellipses. # Greek plural forms: one (1) | other (0, 2+). + +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Μενού File Αρχείο Edit Επεξεργασία diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 1a695aae4d..131d2dacaf 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -9,6 +9,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menú File Archivo Edit Edición diff --git a/crates/ui-egui/src/i18n/fa.tsv b/crates/ui-egui/src/i18n/fa.tsv index 1dcb047198..4f9286b843 100644 --- a/crates/ui-egui/src/i18n/fa.tsv +++ b/crates/ui-egui/src/i18n/fa.tsv @@ -19,6 +19,10 @@ # each rendered line. The Persian comma \u060c and question mark \u061f are used, and \u200c (ZWNJ) joins # compound words. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu منو File فایل Edit ویرایش diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 0ba2f47d06..8c1a9baab9 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -16,6 +16,11 @@ # Labels use sentence case. Product and technology names (PdfCraft, PDF, OCR, JavaScript ...) # stay in Latin letters; the trailing "…" of dialog commands is kept. Calendar headings use the # nominative month (`Lokakuu 2026`); dates use the partitive (`14. lokakuuta 2026`). + +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Valikko File Tiedosto Edit Muokkaa diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 613e0d7107..1c22cf9d81 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -9,6 +9,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menu File Fichier Edit Édition diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 3b38f82719..52c5450bb3 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -2,6 +2,11 @@ # context English source translation # Clean-room translation written from the English source's meaning. # License: MIT OR Apache-2.0. + +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu मेनू File फ़ाइल Edit संपादित करें diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 7d2f8fbf4c..47d9c24b40 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -9,6 +9,11 @@ # consulted or copied; add entries the same way. # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. + +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menü File Fájl Edit Szerkesztés diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index bb3a50830a..89152f62a4 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3,6 +3,11 @@ # Clean-room translations of PdfCraft's English UI labels. Use standard Indonesian computing # terms; keep product and format names (PdfCraft, PDF, PDF/A, OCR, XFA, Word...) unchanged. # Indonesian does not inflect nouns for number, so @plural entries list one form. + +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menu Edit Edit Pages Halaman diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index a43bbbff7e..0cae8f3e98 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -15,6 +15,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) stay in Latin letters; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menu File File Edit Modifica diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 879a68aff1..abc616b817 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -15,6 +15,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) stay in Latin letters; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu メニュー File ファイル Edit 編集 diff --git a/crates/ui-egui/src/i18n/kk.tsv b/crates/ui-egui/src/i18n/kk.tsv index 673ae57b31..fc6a2d783a 100644 --- a/crates/ui-egui/src/i18n/kk.tsv +++ b/crates/ui-egui/src/i18n/kk.tsv @@ -1,3 +1,7 @@ +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Мәзір File Файл Edit Түзету diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index c57b7e4cd2..20315b5fcf 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2,6 +2,11 @@ # context English source translation # Clean-room translation written from the English source's meaning. # License: MIT OR Apache-2.0. + +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu 메뉴 File 파일 Edit 편집 diff --git a/crates/ui-egui/src/i18n/lo.tsv b/crates/ui-egui/src/i18n/lo.tsv index 3be898ead2..ea684f6d59 100644 --- a/crates/ui-egui/src/i18n/lo.tsv +++ b/crates/ui-egui/src/i18n/lo.tsv @@ -15,6 +15,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) stay in Latin letters; # the trailing "..." of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu ເມນູ File ໄຟລ໌ Edit ແກ້ໄຂ diff --git a/crates/ui-egui/src/i18n/lt.tsv b/crates/ui-egui/src/i18n/lt.tsv index e6747a80aa..fd7f431154 100644 --- a/crates/ui-egui/src/i18n/lt.tsv +++ b/crates/ui-egui/src/i18n/lt.tsv @@ -4,6 +4,10 @@ # consult the Lietuvos Respublikos terminų bankas (terminai.vlkk.lt) when selecting terminology. # Product and format names (PdfCraft, PDF, PDF/A, OCR, XFA, Word…) remain unchanged. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Meniu Edit Redaguoti Pages Puslapiai diff --git a/crates/ui-egui/src/i18n/nb.tsv b/crates/ui-egui/src/i18n/nb.tsv index 447897dd34..b54b3e3526 100644 --- a/crates/ui-egui/src/i18n/nb.tsv +++ b/crates/ui-egui/src/i18n/nb.tsv @@ -7,6 +7,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Allow Tillat Cancel Avbryt Only allow this if you trust the source document. Tillat dette bare hvis du stoler på kildedokumentet. diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index e7e6b9d357..208dd1c61e 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -15,6 +15,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) stay in Latin letters; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menu File Plik Edit Edycja diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 3c4d0537cd..a172a03879 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -8,6 +8,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menu File Arquivo Rotation: Rotação: diff --git a/crates/ui-egui/src/i18n/ro.tsv b/crates/ui-egui/src/i18n/ro.tsv index cf6abda4b1..0d0fbf16b4 100644 --- a/crates/ui-egui/src/i18n/ro.tsv +++ b/crates/ui-egui/src/i18n/ro.tsv @@ -9,6 +9,10 @@ # the trailing "…" of dialog commands is kept. Commands use the noun form ("Salvare", "Deschidere…"), # messages address the user formally ("Alegeți", "dvs."). +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Meniu Edit Editare Pages Pagini diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 46ff335c81..b45d73ecab 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -8,6 +8,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Меню File Файл Edit Редактирование diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv index 928aec3a7b..c95f6d6bd6 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -8,6 +8,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Meni File Datoteka Edit Urejanje diff --git a/crates/ui-egui/src/i18n/sq.tsv b/crates/ui-egui/src/i18n/sq.tsv index de28ddf506..eb9eed3a0e 100644 --- a/crates/ui-egui/src/i18n/sq.tsv +++ b/crates/ui-egui/src/i18n/sq.tsv @@ -8,6 +8,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menyja File Skedari Edit Redakto diff --git a/crates/ui-egui/src/i18n/te.tsv b/crates/ui-egui/src/i18n/te.tsv index cac3c73204..9b81ffb8b9 100644 --- a/crates/ui-egui/src/i18n/te.tsv +++ b/crates/ui-egui/src/i18n/te.tsv @@ -12,6 +12,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) are not translated; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu మెనూ File ఫైల్ Edit సవరణ diff --git a/crates/ui-egui/src/i18n/th.tsv b/crates/ui-egui/src/i18n/th.tsv index 72fecec870..f7f8a65c5c 100644 --- a/crates/ui-egui/src/i18n/th.tsv +++ b/crates/ui-egui/src/i18n/th.tsv @@ -7,6 +7,10 @@ # names stay in Latin letters; the trailing ellipsis of dialog commands is kept. Thai has one # plural form. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu เมนู File ไฟล์ Edit แก้ไข diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 1dba482650..6921a6c33e 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -4,6 +4,10 @@ # # Clean-room: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Menü File Dosya Edit Düzen diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 297e1e7fa3..a824769600 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -18,6 +18,10 @@ # Integer plurals (CLDR uk): one (1, 21) | few (2–4, 22–24) | many (0, 5–19); 11–14 take many. # Commands use infinitives. Fixed-count templates use count-neutral wording. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Меню File Файл Edit Редагування diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 2a51854c07..e79a8aacc4 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -7,6 +7,11 @@ # Plural rule: `plural_none` — every @plural row lists exactly one form. # Context-sensitive wording and intentional unchanged strings are regression-tested in i18n/mod.rs. # + +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu Trình đơn Edit Chỉnh sửa Pages Trang diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 1425da8d0d..96e9c3bb75 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -8,6 +8,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) stay in Latin letters; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + # About, contributor/model credits, and multiline interface guidance. About 关于 Contributors 贡献者 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 644ac99d54..a1b933b830 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -8,6 +8,10 @@ # Product and technology names (PdfCraft, PDF, Acrobat JavaScript ...) stay in Latin letters; # the trailing "…" of dialog commands is kept. +# Untranslated: English text until a translator fills these in. + Lowering this forgets the oldest files. At 0 there is no list. Lowering this forgets the oldest files. At 0 there is no list. + Recent files to keep Recent files to keep + Menu 選單 File 檔案 Edit 編輯 diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 37e8e53165..44059ee4aa 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -145,7 +145,7 @@ use state::CopyPage; use state::OpenRequest; pub use state::{ CustomDraft, Dialog, DuplicateDraft, LeftPanel, LinkOrigin, Mode, NumberDraft, OpenOutcome, PageClip, PagePropsDraft, PasswordPrompt, - PendingLink, PendingRemoteLink, PropsTab, QuickTool, RecentFile, RightPanel, UnlockPrompt, + PendingLink, PendingRemoteLink, PropsTab, QuickTool, RECENT_FILES_DEFAULT, RECENT_FILES_MAX, RecentFile, RightPanel, UnlockPrompt, }; use state::{DocumentCopyJob, GridFile}; pub use tools::bulk_fields; @@ -346,6 +346,8 @@ pub struct PdfCraftApp { /// tab coming to the front (Home, a document, Combine files) puts the view away. all_tools_tab: (Option, bool), pub recent: Vec, + /// How many recent files to keep, 0 for no list (Preferences ▸ Documents and view, #5572). + pub recent_limit: usize, /// Preferences: reopen the files that were open when PdfCraft last closed (#442). pub reopen_last_session: bool, /// Restore last view settings when reopening documents (Preferences ▸ Documents and view; diff --git a/crates/ui-egui/src/persist/last_session.rs b/crates/ui-egui/src/persist/last_session.rs index 7bc4833c0a..b46de0de7a 100644 --- a/crates/ui-egui/src/persist/last_session.rs +++ b/crates/ui-egui/src/persist/last_session.rs @@ -98,7 +98,7 @@ impl PdfCraftApp { /// the Recovery dialog offers, so neither opens twice. /// /// Only one password prompt can wait at a time: the first encrypted file asks for its - /// password and later ones stay closed (they are in the recent-files list). + /// password and later ones stay closed (in the recent-files list, if there is one). pub fn reopen_last_files(&mut self, skip: &[String]) { let last = std::mem::take(&mut self.last_session); #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index eb8c3a0e8d..e3a9c540a6 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -365,6 +365,11 @@ impl UnlockPrompt { } } +/// How many recent files are kept until Preferences says otherwise. +pub const RECENT_FILES_DEFAULT: usize = 12; +/// The most Preferences can ask to keep. +pub const RECENT_FILES_MAX: usize = 50; + #[derive(serde::Serialize, serde::Deserialize)] pub struct RecentFile { pub name: String, diff --git a/crates/ui-egui/src/workflows/js_ui.rs b/crates/ui-egui/src/workflows/js_ui.rs index c876992785..4c99ee1db8 100644 --- a/crates/ui-egui/src/workflows/js_ui.rs +++ b/crates/ui-egui/src/workflows/js_ui.rs @@ -492,6 +492,15 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok } }); ui.label(egui::RichText::new(tl!("For page sizes and margins.")).small().color(t.text_muted)); + // The web build has no file paths to list. + #[cfg(not(target_arch = "wasm32"))] + { + ui.horizontal(|ui| { + let label = ui.label(tl!("Recent files to keep")); + ui.add(egui::DragValue::new(&mut app.recent_limit).range(0..=crate::RECENT_FILES_MAX)).labelled_by(label.id); + }); + ui.label(egui::RichText::new(tl!("Lowering this forgets the oldest files. At 0 there is no list.")).small().color(t.text_muted)); + } // The web build has no file paths to reopen. #[cfg(not(target_arch = "wasm32"))] ui.add(widgets::checkbox(&mut app.reopen_last_session, tl!("Reopen the files that were open when PdfCraft last closed"))); diff --git a/crates/ui-egui/tests/it/main.rs b/crates/ui-egui/tests/it/main.rs index 5189548462..bce7c4d069 100644 --- a/crates/ui-egui/tests/it/main.rs +++ b/crates/ui-egui/tests/it/main.rs @@ -67,6 +67,7 @@ mod prepare; mod print; mod protect; mod reading_colors; +mod recent_files; mod recovery; mod redact; mod reload; diff --git a/crates/ui-egui/tests/it/recent_files.rs b/crates/ui-egui/tests/it/recent_files.rs new file mode 100644 index 0000000000..b4eccfb10e --- /dev/null +++ b/crates/ui-egui/tests/it/recent_files.rs @@ -0,0 +1,257 @@ +//! Preferences ▸ Recent files to keep (#5572): how many files the recent list holds, from none +//! up to 50, on every path that adds to it, shows it or saves it. + +use egui::Key; +use egui::accesskit::Role; +use egui_kittest::Harness; +use egui_kittest::kittest::Queryable; +use pdfcraft_ui_egui::{Dialog, PdfCraftApp, RECENT_FILES_DEFAULT, RecentFile}; + +use crate::support; + +const PDF: &[u8] = include_bytes!("../data/form.pdf"); + +fn recent_file(name: &str) -> RecentFile { + RecentFile { name: name.into(), path: format!("/nowhere/{name}"), pages: 1, size: 0 } +} + +fn names(app: &PdfCraftApp) -> Vec<&str> { + app.recent.iter().map(|r| r.name.as_str()).collect() +} + +fn app_with(files: &[&str]) -> PdfCraftApp { + let mut app = PdfCraftApp::new(); + app.set_option("language", "en").unwrap(); + app.recent.extend(files.iter().map(|n| recent_file(n))); + app +} + +/// Tall enough that the whole of Home is on screen without scrolling. +fn harness(limit: usize, files: &[&str]) -> Harness<'static, PdfCraftApp> { + let files: Vec = files.iter().map(ToString::to_string).collect(); + let mut h = Harness::builder().with_size(egui::vec2(1400.0, 1600.0)).build_eframe(move |_cc| { + let mut app = app_with(&files.iter().map(String::as_str).collect::>()); + app.recent_limit = limit; + app + }); + h.run_steps(4); + h +} + +/// Open a document with a file behind it, as a file on disk is recorded. +fn open(app: &mut PdfCraftApp, name: &str) { + app.open_bytes(name, Some(format!("/nowhere/{name}")), PDF.to_vec()).unwrap(); +} + +/// Typed the way a person does: click the field, type, Enter. +fn type_limit(h: &mut Harness<'static, PdfCraftApp>, value: &str) { + h.get_by_role_and_label(Role::SpinButton, "Recent files to keep").click(); + h.run_steps(2); + h.key_press_modifiers(egui::Modifiers::COMMAND, Key::A); + h.run_steps(1); + h.event(egui::Event::Text(value.into())); + h.run_steps(1); + h.key_press(Key::Enter); + h.run_steps(3); +} + +#[test] +fn the_list_keeps_twelve_files_until_preferences_says_otherwise() { + let mut app = app_with(&[]); + assert_eq!(app.recent_limit, 12); + for n in 0..=RECENT_FILES_DEFAULT { + open(&mut app, &format!("{n}.pdf")); + } + assert_eq!(app.recent.len(), 12); + assert_eq!(app.recent[0].name, "12.pdf", "newest first"); + assert_eq!(app.recent[11].name, "1.pdf", "the oldest of the thirteen went"); +} + +#[test] +fn opening_a_file_keeps_the_newest_up_to_the_limit() { + let mut app = app_with(&[]); + app.recent_limit = 2; + for name in ["first.pdf", "second.pdf", "third.pdf"] { + open(&mut app, name); + } + assert_eq!(names(&app), ["third.pdf", "second.pdf"]); + open(&mut app, "second.pdf"); + assert_eq!(names(&app), ["second.pdf", "third.pdf"], "a file opened again moves up and loses nothing"); +} + +#[test] +fn at_zero_opening_a_file_records_nothing() { + let mut app = app_with(&[]); + app.recent_limit = 0; + open(&mut app, "first.pdf"); + assert!(app.recent.is_empty()); + let saved: serde_json::Value = serde_json::from_str(&app.persist()).unwrap(); + assert_eq!(saved["recent"], serde_json::json!([]), "and the saved list is empty"); +} + +#[test] +fn at_zero_home_has_no_recent_section() { + let h = harness(0, &[]); + assert!(h.query_by_label("Recent").is_none(), "no heading"); + assert!(h.query_by_label_contains("Files you open in PdfCraft appear here").is_none(), "and no promise of a list"); + h.get_by_label_contains("PdfCraft works offline"); +} + +#[test] +fn above_zero_an_empty_list_still_says_what_it_is_for() { + let h = harness(1, &[]); + h.get_by_label("Recent"); + h.get_by_label_contains("Files you open in PdfCraft appear here"); +} + +#[test] +fn at_zero_the_menu_and_the_palette_have_nothing_to_open() { + let mut h = harness(0, &[]); + support::open_menu(&mut h, "File"); + assert!(h.query_by(|n| n.label().as_deref() == Some("Open Recent") && n.is_disabled()).is_some(), "Open Recent is disabled"); + assert!(h.state_mut().execute("file.open_recent")); + assert!(h.state().toast.as_ref().is_some_and(|(m, _)| m.contains("No recent files"))); + assert!(h.state().views.is_empty(), "and nothing opened"); +} + +#[test] +fn the_longest_list_scrolls_in_the_smallest_window_down_to_clear() { + let files: Vec = (0..50).map(|n| format!("{n}.pdf")).collect(); + let mut h = Harness::builder().with_size(egui::vec2(1000.0, 520.0)).build_eframe({ + let files = files.clone(); + move |_cc| { + let mut app = app_with(&files.iter().map(String::as_str).collect::>()); + app.recent_limit = 50; + app.open_bytes("doc.pdf", None, PDF.to_vec()).unwrap(); + app + } + }); + h.run_steps(4); + support::open_menu(&mut h, "File"); + support::menu_row(&h, "Open Recent").hover(); + h.run_steps(3); + let foot = |h: &Harness<'static, PdfCraftApp>| support::menu_row(h, "Clear Recent Files").rect().max.y; + assert!(foot(&h) > 520.0, "fifty rows run past the smallest window"); + support::menu_row(&h, "1.pdf").hover(); + h.run_steps(2); + h.event(egui::Event::MouseWheel { + unit: egui::MouseWheelUnit::Point, + delta: egui::vec2(0.0, -5000.0), + phase: egui::TouchPhase::Move, + modifiers: egui::Modifiers::NONE, + source: egui::MouseWheelSource::Unknown, + }); + h.run_steps(8); + assert!(foot(&h) <= 520.0, "the wheel brings the foot of the menu into view"); + support::menu_row(&h, "Clear Recent Files").click(); + h.run_steps(3); + assert!(h.state().recent.is_empty()); +} + +#[test] +fn preferences_cuts_the_list_when_it_closes_and_not_before() { + let mut h = harness(12, &["first.pdf", "second.pdf", "third.pdf"]); + h.state_mut().dialog = Some(Dialog::Preferences); + h.run_steps(4); + type_limit(&mut h, "0"); + assert_eq!(h.state().recent_limit, 0); + assert_eq!(h.state().recent.len(), 3, "0 on the way to another number forgets nothing"); + assert!(h.query_by_label("first.pdf").is_none(), "though Home already leaves them out"); + type_limit(&mut h, "2"); + assert_eq!(h.state().recent.len(), 3); + h.get_by_label("OK").click(); + h.run_steps(4); + assert_eq!(h.state().dialog, None); + assert_eq!(names(h.state()), ["first.pdf", "second.pdf"], "the newest two stay"); +} + +#[test] +fn escape_closes_preferences_with_the_same_result_as_ok() { + let mut h = harness(12, &["first.pdf", "second.pdf"]); + h.state_mut().dialog = Some(Dialog::Preferences); + h.run_steps(4); + type_limit(&mut h, "1"); + h.key_press(Key::Escape); + h.run_steps(4); + assert_eq!(h.state().dialog, None); + assert_eq!(names(h.state()), ["first.pdf"]); +} + +#[test] +fn the_field_does_not_go_past_fifty() { + let mut h = harness(12, &[]); + h.state_mut().dialog = Some(Dialog::Preferences); + h.run_steps(4); + type_limit(&mut h, "999"); + assert_eq!(h.state().recent_limit, 50); +} + +#[test] +fn settings_saved_while_preferences_is_open_already_leave_the_rest_out() { + let mut app = app_with(&["first.pdf", "second.pdf"]); + app.recent_limit = 1; + let saved = app.persist(); + assert!(saved.contains("first.pdf")); + assert!(!saved.contains("second.pdf")); +} + +#[test] +fn the_limit_is_saved_and_restored_and_cuts_a_longer_list() { + let dir = std::env::temp_dir().join(format!("pdfcraft-recent-limit-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let paths: Vec = ["a.pdf", "b.pdf", "c.pdf"] + .iter() + .map(|n| { + let path = dir.join(n); + std::fs::write(&path, PDF).unwrap(); + path.to_string_lossy().into_owned() + }) + .collect(); + let entries: Vec = paths.iter().map(|p| RecentFile { name: "x.pdf".into(), path: p.clone(), pages: 1, size: 0 }).collect(); + let json = serde_json::json!({ "recent": entries, "recent_limit": 2 }).to_string(); + let mut restored = PdfCraftApp::new(); + restored.restore(&json); + assert_eq!(restored.recent_limit, 2); + let kept: Vec<&str> = restored.recent.iter().map(|r| r.path.as_str()).collect(); + assert_eq!(kept, [paths[0].as_str(), paths[1].as_str()]); + let mut again = PdfCraftApp::new(); + again.restore(&restored.persist()); + assert_eq!(again.recent_limit, 2); + assert_eq!(again.recent.len(), 2); + std::fs::remove_dir_all(&dir).unwrap(); +} + +#[test] +fn a_settings_file_can_not_ask_for_a_limit_that_is_not_one() { + for (saved, want) in [ + ("{}", 12), + ("{\"recent_limit\": null}", 12), + ("{\"recent_limit\": \"3\"}", 12), + ("{\"recent_limit\": -1}", 12), + ("{\"recent_limit\": 2.5}", 12), + ("{\"recent_limit\": 0}", 0), + ("{\"recent_limit\": 50}", 50), + ("{\"recent_limit\": 51}", 50), + ("{\"recent_limit\": 18446744073709551615}", 50), + ] { + let mut app = PdfCraftApp::new(); + app.restore(saved); + assert_eq!(app.recent_limit, want, "{saved}"); + } +} + +#[test] +fn the_control_channel_sets_the_limit_and_refuses_what_it_can_not_be() { + let mut app = app_with(&["first.pdf", "second.pdf"]); + for bad in ["51", "-1", "x", "", "1.5"] { + let error = app.set_option("recent-files", bad).unwrap_err(); + assert!(error.contains("between 0 and 50"), "{bad}: {error}"); + } + assert_eq!((app.recent_limit, app.recent.len()), (12, 2), "a refused value changes nothing"); + app.set_option("recent-files", "50").unwrap(); + assert_eq!((app.recent_limit, app.recent.len()), (50, 2)); + app.set_option("recent-files", "1").unwrap(); + assert_eq!(names(&app), ["first.pdf"], "cut at once, with no dialog to wait for"); + app.set_option("recent-files", "0").unwrap(); + assert!(app.recent.is_empty()); +} diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 8035e3873d..af616e7758 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -9957,11 +9957,11 @@ title = "Preferences: documents (autosave interval, recent count)" area = "N" tier = "P1" milestone = "M14" -status = "planned" +status = "partial" commands = [] tools = [] -evidence = [] -notes = "" +evidence = ["crates/ui-egui/tests/it/recent_files.rs::preferences_cuts_the_list_when_it_closes_and_not_before", "crates/ui-egui/tests/it/recent_files.rs::at_zero_home_has_no_recent_section", "crates/ui-egui/tests/it/recent_files.rs::a_settings_file_can_not_ask_for_a_limit_that_is_not_one"] +notes = "Preferences ▸ Documents and view: Recent files to keep, from 0 to 50 with 12 by default (#5572). At 0 Home has no Recent section and File ▸ Open Recent is disabled; Restore the last view keeps its own record of paths under its own switch. Still missing: the autosave interval (recovery snapshots run every AUTOSAVE_SECS, 60 s) and the rest of Acrobat's Documents page." [[feature]] id = "misc.prefs-commenting"