feat(ui): lists-left tabbed/collapsible dock panel (Slice 3 headless backbone)#4
Open
dustenhubbard wants to merge 11 commits into
Open
feat(ui): lists-left tabbed/collapsible dock panel (Slice 3 headless backbone)#4dustenhubbard wants to merge 11 commits into
dustenhubbard wants to merge 11 commits into
Conversation
Add lists_panel_collapsed (False) and open_tables (["object"]) to the global
default_settings dict so they resolve via Series.qsettings_defaults
(QSettings("KHLab","PyReconstruct")), not the per-series options dict — the
series load-prune (series.py:440-444) therefore never touches them. Backs UI v1
Slice 3 (lists-left tabbed/collapsible panel).
Tests (offscreen): defaults registered and global-not-per-series/internal,
getOption returns defaults when unset, setOption/getOption round-trip, and
cross-instance resolution via global QSettings. Adds tests/conftest.py
session-autouse fixture isolating HOME/XDG_CONFIG_HOME/APPDATA + QSettings paths
to a temp dir so the suite never touches real preferences.
Tab the on-demand list docks (object/trace/section/ztrace/flag) into a single left dock group: enable dock nesting in TableManager and tabify each new dock onto the first already-open list dock. Backs UI v1 Slice 3 (lists-left panel). Also fix a pre-existing mutate-while-iterate bug in closeAll: each close() fires closeEvent, which removes the table from the same self.tables[name] list being iterated, so a plain loop skipped about half the tables. Iterate a copy. Tests (offscreen): closeAll closes every table for a multi-table list; newTable places each dock in the left area and groups them via tabifiedDockWidgets; a single dock does not crash; dock nesting is enabled on init. Adds a shared qapp fixture to conftest.
A B2-council lens raised that anchoring newTable to the first open dock could fragment the group if the object (anchor) dock closes. It cannot: once docks are tabified they share one group, so any remaining dock is an equivalent anchor. Add a regression test that closes the anchor and confirms a later dock still joins the single group.
On series open, restore the saved list docks (default Object) via a new FieldWidgetBase._restoreOpenTables, skipping the welcome series. Keep the global open_tables option in sync: TableManager.newTable adds a type, and DataTable.closeEvent -> TableManager.onTableClosed drops it when the last dock of that type closes. closeAll suppresses the sync so a series switch keeps the user's saved preference. Each update writes back a new list (the qsettings defaults are a shallow copy, so the returned list is never mutated in place). Tests (offscreen): sync add/idempotent, remove-when-last-closes, keep-if-another-open, suppress during closeAll, no corruption of the global default, restore opens saved tables, welcome series opens nothing.
A B3-council lens noted that a stale or corrupted open_tables entry (e.g. a list type removed in a future version) would raise KeyError in newTable and crash field loading. Skip unknown types in _restoreOpenTables instead. Test (offscreen): restore with an invalid type opens only the valid lists.
Add a "Collapse lists panel" checkbox action (no accelerator, so no shortcut collision) to the View menu, wired to MainWindow.toggleListsPanel. TableManager.setListsPanelCollapsed hides or shows every list dock and persists lists_panel_collapsed; collapse HIDES (not closes) the docks, so open_tables is untouched. Tests (offscreen): listsPanelCollapsed reads the option; setListsPanelCollapsed hides/shows all docks and persists; collapse leaves open_tables intact; MainWindow.toggleListsPanel flips both the state and the menu checkbox.
Apply the saved lists_panel_collapsed state to the docks and sync the View-menu checkbox after the menu is rebuilt on series open (MainWindow._applyListsPanelState, called in openSeries after createMenuBar, once docks are restored), and re-sync the checkbox in checkActions alongside the other toggle actions. The state lives in global QSettings, so it survives the in-app restart (which recreates the window and re-enters openSeries). Resolves the B4-council findings (restore-on-open and checkbox sync were B5's remit). Tests (offscreen): apply-collapsed hides docks and checks the box; apply-expanded shows docks and unchecks; collapsed state persists across a simulated restart.
Compose the slice: each "boot" is a fresh series + manager + field + window reading the same global QSettings (mirroring run.py recreating MainWindow). Proves open_tables and lists_panel_collapsed both persist and are re-applied on restart, and the View-menu checkbox stays in sync.
Assert the View-menu checkbox updates in real time on toggle (Boot 1), and tighten the post-restart restoration check to assert exactly one Object dock.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
UI v1 Slice 3 — lists-left tabbed/collapsible panel (headless backbone)
Behavior backbone for Slice 3. The look is intentionally out of scope and needs interactive visual sign-off (checklist below). Built test-first; every step reviewed by an adversarial 5-lens council (correctness / simplicity / alternative / reachability / muscle-memory). Held off
main.What changed
default_settings.py):lists_panel_collapsed(False) andopen_tables(["object"]) — global (resolve viaSeries.qsettings_defaults), not per-series, and untouched by the per-series options prune.manager.py):setDockNestingEnabled(True)+tabifyDockWidgetso the five list docks (object/trace/section/ztrace/flag) share one left tab group.closeAllskip-bug fix (manager.py): closing a dock removes it from the very listcloseAlliterates → iterate a copy so no dock is skipped. (Pre-existing bug.)field_widget_1_base.py): restore the savedopen_tables(default Object) on series open; skip the welcome series and unknown/stale types.open_tablesis kept in sync as docks open/close; programmatic teardown (closeAll) suppresses the sync so a series switch preserves the preference. Always writes back a new list (the qsettings defaults are a shallow copy).menubar.py+main_window.py): a "Collapse lists panel" View-menu checkbox (no accelerator → no shortcut collision) that hides/shows the docks viasetHidden(collapse ≠ close, soopen_tablesis untouched) and persists.main_window.py):_applyListsPanelState(aftercreateMenuBar) re-applies the collapsed state and syncs the checkbox;checkActionskeeps the checkbox in sync alongside the other toggles. State lives in global QSettings, so it survives the in-app restart (which recreates the window and re-entersopenSeries).Headless evidence (offscreen,
QT_QPA_PLATFORM=offscreen)tests/test_lists_panel.py(B1–B5 + an end-to-end open→collapse→restart lifecycle) and a session-autouse QSettings-isolation fixture intests/conftest.py(redirects HOME/XDG/APPDATA +setPath, asserts the.confresolves under a temp dir, so a run can never touch real preferences).dockWidgetArea/tabifiedDockWidgets/isHidden()(and thatisVisible()is unreliable when the window is never shown — so assertions useisHidden()).open_tablestypes so a corrupted setting can't crash field loading.Out of scope (deliberately deferred / interactive)
mode_x = 0.99); biasing the trace-selection strip is a one-line cosmetic tweak that risks overlapping the brightness/contrast buttons, so it's left for an interactive pass with eyes on it.Follow-up noted (not fixed here — avoids scope creep)
Series.qsettings_defaults = default_settings.copy()is a shallow copy, so all mutable defaults are aliased. This slice is safe (it never mutates a returned list in place), but adeepcopywould harden every setting — worth a small standalone fix.STATUS delta (for the notes repo, once its in-flight rebase is resolved)
Do not merge — held for interactive visual sign-off. An open PR (or a clear stop report) is the finalized state for this autonomous run.