diff --git a/AGENTS.md b/AGENTS.md
index 874b3c743..ca7dab287 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -62,7 +62,7 @@ bytes ─▶ magic/open_strategy ─▶ DecodedFile ─▶ Document ─▶ Eleme
| `src/odr/internal/file_type_table.*` | **The** per-`FileType` table: extensions, MIME types, category, document type, `FileTypeCapabilities`. Every public lookup in `odr.hpp` is a thin forward into it — extend the table, not the lookups. |
| `src/odr/internal/html/` | Generic HTML renderer. |
| `src/odr/internal/cfb/`, `zip/` | Container formats (CFB, ZIP). |
-| `src/odr/internal/odf/` | OpenDocument (odt/ods/odp/odg); see [`odf/AGENTS.md`](src/odr/internal/odf/AGENTS.md). |
+| `src/odr/internal/odf/` | OpenDocument (odt/ods/odp/odg); see [`odf/AGENTS.md`](src/odr/internal/odf/AGENTS.md) + [`odf/PLAN.md`](src/odr/internal/odf/PLAN.md). |
| `src/odr/internal/ooxml/` | OOXML (docx/pptx/xlsx); see [`ooxml/AGENTS.md`](src/odr/internal/ooxml/AGENTS.md) + per-format docs. |
| `src/odr/internal/oldms/` | **Legacy MS binary** (.doc/.ppt/.xls). |
| `src/odr/internal/iwork/` | Apple iWork (`.pages`, `.key`, `.numbers`); see [`iwork/AGENTS.md`](src/odr/internal/iwork/AGENTS.md) + [`iwork/PLAN.md`](src/odr/internal/iwork/PLAN.md). |
diff --git a/src/odr/internal/odf/AGENTS.md b/src/odr/internal/odf/AGENTS.md
index ad5a5de69..b612bb884 100644
--- a/src/odr/internal/odf/AGENTS.md
+++ b/src/odr/internal/odf/AGENTS.md
@@ -145,6 +145,8 @@ are **tolerated** to keep rendering best-effort.
## Status & open work
Feature coverage (element/style checkboxes) is tracked in [`README.md`](README.md).
+The drawing gap — transforms, the unparsed shape elements, enhanced geometry and
+the embedded chart — has its own staged plan in [`PLAN.md`](PLAN.md) (#771).
The structural/foundational gaps, roughly by value:
1. **Editing is text-content only.** No structural edits (insert/delete/move
diff --git a/src/odr/internal/odf/PLAN.md b/src/odr/internal/odf/PLAN.md
new file mode 100644
index 000000000..d5948cc18
--- /dev/null
+++ b/src/odr/internal/odf/PLAN.md
@@ -0,0 +1,140 @@
+# ODF drawings plan
+
+Closing #771: everything an ODF shape carries beyond its bounding box —
+`draw:transform`, the shape elements with no parser, `draw:enhanced-geometry`,
+and the `draw:object` chart. The module as it stands is in
+[`AGENTS.md`](AGENTS.md); the feature checklist is [`README.md`](README.md).
+Keep this file honest as stages land, and delete it when they all have.
+
+## Today
+
+`parse_any_element_tree` (`odf_parser.cpp`) knows nine drawing tags —
+`draw:frame`, `draw:image`, `draw:rect`, `draw:line`, `draw:circle`,
+`draw:custom-shape`, `draw:text-box`, `draw:g`, `draw:a`. Everything else falls
+through its final `return {null_element_id, …}` and vanishes with its subtree.
+`draw:custom-shape` reaches `html::translate_custom_shape` as a positioned
+`
` with fill and stroke: the box, never the shape. `draw:transform` is not
+read, so a rotated shape draws unrotated.
+
+## The corpus
+
+Every number below is `` fallback stays for a shape with no geometry.
+
+Emitting a path is not "passing the file's markup through": `svg:d` is parsed
+into commands and re-serialised by us, so nothing the file wrote reaches the
+output as live markup.
+
+**A transform is not a style.** `get_intermediate_style` walks the *element*
+parent chain and overrides down it, so a `GraphicStyle` field would leak a
+group's transform onto every child — and CSS already composes a transform
+through the nesting. `draw:transform` is therefore an accessor on the shape
+handles, next to `x()`/`y()`, not a style property.
+
+**A transform is composed, not passed through.** ODF angles are radians and CSS
+wants degrees, so the list has to be read anyway. It composes to
+`internal::util::math::Transform2D`'s shape — four unitless numbers and a
+translation that carries a length — which is also what `matrix(a b c d e f)`
+means in 19.228.
+
+**The SVM replacement image stays.** For `draw:object` it is the fallback when
+the chart cannot be read, and it is what a producer that wrote no chart part
+leaves behind.
+
+## Stages
+
+Each stage is one pull request, stacked on the one before.
+
+### 1 — `draw:transform`
+
+`DrawingTransform` in the public header; `transform()` on `Frame`, `Rect`,
+`Line`, `Circle` and `CustomShape`; the odf adapter reads and composes the
+attribute; the renderer writes `transform` + `transform-origin:0 0`.
+Visible on 4 files. Closes the `transform` box in `README.md`.
+
+### 2 — the missing shape elements
+
+The tag → type table above, the geometry conversions, `path()`/`view_box()` on
+`CustomShape`, and the renderer branch that draws a path. `translate_circle`
+becomes an `` while it is being touched — it writes `r="50%"` today,
+which is wrong for every non-square box.
+
+### 3 — `draw:enhanced-path` and `draw:equation`, parser only
+
+The formula mini-language (`$N` modifiers, `?fN` references, the eleven
+functions the corpus uses plus the rest of 20.36) and the path grammar
+(`M L C Z N U X Y` plus the commands 19.145 defines and the corpus does not
+use). Pure functions over strings, unit-tested from string literals, no
+rendering and no element-model change. The largest single piece.
+
+### 4 — enhanced geometry, rendered
+
+Stage 3 wired into `CustomShape::path()`: `svg:viewBox`, `draw:modifiers`,
+`draw:mirror-vertical` / `-horizontal`, `draw:text-areas` for where the shape's
+text goes. Closes #159. This is the stage that regenerates reference output.
+
+### 5 — `draw:object` charts
+
+`` in the embedded part, rendered from its series, axes and
+`table:table` of plotted data, with the SVM replacement kept as the fallback.
+Closes #179. Large enough to deserve splitting again if it grows; it shares
+nothing with stages 1–4 but the `draw:frame` it hangs off.
+
+## Not scoped
+
+- **`dr3d:scene`** — a 3-D scene, occurring nowhere in the corpus.
+- **`draw:object-ole`** (2 occurrences) — an OLE blob, not ODF markup; what is
+ readable there is the replacement image we already draw.
+- **Glue points and connector routing.** `draw:connector` carries `svg:d`
+ written by the producer, and drawing that is the whole win. Re-routing a
+ connector between the shapes it names is layout, not decoding.
+- **Editing any of this.** The editor is text-content only (`AGENTS.md`).