Skip to content

Commit 0421c45

Browse files
jackgallantclaude
andcommitted
Update session log: the v0.4.0 sulcus export never parsed; fixed and parser-tested
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4b5ffbf commit 0421c45

1 file changed

Lines changed: 88 additions & 0 deletions

File tree

claude-session-memory.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,94 @@
22

33
_Current status file. Most recent session at top._
44

5+
## 2026-07-09 — Adversarial code review: the v0.4.0 sulcus export was broken. Fixed.
6+
7+
User asked for a full, mean code review of the whole tree, then to fix everything found. Read all
8+
~3,000 lines of source, then went to `pycortex-src` and read `cortex/svgoverlay.py` — which nobody
9+
had done during the v0.4.0 build. **The shipped `sulci.svg` could not be read by pycortex at all.**
10+
11+
### The three blockers (all verified against the real parser, all now fixed)
12+
13+
1. **Not well-formed XML.** The exported fragment used the `inkscape:` prefix with no namespace
14+
declaration and no `<svg>` root. `ET.parse``ParseError: unbound prefix`. Nothing — ElementTree,
15+
lxml, a browser, Inkscape — could open the file. `escapeXml` was fastidiously escaping five
16+
entities inside a document no parser would accept.
17+
2. **The `<text data-ptidx>` labels crashed `db.get_overlay()`.** `Labels.__init__` (svgoverlay.py
18+
~line 413) does an unguarded `float(text.get('x'))` over **every** `<text>` in a labels layer.
19+
Ours had no x/y — a vertex index cannot supply one. `TypeError`, before anything rendered.
20+
3. **`data-ptidx` is pycortex's OUTPUT, not its input.** `SVGOverlay.set_coords` computes it by
21+
kd-tree from each label's x/y and overwrites whatever is there. `grep -c '<text' S1/overlays.svg`
22+
**0**: pycortex stores no labels at all; `Shape.get_labelpos()` derives one per path (so a
23+
two-hemisphere sulcus is labelled twice for free). The whole `labelForCurve``nearestVertexTo`
24+
`ptidxFor``<text>` chain computed a value pycortex ignores and emitted it in a form that
25+
made pycortex crash.
26+
27+
Plus a data-loss footgun: README said "paste or merge into overlays.svg". `SVGOverlay` keys layers by
28+
`inkscape:label`, so appending a second `sulci` layer **silently replaces the subject's own** in
29+
`self.layers`. You must copy the shape groups into the existing `#sulci_shapes`.
30+
31+
**And `test/svg-export.test.js` never parsed its own output.** Ten tests, all regex-matching the
32+
string the author meant to write. That is exactly why all three bugs shipped. One
33+
`ET.fromstring` would have caught #1 on day one.
34+
35+
### Other findings from the same read
36+
37+
- Two more facts the old comments got wrong: `[sulci_paths]` is read **only** by
38+
`quickflat/composite.py` at render time — `Shape.__init__` seeds style from `[overlay_paths]` and
39+
`Shape.set()` overwrites every path. So the on-disk `style` matters only to Inkscape. (The v0.4.0
40+
header claimed `Overlay.set()` re-applies `[sulci_paths]` at load.)
41+
- **The stale-index guard covered 2 of 5 edit ops.** `setAnchorSmooth`/`splitSegment` threw
42+
`TypeError`; `deleteAnchor` silently no-op'd; `setAnchorSmooth(closed, i=9)` grew `smooth[]` to
43+
length 10 against 4 anchors, with holes — the *exact* desync `inRange` was written to kill.
44+
- `loadJSON`'s "arrays are defensively copied so the model never aliases the caller's parsed JSON"
45+
was **false**: `r.bezier` aliased wholesale, `outline.slice()` shared every `{h,g}`.
46+
- `setOverlayLayer` returns `false` while the SVG overlay loads; nobody checked → a shape listed in
47+
the panel, counted, and never drawn, with no retry.
48+
- `_editToggle` accepted a bezier-less shape: `editingId` set, overlay not editing → panel shows
49+
"✓ Done editing" while the lasso stays armed. Unreachable only because the panel disables the
50+
button (a UI accident load-bearing for a controller invariant).
51+
- `_frameOnLoad` (60 × 100 ms), `_download` (4 s), and the adapter's `setData` listener all outlived
52+
`destroy()` — and `autoAttach` destroys-then-attaches.
53+
- `"… " + text.length + " bytes"` counts UTF-16 code units.
54+
- Doc rot: `bezier-edit-overlay.js` "EDITING an ROI's bezier" + `this.roi`; `draw-panel.js` "the ROI
55+
control panel"; `transform.js` "Used ONLY by the bezier edit overlay" (curveFromTrace uses it).
56+
- `isClosed(null) === true`; lasso had no degenerate-stroke guard while trace did; `fitHomography`
57+
checked `spans2D(src)` but not `dst`.
58+
59+
### The fix (18 files, +717/−290; suite 152 → 161 JS tests, all Python green)
60+
61+
- **`core/svg-export.js` rewritten.** Standalone `<svg>` root with both namespaces + the overlay's
62+
`width`/`height`/`viewBox`. **No labels** — but the `labels` layer is still emitted, empty, because
63+
`_find_layer(layer, "labels")` raises `ValueError` without it. An XML comment in the file itself
64+
carries the merge instructions. `SULCI_STROKE_WIDTH`/`_OPACITY` exported and imported by the
65+
adapter, so the live stroke and the exported stroke can't drift (they were `6` in two places).
66+
- **`test/test_sulci_svg.py` (new, wired into `npm run test:py`).** Generates the writer's real output
67+
with node, parses it with ElementTree using svgoverlay.py's own namespaces and `findall` queries.
68+
Needs no `cortex`, no subject. Runs in CI.
69+
- All five bezier edit ops share one contract: out-of-range → unchanged copy, never throws. Swept by
70+
test across every op × bad index × curve kind.
71+
- `loadJSON` deep-copies (bezier, outline entries, labelVert). `isClosed(null) → false`.
72+
- `exportSulciMarkup` returns `null` (overlay not loaded) vs `""` (no curve yielded a path); the
73+
controller reports each correctly. `_sync` polls on `setOverlayLayer` failure. `_editToggle`
74+
requires a bezier. Timers tracked + cancelled; adapter grew a `destroy()`.
75+
- `byteLength` via `TextEncoder`. Status strings extracted. Lasso rejects degenerate strokes.
76+
`fitHomography` checks `dst` too. `roi``shape` throughout the edit overlay.
77+
- **Every new test was mutation-checked**: reverted each fix, confirmed the test fails.
78+
79+
### Open / next time
80+
81+
- **Unchanged and still the top gap:** `svgoverlay.py` itself has never run on roidraw's output.
82+
Needs a subject + importable `cortex`. The Python test reproduces the parser's *queries*, which is
83+
as close as CI can get here.
84+
- The browser `_import` (`FileReader`) and `_download` (`Blob`/anchor/`revokeObjectURL`) paths still
85+
have zero coverage; the 4 s teardown exists for Firefox and is untested.
86+
- **The 2026-07-09 CDP live-viewer check predates this rewrite** — it validated the old broken
87+
markup. Re-run it before the next release.
88+
- **A new release is needed**: `dist/` is gitignored, so `/releases/latest` still serves the v0.4.0
89+
bundle with the broken exporter. Cut v0.4.1, then re-bake the demo viewer (see
90+
[[roidraw-release-artifact-ordering]]). pycortex docs PR #656 is still OPEN.
91+
- Lesson recorded in memory as `string-tests-cannot-check-a-format`.
92+
593
## 2026-07-08/09 — Sulcus drawing (v0.4.0): spec → plan → 12 TDD tasks → SHIPPED (release + demo + docs PR)
694

795
User asked to add sulcus + gyrus drawing. Brainstormed; **researched how pycortex actually stores

0 commit comments

Comments
 (0)