mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
Co-authored-by: srt8hellcat2-ui <225485253+srt8hellcat2-ui@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
srt8hellcat2-ui
Brandon Thomas
parent
1af7a38bd0
commit
c1146d0d8d
@@ -2,6 +2,7 @@
|
||||
|
||||
**Stage: alpha** · next: beta, ~25% (ready for real work 50% → 75%) and ~400–700 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (M3 #2080 candidate: explicit search jumps join per-tab history; rail and control-channel Back/Forward; estimates unchanged)
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (M4 #1910: Organize Pages opens on the current page; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (M3 candidate: coordinate control clicks keep press/release together on slow frames and preserve text multi-click selection; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (M3 candidate: native opens retain chosen tools through password retries and defer startup intake while preparation is pending; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
@@ -156,6 +157,8 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
|
||||
## Progress log
|
||||
- **2026-10-13 (G14, Vietnamese localization):** Added Vietnamese (`vi`, Tiếng Việt) as an interface and date language with a 2,591-entry catalog (`plural_none`) covering 100% of the current localizable key union (plus `theme`/`Appearance`). Locale tags `vi` / `vi-VN` / `vi_VN.UTF-8` resolve; calendar months use numeric `Tháng 1`…`12` / `tháng 1`…`12`. Native-speaker community review recorded. The twelve-language localization mean moves from ≈59% to ≈68%; weighted readiness ≈49% → ≈50%. Status stays `partial`: engine/OS details, document contents, filenames, CLI/MCP and external help stay outside the catalog by design.
|
||||
|
||||
- **2026-10-14 (M3 #2080 candidate):** Existing per-tab page history gains explicit search jumps, rail buttons and control-channel commands. Continuous scrolling and automatic search refresh stay outside history; page-level restoration and estimates unchanged.
|
||||
|
||||
- **2026-10-13 (M2, page-unit transform precision):** Preserve small transform coefficients in page marks, added content and print output. Regression cases cover accepted page units of 4096 and 75000; existing page-unit interpretation and estimates are unchanged.
|
||||
- **2026-10-14 (M5, PR #1457 branch refresh):** Merged upstream main and reconciled roadmap entries while preserving comment submission shortcuts. Estimates unchanged.
|
||||
|
||||
|
||||
@@ -188,3 +188,19 @@ unapplied with a warning.
|
||||
`doc_batch` runs conversion, OCR and compression over files and optionally recursive
|
||||
folders. The CLI also exposes it as `pdfcraft-cli batch`. See the
|
||||
[batch guide](../../docs/cli-batch.md) for examples, output protection, errors and limits.
|
||||
|
||||
## Viewer navigation history
|
||||
|
||||
A running PdfCraft instance launched with `--control FILE` exposes its per-tab history through
|
||||
`pdfcraft-cli ui --control FILE command id=view.previous_view` and
|
||||
`pdfcraft-cli ui --control FILE command id=view.next_view`. `commands` reports whether each action
|
||||
is enabled for the active document. These are the same actions as the rail buttons, View → Page
|
||||
navigation, and Ctrl+[ / Ctrl+] (⌘[ / ⌘] on macOS).
|
||||
|
||||
Explicit page jumps, including search-result selection and Next/Previous match, enter history
|
||||
when the page changes. Automatic search refresh, continuous scrolling, and within-page scroll
|
||||
movement do not. Existing page-step commands and single-page wheel turns retain their history
|
||||
behavior. Back/Forward
|
||||
returns to page tops, does not restore zoom, does not switch documents, and is not saved across
|
||||
restarts. Standalone engine/MCP sessions have no viewport, so they do not have a separate
|
||||
navigation-history tool; use the opt-in UI control channel to navigate the displayed document.
|
||||
|
||||
@@ -222,6 +222,9 @@ pub const COMMANDS: &[CommandSpec] = &[
|
||||
// also run from Combine files, which has no document open.
|
||||
c("view.next_tab", "Next tab", VIEW, Some(Shortcut { command: false, shift: false, mac_ctrl: true, key: "Tab" }), Nothing, "chevron-right"),
|
||||
c("view.previous_tab", "Previous tab", VIEW, Some(Shortcut { command: false, shift: true, mac_ctrl: true, key: "Tab" }), Nothing, "chevron-left"),
|
||||
// View-local history also appears in the Page navigation section; don't duplicate its menu.
|
||||
c("view.previous_view", "Previous view", None, Some(Shortcut::cmd("[")), Document, "chevron-left"),
|
||||
c("view.next_view", "Next view", None, Some(Shortcut::cmd("]")), Document, "chevron-right"),
|
||||
c("view.focus_page_input", "Go to page…", VIEW, Some(Shortcut::cmd_shift("N")), Document, "text-cursor-input"),
|
||||
c("ui.dock.reset", "Reset panel layout", VIEW, None, Nothing, "settings"),
|
||||
c("view.theme", "Switch light / dark theme", None, None, Nothing, "moon"),
|
||||
|
||||
@@ -1005,6 +1005,8 @@ impl DocView {
|
||||
let Some(f) = self.find.as_mut() else { return };
|
||||
let Some(&(p, _)) = f.matches.get(i) else { return };
|
||||
f.current = Some(i);
|
||||
self.go_to_page(p);
|
||||
self.goto_point = None;
|
||||
self.flash_match(p);
|
||||
}
|
||||
|
||||
@@ -1019,6 +1021,9 @@ impl DocView {
|
||||
let next = if forward { (c + 1) % n } else { (c + n - 1) % n };
|
||||
f.current = Some(next);
|
||||
let p = f.matches[next].0;
|
||||
// Only explicit search navigation joins history; background refresh uses flash_match alone.
|
||||
self.go_to_page(p);
|
||||
self.goto_point = None;
|
||||
self.flash_match(p);
|
||||
}
|
||||
|
||||
@@ -4887,6 +4892,71 @@ trailer << /Root 1 0 R >>
|
||||
DocView::new(DocId(1), &info, ViewDefaults { layout, continuous, ..Default::default() })
|
||||
}
|
||||
|
||||
fn history_search_view() -> DocView {
|
||||
let mut v = view(5, PageLayout::Single, true);
|
||||
v.texts.insert(
|
||||
3,
|
||||
pdfcraft_render::PageText {
|
||||
glyphs: vec![pdfcraft_render::TextGlyph {
|
||||
text: "match".into(),
|
||||
rect: [20.0, 260.0, 60.0, 280.0],
|
||||
quad: [[20.0, 260.0], [60.0, 260.0], [60.0, 280.0], [20.0, 280.0]],
|
||||
direction: [1.0, 0.0],
|
||||
}],
|
||||
line_of: vec![0],
|
||||
space_before: vec![false],
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
v.find = Some(Find { matches: vec![(3, 0..1), (3, 0..1), (1, 0..1)], current: Some(2), ..Default::default() });
|
||||
v
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_search_jumps_record_history_and_keep_match_destination() {
|
||||
for step in [None, Some(true), Some(false)] {
|
||||
let mut v = history_search_view();
|
||||
v.goto_point = Some((3, Some(0.8), Some(0.9), Vec2::ZERO));
|
||||
if let Some(forward) = step {
|
||||
v.find_step(forward);
|
||||
} else {
|
||||
v.go_to_match(0);
|
||||
}
|
||||
assert_eq!(v.back, vec![0], "explicit search jump must remember its origin");
|
||||
assert_eq!(v.goto, Some((3, 0.5)), "keep the match offset instead of the page top");
|
||||
assert_eq!(v.page_input, "4");
|
||||
assert!(v.goto_point.is_none(), "an earlier link destination cannot override the search offset");
|
||||
v.go_to_match(1);
|
||||
assert_eq!(v.back, vec![0], "same-page matches do not duplicate history");
|
||||
assert!(v.view_history(false));
|
||||
assert_eq!(v.current, 0);
|
||||
assert!(v.view_history(true));
|
||||
assert_eq!(v.current, 3);
|
||||
assert!(v.view_history(false));
|
||||
v.go_to_match(2);
|
||||
assert!(v.forward.is_empty(), "a new explicit jump discards Forward");
|
||||
assert_eq!(v.back, vec![0]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn automatic_search_updates_do_not_record_history_or_discard_forward() {
|
||||
let mut v = history_search_view();
|
||||
v.forward = vec![4];
|
||||
v.find = Some(Find { query: "match".into(), ..Default::default() });
|
||||
v.rerun_find();
|
||||
assert_eq!(v.current, 3);
|
||||
assert_eq!(v.goto, Some((3, 0.5)));
|
||||
assert!(v.back.is_empty());
|
||||
assert_eq!(v.forward, vec![4]);
|
||||
v.current = 0;
|
||||
v.find.as_mut().unwrap().current = None;
|
||||
v.refresh_find_page(3);
|
||||
assert_eq!(v.current, 3);
|
||||
assert!(v.back.is_empty());
|
||||
assert_eq!(v.forward, vec![4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_layers_evict_far_pages_but_keep_visible_and_matches() {
|
||||
let info = thumbnail_info(100);
|
||||
|
||||
@@ -700,6 +700,16 @@ pub fn right_rail(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
|
||||
if icons::button(ui, "chevron-up", 30.0, false, tl!("Previous page")).clicked() {
|
||||
view.step_page(false);
|
||||
}
|
||||
// History is distinct from stepping to the adjacent page, and belongs to this tab.
|
||||
for (forward, icon, label, shortcut, enabled) in [
|
||||
(true, "chevron-right", tl!("Next view"), crate::commands::NEXT_VIEW, !view.forward.is_empty()),
|
||||
(false, "chevron-left", tl!("Previous view"), crate::commands::PREV_VIEW, !view.back.is_empty()),
|
||||
] {
|
||||
let tip = format!("{label} ({})", crate::commands::shortcut_label(ui.ctx(), shortcut));
|
||||
if ui.add_enabled_ui(enabled, |ui| icons::button(ui, icon, 30.0, false, &tip)).inner.clicked() {
|
||||
view.view_history(forward);
|
||||
}
|
||||
}
|
||||
ui.label(egui::RichText::new(page_count.to_string()).font(theme::regular(11.0)).color(t.text_muted));
|
||||
let page_input_id = view.page_input_id();
|
||||
// Consume replacement selection once, including when the editor has lost focus.
|
||||
|
||||
@@ -86,6 +86,11 @@ impl PdfCraftApp {
|
||||
}
|
||||
commands::is_enabled(spec, &self.session, self.active_ids().map(|(_, id)| id))
|
||||
&& (spec.needs != commands::Needs::TwoPageView || self.active.and_then(|i| self.views.get(i)).is_some_and(crate::DocView::cover_applies))
|
||||
&& match spec.id {
|
||||
"view.previous_view" => self.active.and_then(|i| self.views.get(i)).is_some_and(|v| !v.back.is_empty()),
|
||||
"view.next_view" => self.active.and_then(|i| self.views.get(i)).is_some_and(|v| !v.forward.is_empty()),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a registered command by id. Returns `false` when the id is unknown or the command
|
||||
@@ -267,6 +272,11 @@ impl PdfCraftApp {
|
||||
}
|
||||
}
|
||||
"view.read_mode" => self.switch_mode(if self.mode == Mode::Read { Mode::AllTools } else { Mode::Read }, false),
|
||||
"view.previous_view" | "view.next_view" => {
|
||||
if let Some(v) = active.and_then(|i| self.views.get_mut(i)) {
|
||||
return v.view_history(id == "view.next_view");
|
||||
}
|
||||
}
|
||||
"view.next_tab" => self.step_tab(true),
|
||||
"view.previous_tab" => self.step_tab(false),
|
||||
"view.theme" => {
|
||||
@@ -681,6 +691,10 @@ impl PdfCraftApp {
|
||||
specs.sort_by_key(|c| std::cmp::Reverse(c.shortcut.map(|s| s.modifier_count()).unwrap_or(0)));
|
||||
for spec in specs {
|
||||
let Some(s) = spec.shortcut else { continue };
|
||||
// Keep the canvas's silent no-op when there is no previous/next view to visit.
|
||||
if matches!(spec.id, "view.previous_view" | "view.next_view") && !self.command_enabled(spec) {
|
||||
continue;
|
||||
}
|
||||
if typing && !spec.in_text {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -148,6 +148,13 @@ fn every_registered_command_is_implemented() {
|
||||
if spec.id == "view.layout.cover" {
|
||||
app.set_option("layout", "two-up").unwrap();
|
||||
}
|
||||
// History commands need a real jump (and Back first to make Forward available).
|
||||
if matches!(spec.id, "view.previous_view" | "view.next_view") {
|
||||
app.views[0].go_to_page(1);
|
||||
if spec.id == "view.next_view" {
|
||||
assert!(app.views[0].view_history(false));
|
||||
}
|
||||
}
|
||||
let dir = std::env::temp_dir().join(format!("pdfcraft-cmd-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
app.save_override = Some(dir.join("out.pdf").to_string_lossy().into_owned());
|
||||
|
||||
@@ -1599,3 +1599,63 @@ fn page_size_shows_while_the_pointer_is_in_the_bottom_left_quadrant() {
|
||||
h.run_steps(3);
|
||||
assert!(!shown(&h), "pointer left: hidden again");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn navigation_history_commands_and_rail_are_per_tab_and_non_editing() {
|
||||
let (mut h, c) = harness();
|
||||
let enabled =
|
||||
|list: Value, id: &str| list["commands"].as_array().unwrap().iter().find(|cmd| cmd["id"] == id).unwrap()["enabled"].as_bool().unwrap();
|
||||
assert!(!enabled(ok(&mut h, &c, "ui.commands", json!({})), "view.previous_view"));
|
||||
assert!(call(&mut h, &c, "ui.command", json!({"id": "view.previous_view"})).is_err());
|
||||
// Empty-history shortcuts were silent before registry integration and must stay silent.
|
||||
for key in ["[", "]"] {
|
||||
ok(&mut h, &c, "ui.key", json!({"key": key, "modifiers": ["command"]}));
|
||||
assert!(h.state().toast.is_none(), "an unavailable history action must not ask to open the already-open document");
|
||||
}
|
||||
let before = h.state().session.get(h.state().views[0].id).unwrap().bytes.clone();
|
||||
ok(&mut h, &c, "ui.set", json!({"key": "page", "value": "4"}));
|
||||
assert!(enabled(ok(&mut h, &c, "ui.commands", json!({})), "view.previous_view"));
|
||||
// The real rail button is accessible and invokes the same history, not the adjacent page.
|
||||
ok(&mut h, &c, "ui.click", json!({"label": &format!("Previous view ({})", if cfg!(target_os = "macos") { "⌘[" } else { "Ctrl+[" })}));
|
||||
assert_eq!(h.state().views[0].current, 0);
|
||||
assert!(enabled(ok(&mut h, &c, "ui.commands", json!({})), "view.next_view"));
|
||||
ok(&mut h, &c, "ui.command", json!({"id": "view.next_view"}));
|
||||
assert_eq!(h.state().views[0].current, 3);
|
||||
ok(&mut h, &c, "ui.set", json!({"key": "page", "value": "5"}));
|
||||
ok(&mut h, &c, "ui.key", json!({"key": "[", "modifiers": ["command"]}));
|
||||
assert_eq!(h.state().views[0].current, 3, "shortcut traverses exactly one history entry");
|
||||
ok(&mut h, &c, "ui.key", json!({"key": "]", "modifiers": ["command"]}));
|
||||
assert_eq!(h.state().views[0].current, 4);
|
||||
ok(&mut h, &c, "ui.command", json!({"id": "view.previous_view"}));
|
||||
h.state_mut().open_bytes("other.pdf", None, fixture(5)).unwrap();
|
||||
assert!(!enabled(ok(&mut h, &c, "ui.commands", json!({})), "view.previous_view"));
|
||||
ok(&mut h, &c, "ui.set", json!({"key": "page", "value": "2"}));
|
||||
ok(&mut h, &c, "ui.command", json!({"id": "view.previous_view"}));
|
||||
assert_eq!(h.state().views[1].current, 0);
|
||||
assert_eq!(h.state().views[0].current, 3, "other tab's history does not move this document");
|
||||
h.state_mut().active = Some(0);
|
||||
ok(&mut h, &c, "ui.command", json!({"id": "view.previous_view"}));
|
||||
assert_eq!(h.state().views[0].current, 0);
|
||||
assert_eq!(h.state().session.get(h.state().views[0].id).unwrap().bytes, before);
|
||||
assert!(!h.state().session.get(h.state().views[0].id).unwrap().dirty);
|
||||
h.state_mut().open_bytes("reopened.pdf", None, before.as_ref().clone()).unwrap();
|
||||
assert!(h.state().views.last().unwrap().back.is_empty());
|
||||
assert!(h.state().views.last().unwrap().forward.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn routine_continuous_scrolling_does_not_add_navigation_history() {
|
||||
let (mut h, c) = harness_pages(20);
|
||||
ok(&mut h, &c, "ui.set", json!({"key": "animation_effects", "value": "off"}));
|
||||
h.hover_at(egui::pos2(700.0, 450.0));
|
||||
h.event(egui::Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Point,
|
||||
delta: egui::vec2(0.0, -1200.0),
|
||||
modifiers: egui::Modifiers::NONE,
|
||||
phase: egui::TouchPhase::Move,
|
||||
});
|
||||
h.run_steps(8);
|
||||
assert!(h.state().views[0].current > 0, "the wheel really moved to a later page");
|
||||
assert!(h.state().views[0].back.is_empty());
|
||||
assert!(h.state().views[0].forward.is_empty());
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-13 · **Change:** minor (M2/M6: spot-colour images convert once per pixel value; widget placement and the Fields panel scale with the field count; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (M3 candidate: coordinate control clicks on slow frames and coalesced text selection; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (M3 #2080 candidate: explicit search jumps join per-tab history; rail and control-channel Back/Forward; estimates unchanged)
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (M3 candidate: native opens retain chosen tools through password retries and defer startup intake while preparation is pending; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (M4 #1310 #1311 #1312: undo, revert and the next save keep the last saved revision; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (M3 #1980: exact quit modifiers preserve Ctrl+Alt+Q email input; M3 candidate: native opens retain chosen tools through password retries and defer startup intake while preparation is pending; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
@@ -101,6 +102,7 @@ total matches the parity estimate.
|
||||
| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel<br>Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). #1364 candidate: absolute local GoToR links ask before target access; headless callers explicitly opt in. Native-opening candidate: chosen tools survive preparation and password retries; startup intake waits for pending preparation. Later native-open replies stay queued while a modal or picker owns the foreground; regressions cover recipient ownership, completion/cancellation and one-shot wakes. |
|
||||
| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel<br>Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). #1364 candidate: absolute local GoToR links ask before target access; headless callers explicitly opt in. Native-opening candidate: chosen tools survive preparation and password retries; startup intake waits for pending preparation. #1980: exact quit modifiers preserve Ctrl+Alt+Q email input; Ctrl+Q/Cmd+Q asks before discarding unsaved documents. |
|
||||
| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel<br>Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). #1364 candidate: absolute local GoToR links ask before target access; headless callers explicitly opt in. Native-opening candidate: chosen tools survive preparation and password retries; startup intake waits for pending preparation.<br>Candidate: coordinate control clicks pair press/release across slow frames; coalesced primary presses retain text multi-click selection. Regression cases cover slow-frame button activation, disabled controls and text multi-click selection; estimates unchanged. |
|
||||
| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel<br>Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). #1364 candidate: absolute local GoToR links ask before target access; headless callers explicitly opt in. Native-opening candidate: chosen tools survive preparation and password retries; startup intake waits for pending preparation. #2080 candidate: search-result jumps enter per-tab history; rail and control-channel Back/Forward; no exact-view or restart persistence. |
|
||||
| M2 | Model, render, text | 200–350 | 15% | 130–230 | hayro bootstrap renderer (vendored patches); inspector predictor scratch is bounded by available input, and JPX sample rescaling handles 1–32 bits safely. Text extraction reaches word-F1 0.98 against pdftotext. Short same-line redraws split before placement rather than interleaving. A custom text match that ends on a reconstructed separator stops before the next glyph (#1315). Missing: model crate, fonts, DisplayList, renderer independent of hayro. Inspector page-label traversal and output are resource-bounded, with physical-number fallback warnings |
|
||||
| M2 | Model, render, text | 200–350 | 15% | 130–230 | hayro bootstrap renderer (vendored patches); Lab Bradford adaptation uses LMS white-point ratios, covered by independent neutral/chromatic and direct/Indexed render oracles; inspector predictor scratch is bounded by available input, and JPX sample rescaling handles 1–32 bits safely. Text extraction reaches word-F1 0.98 against pdftotext. Short same-line redraws split before placement rather than interleaving. Missing: model crate, fonts, DisplayList, renderer independent of hayro. Inspector page-label traversal and output are resource-bounded, with physical-number fallback warnings |
|
||||
| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel<br>Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). |
|
||||
|
||||
@@ -1164,10 +1164,10 @@ area = "B"
|
||||
tier = "P1"
|
||||
milestone = "M3"
|
||||
status = "shipped"
|
||||
commands = []
|
||||
commands = ["view.previous_view", "view.next_view"]
|
||||
tools = []
|
||||
evidence = ["crates/ui-egui/tests/viewer.rs::view_history_select_all_and_find_options"]
|
||||
notes = "⌘[ / ⌘] and View ▸ Page navigation"
|
||||
evidence = ["crates/ui-egui/tests/viewer.rs::view_history_select_all_and_find_options", "crates/ui-egui/tests/control.rs::navigation_history_commands_and_rail_are_per_tab_and_non_editing", "crates/ui-egui/tests/control.rs::routine_continuous_scrolling_does_not_add_navigation_history"]
|
||||
notes = "Per-tab page history: explicit search-result jumps, page navigation, bookmarks and links; no continuous-scroll history or restart persistence; existing page-step history is retained. Rail buttons, View ▸ Page navigation and Ctrl/⌘[ / Ctrl/⌘]. UI control ui.command exposes view.previous_view / view.next_view; standalone engine sessions have no viewport history. Restores page tops, not exact scroll/zoom positions."
|
||||
|
||||
[[feature]]
|
||||
id = "view.follow-links"
|
||||
|
||||
Reference in New Issue
Block a user