diff --git a/crates/engine/src/cmd/blocks/attedit.rs b/crates/engine/src/cmd/blocks/attedit.rs
--- a/crates/engine/src/cmd/blocks/attedit.rs
+++ b/crates/engine/src/cmd/blocks/attedit.rs
@@ -3,7 +3,7 @@
 //! time or by replacing a string in all of them. Attribute tags match in any case, as in AutoCAD.
 
 use cadcraft_doc::color::Color;
-use cadcraft_doc::{AttribProps, Common, EntityKind, Handle};
+use cadcraft_doc::{AttribProps, Common, EntityKind, HAlign, Handle, Text, VAlign};
 use serde_json::{Map, Value, json};
 
 use crate::cmd::curves::KW;
@@ -67,7 +67,7 @@ fn run(s: &mut Session, p: &Value) -> Result<Value> {
         if let EntityKind::Insert(i) = &mut e.kind {
             for a in &mut i.attribs {
                 if let Some(v) = value_for(&vals, &a.tag) {
-                    a.text.value = v;
+                    set_value(&mut a.text, v);
                     n += 1;
                 }
             }
@@ -130,13 +130,29 @@ fn edit(s: &mut Session, (h, k): (Handle, usize), f: impl FnOnce(&str) -> Option
             && let Some(a) = ins.attribs.get_mut(k)
             && let Some(v) = f(&a.text.value).filter(|v| *v != a.text.value)
         {
-            a.text.value = v;
+            set_value(&mut a.text, v);
             changed = true;
         }
     })?;
     Ok(changed)
 }
 
+/// Refresh the derived baseline after a value change, even one within the writer's
+/// imported-font tolerance. Equal XY points request recomputation on export; retain
+/// both elevations and the independent endpoints used by Aligned/Fit text.
+fn set_value(text: &mut Text, value: String) {
+    if text.value == value {
+        return;
+    }
+    text.value = value;
+    if !matches!((text.halign, text.valign), (HAlign::Left, VAlign::Baseline) | (HAlign::Aligned | HAlign::Fit, _))
+        && let Some(anchor) = text.align_pt
+    {
+        text.insert.x = anchor.x;
+        text.insert.y = anchor.y;
+    }
+}
+
 /// Change the properties set on attribute `k` of block reference `h`.
 fn set_props(s: &mut Session, (h, k): (Handle, usize), f: impl FnOnce(&mut AttribProps)) -> Result<()> {
     s.doc_mut()?.modify_entity(h, |e| {
diff --git a/crates/engine/tests/it/attribute_value_baseline.rs b/crates/engine/tests/it/attribute_value_baseline.rs
new file mode 100644
--- /dev/null
+++ b/crates/engine/tests/it/attribute_value_baseline.rs
@@ -0,0 +1,203 @@
+//! Small value changes must not export an imported justified attribute's old baseline.
+use cadcraft_doc::{Attrib, Drawing, EntityKind, HAlign, Handle, Text, VAlign};
+use cadcraft_engine::{Input, Session};
+use cadcraft_geom::Vec3;
+use serde_json::json;
+
+const BEFORE: &str = "POWER ADAPTER";
+const AFTER: &str = "POWER ADAPTER.";
+
+fn fixture() -> (Session, Handle, Handle) {
+    let mut s = Session::new();
+    for (tag, value, y) in [("T", BEFORE, 15), ("U", "ADPT-01", 0)] {
+        s.execute("attdef", &json!({"tag":tag,"default":value,"at":[0,y],"height":3})).unwrap();
+    }
+    s.execute("line", &json!({"points":[[0,-5],[40,-5]]})).unwrap();
+    let handles: Vec<_> = s.doc().unwrap().model.iter().map(|e| e.handle.hex()).collect();
+    s.execute("block", &json!({"name":"Equipment","handles":handles,"base":[0,0],"keep":"delete"})).unwrap();
+    let a = s.execute("insert", &json!({"name":"Equipment","at":[100,200],"scale":2,"rotation":30})).unwrap();
+    let b = s.execute("insert", &json!({"name":"Equipment","at":[300,200],"attribs":{"T":"Other"}})).unwrap();
+    let a = Handle::parse_hex(a["handle"].as_str().unwrap()).unwrap();
+    let b = Handle::parse_hex(b["handle"].as_str().unwrap()).unwrap();
+    // Set existing document fields directly: this regression does not require Text Options.
+    s.doc_mut()
+        .unwrap()
+        .modify_entity(a, |e| {
+            let EntityKind::Insert(i) = &mut e.kind else { panic!("insert") };
+            let t = &mut i.attribs[0].text;
+            t.insert.z = 7.0;
+            t.align_pt = Some(t.insert);
+            t.halign = HAlign::Center;
+            t.valign = VAlign::Top;
+        })
+        .unwrap();
+    let imported = cadcraft_io::read_dxf(cadcraft_io::write_dxf(s.doc().unwrap()).as_bytes()).unwrap();
+    let mut s = Session::empty();
+    s.open_drawing(imported, "Imported", None);
+    (s, a, b)
+}
+
+fn attributes(d: &Drawing, h: Handle) -> Vec<Attrib> {
+    let EntityKind::Insert(i) = &d.entity(h).unwrap().kind else { panic!("insert") };
+    i.attribs.clone()
+}
+
+fn baseline(d: &Drawing, t: &Text) -> cadcraft_geom::Vec2 {
+    let style = d.text_style(&t.style).unwrap();
+    let (h, v) = cadcraft_render::text_align(t.halign, t.valign);
+    cadcraft_fonts::baseline_start(
+        &cadcraft_fonts::TextFont::resolve(&style.font),
+        &t.value,
+        &cadcraft_fonts::TextParams {
+            insert: t.insert.xy(),
+            align_pt: t.align_pt.map(|p| p.xy()),
+            height: t.height,
+            rotation: t.rotation,
+            width_factor: t.width_factor,
+            oblique: t.oblique,
+            h,
+            v,
+            backwards: style.backwards,
+            upside_down: style.upside_down,
+        },
+    )
+}
+
+#[test]
+fn all_attribute_value_paths_refresh_small_justified_export_changes() {
+    for path in ["json", "interactive", "global-json", "global-interactive"] {
+        let (mut s, a, b) = fixture();
+        let before = attributes(s.doc().unwrap(), a);
+        let other = attributes(s.doc().unwrap(), b);
+        let definition = s.doc().unwrap().block("Equipment").unwrap().clone();
+        match path {
+            "json" => {
+                s.execute("attedit", &json!({"handle":a.hex(),"values":{"t":AFTER}})).unwrap();
+            }
+            "interactive" => {
+                s.start("attedit").unwrap();
+                s.input(Input::Pick(vec![a])).unwrap();
+                s.input(Input::Text(AFTER.into())).unwrap();
+                s.input(Input::Enter).unwrap();
+                assert!(s.running.is_none());
+            }
+            "global-json" => {
+                s.execute("-attedit", &json!({"handles":[a.hex()],"tag":"T","find":"ADAPTER","replace":"ADAPTER."})).unwrap();
+            }
+            "global-interactive" => {
+                s.script("-attedit\nn\nn\nEquipment\nT\n\nADAPTER\nADAPTER.\n").unwrap();
+                assert!(s.running.is_none(), "{}", s.prompt_text());
+            }
+            _ => panic!("path"),
+        }
+        let edited = attributes(s.doc().unwrap(), a);
+        assert_eq!(edited[0].text.value, AFTER, "{path}");
+        assert_eq!(edited[0].text.align_pt, before[0].text.align_pt);
+        assert_eq!(edited[0].text.insert.z, 7.0);
+        assert_eq!(edited[1], before[1]);
+        assert_eq!(attributes(s.doc().unwrap(), b), other);
+        assert_eq!(s.doc().unwrap().block("Equipment").unwrap(), &definition);
+        let expected = baseline(s.doc().unwrap(), &edited[0].text);
+        assert!(expected.dist(before[0].text.insert.xy()) > 0.01, "fixture must change width");
+        for name in ["edited.dxf", "edited.dwg"] {
+            let reopened = cadcraft_io::read(&cadcraft_io::write(s.doc().unwrap(), name).unwrap(), name).unwrap();
+            let restored = attributes(&reopened, a);
+            assert!(
+                restored[0].text.insert.xy().dist(expected) < 1e-8,
+                "{path} {name}: stale {:?}, expected {expected:?}",
+                restored[0].text.insert.xy()
+            );
+            assert_eq!(restored[0].text.align_pt, before[0].text.align_pt);
+            assert_eq!(restored[0].text.value, AFTER);
+            assert_eq!(restored[0].text.insert.z, 7.0);
+        }
+        s.undo().unwrap();
+        assert_eq!(attributes(s.doc().unwrap(), a), before, "{path} undo");
+        s.redo().unwrap();
+        assert_eq!(attributes(s.doc().unwrap(), a), edited, "{path} redo");
+    }
+}
+
+#[test]
+fn equal_attribute_values_preserve_an_imported_font_baseline() {
+    let (mut s, a, _) = fixture();
+    // A small imported-font offset must survive a no-op edit (writer tolerance is deliberate).
+    s.doc_mut()
+        .unwrap()
+        .modify_entity(a, |e| {
+            let EntityKind::Insert(i) = &mut e.kind else { panic!("insert") };
+            i.attribs[0].text.insert.x += 0.1;
+        })
+        .unwrap();
+    let before = attributes(s.doc().unwrap(), a);
+    s.execute("attedit", &json!({"handle":a.hex(),"values":{"T":BEFORE}})).unwrap();
+    assert_eq!(attributes(s.doc().unwrap(), a), before);
+    s.start("attedit").unwrap();
+    s.input(Input::Pick(vec![a])).unwrap();
+    s.input(Input::Enter).unwrap();
+    s.input(Input::Enter).unwrap();
+    assert_eq!(attributes(s.doc().unwrap(), a), before);
+    s.execute("-attedit", &json!({"handles":[a.hex()],"tag":"T","find":"ADAPTER","replace":"ADAPTER"})).unwrap();
+    assert_eq!(attributes(s.doc().unwrap(), a), before, "global JSON no-op");
+    s.script("-attedit\nn\nn\nEquipment\nT\n\nADAPTER\nADAPTER\n").unwrap();
+    assert!(s.running.is_none(), "{}", s.prompt_text());
+    assert_eq!(attributes(s.doc().unwrap(), a), before, "global interactive no-op");
+}
+
+/// Recomputing a justified baseline must not replace its independent insertion elevation.
+#[test]
+fn value_edits_preserve_distinct_insertion_and_alignment_elevations_on_export() {
+    let (mut s, a, _) = fixture();
+    s.doc_mut()
+        .unwrap()
+        .modify_entity(a, |e| {
+            let EntityKind::Insert(i) = &mut e.kind else { panic!("insert") };
+            i.attribs[0].text.insert.z = 9.0;
+        })
+        .unwrap();
+    let anchor = attributes(s.doc().unwrap(), a)[0].text.align_pt;
+    s.execute("attedit", &json!({"handle":a.hex(),"values":{"T":AFTER}})).unwrap();
+    for name in ["elevations.dxf", "elevations.dwg"] {
+        let reopened = cadcraft_io::read(&cadcraft_io::write(s.doc().unwrap(), name).unwrap(), name).unwrap();
+        let restored = attributes(&reopened, a);
+        assert_eq!(restored[0].text.insert.z, 9.0, "{name}: insertion elevation");
+        assert_eq!(restored[0].text.align_pt.map(|p| p.xy()), anchor.map(|p| p.xy()), "{name}: alignment XY");
+        if name.ends_with(".dxf") {
+            assert_eq!(restored[0].text.align_pt, anchor, "DXF retains independent alignment elevation");
+        } else {
+            // The DWG bridge uses one text elevation for both points.
+            assert_eq!(restored[0].text.align_pt.unwrap().z, 9.0);
+        }
+    }
+}
+
+#[test]
+fn value_edits_preserve_left_baseline_and_two_point_placements() {
+    for horizontal in [HAlign::Left, HAlign::Aligned, HAlign::Fit] {
+        let (mut s, a, _) = fixture();
+        s.doc_mut()
+            .unwrap()
+            .modify_entity(a, |e| {
+                let EntityKind::Insert(i) = &mut e.kind else { panic!("insert") };
+                let t = &mut i.attribs[0].text;
+                t.halign = horizontal;
+                t.valign = VAlign::Baseline;
+                t.insert = Vec3::new(10.0, 20.0, 7.0);
+                t.align_pt = (horizontal != HAlign::Left).then_some(Vec3::new(40.0, 30.0, 9.0));
+            })
+            .unwrap();
+        let before = attributes(s.doc().unwrap(), a);
+        s.execute("attedit", &json!({"handle":a.hex(),"values":{"T":AFTER}})).unwrap();
+        let mut expected = before;
+        expected[0].text.value = AFTER.into();
+        assert_eq!(attributes(s.doc().unwrap(), a), expected);
+        if horizontal != HAlign::Left {
+            let reopened = cadcraft_io::read_dxf(cadcraft_io::write_dxf(s.doc().unwrap()).as_bytes()).unwrap();
+            let restored = attributes(&reopened, a);
+            assert_eq!(restored[0].text.insert, expected[0].text.insert);
+            assert_eq!(restored[0].text.align_pt, expected[0].text.align_pt);
+            assert_eq!(restored[0].text.halign, horizontal);
+            assert_eq!(restored[0].text.value, AFTER);
+        }
+    }
+}
diff --git a/crates/engine/tests/it/main.rs b/crates/engine/tests/it/main.rs
--- a/crates/engine/tests/it/main.rs
+++ b/crates/engine/tests/it/main.rs
@@ -10,6 +10,7 @@ mod attedit_prompts;
 mod attrib_modes;
 mod attrib_props;
 mod attribute_tools;
+mod attribute_value_baseline;
 mod block_keep;
 mod block_redefine;
 mod blockreplace;
diff --git a/crates/io/src/dxf_write/common.rs b/crates/io/src/dxf_write/common.rs
--- a/crates/io/src/dxf_write/common.rs
+++ b/crates/io/src/dxf_write/common.rs
@@ -234,7 +234,8 @@ fn computed_text_insert(d: &Drawing, t: &Text) -> Vec3 {
         upside_down: ts.upside_down,
     };
     let q = cadcraft_fonts::baseline_start(&cadcraft_fonts::TextFont::resolve(&ts.font), &t.value, &p);
-    if q.is_finite() { Vec3::new(q.x, q.y, a.z) } else { t.insert }
+    // Justification recalculates the baseline in XY; group 10 keeps its own elevation.
+    if q.is_finite() { Vec3::new(q.x, q.y, t.insert.z) } else { t.insert }
 }
 
 pub(super) fn text_tags(w: &mut W, d: &Drawing, t: &Text, attrib: Option<(&Attrib, Version)>, attdef_prompt: Option<&str>) {
diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md
--- a/docs/file-format-parity.md
+++ b/docs/file-format-parity.md
@@ -1,6 +1,6 @@
 # File-format parity
 
-> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (DIMASSOC objects for angular, diametric and radial dimensions) · **Target:** Autodesk AutoCAD 2027
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-26 · **Change:** minor (attribute value edits refresh exported baselines) · **Target:** Autodesk AutoCAD 2027
 
 Every format AutoCAD 2027 reads or writes, with CADCraft's support, fidelity and tests. Work
 items are in [gaps.md](gaps.md#file-formats); the weighted score feeds
@@ -75,7 +75,7 @@ on read with a note, so such files no longer exceed the DXF limit (#1482). **DWG
 |---|---|---|---|
 | LINE, POINT, CIRCLE, ARC, ELLIPSE, LWPOLYLINE (widths, bulges, elevation), SPLINE, RAY, XLINE | yes | yes | round-trip tests |
 | POLYLINE 2D/3D | yes | yes (as LWPOLYLINE / 3D POLYLINE) | polyface and polygon meshes flattened: lossy |
-| TEXT, MTEXT, ATTDEF, INSERT + ATTRIB | yes; multiline attributes (embedded MTEXT) read and drawn | yes; multiline attributes written as embedded MTEXT (DXF 2007 and later; older versions: one line) | ATTDEF prompt kept (#137) |
+| TEXT, MTEXT, ATTDEF, INSERT + ATTRIB | yes; multiline attributes (embedded MTEXT) read and drawn | yes; multiline attributes written as embedded MTEXT (DXF 2007 and later; older versions: one line); changed attribute values refresh justified baselines, including small width changes | ATTDEF prompt kept (#137); unchanged values retain imported-font placement |
 | DIMENSION (all 7 kinds) | yes | yes, with anonymous `*D` blocks | a reopened dimension's `*D` block is regenerated under its name, unreferenced ones dropped (#495); DIMASSOC objects written for every associated kind — linear/aligned, angular, radial/diametric, ordinate and arc length — and read back without CADCraft's xdata (#1651) |
 | DIMENSION (all 7 kinds) | yes | yes, with anonymous `*D` blocks | a reopened dimension's `*D` block is regenerated under its name, unreferenced ones dropped (#495); DIMASSOC objects written; angular block text honors DIMDSEP, including per-dimension overrides (#1646) |
 | DIMENSION (all 7 kinds) | yes | yes, with anonymous `*D` blocks; their text keeps the diameter sign as `%%c`, so SHX styles plot it (#1645) | a reopened dimension's `*D` block is regenerated under its name, unreferenced ones dropped (#495); DIMASSOC objects written |
@@ -140,6 +140,7 @@ our output in AutoCAD black-box.
 
 | Date | Change | Summary |
 |---|---|---|
+| 2026-10-26 | minor | CC-046: ATTEDIT and -ATTEDIT value changes invalidate derived justified baselines before DXF/DWG export; preserve alignment anchors, unchanged values and Aligned/Fit endpoints. Review follow-up: baseline recomputation preserves insertion elevation independently of alignment elevation. The shared writer correction also applies to justified TEXT. Independent of attribute editor UI; reverting the follow-up restores elevation loss on recomputation, while reverting the value setter restores stale XY baselines. |
 | 2026-10-22 | minor | DXF/DWG save: angular, diametric and radial dimensions get standard DIMASSOC objects (one centre reference to the circle for radial/diametric, the definition points for angular), so AutoCAD follows their geometry; the standard objects of those kinds are read back too, without CADCraft's xdata (#1651) |
 | 2026-10-19 | minor | DXF/DWG save: angular dimension block text honors DIMDSEP, including per-dimension overrides (#1646) |
 | 2026-10-19 | minor | A file's own arrowhead blocks with standard names (AutoCAD's `_Dot`, `_DotSmall`), named only by a dimension style, are kept and saved as they were instead of being replaced by CADCraft's solids; PURGE keeps arrowhead blocks dimension styles name |
diff --git a/xtask/src/parity.rs b/xtask/src/parity.rs
--- a/xtask/src/parity.rs
+++ b/xtask/src/parity.rs
@@ -136,7 +136,9 @@ mod tests {
         assert_eq!(day(20_736), "2026-10-10");
         assert_eq!(date(std::time::UNIX_EPOCH - std::time::Duration::from_secs(86_400)), "1970-01-01");
         // Past chrono's range keeps the proleptic Gregorian date, not the epoch (old `civil_from_days`).
-        assert_eq!(day(115_740_740), "318857-05-20");
+        // Checked on the day count: Windows' SystemTime ends in the year 30828, so building that
+        // SystemTime panics there.
+        assert_eq!(civil_from_days(115_740_740), (318_857, 5, 20));
         // Windows' SystemTime can't hold u64::MAX / 2 seconds, so it is only checked where it exists.
         if let Some(far) = std::time::UNIX_EPOCH.checked_add(std::time::Duration::from_secs(u64::MAX / 2)) {
             assert_eq!(date(far), "292277026596-12-04");
diff --git a/xtask/src/stats.rs b/xtask/src/stats.rs
--- a/xtask/src/stats.rs
+++ b/xtask/src/stats.rs
@@ -134,7 +134,7 @@ pub fn run(_root: &Path, exact: bool) -> Result<(), String> {
 
 #[cfg(test)]
 mod tests {
-    use super::{Counts, count_tests, walk};
+    use super::{Counts, count_tests};
 
     #[test]
     fn counts_attributes_and_proptest_blocks() {
@@ -155,7 +155,7 @@ mod tests {
         // A directory symlink pointing back at `real` would be re-scanned if followed.
         symlink(&real, base.join("alias")).unwrap();
         let mut c = Counts::default();
-        walk(&base, &mut c).unwrap();
+        super::walk(&base, &mut c).unwrap();
         assert_eq!(c.files, 1, "the aliased directory was followed and counted twice");
         std::fs::remove_dir_all(&base).ok();
     }
