535 Commits

Author SHA1 Message Date
775e5a1768 chore: release 1.2.0 v1.2.0 2026-06-12 11:45:12 -04:00
e20680bfe0 chore: check in WIP docs and da07 frame capture before machine switch 2026-06-12 11:36:57 -04:00
0e9761e0c3 docs: queue BL-E13 (da07 subnet bits/mask entry) as next up in the backlog 2026-06-12 11:36:56 -04:00
8ad923657c docs(da07): spec for subnet bits/mask dual display and entry (firmware-verified 1-8 limit) 2026-06-12 11:30:49 -04:00
d83a61e65f docs(da07): record the post-Refresh STATUS lag as accepted firmware behavior
The ~H operational-statistics frame is the only source of device status and
is not solicitable; it arrives once per second only after the full refresh
sequence drains into SVC_POLL. Reviewed and accepted as-is; candidate
perception-only mitigations noted in the entry instead of BACKLOG.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 11:07:38 -04:00
a665993a7c feat(da07): decode ~P channel-serial frames; verify ~H on hardware; refit channel columns on device switch
Second hardware capture (saved as tests/da07/fixtures/capture-2026-06-12-
steady-state.txt, now with "> "-prefixed outbound lines) settles the remaining
protocol questions:

- ~P is a per-channel CT sensor serial (P + device + channel + 16-hex), matching
  the E-frame tails byte-for-byte - decoded as ChannelSerial and applied to the
  channel model. It is NOT a custom-name echo: a ~D field-11 name write is
  Z1-ACKed but never reported back, so channel names are write-only by protocol
  (a typed Tag reverts on Refresh, same as the legacy). BL-E5 part 2 closed.
- The ~H realtime layout is verified against 68 real frames: counters, LE
  buffered count, LE time, then 16 per-device status nibbles that matched the
  live Devices tab (devices 2,3 COM, rest OK). Indicator triples remain
  unobserved (no active alarm groups on the test station).
- The write queue is hardware-confirmed: 26 writes drew 25 ACKs with one
  observed idle-retransmit recovery; both device toggles survived a Refresh.

UI fix: the Channels tab now refits its columns after a device switch. TableTab
auto-fits only when the row count changes, but switching devices swaps the whole
content at the same row count, leaving Serial/Model sized for the previous pod
(fitted to empty serials, truncating 16-char ones).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:58:47 -04:00
b52b780f2b fix(da07): queue outbound commands one-in-flight - the station drops burst writes
Hardware-found: toggling two device rows' Active and refreshing showed only the
second toggle landed. The 2026-06-12 capture explains it - 16 burst channel
writes drew only 2 Z1 ACKs; the station processes one inbound frame at a time
and silently drops the rest. The legacy never burst: MakeCommand only queued,
and each inbound Z popped exactly one command (Main.frm SendCommand).

The controller now queues commands and keeps one in flight: Z1 confirms and
advances; Z0 retransmits; a Z2 idle while unconfirmed means the frame was
dropped, so it retransmits (all queued commands are idempotent - this improves
on the legacy, which lost silent drops), capped at 3 transmissions then dropped
with errorOccurred. Link frames (data-frame ACKs, idles) bypass the queue. The
queue is cleared on stop() so a closed port cannot wedge it; a refresh queued
behind writes re-arms the load-settle timer at transmit time.

pendingWritesChanged(depth) drives a new footer WORKING dot - the modern
version of the legacy "Working n" status caption, per user request: lit while
settings changes await the station's acknowledgement.

The simulator now Z1-ACKs every command frame like the real station (applying
the command BEFORE acking, so synchronous pumping cannot reorder same-field
writes) - its perfect burst handling is exactly why this bug was sim-invisible.
CIM_DA07_CAPTURE now also records outbound frames ("> "-prefixed) so the next
hardware session sees both sides of the handshake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:44:59 -04:00
1d0a68f02c fix(kit): stop SummaryStrip flashing a ghost window on every grid rebuild
set_summary replaced its count labels with label.setParent(None) while they
were visible - reparenting a visible widget to None promotes it to a real
top-level window, which Windows shows with full native decoration (app icon,
min/max/close, label-sized blank body) until the deferred deleteLater runs.
Every grid rebuild in every module flashed one such ghost per replaced label;
the DA-07 optimistic-apply change made it glaring (8 flashes per device-row
Active toggle, one per channel write). Fix: hide the dying label and let
deleteLater collect it while still parented.

Found via a live user session on DA-07 hardware: synthetic probes could not
reproduce it (offscreen platforms swallow the flash; QTest clicks missed the
toggle), so this adds an env-gated diagnostic - CIM_UI_SPY=<log path> installs
a window spy (core/ui/window_spy.py, hooked in shell/app.py) that logs every
top-level Show with the widget creation stack; the user's log produced 84 ghost
windows pointing at the exact line. Screenshot kept in docs/samples; session
write-up in docs/DA07-FIELD-NOTES.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:22:24 -04:00
ec6d2578ae fix(da07): decode the E-frame tail as the CT serial - drop the phantom disp field (BL-E5 part 1)
Verified against the repo's first real DA-07 capture (157 frames, saved as
tests/da07/fixtures/capture-2026-06-12-refresh.txt): the ~E payload ends at the
alarm byte, followed only by an optional 8-byte CT sensor serial - there is no
disp field and no name field on the wire. The phantom disp read (inherited from
the VB6, which had the same bug feeding its hidden Disp column) ate the serial's
first byte, producing the reported "Tag column cuts off the first characters"
symptom.

Removed disp everywhere: decoder E branch, ChannelRecord, the Channels-tab Disp
column (later columns shift left one), controller.set_channel_disp, encoder
CH_DISP (ICD §11 has no ~D field 9), the simulator's disp byte, repo snapshot,
and help text. The simulator now emits the wire-faithful E layout (serial-only
tail). The Tag column shows name -> serial -> catalog default, matching legacy
precedence; a written name lives only in the local model until ~P (the presumed
custom-name frame, absent from this capture) is decoded - BL-E5 part 2 stays
open, needs-capture.

The capture also re-confirmed no ~H arrives during a refresh (it streams
periodically afterwards), which is what made the stale-model revert fire every
second once live. Docs updated: BL-E5 part 1 done, HW-VERIFICATION item 8
resolved, field-notes entry added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:42:57 -04:00
53ab24284f fix(da07): apply settings writes to the local model optimistically
Found on real hardware (first DA-07 + one pod): every edit reverted within
seconds once the STATUS column populated. The DA-07 never echoes a settings
write back (the legacy VB6 grid WAS the model and kept the edited cell), but
the controller only updated its models from inbound frames - so the periodic
~H/~G/~F frames rebuilt the tabs from the stale model and reverted the edit.
The simulator masked it: it only sends ~H during a refresh, never periodically.

Every controller set_* now applies the value to its local model right after
sending and emits the matching *Changed signal (set_channel_active also emits
devicesChanged for the Devices-tab roll-up; remove_device drops the device and
its channels locally). The station stays the source of truth - the next
Refresh overwrites local state with whatever it actually stored.

Hardware findings are now logged in docs/DA07-FIELD-NOTES.md (newest first),
cross-linked from HARDWARE-VERIFICATION.md and CLAUDE.md. Open follow-ups
recorded there: confirm writes survive a Refresh on hardware, and check
whether DA-12 has the same latent bug.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:58:54 -04:00
3ad55161d5 docs: backlog entries for offline device catalog (BL-R2), launcher cable-driver installer (BL-S5), friendly error messages (BL-10) 2026-06-12 08:34:23 -04:00
46f63cd353 chore: release 1.1.0 v1.1.0 2026-06-11 16:45:47 -04:00
921cbbef5f Merge feat/instrument-iomodbus: Instrument design system phase 6 - IOModbus adoption (spec 5.2 toolbar with catalog and export menus plus connection chip, 5.7 device sidebar with bus-parameters footer, 5.8 activity log card, 5.5 grouped device-settings list with register-governed RO and label-raw picklist choices, channels grid kit kinds/summary/write feedback) and the 5.9 launcher redesign (logo header, selectable cable rows, 3-up tool cards); kit hardening (off-catalog choice preservation, export-action closure lifetime contract + regression test, EDIT_HINT constant, PicklistDelegate rebase); DESIGN-SYSTEM.md rewritten around the Instrument spec; BL-DS5 closed - rollout complete 2026-06-11 16:32:31 -04:00
0441dab79d docs: record Instrument phase 6 (IOModbus + launcher); rollout complete 2026-06-11 16:24:30 -04:00
4aa9f9245c fix(theme): transparent backgrounds on launcher card summaries and cable-panel labels (phase 6 visual smoke) 2026-06-11 16:18:36 -04:00
541545a520 docs: rewrite DESIGN-SYSTEM.md around the Instrument spec and kit
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:13:51 -04:00
ad6aee7186 feat(shell): launcher header and 3-up tool cards per spec 5.9
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:04:30 -04:00
206f9c03db fix(theme): cable title defers to type_styles font; row radius token; hide empty detail 2026-06-11 15:58:41 -04:00
378e4ff116 feat(shell): cable card port rows per spec 5.9 - radio, name, detail, mono port id 2026-06-11 15:51:16 -04:00
51db84f161 test(iomodbus): tighten channels-kit kind assertion; note catalog channel count 2026-06-11 15:46:22 -04:00
cac403a502 feat(iomodbus): channels grid adopts kit kinds, summary strip, write feedback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:38:35 -04:00
0f22a1d82d refactor(iomodbus): settings-tab hint hardening, None export sheet, dead-code removal 2026-06-11 15:24:34 -04:00
f645529b98 fix(kit): preserve off-catalog values in settings-list choice editors 2026-06-11 15:24:33 -04:00
9a74b874c3 feat(iomodbus): device settings migrate to the grouped settings list (spec 5.5) 2026-06-11 15:06:24 -04:00
fe62e1982d docs(iomodbus): refresh main-window docstring for the sidebar/activity-log layout 2026-06-11 15:00:26 -04:00
88565662f8 feat(iomodbus): device sidebar (spec 5.7) and activity log card (spec 5.8)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:54:32 -04:00
6978da8805 test(core): guard the export-action closure lifetime contract; themed width measurement
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:49:29 -04:00
e74fa524ec fix(iomodbus): own Export menu via shared engine; fixes dialog teardown crash
2a39bfe folded the xlsx exports into the Catalog menu with hand-rolled handlers
(collect_sheet/collect_all/save_sheets_dialog + bound-method connections),
which broke two things:

1. Teardown crash (tests/iomodbus/test_ui_smoke.py::test_calibration_dialog_
   computes): pytest-qt holds only weakrefs to registered widgets. The shared
   add_export_actions closures (capturing parent) were the only strong,
   C++-anchored reference keeping the MainWindow's Python wrapper alive past
   the test frame; with only weakly-stored bound-method slots left, the window
   wrapper died by refcount at function exit, shiboken deleted the ownerless
   C++ QMainWindow, and the cascade deleted the parented CalibrationDialog's
   C++ object - while the dialog's wrapper survived in qframelesswindow's
   windowEffect reference cycle, so qtbot teardown closed a dead C++ object
   (RuntimeError: Internal C++ object already deleted). Verified by weakref/
   shiboken6.isValid experiments with and without a window-anchoring closure.

2. Wrong menu: spreadsheet export of live grid data is not a device-catalog
   (IOModbus.txt) file operation.

Rework per the Instrument spec: data export gets its own "Export" QToolButton
menu between the Refresh/poll group and the Catalog menu (separator-delimited),
built by the shared add_export_menu_actions engine - restoring the empty-export
notice and the window-anchoring closures. The Catalog menu is back to Import
Devices / Export Devices / Supported Devices. Both menus keep tooltips visible.

Toolbar sizeHint: 1190px themed offscreen (1019px untheme) vs 1240px budget.
Tests: tests/iomodbus 145 passed; full suite 1012 passed, zero errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:33:50 -04:00
2b85def003 feat(core): add add_export_menu_actions - the export pair on a QMenu
Extract the shared _export_actions builder from add_export_actions so the same
dialog/file-naming/collection logic can target either a toolbar (DA-12, DA-07)
or a QMenu (for toolbars on a width budget). The builder's docstring documents
that the handlers must stay closures: a closure slot is stored strongly by the
connection, so the actions anchor the parent window's Python wrapper for the
life of the C++ action - bound-method slots are stored weakly, and rewiring
them as window methods lets an otherwise-unreferenced window be GC'd mid-test,
cascading C++ deletion into child dialogs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:32:46 -04:00
2a39bfec5f feat(iomodbus): spec 5.2 toolbar - suite button, connection chip, catalog menu, disconnect
- Add suiteRequested Signal; act_suite triggers it (SuiteWindow wires this to
  show_launcher via the existing open_module hook - no shell change needed)
- Add ConnectionChip (chip) to toolbar; set_connection_source(source) feeds it
  from module._start_simulator ("SIM") and _connect_to_port (port string)
- _on_connection updates chip and toggles act_connect text Connect/Disconnect
- _toggle_connection dispatches to _ctrl.stop() (disconnect) or _connect() (connect)
- Delete _build_menu / menuBar usage; fold Import Devices, Export Devices,
  Supported Devices into Catalog toolbar QToolButton menu with tooltips visible
- Also fold Export This Tab / Export All Tabs into Catalog menu (BL-DS5 trim):
  with IBM Plex Mono loaded the two export toolbar buttons push sizeHint above
  1240px; grouping all file-level operations under Catalog is semantically
  coherent and keeps the toolbar at ~1093px (themed offscreen) with real margin
- "Log Measurements" -> "Log" (tooltip preserved) - further trim per task spec
- Drop " Address: " and "Update: " bare QLabels; addr_edit gets
  setPlaceholderText("Address"), update_combo gets a tooltip
- resize 1100x720 -> 1280x760 (suite default)
- module.py: set_connection_source("SIM"/"port") before attach in both
  _start_simulator and _connect_to_port

Tests: 5 new in tests/iomodbus/test_main_window_toolbar.py; suite 1010 passed.
Toolbar sizeHint: 1093px (themed+offscreen) vs 1240px limit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:15:26 -04:00
8e79964a8c refactor(iomodbus): rebase PicklistDelegate onto the kit InstrumentDelegate (BL-DS-P3 carry-over) 2026-06-11 13:53:23 -04:00
20ca6d1105 test(kit): hoist EDIT_HINT import to module scope in summary-strip test 2026-06-11 13:50:25 -04:00
927bf0f23b refactor(kit): promote the summary-strip edit hint to kit.EDIT_HINT 2026-06-11 13:44:23 -04:00
7a161fee49 docs: phase 6 (IOModbus adoption + launcher) implementation plan 2026-06-11 13:41:40 -04:00
c2de7c9f94 Merge feat/instrument-da07: Instrument design system phase 5 - DA-07 adoption (spec 5.2 toolbar with connection chip and four-command station menu, settings-list station tab with curated metadata, spec 6 devices empty-slot treatment with kit QUIET_ROLE, channels/alarm status tags and toggles, calibration restyle, ComboBoxDelegate rebased onto the kit delegate) 2026-06-11 13:19:09 -04:00
03eb158ab8 fix(da12): make station-commands menu tooltips visible (backport of the da07 finding) 2026-06-11 13:18:55 -04:00
fda18eb9fd docs: record Instrument phase 5 (DA-07 adoption); BL-DS-P5 closed 2026-06-11 13:12:39 -04:00
3a5bb2ecda feat(da07): calibration grid kinds, units, record count summary
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:05:27 -04:00
5f18cd4aa0 feat(da07): alarm tab adopts status tags, active toggle, mono addresses, summary 2026-06-11 12:57:11 -04:00
b41406a0dd docs(da07): refresh channels docstring and pending caveat; assert toggle precondition 2026-06-11 12:52:12 -04:00
5f51b55c93 feat(da07): channels grid adopts status tags, alarm rows, active toggle, units, summary, write feedback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:42:00 -04:00
8b96fe4e15 test(da07): cover off-status dash and type-delegate pending routing; uniform delegate routing in on_check 2026-06-11 12:36:58 -04:00
7d64d3a8a6 feat(da07): devices tab adopts spec 6 empty-slot treatment, status tags, summary 2026-06-11 12:30:06 -04:00
b774a7d4ac docs(da07): restore the advanced-row signature edge-case note from the da12 sibling 2026-06-11 12:25:17 -04:00
3f56f85dbe feat(da07): station tab migrates to the grouped settings list (spec 5.5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:19:31 -04:00
b19e48c53d feat(da07): curated station-settings metadata for the 5.5 settings list
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:09:45 -04:00
83479bad65 fix(da07): make station-commands menu tooltips visible 2026-06-11 12:06:05 -04:00
f452aa2a90 feat(da07): spec 5.2 toolbar - suite button, connection chip, station-commands menu, disconnect
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:00:42 -04:00
724711c2e6 refactor(kit): rebase ComboBoxDelegate onto InstrumentDelegate (BL-DS-P3 carry-over)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 11:53:22 -04:00
75c3dfd437 test(kit): tighten quiet-role alignment probe; document quiet paint tradeoffs 2026-06-11 11:50:23 -04:00