collective

Browse / github.com/storytold/designcraft / patch

A table size Create Table cannot mean is refused

A table size Create Table cannot mean is refused The same silence as #1567, in `table.insert`. Found by probing after that one. ``` {"rows": 0} → a table with one row (0 clamped up) {"rows": -1} → four rows (as_u64 fails, the default applies) {"rows": 1.5} → four rows (the same way) {"rows": 10000} → 500 rows, quietly capped {"cols": 0} → one column {"headerRows": 101} → 100 header rows ``` A size that is *given* is a number of rows or columns, so each of those is a usage error now: ``` rows 0: 1 to 500 cols 201: 1 to 200 headerRows 101: 0 to 100 ``` A size that is

Status
open
Score
0.00
Adopters
0 other operators
Author
Hamza0101 (imported from pull request #1568, open)
Base
3ce3a24f08d03935bc8cf7cb23755aca8065558c
Size
+63 −4 in 1 file
Review
pending
Shared
2026-10-28
Id
9b5b2906714153dce04ba2de

How it went

No other operator has reported using it yet.

Diff

raw · crates/engine/src/cmd/table.rs

diff --git a/crates/engine/src/cmd/table.rs b/crates/engine/src/cmd/table.rs--- a/crates/engine/src/cmd/table.rs+++ b/crates/engine/src/cmd/table.rs@@ -568,10 +568,23 @@ fn insert(s: &mut Session, p: &Value) -> Result<Value> {     if t.cell.is_some() {         return Err(bad("table.insert", "tables can't be nested in cells"));     }-    let rows = p.get("rows").and_then(Value::as_u64).unwrap_or(4).clamp(1, 500) as usize;-    let cols = p.get("cols").or_else(|| p.get("columns")).and_then(Value::as_u64).unwrap_or(4).clamp(1, 200) as usize;-    let header = p.get("headerRows").and_then(Value::as_u64).unwrap_or(0).min(100) as usize;-    let footer = p.get("footerRows").and_then(Value::as_u64).unwrap_or(0).min(100) as usize;+    // A size that is given but is not one a table can have used to be corrected in silence:+    // `{"rows": 0}` made a table with one row, `-1` and `1.5` made the default four, and 10000+    // was capped at 500.+    let count = |key: &str, alt: Option<&str>, default: u64, range: std::ops::RangeInclusive<u64>| -> Result<usize> {+        let given = p.get(key).or_else(|| alt.and_then(|a| p.get(a))).filter(|v| !v.is_null());+        match given {+            None => Ok(default as usize),+            Some(v) => match v.as_u64().filter(|n| range.contains(n)) {+                Some(n) => Ok(n as usize),+                None => Err(bad("table.insert", format!("{key} {v}: {} to {}", range.start(), range.end()))),+            },+        }+    };+    let rows = count("rows", None, 4, 1..=500)?;+    let cols = count("cols", Some("columns"), 4, 1..=200)?;+    let header = count("headerRows", None, 0, 0..=100)?;+    let footer = count("footerRows", None, 0, 0..=100)?;     let width = p.get("width").and_then(Value::as_f64).unwrap_or_else(|| column_width(s, &t));     s.edit(|d, sel| {         let id = d.alloc();@@ -2444,3 +2457,49 @@ mod resize_border_tests {         assert!(s.execute("table.resizeBorder", &json!({"frame": f, "table": t, "axis": "column", "index": 0, "delta": 5})).is_err());     } }++#[cfg(test)]+mod insert_size_tests {+    use serde_json::json;++    use crate::Session;++    /// A table size that is given but is not one a table can have used to be corrected in+    /// silence: `{"rows": 0}` made a one-row table, `-1` and `1.5` made the default four, and+    /// 10000 was capped at 500 — so a caller's mistake became a table nobody asked for.+    #[test]+    fn a_table_size_insert_cannot_mean_is_refused() {+        let mut s = Session::new();+        s.execute("file.new", &json!({"pages": 1})).unwrap();+        let frame = s.execute("frame.create", &json!({"rect": [20, 20, 400, 300], "content": "text", "text": ""})).unwrap();+        let at = |s: &mut Session| {+            s.execute("text.select", &json!({"story": frame["story"].clone(), "anchor": 0, "focus": 0})).unwrap();+        };+        for (params, names) in [+            (json!({"rows": 0}), "rows 0: 1 to 500"),+            (json!({"rows": -1}), "rows -1: 1 to 500"),+            (json!({"rows": 1.5}), "rows 1.5: 1 to 500"),+            (json!({"rows": 10000}), "rows 10000: 1 to 500"),+            (json!({"cols": 0}), "cols 0: 1 to 200"),+            (json!({"columns": 201}), "cols 201: 1 to 200"),+            (json!({"headerRows": 101}), "headerRows 101: 0 to 100"),+            (json!({"footerRows": -2}), "footerRows -2: 0 to 100"),+        ] {+            at(&mut s);+            let e = s.execute("table.insert", &params).expect_err("refused").to_string();+            assert!(e.contains(names), "{params}: {e}");+        }+        // Nothing was inserted by any of those.+        at(&mut s);+        assert!(s.execute("table.get", &json!({})).is_err(), "no table yet");+        // The sizes a table can have still work, and absent means the default four by four.+        at(&mut s);+        s.execute("table.insert", &json!({})).unwrap();+        let t = s.execute("table.get", &json!({})).unwrap();+        assert_eq!(t["cells"].as_array().unwrap().len(), 16, "four by four");+        at(&mut s);+        s.execute("table.insert", &json!({"rows": 1, "cols": 2, "headerRows": 1})).unwrap();+        let t = s.execute("table.get", &json!({})).unwrap();+        assert_eq!(t["cells"].as_array().unwrap().len(), 4, "one body row and one header row, two columns");+    }+}