From cf0d7fbb147ed482d754042f7924b174a7d96acf Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 11 Nov 2026 12:10:27 -0500 Subject: [PATCH 1/4] ci: run Harden-Runner on every job (#2039) Adds step-security/harden-runner (audit, pinned to v2.22.4 like craft-common-actions) as the first step of each job that did not have it, so every workflow in the org's public repos reports its egress to StepSecurity. Audit, not block: block-mode allow lists come from each job's observed traffic. --- .github/workflows/ci.yml | 4 ++++ .github/workflows/packaging-lint.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 907aa3364..073423629 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,10 @@ jobs: # Same craft-fonts commit as release.yml. CRAFT_FONTS_REF: 37827adc41fd165f9a9234ac40f94dbad353aa69 steps: + - name: Harden runner + uses: step-security/harden-runner@1d88384fd173af6ecb012dfd0264a306b711241a # v2.22.4 + with: + egress-policy: audit - name: Check out craft-fonts uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/packaging-lint.yml b/.github/workflows/packaging-lint.yml index ea61d8430..1771a35fe 100644 --- a/.github/workflows/packaging-lint.yml +++ b/.github/workflows/packaging-lint.yml @@ -27,6 +27,10 @@ jobs: name: Windows installation policy (no app build) runs-on: windows-latest steps: + - name: Harden runner + uses: step-security/harden-runner@1d88384fd173af6ecb012dfd0264a306b711241a # v2.22.4 + with: + egress-policy: audit - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false From 3e2e73aab78338836f475ab65985aa8855ab81d7 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 11 Nov 2026 12:43:42 -0500 Subject: [PATCH 2/4] Updates: take craft-update from craft-libs main at 6e03f65bf --- Cargo.lock | 15 ++++++++------- Cargo.toml | 10 +++++----- xtask/src/pdf_deps.rs | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 862599989..4cf447655 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -847,12 +847,12 @@ dependencies = [ [[package]] name = "craft-fonts-build" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d" +source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b" [[package]] name = "craft-i18n" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d" +source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b" dependencies = [ "fixed_decimal", "icu_decimal", @@ -875,7 +875,7 @@ dependencies = [ [[package]] name = "craft-media" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d" +source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b" dependencies = [ "thiserror 2.0.21", ] @@ -883,7 +883,7 @@ dependencies = [ [[package]] name = "craft-pdf" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d" +source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b" dependencies = [ "krilla", "thiserror 2.0.21", @@ -892,7 +892,7 @@ dependencies = [ [[package]] name = "craft-ui" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d" +source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b" dependencies = [ "craft-fonts-build", "craft-i18n", @@ -912,7 +912,7 @@ dependencies = [ [[package]] name = "craft-update" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d" +source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b" dependencies = [ "chrono", "dunce", @@ -922,6 +922,7 @@ dependencies = [ "image", "log", "plist", + "rustix 1.1.5", "semver", "serde", "serde_json", @@ -2791,7 +2792,7 @@ dependencies = [ [[package]] name = "krilla" version = "0.8.2" -source = "git+https://github.com/storytold/craft-libs?rev=1b34eba41e62138fc2438d2734523b302840b45d#1b34eba41e62138fc2438d2734523b302840b45d" +source = "git+https://github.com/storytold/craft-libs?rev=6e03f65bf45181db77ba6229716e6624db9eb25b#6e03f65bf45181db77ba6229716e6624db9eb25b" dependencies = [ "base64 0.22.1", "bumpalo", diff --git a/Cargo.toml b/Cargo.toml index c1e005125..c12ae375f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,11 +59,11 @@ harfrust = "0.13" vello_cpu = "0.2" vello_common = { version = "0.2", default-features = false } image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif", "tiff", "bmp"] } -craft-media = { git = "https://github.com/storytold/craft-libs", rev = "1b34eba41e62138fc2438d2734523b302840b45d", version = "=0.1.0", default-features = false } -craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "1b34eba41e62138fc2438d2734523b302840b45d", version = "=0.1.0", default-features = false, features = ["write", "write-simple-text"] } +craft-media = { git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", version = "=0.1.0", default-features = false } +craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", version = "=0.1.0", default-features = false, features = ["write", "write-simple-text"] } # craft-update: app-specific pretend versions apply to startup and manual checks. -craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "1b34eba41e62138fc2438d2734523b302840b45d", features = ["egui"] } -craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "1b34eba41e62138fc2438d2734523b302840b45d", version = "=0.1.0" } +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", features = ["egui"] } +craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", version = "=0.1.0" } i_overlay = "9" quick-xml = "0.41" zip = { version = "3", default-features = false, features = ["deflate"] } @@ -109,7 +109,7 @@ codegen-units = 16 debug = 0 [patch.crates-io] -krilla = { git = "https://github.com/storytold/craft-libs", rev = "1b34eba41e62138fc2438d2734523b302840b45d", version = "=0.8.2" } +krilla = { git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", version = "=0.8.2" } ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } diff --git a/xtask/src/pdf_deps.rs b/xtask/src/pdf_deps.rs index fb8e79d9a..314db1e54 100644 --- a/xtask/src/pdf_deps.rs +++ b/xtask/src/pdf_deps.rs @@ -7,7 +7,7 @@ use std::process::Command; use serde_json::Value; const SHARED_REPO: &str = "https://github.com/storytold/craft-libs"; -const SHARED_REV: &str = "1b34eba41e62138fc2438d2734523b302840b45d"; +const SHARED_REV: &str = "6e03f65bf45181db77ba6229716e6624db9eb25b"; const REGISTRY: &str = "registry+https://github.com/rust-lang/crates.io-index"; pub fn run(root: &Path) -> Result<(), String> { From 690171665a8ebefc904061e8e6545b762974f007 Mon Sep 17 00:00:00 2001 From: neilcawse Date: Wed, 11 Nov 2026 13:48:47 -0500 Subject: [PATCH 3/4] pptx: preserve SmartArt node text when dgm:cxnLst has no parent-child connections (#1944) * pptx: preserve SmartArt node text when dgm:cxnLst has no parent-child connections * fix(pptx): apply unconnected-point SmartArt outline fallback when type="doc" point is absent * fix(pptx): restrict SmartArt pt_order fallback to empty parOf graphs and preserve cached Picture/Connector children * fix(pptx): convert default rect geometry to line for connectors in diagram drawings * docs(pptx): note flat ptLst order fallback on dgm::outline --- crates/pptx/src/dgm.rs | 34 ++++++++++++++++++++++++++++++--- crates/pptx/src/write/shapes.rs | 18 ++++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/crates/pptx/src/dgm.rs b/crates/pptx/src/dgm.rs index 5f85db00b..70cb89cf2 100644 --- a/crates/pptx/src/dgm.rs +++ b/crates/pptx/src/dgm.rs @@ -403,7 +403,7 @@ pub fn data_xml(d: &Diagram, seed: u32, drawing_rel: &str, pics: &[(usize, Strin w.finish() } -/// The outline of a data part: the `parOf` tree under the document point, depth first. +/// The outline of a data part: the `parOf` tree under the document point, depth first, or the data points in `ptLst` order when no `parOf` connections exist. pub fn outline(data: &El) -> Vec { outline_ids(data).into_iter().map(|(_, n)| n).collect() } @@ -429,11 +429,13 @@ pub fn node_pictures(data: &El) -> Vec<(usize, String)> { out } +/// Extracts diagram outline `(modelId, DiagramNode)` pairs in tree order from a `dgm:dataModel` element. fn outline_ids(data: &El) -> Vec<(String, DiagramNode)> { let Some(pts) = data.child("ptLst") else { return vec![] }; let mut texts: HashMap<&str, String> = HashMap::new(); let mut assistants: std::collections::HashSet<&str> = std::collections::HashSet::new(); let mut titles: HashMap<&str, String> = HashMap::new(); + let mut pt_order: Vec<&str> = Vec::new(); let mut doc: Option<&str> = None; for pt in pts.children_named("pt").take(MAX_NODES * 4) { let Some(id) = pt.attr("modelId") else { continue }; @@ -442,6 +444,7 @@ fn outline_ids(data: &El) -> Vec<(String, DiagramNode)> { ty @ ("node" | "asst") => { let t = pt.child("t").map(paragraphs).unwrap_or_default(); texts.insert(id, t); + pt_order.push(id); if ty == "asst" { assistants.insert(id); } @@ -459,7 +462,6 @@ fn outline_ids(data: &El) -> Vec<(String, DiagramNode)> { _ => {} } } - let Some(doc) = doc else { return vec![] }; let mut kids: HashMap<&str, Vec<(i64, &str)>> = HashMap::new(); if let Some(cx) = data.child("cxnLst") { for c in cx.children_named("cxn").take(MAX_NODES * 4) { @@ -478,7 +480,7 @@ fn outline_ids(data: &El) -> Vec<(String, DiagramNode)> { let mut out = vec![]; let mut seen = std::collections::HashSet::new(); // Depth first, iteratively; a node reached twice (a cycle in a hostile file) is skipped. - let mut stack: Vec<(&str, u8)> = kids.get(doc).map(|v| v.iter().rev().map(|(_, id)| (*id, 0u8)).collect()).unwrap_or_default(); + let mut stack: Vec<(&str, u8)> = doc.and_then(|d| kids.get(d)).map(|v| v.iter().rev().map(|(_, id)| (*id, 0u8)).collect()).unwrap_or_default(); while let Some((id, level)) = stack.pop() { if out.len() >= MAX_NODES || !seen.insert(id) { continue; @@ -497,6 +499,23 @@ fn outline_ids(data: &El) -> Vec<(String, DiagramNode)> { stack.extend(v.iter().rev().map(|(_, c)| (*c, level.saturating_add(1)))); } } + if out.is_empty() && kids.is_empty() { + for id in pt_order { + if out.len() >= MAX_NODES || !seen.insert(id) { + continue; + } + out.push(( + id.to_string(), + DiagramNode { + text: texts.get(id).cloned().unwrap_or_default(), + level: 0, + picture: None, + assistant: assistants.contains(id), + title: titles.get(id).cloned().unwrap_or_default(), + }, + )); + } + } out } @@ -878,5 +897,14 @@ mod tests { assert_eq!(colors_for(&cs_id("accent4")), "accent4"); assert_eq!(colors_for(&cs_id("")), ""); assert_eq!(style_for(&qs_id("outline")), "outline"); + + let unconnected = br#"AlphaBeta"#; + let unconnected_doc = crate::xml::parse(unconnected).unwrap(); + assert_eq!(outline(&unconnected_doc.root), nodes(&[("Alpha", 0), ("Beta", 0)])); + + // When parOf connections exist but no root (type="doc") reaches them, do not flatten the hierarchy to level 0. + let unrooted_hierarchy = br#"ParentChild"#; + let unrooted_doc = crate::xml::parse(unrooted_hierarchy).unwrap(); + assert!(outline(&unrooted_doc.root).is_empty()); } } diff --git a/crates/pptx/src/write/shapes.rs b/crates/pptx/src/write/shapes.rs index 80eabbcf2..e799ea84f 100644 --- a/crates/pptx/src/write/shapes.rs +++ b/crates/pptx/src/write/shapes.rs @@ -542,10 +542,22 @@ fn diagram(w: &mut W, x: &mut Exp, o: &mut Out, cx: &mut Ctx, s: &Shape, d: &dec let (sx, sy) = (frame.w / child.w.max(1e-6), frame.h / child.h.max(1e-6)); let mut k = 0; let mut flat = vec![]; - walk(children, &mut |c, _| { - if matches!(c.kind, ShapeKind::Shape) { - flat.push(c.clone()); + walk(children, &mut |c, _| match &c.kind { + ShapeKind::Shape => flat.push(c.clone()), + ShapeKind::Connector { .. } => { + let mut sc = c.clone(); + if sc.geom.preset_name() == Some("rect") { + sc.geom = Geom::preset("line"); + } + flat.push(sc); } + ShapeKind::Picture { fill } => { + let mut sc = c.clone(); + sc.kind = ShapeKind::Shape; + sc.fill = Some(deckcraft_model::style::Fill::Picture(fill.clone())); + flat.push(sc); + } + _ => {} }); for mut c in flat.into_iter().take(10_000) { if let Some(xf) = &mut c.xfrm { From 7fe979aa2411759a5d7e98c8ce269b938f37c534 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 11 Nov 2026 14:18:59 -0500 Subject: [PATCH 4/4] Fix Flatpak permissions for update checks and self-updates (#2045) * fix(flatpak): allow release checks and self-update helpers * test: keep Flatpak permission parsing within strict Clippy rules --- crates/ui-egui/tests/flatpak_updates.rs | 27 +++++++++++++++++++ .../ai.storyteller.deckcraft.bundle.yml | 2 ++ .../flatpak/ai.storyteller.deckcraft.yml | 2 ++ 3 files changed, 31 insertions(+) create mode 100644 crates/ui-egui/tests/flatpak_updates.rs diff --git a/crates/ui-egui/tests/flatpak_updates.rs b/crates/ui-egui/tests/flatpak_updates.rs new file mode 100644 index 000000000..cc70ea46d --- /dev/null +++ b/crates/ui-egui/tests/flatpak_updates.rs @@ -0,0 +1,27 @@ +//! Release and source Flatpaks must allow release checks and host update helpers. +fn finish_args(manifest: &str) -> Vec<&str> { + manifest + .split("finish-args:") + .nth(1) + .unwrap_or_default() + .lines() + .take_while(|line| line.is_empty() || line.starts_with(' ') || line.starts_with('#')) + .filter_map(|line| line.trim().strip_prefix("- ")) + .map(|arg| arg.trim_matches(['\'', '"'])) + .collect() +} + +#[test] +fn flatpak_update_permissions() { + let manifests = [ + include_str!("../../../packaging/linux/flatpak/ai.storyteller.deckcraft.bundle.yml"), + include_str!("../../../packaging/linux/flatpak/ai.storyteller.deckcraft.yml"), + ]; + let args = manifests.map(finish_args); + assert_eq!(args[0], args[1], "source and release permissions must agree"); + for permissions in args { + for required in ["--share=network", "--talk-name=org.freedesktop.Flatpak"] { + assert!(permissions.contains(&required), "missing update permission: {required}"); + } + } +} diff --git a/packaging/linux/flatpak/ai.storyteller.deckcraft.bundle.yml b/packaging/linux/flatpak/ai.storyteller.deckcraft.bundle.yml index 5b54f575f..a86186225 100644 --- a/packaging/linux/flatpak/ai.storyteller.deckcraft.bundle.yml +++ b/packaging/linux/flatpak/ai.storyteller.deckcraft.bundle.yml @@ -17,6 +17,8 @@ runtime-version: '25.08' sdk: org.freedesktop.Sdk command: deckcraft finish-args: + # The shared updater inspects and updates this installation via flatpak-spawn --host. + - --talk-name=org.freedesktop.Flatpak - --filesystem=xdg-data/ArtCraft/versions:ro # Display: Wayland first; X11 only when no Wayland compositor is running. - --socket=wayland diff --git a/packaging/linux/flatpak/ai.storyteller.deckcraft.yml b/packaging/linux/flatpak/ai.storyteller.deckcraft.yml index 8c1d1e24f..5faa9da72 100644 --- a/packaging/linux/flatpak/ai.storyteller.deckcraft.yml +++ b/packaging/linux/flatpak/ai.storyteller.deckcraft.yml @@ -18,6 +18,8 @@ sdk-extensions: - org.freedesktop.Sdk.Extension.rust-stable command: deckcraft finish-args: + # The shared updater inspects and updates this installation via flatpak-spawn --host. + - --talk-name=org.freedesktop.Flatpak - --filesystem=xdg-data/ArtCraft/versions:ro # Display: Wayland first; X11 only when no Wayland compositor is running. - --socket=wayland