Merge PR #440 from NikitkaRa: Palette: skip commands that need a value

# Conflicts:
#	crates/ui-egui/src/menus.rs
This commit is contained in:
Brandon Thomas
2026-10-14 14:01:08 -04:00
6 changed files with 116 additions and 4 deletions
+2
View File
@@ -199,6 +199,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-14 | A custom show plays only its slides: Show in the Custom Shows dialog, Set Up Slide Show, or a click action. Breadth stays 79% |
| 2026-10-14 | Presenter view pauses and resets its timer (T, R) and changes notes size (A-/A+, brackets). The clock shows local time. Second-display placement stays with #433. Breadth stays 79% |
| 2026-10-14 | See All Slides and zoom during a show: a thumbnail grid (G, or All), click to jump, and click-to-zoom at 2× (Z). Presenter view has Black, White, All slides and Zoom. Breadth stays 79% |
| 2026-10-13 | The command palette skips commands that need a value, so searching for Export runs the export dialog (#79) |
| 2026-10-13 | Slide transitions like PowerPoint: every transition and option measured frame by frame against PowerPoint's video export and retuned (curves, directions, soft edges, 3-D perspective, contents moving over backgrounds); PowerPoint's own XML elements and defaults read and written (diagonal Wipe, Zoom, Shape In/Out, Glitter Hexagons, Shred In/Out, Page Curl Single); `deckcraft-cli render-transition` renders frames headless |
| 2026-10-13 | Wild text cases measured against PowerPoint (102 synthetic slides): bidi by run language, East Asian vertical text upright, mongolianVert/wordArtVert/wordArtVertRtl, underline styles and strikethrough at the font's metrics, centre/right/decimal tabs, East Asian, Thai, Hindi, Arabic and Hebrew numbering, small caps, all-caps "ß", transparent text, soft hyphens, kinsoku, rtlCol and just/dist anchoring, emoji presentation, fallback faces that no longer move the baseline |
| 2026-10-13 | Render matches PowerPoint more closely on wild shape, picture and table cases: even-odd custom paths, dashes and caps, arrowheads, radial and path gradients, tile rects, rotWithShape, gradient lines, shadows cast by what is drawn (group and perspective too), group fill, picture borders, tiles, reflection, recolour (biLevel, alphaInv, brightness/contrast), table borders and grid default, 20+ preset shapes by their ECMA formulas, default miter joins, and approximate 3-D bevels, cameras and face lighting |
@@ -260,6 +261,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-14 | trivial | Progress log: custom show playback. Breadth stays 79% |
| 2026-10-14 | trivial | Progress log: presenter timer pause/reset, clock and notes size. Breadth stays 79% |
| 2026-10-14 | trivial | Progress log: See All Slides and zoom during a show. Breadth stays 79% |
| 2026-10-13 | trivial | Progress log: command palette Export (#79) |
| 2026-10-13 | trivial | Progress log: wild text cases |
| 2026-10-13 | trivial | Progress log: slide show for interactive decks |
| 2026-10-12 | trivial | Progress log: File tab (Backstage, #76) |
+101
View File
@@ -358,6 +358,78 @@ pub(crate) fn current_shapes<'a>(doc: &'a deckcraft_model::Presentation, sel: &c
}
}
/// The command palette runs a command with `{}`. True when the params doc names a value that
/// call would have to supply (`path`, `rows`, `path? | data`), so the palette must not offer it.
/// A name ending in `?` is optional. Text after `→` is the result, not an input.
pub fn needs_arguments(params: &str) -> bool {
let input = params.split('→').next().unwrap_or(params);
let Some(start) = input.find('{') else { return false };
let inside = input.get(start + 1..).unwrap_or("");
let inside = inside.split('}').next().unwrap_or(inside);
split_fields(inside).iter().any(|f| field_requires(f.trim()))
}
fn split_fields(s: &str) -> Vec<&str> {
let mut out = Vec::new();
let mut start = 0;
let mut depth = 0i32;
let mut quote = false;
for (i, c) in s.char_indices() {
if quote {
if c == '"' {
quote = false;
}
continue;
}
match c {
'"' => quote = true,
'[' | '(' => depth += 1,
']' | ')' => depth = depth.saturating_sub(1),
',' if depth == 0 => {
out.push(s.get(start..i).unwrap_or(""));
start = i + c.len_utf8();
}
_ => {}
}
}
out.push(s.get(start..).unwrap_or(""));
out
}
/// A field needs a value unless every alternative (`path? | data`) is an optional parameter.
fn field_requires(field: &str) -> bool {
if field.is_empty() {
return false;
}
let alts: Vec<&str> = if field.contains(" | ") { field.split(" | ").map(str::trim).filter(|s| !s.is_empty()).collect() } else { vec![field] };
let mut saw = false;
let mut all_optional = true;
for alt in alts {
let Some(optional) = param_optional(alt) else { continue };
saw = true;
all_optional &= optional;
}
saw && !all_optional
}
/// `Some(true)` when `alt` starts with an optional parameter name, `Some(false)` when required.
fn param_optional(alt: &str) -> Option<bool> {
let alt = alt.trim();
let name_end = alt.find(|c: char| !c.is_ascii_alphanumeric()).unwrap_or(alt.len());
let name = alt.get(..name_end).unwrap_or("");
if name.is_empty() || !name.starts_with(|c: char| c.is_ascii_alphabetic()) {
return None;
}
let rest = alt.get(name_end..).unwrap_or("").trim_start();
if rest.starts_with('?') {
Some(true)
} else if rest.is_empty() || rest.starts_with(':') {
Some(false)
} else {
None
}
}
/// Base64 (standard alphabet) for binary payloads in JSON params and results.
pub fn base64_encode(data: &[u8]) -> String {
const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -400,3 +472,32 @@ pub fn base64_decode(s: &str) -> Option<Vec<u8>> {
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_palette_skips_a_command_that_needs_a_value() {
assert!(needs_arguments("{path}"));
assert!(needs_arguments("{path, format: png|jpeg|pptx, slide?: index}"));
assert!(needs_arguments("{path? | data: base64, name?, rect?}"));
assert!(needs_arguments("{rows, cols, rect?}"));
assert!(needs_arguments("{axis: horizontal|vertical, ids?}"));
assert!(needs_arguments("{percent}"));
assert!(!needs_arguments("{}"));
assert!(!needs_arguments("{on?: bool}"));
assert!(!needs_arguments("{theme?: name, size?: [w,h] pt, blank?: bool}"));
assert!(!needs_arguments("{uid?: one entry, else all}"));
assert!(!needs_arguments("{locked?: bool (default: toggle), ids?}"));
assert!(!needs_arguments("{} → [{name, label, category}]"));
assert!(!needs_arguments("{title?, subject?} → properties"));
assert!(!needs_arguments("{slides?: [index] | \"1-3,5\", includeHidden?}"));
let export = command_specs().iter().find(|c| c.id == "file.export").expect("export");
let save = command_specs().iter().find(|c| c.id == "file.save").expect("save");
let audio = command_specs().iter().find(|c| c.id == "insert.audio").expect("audio");
assert!(needs_arguments(export.params));
assert!(!needs_arguments(save.params));
assert!(needs_arguments(audio.params));
}
}
+3 -2
View File
@@ -86,9 +86,10 @@ fn palette_items(session: &deckcraft_engine::Session, q: &str) -> Vec<(String, S
let mut items: Vec<(String, String, Option<&'static str>)> = session
.commands()
.into_iter()
.filter(|c| c.enabled && !crate::UI_COMMANDS.iter().any(|u| u.1 == c.label))
// Commands that need a path or other value fail when the palette runs them with `{}`.
.filter(|c| c.enabled && !crate::UI_COMMANDS.iter().any(|u| u.1 == c.label) && !deckcraft_engine::cmd::needs_arguments(c.params))
.map(|c| (c.id.to_string(), c.label.to_string(), c.shortcut))
.chain(crate::UI_COMMANDS.iter().map(|c| (c.0.to_string(), c.1.to_string(), c.2)))
.chain(crate::UI_COMMANDS.iter().filter(|c| !deckcraft_engine::cmd::needs_arguments(c.3)).map(|c| (c.0.to_string(), c.1.to_string(), c.2)))
.filter(|(id, label, _)| q.is_empty() || label.to_lowercase().contains(q) || id.to_lowercase().contains(q))
.collect();
items.sort_by_key(|(_, l, _)| !l.to_lowercase().starts_with(q));
+2
View File
@@ -21,6 +21,7 @@
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Date and Time dialog; 28 dialogs; previous: 3-D bevel, camera and face lighting) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (owned PPTX corpus for more deck kinds; handout-master text kept; previous: minor, 3-D bevel, camera and face lighting rendered) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (laser pointer in the slide show; previous: minor, 3-D bevel, camera and face lighting rendered) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** trivial (command palette no longer offers Export without a path, #79; previous: minor, 3-D bevel, camera and face lighting rendered) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
Every known shortfall, one entry each, ranked by how much it stops someone who presents with
PowerPoint from switching. This is the work list: pick from the top. Numbers are summarized in
@@ -428,6 +429,7 @@ Detail in [localization-parity.md](localization-parity.md).
| 2026-10-14 | minor | Handout Master view: the masters gap is now the Notes Master view and handout text in PDF |
| 2026-10-13 | minor | Owned PPTX corpus for tables, charts, animations, media, sections and masters. Handout-master text is kept; the corpus gap is now a larger set and PowerPoint reference renders |
| 2026-10-13 | minor | Laser pointer in the slide show (checklist row Partial). Highlighter, eraser and a laser driven from presenter view remain |
| 2026-10-13 | trivial | Command palette skips commands that need a value, so Export opens the dialog (#79) |
| 2026-10-13 | minor | Shape, picture and table rendering checked against PowerPoint on ~150 synthetic edge-case slides; 3-D bevels, cameras and face lighting now render approximately (gap narrowed to extrusion, rigs and the 3-D pane) |
| 2026-10-13 | minor | Interactive decks: triggers, return-to-slide state and click rules landed in the slide show; the remaining pieces are one new entry under Features |
| 2026-10-12 | minor | IME composition in slide text and notes, and CJK interface text with real glyphs (#101): the IME gap is now Korean on the web and table-cell underlines |
+3 -1
View File
@@ -14,6 +14,7 @@
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (slide show lists presenter timer pause/reset, clock and notes size; breadth stays 79%; previous: trivial, See All Slides and zoom) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (slide show plays a custom show; breadth stays 79%; previous: trivial, presenter timer) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (go to slide shows the typed number; breadth stays 79%; previous: trivial, custom show playback) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** trivial (essentials discount no longer cites the palette Export failure, #79; percentages unchanged; previous: minor, hours to ~95% per audience) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
The authoritative answer to "how close is DeckCraft to PowerPoint, and how much work is left?".
[ROADMAP.md](../ROADMAP.md) summarizes it; [gaps.md](gaps.md) itemizes every shortfall;
@@ -163,7 +164,7 @@ mainstream number leaves out.
| Discount | Factor | Evidence |
|---|---:|---|
| Launch and stability | ×0.90 | Some Windows PCs can't start the app (#40, #59), and the workaround is an environment variable; Close loses work (#60) |
| Discoverability and UI clarity | ×0.95 | Familiar ribbon and tabs; no native menu bar, start-screen glitches, a command palette whose default entry fails (#79) |
| Discoverability and UI clarity | ×0.95 | Familiar ribbon and tabs; no native menu bar, start-screen glitches |
| Opening files people send them | ×0.90 | `.pptx` opens, but real decks are untested and EMF pictures and PowerPoint icons can come in blank (#56) |
80.1 × 0.90 × 0.95 × 0.90 = **61.6 → ~62%**.
@@ -320,6 +321,7 @@ scored done (16 open issues).
| 2026-10-14 | trivial | Slide show evidence includes custom show playback. Breadth stays 79% |
| 2026-10-14 | trivial | Slide show evidence includes presenter timer pause/reset, a local clock and notes size. Breadth stays 79% |
| 2026-10-14 | trivial | Slide show evidence includes See All Slides and zoom. Breadth stays 79% |
| 2026-10-13 | trivial | Essentials discoverability note: the command palette's Export failure (#79) is fixed; the ×0.95 discount stays for the menu bar and start screen |
| 2026-10-10 | minor | Added the readiness-by-audience table: each number gets its own hours to ~95% (full 340–600, mainstream 150–250, essentials 50–90). The full number stays an additive weighted sum; no change to the percentages |
| 2026-10-10 | minor | Added the mainstream practitioner (~53%) and essentials user (~62%) numbers with written weights, discounts and user evidence. Features dimension recomputed from written area weights (66% judgement → 62.4% weighted mean), which moves ready for real work from ~56% to ~55%; stage unchanged |
| 2026-10-10 | minor | Stage checked against the core-workflow gate (all six pass); stays alpha |
+5 -1
View File
@@ -16,6 +16,7 @@
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Hyperlink dialog follows the selection; previous: File tab / Backstage, #76) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Insert › Date and Time dialog; previous: File tab / Backstage, #76) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Zoom dialog: percent or Fit, applies on OK; previous: File tab / Backstage, #76) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** trivial (command search skips Export without a path, #79; previous: minor, File tab / Backstage, #76) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-14 · **Change:** minor (File › New starter decks; previous: minor, text editing keys observed in PowerPoint for Windows, #268) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Slide Sorter sections: add, rename, collapse, drag a whole section; previous: text editing keys, #268) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
@@ -41,7 +42,7 @@ AGENTS.md §2). Dimension score: **~60% ready** (estimated), **30–50 h** to pa
| File tab (Backstage): Home, New, Open, Info, Save, Save As, Print, Share, Export, Close, Account, Options | yes | yes (#76), see [File tab](#file-tab-backstage); no Share (cloud) | Video/GIF/CD exports, templates beyond the built-in themes |
| Start screen: new, themes, templates, recent, open | yes, the same page as File › Home | yes: File › Home with Home, New, Open, Account and Options (#76) | |
| Status bar: slide counter, language, notes, comments, views, zoom slider | yes | yes | |
| Command search (Tell Me) | yes | command palette | #79 default entry fails |
| Command search (Tell Me) | yes | command palette | |
| Light / dark appearance | follows the system | light or dark; doesn't follow the system (#41) | 1–2 h |
| One window per presentation, New Window, Arrange All | yes | New Window opens one more window on the same presentation (its own slide, live with edits); tabs stay in the first window; no Arrange All | Arrange All |
| Quick Access Toolbar customization | yes | no | P1 partial |
@@ -175,6 +176,8 @@ on the slide as shapes. No pixel comparison against PowerPoint screenshots has b
| Missing dialogs (Font, Bullets and Numbering, Find/Replace, Replace Fonts, Action Settings, Print, Insert Object; Create New Theme Fonts is in) | 8–14 |
| Missing dialogs (Font, Bullets and Numbering, Find/Replace, Action Settings, Print, Insert Object) | 8–14 |
| Window chrome, system appearance, palette fixes (#41, #71, #79) | 3–5 |
| Missing dialogs (Font, Bullets and Numbering, Find/Replace, Replace Fonts, Action Settings, Print, Insert Object) | 8–14 |
| Window chrome and system appearance (#41, #71) | 3–5 |
| Grid Options, edit points UI, crop depth | 4–7 |
| Arrange All (New Window is one extra window) | 2–3 |
| A screenshot comparison pass on the most-used tabs | 2–4 |
@@ -202,6 +205,7 @@ on the slide as shapes. No pixel comparison against PowerPoint screenshots has b
| 2026-10-14 | trivial | Hyperlink dialog: address, email, slide and ScreenTip follow the selection |
| 2026-10-14 | minor | Insert › Date and Time: format list, language, Update automatically. 28 dialogs. Languages are the ones the date field can compute (English, plus numeric dates for en-GB, fr-FR, ja-JP, sv-SE) |
| 2026-10-14 | trivial | Zoom dialog: percent or Fit, applied on OK. View ▸ Zoom opens it; the arrow keeps the presets |
| 2026-10-13 | trivial | Command search no longer offers an Export command that fails without a path (#79) |
| 2026-10-12 | minor | File tab (Backstage) observed in PowerPoint for Windows and matched (#76); the start screen is File › Home |
| 2026-10-12 | minor | Off-slide objects drawn and editable around the slide in Normal view (#270) |
| 2026-10-12 | minor | Selection Pane matched to PowerPoint (#133): drag and drop, arrows, groups, eye, Lock |