visualdynamics.gui.report_editor¶
report_editor
¶
The report editor: the exported page as the preview, the acts on the bar.
The page shown is the very HTML the export writes — a browser view of
exactly what the reader will get, figures drawn by the same JavaScript
their browser will run — and it does two things the export's page does
not: it frames the selected block, and it says which block was clicked.
Everything else lives in Qt (Brandon, 2026-09-08: "the toolbar should be
the same as the task bar at the top of the report screen how we have for
every other GUI object"): a bar of acts above the page — Insert,
Reference, Move Up, Move Down, Delete, Export — and a settings pane
beside it carrying what the selected block has to say: a figure block's
sources and caption, a text block's Markdown in a Qt editor, the
report's title and marking when nothing is selected. Every act is one
operation on the Report model, journaled by the window, followed by a
re-render of the page; the exported file never carries any chrome.
Classes:
| Name | Description |
|---|---|
ReportEditor |
Owns the web view, the bar and the pane; mutates the report the |
Classes¶
ReportEditor
¶
Bases: QWidget
Owns the web view, the bar and the pane; mutates the report the operations describe.
Methods:
| Name | Description |
|---|---|
insert |
Insert a block of |
insert_reference |
|
flush_text |
Land the text editor's Markdown on the block now — what the |
stand_down |
Leave the page with nothing for the view's destructor to wait on. |
Source code in src/visualdynamics/gui/report_editor.py
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | |
Methods:¶
insert
¶
Insert a block of kind after the selected block, or at the
end, and select it.
Source code in src/visualdynamics/gui/report_editor.py
insert_reference
¶
{{figure:caption}} or {{table:caption}} at the text
editor's cursor — the token the model stores, renumbered by
the same code that numbers the page.
Source code in src/visualdynamics/gui/report_editor.py
flush_text
¶
Land the text editor's Markdown on the block now — what the debounce does after typing rests, and what a test calls.
Source code in src/visualdynamics/gui/report_editor.py
stand_down
¶
Leave the page with nothing for the view's destructor to wait on.
A QWebEngineView destroyed while its page is live blocked the
whole process: Chromium's teardown waits on the render process
in a synchronous mach_msg call that never returned. That was
the gate's "stall at 98 %" — sampled on 2026-09-18 with a
worker's main thread parked inside QtWebEngineCore, and then
named exactly by a faulthandler dump: the window fixture
delivering the deferred delete to a window whose report page
had just finished loading. Stopping a navigation in flight
(the first cut of this) was not enough; a loaded, rendering
page hangs the destructor just the same.
The cure is Qt's own: a hidden page can be discarded — its render process shut down gracefully, the page unloaded — and a discarded page is destroyed in a millisecond (measured: the destructor went from a hang to 0.001 s). So the view is hidden, the page discarded, and the events that carry the change are pumped before the caller goes on to destroy anything. The scroll-restoring slot of a navigation in flight is dropped too, since it would fire into the discarded page.
Then the discard itself hung (2026-09-28, a stack this time:
setLifecycleState(Discarded) never returning from
WebContentsAdapter::discard()). Qt's source says why it can:
discard asks the render process to shut down and then destroys
the live web contents synchronously, right there — the same
teardown the destructor did — while the page is still bound
to the widget it drew into, with that widget's compositor and
surfaces alive to be waited on. The view's destructor never
did better: it deletes an owned page in the very call that
detaches it. So the page is detached from the view first,
the events that carry the widget away are pumped, and only
then is the page discarded, with nothing left to wait on but
the renderer it is asking to leave. Which is a mechanism, not
a witnessed cure — the hang wants a starved machine and never
reproduced alone — so the log still records each step.