Commit 72b35d7
* [Audit v2 #360] Extract LogViewer + PortPickerPanel from mcp_dock.gd
Audit-v2 #360 partial extraction. Two cohesive Control subtrees move to
res://addons/godot_ai/dock_panels/:
- log_viewer.gd: owns _log_section, _log_display, _log_toggle,
_last_log_count, the tick() that appends new buffer lines, and the
_on_log_toggled signal handler. McpDock._process now calls
_log_viewer.tick() instead of the inline _update_log helper, and
_apply_dev_mode_visibility flips _log_viewer.visible directly.
- port_picker_panel.gd: owns _port_picker_section, _port_picker_spinbox,
range-validates the spinbox value, and emits port_apply_requested(int).
McpDock listens, writes the EditorSetting, and reloads. The panel
exposes seed_suggested_port() for the on-surface refresh that
_update_crash_panel calls when the picker becomes visible.
Why only these two and not the audit's full four-panel list:
ServerStatusPanel and ClientRowController were deliberately deferred
because their UI scatters across the dock layout — extracting them
needs either visible UI reorganization (out of scope for an internal
refactor) or a coordinator-Node pattern with property-accessor
forwarders on McpDock that re-tangle the very state they claim to
move. A future refactor probably wants extract-by-concern (e.g.
mcp_async_refresh_state_machine.gd, mcp_client_action_dispatcher.gd)
rather than extract-by-panel. mcp_dock.gd carries a comment block
explaining this so the next contributor doesn't redo the analysis
from scratch.
Side-effect on script/local-self-update-smoke: the field-storage smoke
harness anchored its vNext Dict/Array injection to "var _last_log_count
:= 0", which moved into LogViewer. The harness comment explicitly
predicted this ("anchoring to a recently-moved field would silently
miss the regression rather than failing loudly") — the loud failure
fired on this PR's pytest run. Anchor migrated to the equally-stable
"var _last_connected := false" with an updated comment so the next
field move is guided.
Local pre-commit gate:
- ruff check src/ tests/ — All checks passed
- pytest -q — 892 passed
- script/ci-check-gdscript — All GDScript files OK
- Headless GDScript test_run via MCP — 1211/1227 passed, 0 failed
(16 skipped are pre-existing macOS-headless camera-current cases)
mcp_dock.gd: 2424 → 2371 LOC (-53). Two new files, 80 + 65 LOC.
Closes #360 partially. A follow-up issue will track the deferred
ServerStatusPanel / ClientRowController extractions with the
extract-by-concern direction noted in mcp_dock.gd.
https://claude.ai/code/session_01B8h9tYowSrjvzUA1hEc1KC
* [Audit v2 #360] /simplify cleanup on extracted dock panels
Three findings from /simplify's reuse + quality agents, applied:
1. Promote McpDock._make_header to `static`. LogViewer was duplicating
the exact same 5-line helper plus the COLOR_HEADER constant. Now
the panel calls McpDock._make_header(text) directly — no circular
preload (class_name references are symbolic, resolved by the
compiler, not via preload). Net: -7 LOC across the two files,
single source of truth for the dock's section-header style.
2. Type annotations on the extraction seams:
- LogViewer._log_buffer: McpLogBuffer (was untyped)
- LogViewer.setup(log_buffer: McpLogBuffer) (was untyped)
- McpDock._log_viewer: LogViewerScript (was comment-as-type)
- McpDock._port_picker_panel: PortPickerPanelScript (was comment-as-type)
The Mcp* class_name'd types were registered globally already; the
preload-script types work as variable type annotations in GDScript
4.x. No new files needed.
3. Signal-based logging-toggled instead of Demeter reach-through.
LogViewer was reaching into _connection.dispatcher.mcp_logging from
_on_log_toggled — knowing about a connection field with a dispatcher
field with an mcp_logging flag. Inconsistent with PortPickerPanel,
which already routes through a clean signal. Now LogViewer emits
`logging_enabled_changed(enabled: bool)`, the dock connects and
forwards onto the dispatcher in `_on_log_logging_enabled_changed`.
Side benefit: LogViewer.setup() no longer needs the connection
parameter, so its signature shrinks to `setup(log_buffer)`.
Local re-run of the pre-commit gate after these edits:
- script/ci-check-gdscript — All GDScript files OK
- pytest -q — 892 passed
- Headless GDScript test_run via MCP — 1211/1227 passed, 0 failed
https://claude.ai/code/session_01B8h9tYowSrjvzUA1hEc1KC
* [Audit v2 #360] /review: signal-emit tests + setup() fold
/review surfaced one actionable gap: no direct tests for the new panels'
emit contracts. This commit pins both:
- test_port_picker_panel_emits_apply_requested_for_in_range_port: in-range
port → spy receives the value.
- test_port_picker_panel_skips_emit_for_out_of_range_port: out-of-range
port (after relaxing min_value) → spy receives nothing. The clamp on
the panel side is the dock's only defense against bogus
EditorInterface.set_setting writes if a future SpinBox replacement
doesn't clamp.
- test_log_viewer_emits_logging_enabled_changed_on_toggle: toggle false
then true → spy receives [false, true] in order.
All three tests instantiate panels in isolation rather than going
through `_dock._build_ui()`. The port-picker tests have to: emitting
through the dock-wired signal fires `_dock._on_port_apply_requested`
which calls `_on_reload_plugin()` → `EditorInterface.set_plugin_enabled
(false/true)`, tearing down the test runner mid-suite. Caught the hang
on the first run; fixed by isolating. The log-viewer test is also
isolated for symmetry and to avoid pulling in the dock's heavier
`_build_ui()` for what's a single-signal contract test.
The spies are inner classes (`_PortApplySpy`, `_LogToggleSpy`) matching
the existing `_RestartDispatchPlugin` spy pattern at the top of the
file. Multi-line lambdas with closure-captured locals didn't reliably
evaluate the body under the test runner — typed-receiver methods are
the safe form.
Side effect: panels' UI build moved from `_ready()` → `setup()`. The
dock instantiates panels detached from any tree in test fixtures
(`_dock = McpDockScript.new()` then `_dock._build_ui()`), so `_ready`
never fires and the panel's internal Controls stayed null when tests
poked at them. Building synchronously inside `setup()` (idempotent via
a null-check on the canonical Control) mirrors the pre-extraction
inline-build behavior. Production code paths get the same UI; only
the timing changed.
Local re-run after these edits:
- script/ci-check-gdscript — All GDScript files OK
- pytest -q — 892 passed
- Headless GDScript test_run via MCP — 1214/1230 passed, 0 failed
(1227 baseline + 3 new tests; 16 pre-existing macOS-headless skips)
https://claude.ai/code/session_01B8h9tYowSrjvzUA1hEc1KC
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 26f10d1 commit 72b35d7
7 files changed
Lines changed: 288 additions & 103 deletions
File tree
- plugin/addons/godot_ai
- dock_panels
- script
- test_project/tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
0 commit comments