docs: Track E (settings and environment) design spec

38 tools in three sections: ISO-25/ANSI dimension, text and leader styles
with drawing_apply_standard; drawing_settings growth, a system-variable
catalogue and document properties; page setup with PDF mediabox read-back,
batch plot and five bundled templates built by the server's own tools;
multi-document on both engines, portable layer states, named views, UCS,
launch, preferences and operator prompts. Track A hardening is wave 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
Umut CAN
2026-09-16 15:22:01 +03:00
co-authored by Claude Opus 5
parent 2be5e4180d
commit ab258d1383
@@ -0,0 +1,267 @@
# v1.6 Track E — AutoCAD settings and environment
**Date:** 2026-09-16 · **Status:** design approved in conversation; plan at
`docs/superpowers/plans/2026-09-16-v1.6-settings-implementation.md` · **Parent:**
`2026-09-15-v1.6-roadmap-design.md` (track E) · **Baseline:** main after
Track A (166 tools, 8 resources)
## 1. Summary
v1.5 lets a model read and write raw system variables and a friendly
`drawing_settings` facade, and nothing else about the environment a drawing
lives in: no dimension/text/leader styles (every dimension inherits
`Standard`), no page setup (a PDF is whatever the renderer's default is), no
templates beyond a layer list, one document at a time, no named views, UCS or
layer states, and no way to ask AutoCAD itself anything (preferences, document
properties, a point picked on screen).
This track adds those as **38 tools** in three new sections, on both engines
wherever a headless file can hold the result, with an honest `@capability`
refusal where only a live AutoCAD can (preferences, document properties'
summary fields, launching, prompting the operator). Authored standards data —
ISO-25 and ANSI dimension styles, text styles, five drawing templates built by
the server's own tools — is pinned by tests. `drawing_apply_standard` makes the
common case one call.
**The user's principle for this track (2026-09-16):** tool count is not the
goal. Every tool here has a rich alias record, a docstring that names its
refusals, a pack, and a place in the `lean` profile decision; a near-duplicate
is folded into an `action` enum rather than registered twice.
Tool budget: **+38** → 204 registered. Packs: styles, settings, page setup and
templates are `core` (drafting essentials); the environment and interactive
groups form the new `settings` pack.
## 2. Scope
**In:** sections 4–10. **Track A hardening (wave 0 of this track's plan):** the
review findings from Track A that are real defects — `entity_create_block_ref`
accepts an undefined block, `block_explode` drops ATTRIB text, `pid_from_spec`
lets NaN reach the drawing and does not roll back on `BaseException`, signal
lines receive a fabricated line number and label, a bubble's exit point ignores
the first waypoint, `block_insert` coerces attribute values with `str()`,
`block_define` writes NaN base points and dangling layer references — each a
fix with a test; the rest of the ~80 findings are filed in
`docs/analysis/track-a-review-backlog.md` with a one-line disposition each.
**Out of 1.6 (recorded):** TABLESTYLE objects (ezdxf cannot author them),
AutoCAD-native `LAYERSTATE` entries (our layer states are portable XRECORDs,
see §8.3), CUI/ribbon/workspace changes, plotter (`.pc3`) authoring, annotative
object *creation* (scales are settable; entities stay non-annotative), ANSI
*inch* templates (ANSI templates here are metric ANSI: `ansi` dimstyle with
decimal inches is a 1.7 item once an inch drawing benchmark exists).
## 3. Architecture
```
engineering/standards/
__init__.py
dimstyles.py ISO_25, ANSI presets as data; DIM_VARIABLE_WHITELIST; apply/describe
textstyles.py ISOCP, ISOCPEUR, ARIAL, ROMANS presets (font file, width, oblique)
mleaderstyles.py ISO / ANSI leader presets
sysvars.py SYSVAR_CATALOG (~60 entries: name, type, range, meaning, engines)
papers.py PAPER_SIZES (ISO A0–A4, ANSI A–E), CTB_CATALOG, scale vocabulary
templates.py TEMPLATE_CATALOG (5 bundled templates: what each contains)
engineering/environment/
layer_states.py portable layer-state snapshot <-> XRECORD codec
templates/ built artefacts: <name>.dxf (headless) + <name>.dwt (COM), README
scripts/build_templates.py, scripts/smoke_settings_com.py
backends/contracts/styles.py (new) dimstyle_*, textstyle_*, mleaderstyle_*
backends/contracts/pagesetup.py (new) page_setup_*, plot_style_list, batch_plot, template_save
backends/contracts/environment.py (new) document_*, layer_state_*, view_named_*, ucs_*,
preferences_*, drawing_properties_*, system_launch,
user_pick_point, user_select, system_prompt_message
backends/contracts/settings.py (edit) drawing_settings facade grows
server.py SECTION 18 Styles (10) · SECTION 19 Page setup & templates (6) · SECTION 20 Environment (22)
```
Every new contract method is `@abstractmethod` unless the table in §9 marks it
`@capability`. Both backends implement every method; COM paths are fake-tested
and executed once live by `scripts/smoke_settings_com.py`.
## 4. Styles (SECTION 18, 10 tools, pack `core`)
### 4.1 Authored presets (`engineering/standards/dimstyles.py`)
| Variable | `iso-25` | `ansi` (metric) | Meaning |
|---|---|---|---|
| DIMTXT | 2.5 | 3.0 | text height (mm) |
| DIMASZ | 2.5 | 3.0 | arrow size |
| DIMEXE | 1.25 | 1.5 | extension beyond dim line |
| DIMEXO | 0.625 | 1.5 | extension line offset |
| DIMGAP | 0.625 | 1.0 | text gap |
| DIMTAD | 1 (above) | 0 (centred) | text vertical placement |
| DIMTIH / DIMTOH | 0 / 0 (aligned) | 1 / 1 (horizontal) | text orientation |
| DIMDEC | 2 | 2 | linear decimals |
| DIMDSEP | `,` | `.` | decimal separator |
| DIMLUNIT | 2 (decimal) | 2 | linear unit format |
| DIMZIN | 8 (trailing zeros suppressed) | 8 | zero suppression |
| DIMBLK | `""` (closed filled) | `""` | arrowhead |
| DIMTXSTY | `ISOCP` | `ROMANS` | text style (created if missing) |
| DIMLWD / DIMLWE | -2 (ByBlock) | -2 | line/extension lineweight |
| DIMSCALE | 1.0 | 1.0 | overall scale |
Source: ISO 129-1:2018 (text and arrow sizes for the 2.5 mm lettering row of
ISO 3098-1) and ASME Y14.2-2014 (line conventions and lettering: 3 mm / .12 in
lettering, .125 in arrowheads), metric values.
A provenance test asserts each preset's values against this table by name.
`DIM_VARIABLE_WHITELIST` = the 15 variables above plus DIMTOL/DIMTP/DIMTM/
DIMTOLJ/DIMTFAC/DIMLFAC/DIMRND/DIMATFIT/DIMTMOVE/DIMCLRD/DIMCLRE/DIMCLRT/
DIMSAH/DIMBLK1/DIMBLK2/DIMCEN — `dimstyle_modify` refuses anything else by
name.
### 4.2 Tools
- `dimstyle_list()` → `[{name, current, values: {15 whitelist vars}}]`
- `dimstyle_create(name, preset: "iso-25"|"ansi"|None = None, overrides: dict|None = None, set_current=False)` — preset values then overrides; unknown override key or a value outside the variable's range (`DIMDEC` 0–8, sizes > 0, DIMTAD 0–4, DIMDSEP one char) → `ValueError` naming it, nothing written; name clash → `ValueError` (use `dimstyle_modify`).
- `dimstyle_modify(name, overrides)` — `changed` reports only variables that actually moved (the `hatch_edit` rule). Dimensions already using the style are listed in `dimensions_using_style` with `rerender_required: true` on the headless engine: AutoCAD re-renders them on the next regen, but a headless DIMENSION carries its rendered block, so the drawing shows the old style until the dimension is redrawn — stated rather than silent.
- `dimstyle_set_current(name)` — `$DIMSTYLE`; the dimension tools then use it (the existing per-dimension override path stays as is).
- `textstyle_list()`, `textstyle_create(name, font: "isocp.shx"|"isocpeur.ttf"|"arial.ttf"|"romans.shx"|<file>, height=0.0, width_factor=1.0, oblique_deg=0.0, set_current=False)`, `textstyle_set_current(name)` — `$TEXTSTYLE`; a font file that is neither a bundled preset nor present in the engine's font path is written but reported `font_resolved: false` (AutoCAD substitutes at open; the headless renderer falls back), never refused, because DXF only stores the name.
- `mleaderstyle_list()`, `mleaderstyle_create(name, preset: "iso"|"ansi", overrides)` — arrow size, landing gap, text style, text height on ezdxf's `doc.mleader_styles`. ActiveX exposes no MLeaderStyle collection, so the COM engine refuses both with `capability: "mleaderstyle"` (the reason names `leader_create_mleader`'s existing per-leader parameters as the route); `mleaderstyle_list` on COM still reads the names from the `ACAD_MLEADERSTYLE` dictionary.
- `drawing_apply_standard(standard: "iso"|"ansi", layers: bool = True, units: bool = True)` — one call: the dimstyle preset (created and set current), its text style, `drawing_settings` (units mm, decimal linear and angular units, LTSCALE 1, DIMSCALE 1; the decimal separator comes from the preset) and, with `layers`, the `mech` layer set for both standards (ANSI layer naming is company-specific; the set reuses the mech names with ISO 128 lineweights — only styles and the separator differ between the two). Returns, per item, whether it was created or was already present.
## 5. Settings (SECTION 20 shares these; 3 tools + facade growth)
- `drawing_settings` facade gains: `limits` (`[[xmin,ymin],[xmax,ymax]]` → `$LIMMIN/$LIMMAX`), `grid` (bool → GRIDMODE) and `grid_spacing`, `snap` (bool) and `snap_spacing`, `ortho` (bool), `polar` (bool + `polar_angle`), `psltscale` (0/1), `annotation_scale` (`"1:50"` → CANNOSCALE string + CANNOSCALEVALUE), `linear_units` (`decimal|engineering|architectural|fractional|scientific` → LUNITS 2/3/4/5/1), `angular_units` (`degrees|dms|grads|radians|surveyor` → AUNITS), `dimstyle` and `textstyle` (current names). Ranges enforced as today (`_RANGES`), unknown keys refused, and the read side reports every key.
- `system_variable_describe(name=None, search=None)` — from `SYSVAR_CATALOG`: `{name, type, range|enum, default, meaning, saved_in: drawing|registry|not_saved, engines: {ezdxf: bool, com: bool}, friendly_key: str|None}`; `search` matches names and meanings; unknown name → `known: false` plus the nearest names, never an error. The catalog is authored (~60: the DIM* whitelist, LTSCALE, PSLTSCALE, INSUNITS, LUNITS, LUPREC, AUNITS, AUPREC, LIMMIN/LIMMAX, GRIDMODE/GRIDUNIT, SNAPMODE/SNAPUNIT, ORTHOMODE, POLARMODE/POLARANG, OSMODE, CANNOSCALE, TEXTSIZE, TEXTSTYLE, DIMSTYLE, CLAYER, CELTYPE, CECOLOR, CELWEIGHT, PDMODE/PDSIZE, FILLETRAD, MIRRTEXT, ATTREQ/ATTDIA, TILEMODE, CTAB, DWGNAME/DWGPREFIX, SAVETIME, FILEDIA, CMDECHO, HIGHLIGHT, REGENMODE, UCSNAME, VIEWCTR/VIEWSIZE, ANGBASE/ANGDIR, PLINEWID, HPNAME/HPSCALE/HPANG, LWDISPLAY, XREFCTL, PROXYSHOW, ISOLINES, FACETRES, DISPSILH). `system_set_variable` gains a soft check: a name in the catalog with a range refuses out-of-range values before writing; a name not in the catalog behaves as today.
- `drawing_properties_get()` / `drawing_properties_set(title=None, subject=None, author=None, keywords=None, comments=None, custom: dict|None=None)` — COM: `SummaryInfo` (+ `AddCustomInfo/SetCustomByKey`); ezdxf: `custom` through `doc.header.custom_vars` (`$CUSTOMPROPERTYTAG`/`$CUSTOMPROPERTY` pairs, both engines) and the five summary fields refused with `capability: "dwgprops"` when any of them is passed headlessly (`get` returns them as `null` with `summary_available: false`).
## 6. Page setup, plotting, templates (SECTION 19, 6 tools, pack `core`)
- `page_setup_list(layout=None)` → per layout: `{paper: "ISO_A3", size_mm: [420,297], orientation, plot_style, scale, plot_area, device, margins_mm}` — read from LAYOUT plot settings (ezdxf `dxf_layout.dxf.*`; COM `Layout.*`).
- `page_setup_apply(layout, paper: "ISO_A4"|"ISO_A3"|"ISO_A2"|"ISO_A1"|"ISO_A0"|"ANSI_A"|…|"ANSI_E", orientation: "landscape"|"portrait" = "landscape", plot_style: str = "monochrome.ctb", scale: "fit"|"1:1"|"1:2"|"1:5"|"1:10"|"1:20"|"1:50"|"1:100"|"2:1"|"5:1"|"10:1" = "fit", plot_area: "layout"|"extents" = "layout", device: str = "DWG To PDF.pc3", margins_mm=None, center=True)` — `PAPER_SIZES` authored (ISO 216 / ANSI Y14.1 sizes in mm), `CTB_CATALOG` names the ctb files AutoCAD ships (`monochrome.ctb`, `acad.ctb`, `Grayscale.ctb`, `Screening 100%.ctb`, …); an unknown ctb is written and reported `plot_style_known: false` (a missing ctb only matters at plot time). Returns the applied setup and `changed`.
- `plot_style_list()` — the authored catalog plus, on COM, the files actually present in `Preferences.Files.PrintStyleSheetPath` (`source: "catalog"|"installed"`).
- `batch_plot(layouts: list[str]|None = None, output_dir: str, format: "pdf" = "pdf", file_pattern: "{drawing}-{layout}.pdf")` — every named (default: every paper) layout through the existing `drawing_export_pdf(layout=)` path; returns one row per layout `{layout, path, bytes, mediabox_mm}` where `mediabox_mm` is **read back from the PDF** (`/MediaBox` parsed from the file) so the sheet size is verified, not assumed. Model space is included only when named explicitly.
- `drawing_template_list()` → `TEMPLATE_CATALOG` rows `{name, standard, sheet, layer_set, dimstyle, textstyle, title_block, files: {dxf, dwt}}`.
- `drawing_new(template=…)` (existing tool, parameter grows): a catalog name resolves to the bundled file (`templates/<name>.dxf` headless, `templates/<name>.dwt` on COM); a path is used as today. Returns `template: {name|path, source: "bundled"|"path"}`.
- `drawing_template_save(path, name=None, description=None)` — current drawing as a template: ezdxf writes DXF (a `.dwt` request headlessly is refused with `capability: "dwt_write"` naming the DXF route); COM `SaveAs(path, acTemplateDwg)` and the description goes to the template's summary. Path validated for write.
**Bundled templates** (`scripts/build_templates.py` builds all five through the
server's own backend methods — layers, styles, settings, ISO 5457 frame from
`titleblock_apply_iso_a3` where the sheet is A3, page setup — then saves DXF;
the `.dwt` twins are produced on the live machine by opening each DXF over COM
and `SaveAs(..., acTemplateDwg)`, committed once, and a test asserts the DXF
side is byte-reproducible from the script):
| name | standard | sheet | layer set | styles | page setup |
|---|---|---|---|---|---|
| `iso_a3_mech` | ISO | A3 landscape | mech | ISO-25 / ISOCP | ISO_A3, monochrome.ctb, 1:1 |
| `iso_a1_arch` | ISO | A1 landscape | iso13567 | ISO-25 / ISOCP, CANNOSCALE 1:50 | ISO_A1, monochrome.ctb, fit |
| `iso_a3_pid` | ISO | A3 landscape | pid | ISO-25 / ISOCP | ISO_A3, monochrome.ctb, 1:1 |
| `ansi_b_mech` | ANSI | ANSI B landscape | mech | ANSI / ROMANS | ANSI_B, monochrome.ctb, 1:1 |
| `ansi_d_arch` | ANSI | ANSI D landscape | iso13567 | ANSI / ROMANS | ANSI_D, monochrome.ctb, fit |
## 7. Environment (SECTION 20, 22 tools, pack `settings`)
### 7.1 Documents (3, both engines)
- `document_list()` → `[{name, path, active, saved, entity_count}]`.
- `document_activate(name_or_path)` → switches the backend's active document; every later tool call targets it. ezdxf: the backend keeps `_docs: dict[key, DocState]` (document, path, dirty flag, undo stack, plan spec, transaction snapshot) and `_doc` becomes a property over the active key — `drawing_new` / `drawing_open` register a new entry instead of replacing the only one; `drawing_close` removes the active entry and activates the most recent other one (or none). COM: `Documents.Item(...).Activate()`.
- `document_close(name_or_path, save=False)` — refuses to discard unsaved changes unless `discard=True` (the message names the unsaved document); closing the last document leaves the backend in the "no document" state every tool already handles.
- `drawing_close` (existing) becomes the active-document case of `document_close`.
### 7.2 Layer states (4, both engines, portable)
`layer_state_save(name, description=None)`, `layer_state_restore(name, properties: list|None = None)`, `layer_state_list()`, `layer_state_delete(name)`.
A state is the layer table snapshot `{layer: {on, frozen, locked, color, linetype, lineweight, plot}}` plus `current_layer`, stored as JSON chunks in an XRECORD under the drawing's named object dictionary `ACADMCP_LAYERSTATES` → `<name>` (ezdxf `rootdict.add_new_dict` / `add_xrecord`; COM `Dictionaries.Add` / `AddXRecord` / `SetXRecordData` with 1000-code chunks ≤ 255 chars). Restore applies only the requested `properties` (default all) and reports `applied`, `missing_layers` (in the state, not in the drawing — skipped, never created) and `new_layers` (in the drawing, not in the state — untouched). The docstring and `system_capabilities` say plainly that these states live in the file and travel with it but **do not appear in AutoCAD's Layer States Manager**.
### 7.3 Named views (3, both engines)
`view_named_save(name, center=None, height=None, width=None)` (defaults: the current view on COM; on ezdxf the drawing's extents), `view_named_restore(name)` (COM: `ActiveViewport` set; ezdxf: `$VIEWCTR/$VIEWSIZE` and the reported no-op rule of the other view tools), `view_named_list()`. Refuses `height <= 0`; a name clash replaces (`replaced: true`).
### 7.4 UCS (3, both engines)
`ucs_list()`, `ucs_set(name, origin, x_axis, y_axis)` (unit vectors normalised; non-orthogonal axes refused with the measured angle), `ucs_restore(name|"world")`. **Tool coordinates stay WCS** (the repository rule) — a UCS is stored and made current for the operator's benefit; no tool starts interpreting inputs in UCS. The docstring says so.
### 7.5 Application, preferences, properties, interactive (9)
- `system_launch(visible=True, open_path=None)` — COM: connect or start the ProgID, optionally open a file; returns `{launched: bool, attached: bool, version, document}`; ezdxf: `capability: "live_application"`.
- `system_preferences_get(keys=None)` / `system_preferences_set(key, value)` — whitelist (COM `Preferences.*`):
`OpenSave.SaveAsType` (enum name ↔ value), `OpenSave.AutoSaveInterval` (0–600 min), `OpenSave.CreateBackup`, `OpenSave.IncrementalSavePercent` (0–100), `Display.CursorSize` (1–100), `Drafting.AutoSnapMarkerSize` (1–20), `Drafting.AutoSnapTooltip`, `Selection.PickBoxSize` (0–50), `Output.DefaultPlotStyleTable`, `Output.DefaultOutputDevice`, and read-only `Files.SupportPath`, `Files.TemplateDwgPath`, `Files.PrintStyleSheetPath`, `Files.PrinterConfigPath`. Writes report `old`/`new`; a read-only key or a value outside its range is refused by name; ezdxf: `capability: "preferences"`.
- `drawing_properties_get/set` — §5.
- `user_pick_point(prompt)`, `user_select(prompt, mode: "single"|"multiple")`, `system_prompt_message(text)` — COM `Utility.GetPoint` / `GetEntity` + `SelectionSets.Add(...).SelectOnScreen` / `Utility.Prompt`; a pick cancelled by the operator returns `{cancelled: true}` (not an error); a wall-clock timeout (`COM_CALL_TIMEOUT` applies) reports `timed_out`; ezdxf: `capability: "interactive_prompt"`.
## 8. Cross-cutting
### 8.1 Capability keys (added to **both** maps)
`mleaderstyle` (COM false), `dwgprops` (ezdxf false: summary fields), `dwt_write`
(ezdxf false), `live_application`, `preferences`, `interactive_prompt` (ezdxf
false). `tests/test_capability_contract.py` enforces both maps.
### 8.2 Packs, profiles, discovery
`PACK_TOOL_NAMES["settings"]` = the 22 SECTION 20 tools; SECTION 18/19 are
core. `LEAN_TOOL_NAMES` gains `drawing_apply_standard`, `page_setup_apply`,
`batch_plot`, `dimstyle_set_current`, `textstyle_set_current` (lean = 55).
Alias records for all 38 (AutoCAD commands: `DIMSTYLE`, `DDIM`, `STYLE`,
`MLEADERSTYLE`, `PAGESETUP`, `PLOT`, `PUBLISH`, `NEW`, `QNEW`, `SAVEAS`,
`OPTIONS`, `DWGPROPS`, `LAYERSTATE`, `VIEW`, `UCS`, `UCSMAN`, `LIMITS`,
`GRID`, `SNAP`, `ORTHO`, `UNITS`, `CANNOSCALE`, `SYSVAR`, `SETVAR`) and
shop-floor synonyms; the discovery golden set gains ≥ 10 queries for them
(`tests/test_discovery_transform.py` golden file).
### 8.3 Honesty rules specific to this track
- A style or setting write is reported with `changed` (what moved) — re-setting
a value to itself is not a change.
- Page setup evidence is the PDF's own `/MediaBox`, read back.
- Layer states are ours, not AutoCAD's; documented in three places (tool
docstring, `system_capabilities`, README).
- Nothing here starts interpreting coordinates in UCS.
## 9. Testing and evidence
- Presets provenance: `tests/test_standards_presets.py` asserts every ISO-25 /
ANSI value in §4.1 by name, every paper size against ISO 216 / ANSI Y14.1
(A0 841×1189 … A4 210×297; ANSI A 216×279 … E 864×1118), every catalog
sysvar has type+meaning+engines.
- Dual-engine tests for every tool (ezdxf real; COM fake-ActiveX asserting call
shapes: `DimStyles.Add`, `TextStyles.Add`, `Layout.ConfigName/…`,
`Documents.Item().Activate`, `Dictionaries.Add`+`AddXRecord`, `Views.Add`,
`UserCoordinateSystems.Add`, `Preferences.OpenSave.*`, `SummaryInfo.*`,
`Utility.GetPoint`).
- Multi-document: two headless documents open, entity created in one is not in
the other, activate switches, close-with-unsaved refuses, closing the last
leaves "no document".
- Layer states: save → change layers → restore → table equals snapshot;
`missing_layers`/`new_layers` reported; the XRECORD survives save/reopen.
- Page setup: apply ISO_A3 landscape → `batch_plot` → parsed `/MediaBox` is
420×297 mm (±0.5 mm at 72 dpi units); ANSI_B → 432×279.
- Templates: `scripts/build_templates.py --check` proves the five DXFs are
reproducible; `drawing_new(template="iso_a3_mech")` yields the mech layers,
ISO-25 current, ISO_A3 page setup; a `.dwt` twin exists for each and opens on
COM in the smoke.
- `drawing_apply_standard("iso")` then `dimension_linear` → the DIMENSION carries
DIMTXT 2.5 / DIMDSEP `,`; `"ansi"` → 3.0 / `.`.
- Live COM smoke `scripts/smoke_settings_com.py`: styles, page setup + PDF
mediabox, template `.dwt` open, layer state round trip, named view, UCS,
preferences get (+ one reversible set), `SummaryInfo`, `system_prompt_message`;
`user_pick_point` is exercised only if `--interactive` is passed (it needs the
operator). Result recorded in the CHANGELOG.
- Benchmark v4 gains `page_setup_truth` (`benchmarks/tasks_v4.py`, the same
v3→v4 rule: competitors `not run`); correctness suite +3
(`settings_dimstyle_iso25_values`, `settings_layer_state_roundtrip`,
`settings_pdf_mediabox_a3`).
- Docs: CLAUDE.md sections 18–20 + packs, README rows + config table
(`TOOL_PACKS=settings`), CHANGELOG `Unreleased`.
## 10. Milestones (input to the plan)
0. Track A hardening (7 fixes + backlog file) — independent, parallel wave.
1. `engineering/standards/` data modules + provenance tests (pure).
2. Styles contract + both engines + SECTION 18 tools.
3. Settings facade growth + `system_variable_describe` + properties.
4. Page setup + plot style list + batch_plot with mediabox read-back.
5. Templates: build script, bundled DXF, catalog, `drawing_new(template=)`, `drawing_template_save`; `.dwt` twins from the live machine.
6. Multi-document on both engines (+ `drawing_close` alignment).
7. Layer states, named views, UCS.
8. Launch, preferences, interactive tools, capability keys.
9. Packs/lean/aliases/golden queries, benchmark + correctness, live smoke, docs, gates.
Groups 2, 4, 6+7 touch disjoint modules and get their own SECTIONs, so the
plan runs them in parallel worktrees (the Track A wave-1 pattern) and a merge
agent regenerates the three count files (`docs/tool-inventory.json`, README
snapshot line, `tests/test_tool_registry.py`) after merging.