Repository navigation
Phase 7: GUI - #101
Draft
ejams1 wants to merge 18 commits into
Draft
Phase 7: GUI#101ejams1 wants to merge 18 commits into
ejams1 wants to merge 18 commits into
Conversation
…form shell New crate crates/xedit-gui and binary xedit-gui on eframe/egui 0.37 (added to [workspace.dependencies]; the GUI draws through its own widgets). The in-process CommandBridge mirrors xedit_cli::engine onto xedit_session's Registry: call(name, json) and typed request<Req, Res> with the CLI, serve and mcp request/response structs, and no new command. The initial load is the CLI's startup step (Session::load in command::load_session, on a stack-sized worker thread). Ported forms: xeMainForm (main_form.rs) with pnlTop (bnMainMenu opening pmuMain, bnBack/bnForward, lblPath, the pnlBtn project links with pmuBtnMenu), pnlNav/vstNav with the splitter, pgMain's bottom tab strip (View, Referenced By, Messages, Information, the three spreadsheets), stbMain with the last message and the load gauge, FormShow/DoInit's startup flow, AddMessage and the FormClose settings write; xeGameSelectForm and xeModuleSelectForm as the minimal startup dialogs. 4.1.5q has no TMainMenu, so the command surface is pmuMain and the nine context menus; menu_model.rs holds all 191 items with their DFM Caption, ShortCut, OnClick, Checked and RadioItem and the command of each covered ledger binding, and a later step's item is drawn disabled with its step and command in the tooltip. Widgets: widgets/virtual_tree.rs and widgets/grid.rs (TreeSource/TreeRow/ TreeCell, GridSource/GridCell, pure selection/focus/edit state machines, virtualised rows, column resizing), painted through widgets/conflict.rs with wbColorConflictAll/wbColorConflictThis, RGBtoHSL/HSLtoRGB, Lighter/Darker and wbLighter/wbDarker plus the ini overrides. theme.rs applies UI.Theme, FontRecords and FontMessages; settings.rs reads and writes the GUI keys of xeSettingsFileName through the port's TMemIniFile (frmMain placement and pnlNavWidth, vstNavColumnWidth<i>, UI fonts and theme, ColorConflictAll/ColorConflictThis, View/Nav keys), keeping every other line. icon.rs decodes the DIB entries of the three upstream .ico files per game family. startup.rs holds the game list (GameModes order, tes4r refused with issue #80) and the module check state with SimulateLoad. Session: module_select::selectable_modules runs DoInitPath and wbLoadModules for the dialog without a session (not a command), and NoParams, Version, SessionInfo, SessionLoadRequest and SessionLoadResponse gained the opposite serde derive for the typed bridge; no command or schema changed. upstream-map.toml and README updated. Verification: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace green (47 GUI unit tests, 6 tests/menu_model.rs tests that check every mni*:OnClick ledger binding is modelled and carries the ledger's command); cargo xtask check green (gui 661 entries, 0 pending). Smoke: the binary ran on a non-interactive desktop (CreateDesktop + SW_HIDE) with --game sse and --game sse --load Update.esm, alive 8 s and 25 s; the drawing is unwitnessed until cargo xtask gui-shot lands. Co-Authored-By: Claude Code <[email protected]>
`cargo xtask gui launch|windows|shot|keys|click|menus|close` drives a GUI program on a private desktop for the gate's screen checks. A session is a JSON file; a helper process (`gui session`, run from a copy of xtask) creates the desktop, starts the program on it, holds the desktop open and serves every request through files beside the session. Windows belong to their desktop: a process that opens the desktop by name enumerates none of them, so listing, PrintWindow capture (BitBlt fallback), key posting, clicking, menu reading and closing all happen in the helper. Verified against the 4.1.5q oracle `xFOEdit.exe -FO4` (launch, window list showing TfrmMain and TfrmModuleSelect, screenshots of the main form, What's New, the developer message and the module selection, `text:FO4` typed into the module selection's filter with --control TLabeledEdit, caption clicks for the DPI-clipped dialog buttons, close) and against dxdiag.exe. Findings: 4.1.5q has no Win32 menu (its File menu is pmuMain on the bnMainMenu speed button, so `gui menus` reports no menu); notepad.exe on this Windows build is the packaged app and exits with code 0 outside its package. cargo fmt --all --check, cargo clippy --workspace --all-targets -D warnings, cargo test --workspace and cargo xtask check are green (17 gui tests, including the menu tree read from a window built for the test). Co-Authored-By: Claude Code <[email protected]>
… need The phase-7 reconnaissance found five holes below the GUI; this builds each as a first-class typed command (schema, CLI route, edit gate, tests) with the upstream semantics read from the Pascal. modules.list (crates/xedit-session/src/module_select.rs, `xedit modules list [--all] [--active] [--masters-present]`): `wbModulesByLoadOrder` as `TfrmModuleSelect.ShowModal` and `TfrmModGroupEdit.vstModGroupItemsKeyDown` build it, with every field `vstModulesGetText`/`vstModulesInitNode` read -- name and original (`.ghost`) name, load order and FileID, path, size, `valid`, `loaded`, `active`, `active_in_plugins_txt`, `masters_missing`, `has_index`, the ESM/ESM-extension/light/medium/update/localized/blueprint/ghost/hardcoded flags, the official/CC/plugins.txt/loadorder.txt indices, the masters and the CRC32. `core/wbLoadOrder.pas`'s `TwbModuleInfo` gained those header flags (`wbMastersForFile` through `MainRecordStructFlags`, incl. the `.esl` extension rule of `wbLoadModules`) and `file_size`. signatures.list (crates/xedit-session/src/filter.rs, `xedit signatures list`): `TfrmFilterOptions.FormCreate`'s `wbRecordDefs` loop -- `DefaultSignature - GetName` for every record definition, the base-record list only for `ReferenceSignatureCount > 0`, both sorted by that text. SessionLoadRequest.compare_to (`xedit session load --compare-to`): the "Compare to..." load is a request field now, winning over the process's `--compare-to` (consumed either way) and reported in the response. Found on the way: the CLI's global `--compare-to` shadowed the subcommand flag through clap's global propagation, so the startup flag is no longer global and both routes were run on a synthetic folder. settings.get/settings.set (crates/xedit-session/src/settings.rs, `xedit settings get|set`): any `xEdit.ini` section and key read and written with the `TMemIniFile` shapes (`ReadString`/`ReadInteger`/`ReadBool`/`ReadFloat`/ `ReadDouble`, `WriteBool` as 1/0, floats as `FloatToStr`, the file normalised as `UpdateFile` does, the folder created as FormCreate's ForceDirectories does), covering the GUI keys: `[frmMain]` placement, `pnlNavWidth` and `vstNavColumnWidth<i>`, the per-form placement sections, `[ColorConflictThis]`/ `[ColorConflictAll]`, `[Nav] FilesSort`/`INFOSort` and `[View]`. `settings.set` is mutating with a dry run. Core callback seams (crates/xedit-core/src/interface/globals.rs): `wbGetFormIDCallback` and `wbGetCellDetailsForWorldspaceCallback` with `wb_get_form_id`/`wb_get_cell_details_for_worldspace`; `add.rs` asks them exactly where `TwbGroupRecord.Add` does (`wbGetFormID(Self)` through `LoadOrderFormIDtoFileFormID`, `Exit` on Null; the world-cell dialog's persistent flag and grid cell, its cancellation adding nothing). With no callback installed the headless default keeps today's behaviour (the world-cell parameter error, a new FormID), which every existing front end relies on. Ledger notes (the two dialogs' data sources, the compare load), upstream-map notes (wbLoadOrder, wbInterface, the four forms, xeMainForm), PLAN step 1b, using-xedit-cli rows. Verification 2026-10-18/19: cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings and cargo xtask check green (gui 661/0 pending, switch 156/2, game_mode 14/4, tool_mode 17/0, script 3663/0, units 172/49); cargo test --workspace green except the known environmental copies_a_skyrim_npc_as_an_override, which fails identically with these changes stashed; cargo xtask parity roundtrip --file Fallout4.esm: 2 cases (fo4, fo4vr) both structure-edited (every record the input's, the OFST data dropped, groups 112381 (728 empty) -> 112367 (727 empty)), 0 different, 0 failed -- the documented expected class without a cached oracle save. Live smokes on the SSE install: modules list (Skyrim.esm 249752131 bytes, official index MIN, Update.esm -1, Dawnguard 0, CC cc_index 2/17/27), signatures list (134 definitions, 30 base), settings get/set on a temp ini, both compare-to routes. Tests: crates/xedit-session/tests/gui_commands_batch4.rs, 7 green (three runs). Co-Authored-By: Claude Code <[email protected]>
Ported xeModuleSelectForm, xeModGroupSelectForm, xeModGroupEditForm, xeFilterOptionsForm, xeFileSelectForm and xeGameSelectForm as one module per form under crates/xedit-gui/src/dialogs/, over one shared selection.rs (NameFilter with the PerlRegEx case-less mode and the red box, PresetCombo with the acPreset*Update rules and disabled reasons, Checklist with the selected-or-all bulk actions, the owner-drawn search, Question/show_question, fit_size and DialogPlacement). The module dialog draws the eight vstModules columns (Filename, File Order from LoadOrderDescription, ESM, Light, Load Order, FormID Prefix, Update, Med), the master children with CheckStateForModule, the regex edFilter, Select All/None/Invert, the modules.presets/preset_* combo with <plugins.txt> and SimulateLoad's miLoadOrder/miFileID slot assignment; its tree is modules.list, served at startup by a session over no files after selectable_modules primes the game globals, and OK hands session.load the full paths. The mod group selection draws the group/item/CRC32 tree with its presets and MinSelect/MaxSelect errors and confirms through modgroups.select; its edit flow opens modgroups.show in the edit form (CRC32 question, CheckState, the six flag columns, CRC32 children, Delete/Insert questions, Ctrl+Up/Down, Ctrl+C, Ctrl+N, Space) and sends modgroups.edit. The filter options form holds the two conflict lists, the option check boxes and texts, the signatures.list check lists, the preset combo over filter.presets, settings.get and filter.preset.save|delete, writes View|LastUsedFilter, asks the changed-preset question from the dry run and runs filter.apply. The file selection is the searchable check list with Backup plugins; the game selection gained NoEscape, placement and the highlight and keeps the --game bypass. main_form intercepts filter.apply, modgroups.select and modgroups.edit to open the dialogs, and the screen-check switches --dialog/--dialog-arg plus the XEDIT_GUI_* env overrides open one without the menus. The typed bridge needed additive Serialize/ Deserialize derives on the step-1b request/response structs; the virtual tree gained the additive check/column/expander/header clicks and focused_column. Placement is written through settings.get/set under the upstream form names (PORT-EXTENSION: 4.1.5q keeps none for these six forms), clamped to the desktop. Verification: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace green except the known environmental copies_a_skyrim_npc_as_an_override; xedit-gui 87 unit tests plus 4 install-backed tests (FO4 modules.list and modules.preset_* round trip, SSE signatures.list and the filter.preset.save -> settings.get -> FilterLoadPreset round trip, a private mod group file through modgroups.list/show/edit, placement); cargo xtask check green (gui 661/0; units 172 tracked, 43 pending; the six units are ported in upstream-map.toml). Screen checks with cargo xtask gui on the private desktop: all six dialogs against the FO4 install in M:\projects\scratch\p7-step5-evidence, plus the 4.1.5q module selection from xFOEdit.exe -FO4. Co-Authored-By: Claude Code <[email protected]>
Port of xEdit/xeMainForm.pas's vstNav into crates/xedit-gui/src/nav_tree.rs: the lazy node tree (AddFileInternal roots in load order, vstNavInitChildren and vstNavInitNode's grouping: a parented group of types 1/6/7 and, under wbVWDAsQuestChildren, 10 shows below its record and its own node carries ivsHidden), vstNavGetText's cells for groups, main records, the file header and files, vstNavChange's header titles and path, the conflict colours via widgets::conflict, ReInitTree/NavUpdate, Container.GetAddList and the action of every pmuNav, pmuNavHeaderPopup and pmuNavAdd item. Conflict statuses are asked for the painted rows in batches of 200 per frame through conflicts.list's new records field (conflict_statuses_of), so expanding a large master does not scan it. filter.apply's response now carries the surviving node element ids and the InheritStateFromChildren statuses; the GUI installs them as the Conflicted and Filter views (a filtered tree builds only the survivors, as the filter deleted the other nodes), and filter.remove re-runs ReInitTree. lblFilterHint, edFormIDSearch, edEditorIDSearch, edFileNameFilter and cbRegExFilter are ported with the upstream colours and keyboard behaviour. selection.rs is the shared GetSortedSelection(True) plus focused node model (records with their file, signature, editor ID and name), refreshed every frame for the view grid of step 3. Menu items dispatch the ledger command with the upstream selection semantics; items whose handler opens a dialog of step 5, 6 or 7 call a named Placeholder in dialogs/mod.rs with its tooltip. mniNavAdd lists Container.GetAddList and each entry calls elements.add. Verification: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace green except the known environmental failure `copies_a_skyrim_npc_as_an_override` (`xedit-session/tests/structure.rs`, the same on the phase branch before this step) (xedit-gui: 53 unit tests, including the schema check of every navigation command's request and the dispatch of every covered menu item, 6 menu-model tests, and 5 headless tests over SSE Update.esm: roots and load order, the header/groups/records cells, a parented group below a CELL, a record selection, the add list, and the filtered tree keeping only the survivor CELL/WRLD records and groups); cargo xtask check (gui 661 entries, 0 pending). Witnessing: cargo xtask gui launch/windows/shot/click/close finds the window (the title and icon are the app's viewport commands) but the wgpu client area captures black on the private desktop, so the drawing is unwitnessed. Co-Authored-By: Claude Code <[email protected]>
The GUI now also carries step 5's dialog tests; the note says 53 tests of this step and 99 at the rebased tip. Co-Authored-By: Claude Code <[email protected]>
Co-Authored-By: Claude Code <[email protected]>
Port xeLogAnalyzerForm, xeScriptForm, xeViewElementsForm, xeRichEditForm, xeLocalizationForm, xeDeveloperMessageForm, xeTipForm, xeScriptHost and xeLocalizePluginForm as one module each under crates/xedit-gui/src/dialogs/, with the shared split tree+memo viewer and the shared RTF/text viewer (the RTF parser, BuildTOC, zoom, search and the virtualised paragraphs) in dialogs/viewers.rs. The release's own developer messages, tips and What's New text are decompressed from the 4.1.5q DFMs into resources/ (MPL 2.0); the 200x200 developer bitmap is decoded by the form. Command layer (CLI routes stay `xedit call`, no new command names): script.list gains `subdirectories` (soAllDirectories with relative names), script.read returns a script's text and unit name, script.save ports btnSaveClick (tabs to two spaces, .backup.<timestamp> copy, CRLF lines), script.run takes the editor's `source` over the file as ApplyScript(aScriptName, aScript) does, and the log_analyzer/localization/settings/script/elements response and request types gain the Deserialize/Serialize derives the typed GUI bridge needs. The main form hosts the dialogs, routes the four menu command names, opens the developer message and tip after a load as DoInit does, persists the forms' settings through settings.get/settings.set, and `--dialog <name>` opens one dialog alone for the screen check (presentation only). Landed on phase-7-gui with steps 5 and 2 kept. The CLI stays step 5's one `--dialog <String>` (plus `--dialog-arg`): a name DialogKind knows opens its viewer or editor alone, while filter|modgroups|modgroup-edit|files still open through open_pending_dialog once the session is loaded; TfrmMain::new keeps its three arguments and pending_dialog is an Option<String>. Both dialog hosts, both menu route tables and both post-load paths are kept, and step 2's tree, selection, run_session_command and confirm_window run beside the step-6 dialogs. The screen-tool helper (`cargo xtask gui`) no longer inherits the caller's stdio, records a panic in the session's .error file instead of writing to the closed stderr, catches a panic in run_session and in each request, and holds the desktop after one, so a later shot no longer finds a dead desktop. Tests: 92 GUI unit tests (script combo/filter, analyzer tree building and sort, view-elements tabs/find/compare, localization table model, tip rotation, RTF parser/TOC/search, script host) and 3 new session script tests (subdirectory listing, read, save backup/CRLF); fmt, clippy -D warnings and xtask check green; the workspace test run is green apart from the recorded script elapsed-time flake under load (the_string_list_surface_works printed 00:01 where it expects 00:00 and passes alone). Screen checks captured seven dialogs on private desktops under M:\projects\scratch\p7-step6-evidence, and rich-edit is witnessed at the rebased tip (rich-edit-fixed2.png shows the What's New document with its search, TOC and zoom strip); `cargo xtask gui launch|windows|shot|close` also ran with the launching pipe closed as soon as launch returned. Co-Authored-By: Claude Code <[email protected]>
…ting `crates/xedit-gui/src/screenshot.rs` adds `xedit-gui --screenshot <path> [--screenshot-frames <n>] [--screenshot-delay <ms>] --exit`: the app takes its own capture through egui/eframe's screenshot path (`ViewportCommand::Screenshot` -> `ColorImage`) and writes it as 8-bit RGBA PNG through the `png` crate (MIT OR Apache-2.0, already in the lock file via epaint). The capture waits for the screen the switches name - the load's completion with `--load`, the game or module selection without it, the `Fatal:` screen of a failed load - and `--dialog <name>` opens its dialog inside the same `finish_load` step, so the dialog is in the captured frame. Two painted frames follow by default (`--screenshot-frames`), `--screenshot-delay` adds a wall-clock wait, and `--exit` closes the window once the PNG is written; a screen that is not ready after 180 s closes without one, so a test cannot hang. egui 0.37 runs `App::ui` only on repaint requests and `logic` alone while hidden, so the load result and the capture state machine are polled from `logic` too, and `ViewportCommand::RequestPaintWhileHidden` asks for the painted frames the switch counts. Idle efficiency: the loaded window had been repainting continuously (a full core, hidden and idle) because `ViewportCommand::Title`/`Icon` were sent every frame and every `send_viewport_cmd` requests a repaint of its viewport (`Context::send_viewport_cmd_to`). They are now sent once (`window_titled`), and the repaint policy is documented on the `eframe::App` impl. Measured on the private desktop, hidden, idle after `--game sse --load Update.esm`, plain `Get-Process.TotalProcessorTime` samples over 10 s: before 11.125 CPU-seconds (1.113 cores), after 0.000 CPU-seconds (0.000 cores). Witnessed by the app's own captures on the private desktop (SSE and FO4 installs via `M:/projects/parity-env.sh`), all in `M:\projects\scratch\p7-shot-evidence` and none blank: `sse-main.png` (Update.esm, bare shell; an unknown `--dialog main` suppresses step 6's first-run developer message), `sse-main-developer-message.png` (the same load with that message), `sse-dialog-filter.png`, `sse-dialog-modgroups.png`, `sse-dialog-files.png`, `sse-module-select.png`, `game-select.png` (captured with `--screenshot-frames 5 --screenshot-delay 2000`), `fo4-main.png` (Fallout4.esm), `fo4-main-developer-message.png` and `fo4-dialog-filter.png`. A step-6 `--dialog` name is not capturable alone yet: the dialog-only path's `show_dialogs` consumes the pending name on a Loading pass, so `finish_load` sees none and `startup_dialogs` opens the developer message over it (`--dialog tip` proved it); that is a step-6 ordering bug, recorded in the skill and PLAN notes. The capture recipe and the idle-repaint rule are in `.claude/skills/checking-parity/SKILL.md`. Verification: `cargo fmt --all --check`; `cargo clippy --workspace --all-targets -- -D warnings`; `cargo test --workspace` (the only failure was the documented timing-sensitive `a_child_group_is_walked_through_its_record_once`, which passes in isolation in 0.43 s; the known environmental `copies_a_skyrim_npc_as_an_override` was not reached under fail-fast); `cargo xtask check` (gui 661/0, switch 156/2, game_mode 14/4, tool_mode 17/0, script 3663/0, units 172/43). Co-Authored-By: Claude Code <[email protected]>
Ports the View tab of xeMainForm.pas: DoSetActiveRecord (one column per version of a record, headed by the file name), DoSetActiveRecord(aMainRecords) (one column per selected record, headed EditorID [FileName]), the filler column when more than one record or none is shown, InitNodes/InitChildren rows (MemberCount + AdditionalElementCount and their children), ExpandView with per-node folds, InitConflictStatus colours through widgets::conflict, vstViewBeforeCellPaint's per-cell fill, vstViewPaintText (including the sibling comparison's ctMaster/ctOverride/ctConflictLoses painting), vstViewGetText (value, summary of a collapsed row, focus-dependent label), vstViewNewText/vstViewEditing (in-place edits of editable cells), vstViewHeaderClick/UpdateColumnWidths (standard and fit-all widths) and the bnBack/bnForward selection history (History, CheckHistoryAdd). The model is built from the command layer: records.compare for one record, records.compare_selected for the sibling comparison, whose CompareCell now also reports the element path elements.set takes (the names the view shows, such as CNAM - Author, resolved back to the element before they are reported), EditValue, DisplayName, Summary, DisplaySortKey, IsEditable, Modified, Name and the cell's own ConflictAll, and whose CompareRow reports Node.Index and the definition member name elements.add takes. The view never reads the session directly. A local FormID of zero now resolves to the file header (File.Elements[0]) in record_by_load_order_form_id (with session.info reporting each file's load order file id as file_ids, -1 for the hardcoded executable), the record a selected file row shows and the only record the group index does not hold. The context menus pmuView and pmuViewHeader are drawn per cell and per header column with the pmuViewPopup applicability rules; the items run the commands their ledger entries name: elements.set (Edit and Reset structure), elements.add, elements.remove, elements.clear, elements.remove_from_records, elements.change_member (Next/Previous Member), elements.copy_to_records (both copy items), elements.move (Move Up/Down), records.copy (the six copy-into items, through a small target-file dialog until step 5's xeFileSelectForm), records.delete (Remove), records.set_hidden (Hide and Unhide all), modgroups.create (Create ModGroup, through a small name dialog until step 5's xeModGroupEditForm), modgroups.select (Reload ModGroups), records.compare_selected (Compare referenced records and Sort by this row of a sibling view), refs.get for the referenced records, records.compare for Hide no conflict and Master and Leafs, and elements.get for the clipboard items that need a full path or a signature. After every edit the view is built again (PostResetActiveTree) and the focus follows the edited element. fpnlViewFilter is wired: the name and value texts, and/or and the three keep boxes go to the command's ApplyViewFilter. A navigation tree file row shows its header record (vstNavSelectionChanged's _File.Elements[0]); the tree takes the keyboard as vstNav does (Tab reaches the tree and its rows, the arrows walk the rows, Enter activates -- vstNavKeyDown); a panic in the GUI thread writes xedit-gui-panic.log beside the binary. The shared Selection model of step 2 drives the view: show_nav_selection maps Selection::active_records (and a focused file node to its header record) into set_active_records, the only interface the view consumes, so no selection state is duplicated. Verification: `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace` (the GUI crate: 114 unit tests after this branch's rebase over steps 1b, 2 and 5 (step 3 contributes 55: 10 for the view model, 9 for the grid, 7 for the tree including one that Tabs onto a row), 6 menu-model and 4 selection-dialog integration tests, the conflicts and filter session tests, all green) and `cargo xtask check` (gui 661 entries, 0 pending). Integration (`crates/xedit-gui/tests/view_grid.rs`, skipped without `XEDIT_SSE_DATA`/`XEDIT_FO4_DATA`): an Update.esm override (MISC 0000000A Lockpick) gives two columns headed by file name with the Update.esm column active and definition rows with element paths; the Update.esm header's `CNAM - Author` cell goes through `ViewSource::set_cell` and `elements.set` and reads back with `elements.get`; a navigation `Update.esm` row shows the header's rows through `TfrmMain::activate_nav_file` and the hardcoded `SkyrimSE.exe` row refuses; the Fallout4.esm header shows its Record Header/HEDR/member rows in one column without a filler. Screen check (`cargo xtask gui`, 2026-10-19, evidence in `M:\projects\scratch\p7-screen-evidence\view-step3`): the main window over Skyrim.esm + Update.esm (title `SSEEdit 0.1.0`), the `pmuMain` popup opened with Tab+Enter, the Messages tab, and the View tab with its filter strip. The tooling's posted mouse clicks are not delivered to eframe/winit (a click on `bnMainMenu` does nothing while Tab+Enter opens `pmuMain`), so a navigation row could not be clicked and the grid's cells are proven by the headless tests and the CLI dump rather than by a screenshot. The screen check found two real bugs, both fixed: `then_some(column - 1)` and `then_some(widths.len() - 1)` evaluate eagerly and underflowed at the label column and at an empty header (a panic the first time the View tab drew), and the navigation tree now gives a Tab-focused row the tree's arrows and Enter (`vstNavKeyDown`). Co-Authored-By: Claude Code <[email protected]>
New crates/xedit-gui/src/panes.rs and element_detail.rs, wired into main_form.rs over step 2's shared Selection. Referenced By: lvReferencedBy with fpnlReferencedByFilter and pmuRefBy (ApplyReferencedByFilter through refs.get's filter fields, ApplyReferencedByAlphaSort with the Record->FormID and File->RawFileName click cycle and the upstream captions, UpdateReferencedByTabCaption, pgMainChange's lazy population, Ctrl+A/Ctrl+C/Delete, double-click jump and the covered commands records.compare_selected, records.copy (with disable), records.delete, elements.mark_modified, records.set_visible_when_distant and script.run). Messages: AddMessage, mmoMessagesDblClick's FormID parse and pmuMessages Clear/Save selected/Autoscroll. Information: the Memo1 text verbatim from the DFM, read only and wrapped. The localization view of the selected record through localization.files/localization.get. The three spreadsheets: the DFM columns in Position order, the Skyrim column tweaks, vstSpreadSheet*InitNode's per-game element mapping, header-click sorting, every row of the group, shown only for Oblivion and Skyrim. Element detail: the rules xeElementDetailForm leaves to the view (vstViewEditing, vstViewGetEditText/NewText/DblClick, vstCreateEditor, mniViewEditClick, wbConvertIntFormID) as an inspector whose every commit is elements.set. The navigation selection's focused (else first) main record becomes the active record, and a pane jump reveals the record in the tree. Grid gained header clicks and cell-click focus; menu_model::draw_menu is the shared popup drawer and the form's presentation items run when clicked. Session::file falls back to the file registry so a command can name a master the plugins loaded with (records.list --file Skyrim.esm answered "not loaded" although files.list shows it; the spreadsheet lists the masters' records now); RecordRef gained name, and ElementNode's optional fields deserialize without them. XEDIT_GUI_TAB opens the window on a tab for the screen check (the private desktop delivers no mouse input to the egui window), alongside the existing XEDIT_GUI_SCALE/DPI_UNAWARE/WINDOW/LOG. Verification: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace (1029 passed, 0 failed; the GUI crate 162 unit tests, 13 of them this step's); cargo xtask check (gui 661 entries 0 pending, units 172 tracked 42 pending); tests/panes.rs 5/5 over DLCworkshop01.esm read from its bytes with a stub master and a synthetic Skyrim SE plugin (referenced-by filter/sort, the element editor's dry run and commit, the spreadsheet rows, the localization view, a master named as a file); cargo xtask gui screenshots on the private desktop of the Messages log, the Information memo and the Weapon Spreadsheet (2509 records of Skyrim.esm and Update.esm). Co-Authored-By: Claude Code <[email protected]>
…ob runner
Ports xeWaitForm (IwbWaitForm/IwbProgress, TwbNullWaitForm/TwbNullProgress,
NullCreateWaitForm, the fwDoUpdate delays, GetIsCanceled, TwbProgress's
UpdateStatus), xeOptionsForm (the General/View/Cleaning/UI Settings/UI
Theme/Experts tabs), xeLODGenForm (the worldspace checklist and option
controls), xeLegendForm (the conflict colour grid), xePushLikeButton,
xeEditWarningForm and xeWorldspaceCellDetailsForm, with the GetFormID
prompt beside it.
Core: crates/xedit-core/src/interface/wait_form.rs is the progress seam.
wbCreateWaitForm answers the null form headless (the CLI is unchanged);
GeneralProgress/poll_cancel is the `if wbForceTerminate then Abort` check
and Cancelled is EAbort. The commands poll at upstream's points:
filter.apply (status pre-walk, pass 1, pass 2 -> FilterReport.cancelled),
files.clean (between passes and the cancelled cleaning filter), refs.build
(per file), lodgen.generate (per worldspace, and per reference in the FO4
and Oblivion generators), session.load (per plugin, per module's archives
and around the resources cache). A stop is the `cancelled` error.
GUI: job_runner.rs runs a command on a worker thread with the session
moved to it and handed back; dialogs/wait_form.rs is TfrmWait with the
progress list and a working Cancel. main_form routes filter.apply,
refs.build, files.clean (the UDR/ITM items, which widgets::menu_command
now reports by item id), the LODGen dry run and run and the startup
session.load through it, keeps the window repainting and disables the
menus while one runs. dialogs/options_form.rs is every tab with its keys
read and written through settings.get/settings.set, apply_globals for the
wb* switches, and the live main form taking theme, fonts, colours,
column/row sizes and the shrink flag. dialogs/lodgen_form.rs is the
checklist and options from a lodgen.generate dry run with the per-game
enable rules. dialogs/legend_form.rs is the 7x12 colour grid with
[frmLegend] placement. widgets/push_like_button.rs is the checkbox-style
button state machine. dialogs/edit_warning_form.rs counts down with
[Usage] EditWarnCount. dialogs/prompts.rs installs both core callbacks and
hands the synchronous call to the frame loop (NextObjectID is restored on
a cancelled FormID prompt); dialogs/worldspace_cell_details_form.rs and
dialogs/form_id_prompt.rs are the dialogs.
Verification: cargo fmt --all --check, cargo clippy --workspace
--all-targets -- -D warnings and cargo xtask check green (gui 661/0,
units 172 tracked 36 pending; the seven units are ported in
upstream-map.toml), cargo test --workspace green. GUI 133 unit tests plus
tests/step7.rs 9 tests: wbForceTerminate and a wait form cancel stop
filter.apply at their poll points, files.clean/refs.build/lodgen.generate
return cancelled, the options round trip through settings.set ->
settings.get -> state_from_values, both prompt seams answered. Screen
checks on the private desktop (FO4): options-{general,view,cleaning,ui,
theme,experts}.png, lodgen.png, legend.png, edit-warning.png,
worldspace-cell.png, formid.png, wait-form.png, cancel-early.png
("Loading cancelled" and the module selection back), logs beside them in
M:\projects\scratch\p7-step7-evidence.
Co-Authored-By: Claude Code <[email protected]>
Steps 3, 4 and 7 were three parallel forks of `crates/xedit-gui/src/main_form.rs` from the same base (the step-6 tip), so they could not be fast-forwarded onto each other. This merges them on the phase branch. Step 3's view grid wins the conflicts: its grid carries the tree rows, the per-cell conflict fill, the header context menu and the `(column, shift)` header click, which step 4's simpler grid did not have. Step 4's record panes (`panes.rs`, `element_detail.rs`) are grafted onto it and adapted to the step-3 grid API; step 7's job runner, wait form and core-callback prompts go on top of both. `DoSetActiveRecord` had two halves by then, so the names are split: the view grid keeps `set_active_record` (`ViewRecord`) and the panes get `set_pane_record` (`RecordSelection`). The message log is now the step-4 `MessagesPane`, so the view's own message items moved with it. `cargo check --workspace --all-targets`, clippy, `cargo fmt --check`, `cargo test --workspace` (75 test binaries, no failures) and `cargo xtask check` are green. Co-Authored-By: Claude Code <[email protected]>
The element detail editor was shown as a right panel inside the central panel's closure, so egui gave the central area no room for it and the panel painted over the view's filter strip. It is now a side panel of the tab area, beside the central panel. The filter strip is a wrapped row, like upstream's `fpnlViewFilter` flow panel, so the filter controls wrap instead of running under the detail panel. Co-Authored-By: Claude Code <[email protected]>
The GUI is at step 7 of 9, not step 1: the selection dialogs, the viewers and editors and the modal tools are all in place, and the screenshot of each dialog confirms that it renders. Co-Authored-By: Claude Code <[email protected]>
ejams1
added this pull request to stack #106
October 20, 2026 22:39
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.
Ports the upstream GUI (xeMainForm and 21 dialogs) onto the existing command layer with egui, per decision 1 of docs/PLAN.md. Step list and premises are in docs/PLAN.md Phase 7. Every action calls a session command; no new commands are added.
Landed so far
Steps 3, 4 and 7 were parallel forks of
main_form.rsfrom the same base and were integrated by hand; the merge notes are under step 3 in docs/PLAN.md. The integration also fixed the view tab's element detail panel, which was drawn inside the central panel and painted over the filter strip.Validation
cargo check --workspace --all-targets,cargo clippy --workspace --all-targets,cargo fmt --check: cleancargo test --workspace: 75 test binaries, 0 failurescargo xtask check: the gui coverage has 661 entries and 0 pending🤖 Generated with Claude Code