Merge PR #2468: Print: Japanese photo and postcard paper sizes (L判・2L・KG・はがき

# Conflicts:
#	docs/gaps.md
#	docs/parity-checklist.md
This commit is contained in:
francoisgn
2026-10-22 22:28:41 +03:00
6 changed files with 194 additions and 8 deletions
+7 -1
View File
@@ -2,7 +2,7 @@
**Stage: alpha** · next: beta, ~14 points and ~340–600 h away
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** minor (Minolta MRW decoded, 6 bodies verified; RAW: 83% of Adobe's models decode, 38% verified after the camera-coverage sweep #1327; German catalog 91% → 97%: interface strings and What's New lines translated) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (Print: Japanese photo and postcard paper sizes — L判, 2L判, KG判, はがき, 往復はがき, 六切, 四切, 名刺; Minolta MRW decoded, 6 bodies verified; RAW: 83% of Adobe's models decode, 38% verified after the camera-coverage sweep #1327; German catalog 91% → 97%: interface strings and What's New lines translated) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
One-page summary of how close LightCraft is to Adobe Lightroom. Agents: read this, then pick work
from [docs/gaps.md](docs/gaps.md). The full assessment, method and evidence are in
@@ -114,6 +114,11 @@ Ranked toward beta; detail in [docs/roadmap.md](docs/roadmap.md), gaps in [docs/
## Progress log
- 2026-10-22: Print page setup and PDF output gain the Japanese photo and postcard papers — L判
(89 × 127 mm), 2L判 (127 × 178), KG判 (102 × 152), はがき (100 × 148), 往復はがき (148 × 200),
六切 (203 × 254), 四切 (254 × 305) and 名刺 (55 × 91) — at their exact metric sizes. The Print dialog
and `print.set` take their customary names, and names are matched case-, space- and
punctuation-insensitively (`L判`, `l`, `L-size` are one paper).
- 2026-10-14: Camera-coverage sweep verifies 30 more models on 40 CC0 files, with file and sensor
hashes pinned (`docs/raw/coverage.py`: 515 → 545 verified). Eleven candidates excluded after rechecking
identity, provenance and camera-image agreement. Phone and drone DNG tone differences remain tracked in #1277.
@@ -186,6 +191,7 @@ Ranked toward beta; detail in [docs/roadmap.md](docs/roadmap.md), gaps in [docs/
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | minor | Print: Japanese photo and postcard paper sizes (L判, 2L判, KG判, はがき, 往復はがき, 六切, 四切, 名刺) in the page setup, Print dialog and PDF output; headline numbers unchanged |
| 2026-10-19 | minor | Indonesian interface (`id`): every plain and formats message, plus 69 command labels, locale, menu command and tests; What's New left in English; Localization ~55% → ~64% (mean of the 12 key languages), 8 of 12 present |
| 2026-10-12 | minor | Italian interface (`it`, #1192): 2,201 of 2,398 messages, locale, menu command and tests; headline numbers unchanged |
| 2026-10-13 | minor | Sony ILCA-99M2 camera colour profile in the log; numbers unchanged |
+126 -3
View File
@@ -46,6 +46,41 @@ const PAPER: &[(&str, f64, f64)] = &[
("5x7", 5.0, 7.0),
("8x10", 8.0, 10.0),
("13x19", 13.0, 19.0),
// Japanese photo printing and postcard sizes, at their exact metric dimensions (the US photo
// sizes above are close but not identical: L is 89 x 127 mm, 5x7 in is 127 x 177.8 mm).
("l", 89.0 / 25.4, 127.0 / 25.4),
("2l", 127.0 / 25.4, 178.0 / 25.4),
("kg", 102.0 / 25.4, 152.0 / 25.4),
("postcard", 100.0 / 25.4, 148.0 / 25.4),
("double-postcard", 148.0 / 25.4, 200.0 / 25.4),
("6cut", 203.0 / 25.4, 254.0 / 25.4),
("4cut", 254.0 / 25.4, 305.0 / 25.4),
("business-card", 55.0 / 25.4, 91.0 / 25.4),
];
/// Japanese customary names (and a few English spellings) for the papers in [`PAPER`], matched
/// after [`normalize_paper`]: `L判` is `l`, `はがき` is `postcard`, `六切` is `6cut`, and so on.
const PAPER_ALIASES: &[(&str, &str)] = &[
("L判", "l"),
("Lサイズ", "l"),
("L-size", "l"),
("2L判", "2l"),
("2Lサイズ", "2l"),
("2L-size", "2l"),
("KG判", "kg"),
("KGサイズ", "kg"),
("KG-size", "kg"),
("はがき", "postcard"),
("ハガキ", "postcard"),
("葉書", "postcard"),
("ポストカード", "postcard"),
("往復はがき", "double-postcard"),
("往復ハガキ", "double-postcard"),
("六切", "6cut"),
("6切", "6cut"),
("四切", "4cut"),
("4切", "4cut"),
("名刺", "business-card"),
];
/// Names accepted by [`PageSetup::paper`] (besides `custom`).
@@ -53,10 +88,39 @@ pub fn paper_names() -> impl Iterator<Item = &'static str> {
PAPER.iter().map(|p| p.0)
}
/// Portrait size in inches of a named paper (case-insensitive), `None` for unknown names and `custom`.
/// The form [`paper_name`] matches names in: lower case, letters and digits only, and `×`/`*` read
/// as `x`, so `L判`, `L判。`, `l-size` and `(l)` are one name and `4 × 6` is `4x6`.
fn normalize_paper(name: &str) -> String {
name.chars()
.filter_map(|c| match c {
'×' | '*' => Some('x'),
c if c.is_alphanumeric() => Some(c),
_ => None,
})
.collect::<String>()
.to_lowercase()
}
/// The canonical name of the paper `name` stands for, `None` for unknown names and `custom`.
/// `L判` is `l`, `はがき` is `postcard`, `六切` is `6cut`, and so on.
pub fn paper_name(name: &str) -> Option<&'static str> {
let n = normalize_paper(name);
if n.is_empty() {
return None;
}
PAPER
.iter()
.find(|p| normalize_paper(p.0) == n)
.map(|p| p.0)
.or_else(|| PAPER_ALIASES.iter().find(|(alias, _)| normalize_paper(alias) == n).map(|(_, name)| *name))
}
/// Portrait size in inches of a named paper, `None` for unknown names and `custom`. Case, spaces
/// and punctuation don't matter, and the Japanese customary names (`L判`, `2L判`, `KG判`, `はがき`,
/// `往復はがき`, `六切`, `四切`, `名刺`) are aliases of the papers above.
pub fn paper_size(name: &str) -> Option<(f64, f64)> {
let name = name.to_ascii_lowercase();
PAPER.iter().find(|p| p.0 == name).map(|p| (p.1, p.2))
let canonical = paper_name(name)?;
PAPER.iter().find(|p| p.0 == canonical).map(|p| (p.1, p.2))
}
/// An axis-aligned rectangle in inches.
@@ -935,6 +999,65 @@ mod tests {
assert!(p.size().is_err());
}
/// Japanese photo and postcard papers: exact metric sizes to 0.1 pt, the customary names as
/// aliases, and the ISO/US papers around them unchanged.
#[test]
fn japanese_photo_and_postcard_papers() {
let pt = |name: &str| {
let p = PageSetup { paper: name.into(), ..Default::default() };
p.size_points().unwrap_or_else(|e| panic!("{name}: {e}"))
};
for (name, w_mm, h_mm) in [
("L判", 89.0, 127.0),
("2L判", 127.0, 178.0),
("KG判", 102.0, 152.0),
("はがき", 100.0, 148.0),
("往復はがき", 148.0, 200.0),
("六切", 203.0, 254.0),
("四切", 254.0, 305.0),
("名刺", 55.0, 91.0),
] {
let (w, h) = pt(name);
let want = (w_mm / 25.4 * 72.0, h_mm / 25.4 * 72.0);
assert!((w - want.0).abs() < 0.1 && (h - want.1).abs() < 0.1, "{name}: {w} x {h} pt, wanted {want:?}");
assert!((w - h).abs() > 1e-6, "{name} is portrait");
}
// The customary and English spellings resolve to the same paper, including spaces and case.
for (a, b) in [
("L判", "l"),
("L判", " L "),
("L判", "L-size"),
("2L判", "2l"),
("KG判", "kg"),
("はがき", "postcard"),
("はがき", "ハガキ"),
("往復はがき", "double-postcard"),
("六切", "6cut"),
("六切", "6切"),
("四切", "4cut"),
("名刺", "business-card"),
("A4", "a 4"),
("4 × 6", "4x6"),
] {
assert_eq!(paper_size(a), paper_size(b), "{a} vs {b}");
}
// Punctuation is ignored, including the periods and Japanese marks a pasted name may carry.
for (name, canonical) in [("L判。", "l"), ("L判、", "l"), ("(L判)", "l"), ("l-size.", "l"), ("はがき。", "postcard"), ("6切。", "6cut")]
{
assert_eq!(paper_name(name), Some(canonical), "{name}");
}
assert_eq!(paper_name("custom"), None);
assert!(paper_size("poster").is_none());
// The papers that were already there keep their sizes.
assert_eq!(pt("letter"), (612.0, 792.0));
assert_eq!(pt("4x6"), (288.0, 432.0));
assert_eq!(pt("5x7"), (360.0, 504.0));
assert_eq!(pt("8x10"), (576.0, 720.0));
assert_eq!(pt("13x19"), (936.0, 1368.0));
let (w, h) = pt("A4");
assert!((w - 595.28).abs() < 0.01 && (h - 841.89).abs() < 0.01, "A4 {w} x {h}");
}
#[test]
fn hostile_numbers_are_rejected() {
for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -1.0, 1e9] {
+29 -1
View File
@@ -26,7 +26,13 @@ fn get_set_and_reset() {
assert_eq!(g["pageWidth"], 8.5);
assert_eq!(g["printable"]["x"], 0.25);
assert_eq!(g["settings"]["job"]["ppi"], 240.0);
assert!(g["papers"].as_array().unwrap().iter().any(|p| p["name"] == "a4"));
let papers = g["papers"].as_array().unwrap();
assert!(papers.iter().any(|p| p["name"] == "a4"));
// The Japanese photo and postcard papers are listed with their metric sizes in inches.
let paper = |name: &str| papers.iter().find(|p| p["name"] == name).cloned().unwrap_or_else(|| panic!("{name}"));
let l = paper("l");
assert!((l["width"].as_f64().unwrap() - 89.0 / 25.4).abs() < 1e-9 && (l["height"].as_f64().unwrap() - 127.0 / 25.4).abs() < 1e-9);
assert!(papers.iter().any(|p| p["name"] == "postcard") && papers.iter().any(|p| p["name"] == "6cut"));
assert_eq!(g["gridCells"].as_array().unwrap().len(), 1);
let g = run(
@@ -213,6 +219,28 @@ fn export_pdf_prints_the_current_settings() {
fails(&mut s, "print.exportPdf", json!({"path": path, "ids": ids}));
}
/// The Japanese photo and postcard papers reach the PDF page at their metric sizes: はがき is
/// 100 × 148 mm (283.46 × 419.53 pt) and L is 89 × 127 mm (252.28 × 359.99 pt). 254 ppi is exactly
/// ten pixels per millimetre, so the page pixels are whole millimetres and the MediaBox is exact.
#[test]
fn export_pdf_uses_the_japanese_paper_sizes() {
let mut s = Session::with_demo();
let id = s.catalog.photos().next().map(|p| p.id.0).unwrap();
for (paper, w_pt, h_pt) in [("postcard", 283.46, 419.53), ("はがき", 283.46, 419.53), ("l", 252.28, 359.99)] {
run(&mut s, "print.set", json!({"layout": {"style": "singleImage", "page": {"paper": paper}}, "job": {"ppi": 254.0}}));
let path = temp_pdf(paper);
run(&mut s, "print.exportPdf", json!({"path": path, "ids": [id]}));
let bytes = std::fs::read(&path).unwrap();
let (w, h) = media_box(&bytes);
assert!((w - w_pt).abs() < 0.1 && (h - h_pt).abs() < 0.1, "{paper}: {w} x {h} pt, wanted {w_pt} x {h_pt}");
}
// The customary names are accepted and the page size follows them; an unknown name is refused.
let g = run(&mut s, "print.set", json!({"layout": {"page": {"paper": "はがき"}}}));
assert!((g["pageWidth"].as_f64().unwrap() - 100.0 / 25.4).abs() < 1e-9);
assert!((g["pageHeight"].as_f64().unwrap() - 148.0 / 25.4).abs() < 1e-9);
fails(&mut s, "print.set", json!({"layout": {"page": {"paper": "poster"}}}));
}
#[test]
fn export_pdf_with_a_printer_profile() {
let mut s = Session::with_demo();
+29 -2
View File
@@ -141,10 +141,25 @@ fn paper_label(name: &str) -> String {
"legal" => "Legal".into(),
"tabloid" => "Tabloid".into(),
"custom" => "Custom".into(),
// Japanese photo and postcard papers: the customary name and its metric size.
"l" => "L判 (89 × 127 mm)".into(),
"2l" => "2L判 (127 × 178 mm)".into(),
"kg" => "KG判 (102 × 152 mm)".into(),
"postcard" => "はがき (100 × 148 mm)".into(),
"double-postcard" => "往復はがき (148 × 200 mm)".into(),
"6cut" => "六切 (203 × 254 mm)".into(),
"4cut" => "四切 (254 × 305 mm)".into(),
"business-card" => "名刺 (55 × 91 mm)".into(),
other => other.replace('x', " × ") + " in",
}
}
/// The paper the selector shows as current: `custom`, or the canonical name an alias stands for,
/// so a `print.set` with `はがき` still selects the `postcard` option.
fn current_paper(paper: &str) -> &str {
if paper.eq_ignore_ascii_case("custom") { "custom" } else { layout::paper_name(paper).unwrap_or(paper) }
}
/// The dialog's body: the settings column and the page preview side by side.
pub fn body(app: &mut LightcraftApp, ui: &mut Ui, t: &Tokens) {
let ctx = ui.ctx().clone();
@@ -374,8 +389,8 @@ fn settings(app: &mut LightcraftApp, ui: &mut Ui, t: &Tokens, pu: &mut PrintUi,
heading(ui, t, "Page Setup");
row(ui, t, "Paper", |ui| {
let current = if l.page.paper.eq_ignore_ascii_case("custom") { "custom".to_string() } else { l.page.paper.to_ascii_lowercase() };
let c = egui::ComboBox::from_id_salt("printPaper").selected_text(paper_label(&current)).show_ui(ui, |ui| {
let current = current_paper(&l.page.paper);
let c = egui::ComboBox::from_id_salt("printPaper").selected_text(paper_label(current)).show_ui(ui, |ui| {
for name in layout::paper_names().chain(std::iter::once("custom")) {
if ui.selectable_label(current == name, paper_label(name)).clicked() {
patch(app, &ctx, json!({"layout": {"page": {"paper": name}}}));
@@ -961,5 +976,17 @@ mod tests {
assert_eq!(trim(2.5), "2.5");
assert_eq!(paper_label("a4"), "A4");
assert_eq!(paper_label("4x6"), "4 × 6 in");
// The Japanese photo and postcard papers show their customary name and metric size.
assert_eq!(paper_label("l"), "L判 (89 × 127 mm)");
assert_eq!(paper_label("postcard"), "はがき (100 × 148 mm)");
assert_eq!(paper_label("double-postcard"), "往復はがき (148 × 200 mm)");
assert_eq!(paper_label("6cut"), "六切 (203 × 254 mm)");
assert_eq!(paper_label("4cut"), "四切 (254 × 305 mm)");
// The selector resolves an alias stored by print.set to the canonical option.
assert_eq!(current_paper("はがき"), "postcard");
assert_eq!(current_paper("L判"), "l");
assert_eq!(current_paper("Custom"), "custom");
assert_eq!(paper_label(current_paper("はがき")), "はがき (100 × 148 mm)");
assert_eq!(paper_label(current_paper("a4")), "A4");
}
}
+1
View File
@@ -80,6 +80,7 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | patch | Gap 28: View shows the current grid-cycle key beside Photo Grid and Square Grid (#2500); no new binding and no change to the remaining shortcut gap or hours |
| 2026-10-22 | minor | Gap 15: the Print page setup and PDF gain the Japanese photo and postcard paper sizes (L判 89×127, 2L判 127×178, KG判 102×152, はがき 100×148, 往復はがき 148×200, 六切 203×254, 四切 254×305, 名刺 55×91 mm), by their customary names; gap not closed |
| 2026-10-21 | minor | Gap 28 shrinks: F8 shows / hides the right panel (`view.rightPanel`); rating `[` `]`, flag cycling and F1 help were already bound, so the entry lists only filter-bar keys, Tab / ⇧Tab and F5; 2–4 h (was 4–8) |
| 2026-10-20 | minor | #2362: import destination albums use the shared hierarchy/search picker instead of a flat name list; existing import workflows retained; ranked gaps/readiness counts unchanged |
| 2026-10-19 | minor | Gap 21: Indonesian (`id`) shipped with every catalog message but What's New; four new languages left (Hindi, Arabic, Korean, Vietnamese), 135–239 h |
+2 -1
View File
@@ -765,7 +765,7 @@ From `08-lightroom-classic-extras.md` (Classic-only features) and part 2 of `06-
| LRC-SS-EXPORT | Slideshow export | P2 | 🟡 | `cmd:slideshow.export`, `cmd:slideshow.exportStatus`, `cmd:dialog.slideshowExport`, `cmd:app.slideshowExport`, `path:crates/engine/src/slideshow_export.rs`, `path:crates/codecs/src/video.rs`, `path:crates/ui-egui/src/slideshow_export.rs` | View ▸ Export Slideshow… (and Export PDF / JPEG / Video in Settings ▸ Slideshow): the photos in view as their composed slides (layout, overlays) between the intro and ending screens that are on, in view order. **PDF**: one page per slide at 1024 / 1920 / 3840 px (any 64–8192 by command; page = slide pixels in points, aspect from the aspect preview, "screen" = 16:9), JPEG quality, each page carrying the slide duration and a fade transition for presentation mode, "Automatically show full screen" (`/PageMode /FullScreen`). **JPEG**: `<name>-001.jpg`… in a new folder. **Video**: AV1 (rav1e, fast preset) in MP4 or WebM written by our own muxers, 720p / 1080p (64–2160 by command), 24 / 30 fps (25 / 60 by command); each slide held for its duration (one long sample: variable frame rate) and fades rendered as blended frames (crossfade or through the fade colour); at most 2 hours. Runs in the background with a row in the activity stack (frames or slides done, ✕ cancels); files are written as `.part` and renamed when complete, so a cancelled or failed export leaves nothing; a photo that can't be rendered is skipped with a warning. Missing: H.264 (Classic's codec; patents, no pure-Rust encoder), audio / music in the video (LRC-SS-MUSIC), random order and pan & zoom in exports, the PDF's per-page fades follow the viewer (Acrobat), export presets, Classic's export dialog sizes beyond these, the web build (no export) |
| LRC-PRINT-LAYOUTSTYLE | Print layout styles | P2 | ✅ | `cmd:dialog.print`, `cmd:print.set`, `cmd:print.get`, `cmd:print.exportPdf`, `cmd:export.contactSheet`, `crates/ui-egui/src/print_view.rs` | File → Print… (⌘P): Single Image, Contact Sheet, Picture Package and Custom Package, one page preview each; also `print.*` commands for the CLI, control channel and MCP; the older File → Contact Sheet PDF stays |
| LRC-PRINT-IMAGESETTINGS | Print image settings | P2 | ✅ | `cmd:print.set`, `crates/engine/src/print/compose.rs`, `crates/ui-egui/src/print_view.rs` | Zoom to Fill Frame, Rotate to Fit, Photo Border (width, colour; package styles) and Inner Stroke (width in points, colour) in the dialog, drawn in the preview and in the PDF; pixel-tested |
| LRC-PRINT-LAYOUT | Print layout | P2 | 🟡 | `cmd:print.set`, `cmd:print.layout.autoLayout`, `cmd:print.layout.newPage`, `cmd:print.layout.clear`, `cmd:app.print`, `cmd:export.contactSheet`, `crates/engine/src/print/layout.rs` | A3/A4/A5/Letter/Legal/Tabloid/4x6/5x7/8x10/13x19 and custom paper, portrait/landscape, per-side margins, grid with column/row spacing and square cells, 72–720 ppi, in a Print dialog with a scaled page preview; no printer-driver Page Setup, no cell size / alignment presets |
| LRC-PRINT-LAYOUT | Print layout | P2 | 🟡 | `cmd:print.set`, `cmd:print.layout.autoLayout`, `cmd:print.layout.newPage`, `cmd:print.layout.clear`, `cmd:app.print`, `cmd:export.contactSheet`, `crates/engine/src/print/layout.rs` | A3/A4/A5/Letter/Legal/Tabloid/4x6/5x7/8x10/13x19, the Japanese photo and postcard sizes (L判 89×127, 2L判 127×178, KG判 102×152, はがき 100×148, 往復はがき 148×200, 六切 203×254, 四切 254×305, 名刺 55×91 mm, by their customary names) and custom paper, portrait/landscape, per-side margins, grid with column/row spacing and square cells, 72–720 ppi, in a Print dialog with a scaled page preview; no printer-driver Page Setup, no cell size / alignment presets |
| LRC-PRINT-GUIDES | Print guides | P2 | ✅ | `cmd:print.set`, `cmd:print.cell.move`, `crates/ui-egui/src/print_view.rs` | rulers in in/cm/mm/pt, snap off/cells/grid with a grid size, and Page Margins / Page Bleed (a fixed 0.125 in outline) / Page Grid / Image Cells / Dimensions guides drawn on the preview |
| LRC-PRINT-CELLS | Picture-package cells | P2 | ✅ | `cmd:print.cell.add`, `cmd:print.cell.move`, `cmd:print.cell.resize`, `cmd:print.cell.delete`, `cmd:print.cell.stack`, `cmd:print.cell.photo`, `cmd:print.cell.page`, `cmd:print.layout.autoLayout`, `crates/ui-egui/src/print_view.rs` | add cells at standard or custom sizes, drag and resize them on the page preview (snap to cells, grid or off), Send to Back / Backward / Forward / Bring to Front, a photo per cell (Custom Package), Auto Layout packing, New Page, Clear Layout; no Auto Layout for Custom Package |
| LRC-PRINT-PAGE | Print page options | P2 | 🟡 | `cmd:print.set`, `cmd:export.contactSheet`, `crates/engine/src/print/compose.rs`, `crates/ui-egui/src/print_view.rs` | page background colour and margins in the Print dialog; Page Options for Single Image and Contact Sheet (`print.set {page: {pageNumbers, pageInfo, cropMarks}}`, checkboxes in the Page section, kept in templates): the page number at the bottom right, the print settings line (sharpening, profile, resolution) at the bottom left, crop marks at each photo's corners, drawn in the preview and in the PDF (`print::compose::tests::crop_marks_sit_outside_each_photo_corner`); filename captions in the contact-sheet PDF; no identity plate, watermark, photo info or package Cut Guides |
@@ -805,6 +805,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove
|---|---|---|
| 2026-10-22 | minor | LR-VIEW-INFOOVERLAY: the info overlay shows the star rating and flag, and in the full-screen preview on macOS it starts below the window controls (issue #2460); status unchanged |
| 2026-10-22 | patch | KEY-GRID: both grid rows in View show the cycle shortcut and follow keymap changes; direct bindings and native accelerator ownership are preserved (#2500); status and counts unchanged |
| 2026-10-22 | minor | LRC-PRINT-LAYOUT: the Japanese photo and postcard paper sizes (L判, 2L判, KG判, はがき, 往復はがき, 六切, 四切, 名刺) are selectable in the page setup and Print dialog and print to PDF at their metric sizes |
| 2026-10-21 | patch | LR-IMP-ADD-DIALOG: reconcile #2362 with current main; deleted-album guard and hierarchical picker retained; counts unchanged |
| 2026-10-20 | minor | LR-IMP-ADD-DIALOG: #2362 gives the import review the shared hierarchy/search album picker while retaining None/New album and existing destination IDs; status and feature counts unchanged |
| 2026-10-21 | patch | KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the slider's row; leaving it (even between two frames) or Tab focus alone does not arm them (issue #1355); status unchanged |