Rectangle selection moves to the left button on bare sheet (Ctrl adds);
right-click is the menu again. Dragging a selected box by its title bar
moves the whole selection as one undoable step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tidy Layout
- Box heights are settled in left-to-right and right-to-left sweeps so each
box lines its slot up with the other end of its wires; gaps above the
minimum are free, and columns are re-sorted to untangle crossings.
- A wire that skips columns takes a placeholder in each column it crosses,
so boxes are ordered and spaced around it.
- A box nothing feeds is held back far enough left that wires from further
left always enter higher slots of the same target.
Wire routing
- Only the short stubs at a wire's two slots may pass close to a box; the
rest of every run keeps its clearance, so no wire grazes a box edge. A link
with no clear route is shown as knobs.
- Lanes that cross no other wire are tried first; climbing wires route top
source first, falling ones bottom first, and a deferred wire defers the
rest of its gap so lanes keep that order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The top/left margin no box may enter is grey and ungridded, so the white
sheet starts where boxes can actually be placed.
- Saving/unsaved state no longer resets the canvas to white: the sheet goes
white/pink and the area around it grey/greyish pink.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Tidy Layout lays linked boxes out left to right by signal flow, in as few
columns as the longest chain allows; feedback loops are cut where they run
backwards, and sources sit just before the boxes they feed.
- Boxes with no link on the open sheet go to the bottom of the first column,
alphabetically.
- A box that wires skipping its column would cross is pushed down below them.
- The wiresheet is fixed at meta's 255-step reach; outside it is light grey.
Dragging keeps a box's top-left corner on the sheet.
- Sox write_i4 wraps to signed, so meta with x >= 128 reaches the device.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preference 'Lazy loading on connect?' (on by default). Off reads the whole app
from app.sab when connecting: 8 s instead of 116 s on a P-ION, 0.9 s instead of
16 s on an Orion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Off reads the whole app from app.sab when connecting. Read at each connect, so a
change takes effect on the next one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saves the app on the device, reads app.sab and fills the Sox cache with the tree,
config and links in one go: 8 s instead of 116 s for 230 components on a P-ION,
0.9 s instead of 16 s for 194 on an Orion. Sox keeps the cache current from
there as before. On any failure, or when app.sab does not match the running
tree, it falls back to lazy loading. A sheet's first open reads only runtime
values; a tree expand with nothing to load no longer rebuilds the DOM.
bench_sab_seed.py times both ways against a device.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a wiresheet open, disconnecting first unsubscribed its components and
waited for the reply, which a restarting device never sends, so the editor
froze until the request timed out. After restart, reboot, quit or hibernate
the session is now closed without asking the device anything; a plain
Disconnect still unsubscribes first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The device goes down without answering, so the editor waited out the 30 s
Sox timeout before dropping the session. The invoke now waits 3 s (time for
DASP to resend it once) and the editor disconnects either way; an error
reply from the device is still reported and keeps the connection. Covers the
App right-click actions and the restart Put with Files offers.
- sox: request/requests/invoke take a timeout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A kits step after the zip is chosen, laid out as the Manage Kits panel:
installed kits (what the app needs, greyed), installed but not used, kits
available on the computer, not supported; the Action cell offers Keep at /
Upgrade to / Downgrade to / Add at / REMOVE; Upgrade all kits to latest
version. Kits not in the backup can be added, with their dependencies.
The ticked kits make app.sab's schema (encode_sax takes the kit list, as
Ontrol's PutApp rewrites it). When they are not the device's kits, or
Rebuild kits.scode is ticked, kits.scode is built for them with Ontrol's
sedonac from the Niagara install, after checking the natives the kits call
against the device's platform manifest (PutApp.checkNativeCompatibility).
- kit_build.py: platform manifest lookup, native check, sedonac run
- palette.kit_versions: a checksum released more than once stands for its
highest version (ontrolPlatform 1.0.1, not 1.0.0)
- palette.unsupported_kits takes the kit list to judge against
- sab_app: a type from a kit the app does not list is a clear error
Tested on a P-ION: a rebuild for its own kits is byte-identical to the one
it runs; adding and then removing the logic kit both come back running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Right-click a component (or the app) with two or more children to move them
up and down; OK applies the new order, which is the order they run in. On a
device the order goes there first via Sox reorder, checked against the
device's children, and the file is left alone if it is refused. Undoable.
Also: device host in editor.properties.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follows Ontrol's Workbench tool (BPutWithFilesTool / PutApp): the backup's
app.sax is encoded for the device's kits and put as app.sab.writing, renamed
to app.sab.stage; the platform service's deleteFile clears fonts/* and
images/*, then images, fonts (from Sedona home's bank, in the device's cut)
and the boot logo go. The zip is checked against the device first: same
platform (Orion, Rion or P-ION, from the platform id), and kits the device
can load. Falls back to the backup's app.sab, with its kits.scode if needed.
- sab_app.encode_sax: .sax -> .sab, ported from Py_sab2sax with Java's value
parsing (fromLiteral, nullbool/nullfloat, Integer.decode); byte-identical
to sedonac on the saved backups
- app_backup.native_image: port of OntOxImageUtil's png -> native .bin
- sox.put_file / rename_file
- Get with Files: Orion's boot logo is system/logo.jpg
- A restart that goes unanswered is taken as the device going down
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ctrl+click toggles a link in the selection; Del or the link popup deletes
every selected link after one confirmation, as a single undoable step.
On a device, links the device refuses stay and are named in one warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wires now round their bends, hop over the wires they cross, and show a dot
where wires from one output split. Wires sharing a pin may share a trunk.
Routes keep one grid square from a box's top and bottom and two from its
sides, never turn closer than two squares beside a box, and keep half a
square from other wires. The runs pinned to every slot are claimed before
any wire is routed, so an early wire no longer lays its run along a row a
later slot has to leave by. A wire that cannot fit the grid spacing falls
back to the old tighter one rather than turning into knobs.
The styling geometry lives in wire_style.py; draw_links is split into
collecting, routing and painting around a WireJob dataclass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a sheet on a device rebuilds the DOM, so every element is new and
the history, which matched by identity, pruned each sheet as soon as it was
recorded. On a device entries now match by id, and Back reopens a view on
the current element rather than the snapshot it was recorded with.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ctrl+click picks out several boxes on the sheet, and Ctrl/Shift+click several
rows in the tree. Copies are named on from their original (Add1 -> Add2), sit
one step right and down, and keep the links that ran between the copied
components. One undo step; on a device the originals are read back in full
and the copies made there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A palette drag dropped on a tree row adds the component inside that row's
component, placed under its lowest child. The row under the pointer is
highlighted, and resting on a collapsed branch opens it, loading it from
the device when live without moving the selection or the open sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chosen kit is remembered in editor.properties [Palette]; a file without it
opens on its first kit. Saving preferences now keeps the other sections.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Redo is gone in both file and device mode: the Edit menu entry, Ctrl/Cmd+Y
and Ctrl/Cmd+Shift+Z, and the redo stack. An undone step is dropped.
Undoing past the last save now leaves the saved depth unreachable, so a
new edit cannot be mistaken for the saved state.
Renames on a device are undoable: LiveRenameCommand renames back on the
device first, and warns and clears history if the device refuses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Right-clicking a tree row selects it, and on a device that reopens the
sheet and rebuilds the DOM after the popup captured the row's element.
Delete then ran on a stale element: the device delete went through but
no undo step was recorded. Popup actions now look the component up by
id when they run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Uses LiveComponentCommand, so undoing a delete puts the user back on
the device with its exact cred, perm and prov.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Undo and redo now work against a connected device; each step is sent
there through Sox. Deleting a component first reads its whole subtree
back (config, unloaded children, links, place among siblings) so undo
can recreate it; recreated components take new device ids, which the
DOM elements pick up so later steps still reach them. Binary Buf
config (a user's cred) is read with read_prop, since the cache decodes
every Buf as a Str. Moves, tidy layout, and Properties / page editor /
user edits record only what the device accepted.
A step that does not fully reach the device warns and clears the
history. History is also cleared on every view change in live mode,
as opening a sheet rebuilds the DOM from the device.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The final vertical run into an input slot now stands at least
1.5 * GRID_SIZE in front of the box, so the arrowhead sits on a straight
run instead of on the bend. Column gaps in tidy layout widen to match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuilding the tree (live child loads, edits) now restores every row that
was expanded, matched by component id or name path. Double-click no longer
toggles a row through ttk's default binding.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tested on a P-ION (fw 1.32) at 192.168.1.252 with everything else as
in 5acca52: with the 0x81 header forced on, the app save is answered
but the app.sab file open never is, and the device drops off the
network. Without it, the same Get with File works.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The save and file-open requests used their own fixed resend intervals
(5 s / 15 s) and the hello asked for 256-byte packets. Ontrol's Sedona
Java stack does neither and works with the P-ION: the standard backoff
(1, 2, 4, 8 s) sends a busy device a few copies with the same seqNum,
which its receive window drops. Both are removed.
Also fixes a race in SendWindow.send(): the packet entered the window
before its payload and sent_time were set, so housekeeping could resend
it with an empty payload, and the device would then drop the real one
as a duplicate.
Tested on a P-ION at 192.168.1.252, App > Get with File: save 3.2 s
(2 resends), app.sab + kits.scode in 11.3 s with 500-byte chunks
(was 14.6 s at 256), device stayed up, files identical to a 256-byte
backup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A real P-ION (fw 1.32) restarted on every Sox file get. Ontrol's Niagara 4
driver gets app.sab from the same device, so its settings are copied:
- DASP hello asks for 256-byte packets (idealMax/absMax), so file chunks
are 238 bytes
- the message-length header (0x81) goes to Orion only, decided once the
platform id is read
- the app is saved on the device before app.sab is read, the save resent
every 5 s rather than 1 s (a P-ION save takes 2-3+ s)
- the file open is resent every 15 s; DASP gains a per-packet resend
interval for this
Tested on a P-ION at 192.168.1.252: save 3.2 s, app.sab + kits.scode +
app.sax in 15.8 s, no resends, device stayed up. The unsaved-changes
question became a notice, since the app is now always saved first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reads app.sab and kits.scode over Sox file transfer, decodes app.sab to
app.sax (byte-identical to sedonac), and on Orion/Rion also fetches the
images and fonts the app names, with their sources from Sedona home's
banks. Offers to save the app on the device first when it is dirty, and
asks where to save with <Sedona home>/store/apps and
app-<device>-<app>-<timestamp>.zip filled in.
DASP trace lines are now printed by their own thread: printing from the
network threads to an unread stdout pipe blocked the receiver and made
file transfers time out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Connect and Discover stay enabled while a device is connected. Pressing
Connect in the dialog drops the current device before the new one is
tried; cancelling the dialog leaves the connection alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a file now drops the live connection once a file is chosen, without
asking. File > Exit and the window close box go through quit_app, which
closes the Sox session properly instead of leaving it for the device to
time out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hovering a slot row highlights the half under the pointer and shows a
direction cursor. Pressing on a slot drags a rubber band; dropping on
another slot links them, dropping on a title bar opens the Link dialog
with the dragged slot preselected, and a no-entry cursor marks a slot
that would refuse the link. A right click that never moves still opens
the context menu.
The palette now leaves out types whose facets carry @palette=false, and
kits with nothing placeable left.
Also points the default device host at 192.168.1.251.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The schema built from a Sox session spelled checksums as signed decimals,
so no kit matched a manifest: Installed showed raw checksums and Action
read "Keep at checksum ...". They are now 8 hex digits like a .sax file,
and checksums compare regardless of zero-padding.
A device session only loads folders as they are opened, so only sys
counted as used. Used kits now come from the whole component tree, read
one request at a time so a small controller is not flooded into dropping
the session.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rename is offered from both the canvas and the navigation tree. Names are
checked against Sedona's Component.checkName rules (1-7 ASCII chars, a
letter first, then letters, digits or '_') and must be unique among
siblings. In file mode link paths through the old name are rewritten and
the rename is one undo step; in live mode it goes to the device via Sox
and warns if the device rejects it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In live mode the device is now written first and the document only takes
what the device accepted. Every live_hooks failure records a reason, and
warn_failure() shows it in a dialog and the status bar instead of failing
silently.
- Move, properties, OX page editor: rejected values are rolled back and listed
- Add/remove link: no longer edits the DOM alone when an end can't be resolved
- Add/delete component: report why the device refused
- Tidy Layout: sends each new position to the device, reverts refusals,
marks the document dirty, and pushes no undo step in live mode
- User Manager: add (one Sox add carrying cred/perm/prov), edit and delete
now go to the device
- Binary Buf slots (e.g. User.cred) are sent as Buf bytes, not as Str text
- Bump version to 0.0.0.136
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The root tree row now names the app component, "app (<host:port>)" or
"app (<file>)", and offers Properties from its context menu and on
double-click. The live DOM's <app> element now carries its type and slot
values, and the refresh-by-id lookup finds it, so the view shows real
sys::App values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The device builds the schema and version replies in a fixed 512-byte
packet and drops whatever does not fit. Read strings defensively, recover
a cut-off checksum from the one local manifest matching its prefix, and
fail with a clear message when the kit list cannot be recovered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The font and image rows are now dropdowns filled from <sedona home>/ontrol:
fontBank/imageBank on Rion, fontBankXL/imageBankXL on Orion. Orion lists
.oxfl fonts in landscape and .oxfp fonts in portrait, read from the
platform service's screenOrientation (loaded from the device in live mode
when the tree has not reached it). Images show their file extension while
the slot keeps the bare name. The font dialog is gone.
In portrait the Page Editor draws a 480x800 screen in
frame_orion_portrait.png, and the canvas renders text with the .oxfp
fonts, whose glyphs are stored a quarter turn anticlockwise.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit mode, snap to grid and the widgets/properties pane are now toggles on
the main toolbar while the Page Editor is open. Outside edit mode the page
and the property sheet are read-only; drags snap to the 10px grid when
snapping is on. The default window grows by the right pane's width so the
canvas is not squeezed when the pane shows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Page Editor canvas now paints widgets WYSIWYG instead of as
labelled placeholder boxes, following the ontrolOx draw() code and the
R-ION firmware:
- text drawn with the device's own bitmap fonts (fontBank .bin /
fontBankXL .oxfl), Windows font fallback; alignment, clipping at the
box edge, half-size subscript digits, transparent vs filled boxes;
the line is centred on the box even when the font is taller
- each widget shows the text its `in` selects: NumericLabel formats,
lookup items, Boolean true/false text and colours, date and time
- images from the local image banks, centred or cropped; missing ones
drawn as the device's red/white checker; page background image
- 10x10 pixel grid over the page colour, with a Grid toggle
- widgets hit-tested by layout box so transparent labels stay
clickable; hidden widgets shown as a dashed outline
- layout row gets an X/Y/Width/Height dialog
- scaled device frame cached per zoom (redraws ~15 ms)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Page Editor's right pane now shows the selected widget's properties
under the widget tree, laid out like Workbench's Ox editor rather than
as raw Sedona slots:
- layout (x, y, width, height), font (name, size, style)
- alignment / transparent / subscriptDigits from the attributes bits
- shape, RGB565 colours with a swatch and picker
- DateLabelSet date options split out of dateFormatOptions
- lookup labelText editor (prefix, items, suffix) and NumericLabel
printf-format editor, min/max as one row
- image name picker, slot link chooser for in/blink/visibility/touch
- Buf text decoded from base64 (Latin-1), plain text in live mode
Edits commit on Enter/focus-out/selection as one undo step and are
written to the device in live mode. Canvas drag-moves now use the same
path, so they are undoable and reach the device too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Types with intermediate parents (e.g. ontrolOx::PushButtonImage ->
Widget -> Service -> Component) only got Component + own slots, so
config/runtime streams were decoded against the wrong slot list and
x/y/imageFileName came out wrong.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Makes the 320x240 screen cutout at offset (76,62) transparent,
matching the Orion frame style so the page background color
shows through correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Toolbar zoom buttons (in/out/reset/fit) and Ctrl+MouseWheel now
control the page editor canvas when it is active. Frame image is
scaled via PIL for smooth rendering at any zoom level.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three PNG frames in assets/ with transparent screen cutouts:
- frame_orion.png (1144x849, screen at 191,169 = 800x480)
- frame_rion.png (433x396, screen centered at 76,62 = 320x240)
- frame_rion_portrait.png (856x1134, screen at 190,170 = 480x800)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New ox_page_view.py with OxPageViewPanel: canvas rendering at device
resolution (800x480 Orion, 320x240 Rion), RGB565 color conversion,
widget tree, selection handles, drag-move, device frame image support.
Wired into main.py with context menu, history, panel lifecycle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Permissions dialog now uses two-level UI: compact Edit User with >> buttons
opening a separate grid dialog (Groups x Operator/Admin columns) with
Clear All / Set All and auto-cascade (write implies read, admin implies operator)
- Provisioning has its own >> popup dialog
- Removed live mode restrictions for add/edit/delete user operations
- Broadened type detection and added safe int conversion for live mode values
- Double-click on tree node or canvas box opens special view (User Manager for
UserService, Properties for other components)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New user_service_view.py with table of sys::User children, add/edit/delete
dialogs, SHA-1 credential hashing, 32-bit permission bitmask (4 groups x 7
flags), 8-bit provisioning byte, and undo support. Wired into main.py with
context menu entry, panel management, history navigation, and live-mode DOM
resolution for correct display on connected devices.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Populate prop elements with manifest default values when adding
components in both offline and live mode
- Send config values from manifest in SOX add command for live devices
- Refresh button in properties panel now fetches fresh values from
the connected device before re-reading the DOM
- Properties panel fetches device values on open in live mode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix add component from palette in live mode: resolve <app> element
as comp_id 0, fix type_id lookup to use type_name_map, set type_name
on newly added SoxComponent, sync device values into DOM after add,
write meta position back to device, and subscribe new component to COV.
Fix delete component in live mode: clean up subscriptions for deleted
component and all its descendants.
Add tree root context menu with device app actions (save, hibernate,
quit, restart, reboot) in live mode. Device save clears dirty state.
Clean up connection lost handling to do a full disconnect.
Fix slot registry to copy base slots and renumber IDs correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Authentication failures now show "check username and password", busy
devices say "device is busy", etc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Discover dialog shows a device list with Port, Wait Time fields and a
Discover button. Selecting a device and clicking Connect (or double-
clicking) opens a credentials prompt for username/password, then
connects. The Connect dialog is reverted to its original simple form.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Send DISCOVER broadcast (type 0) to 255.255.255.255 and the Sedona
multicast address 239.255.18.76 to find devices on the network.
Responses are listed in a Treeview showing IP and platform ID.
Selecting a device fills the Host field; double-click connects.
Wait time is configurable (default 10s, range 1-60s).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The dispatch check compared the raw hostname string against the UDP
source IP, silently dropping responses when they differed (e.g.
"localhost" vs "127.0.0.1"). Resolve hostname to IP at session init.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In live mode the DOM is rebuilt from the device when switching sheets,
creating new element objects. The pending link from the old tree could
not be found by identity in canvas_link_map. Re-resolve it by from/to
attributes after the rebuild so the selection matches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_slot_name_by_id() now resolves SOX link slot IDs against the
inheritance-flattened final_components_registry instead of the
per-type slot_registry whose local IDs were wrong for types with
intermediate parents (e.g. RegFloat→Register→Component). This
produced incorrect link endpoints, inflated linked-slot visibility,
and different box sizes between file and live modes.
Also: tighten routing constants for denser wiring, add wiresheet
PNG print/export (Ctrl+P), sort link routing jobs by gap distance,
remove debug route logging, and fix live tree-expand re-selection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Slots with no flags (sensor, controlSignal, out_*) were not skipped
during config property reads, consuming bytes that the device never
sent and misaligning every subsequent config value (setpoint,
proportionalBand, etc.) into tiny denormalized floats like 2.36875e-41.
Changed the filter in _apply_props from "skip if runtime" to "skip if
not config" so only explicitly config-flagged slots are read from config
messages. Also switched iter("slot") to findall("slot") in the slot
registry builder to avoid picking up nested descendants.
Float display now defaults to 2 decimal places everywhere (wiresheet
and properties panel), honouring the precision facet when one exists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds _fmt_float() helper that formats 32-bit floats with :.6g precision
but appends .0 when the result has no decimal point, matching Niagara
and .sax file display conventions (e.g. 175.0 not 175, 0.0 not 0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sox reads 32-bit floats that Python promotes to 64-bit doubles, producing
display artifacts like 22.011558532714844. Using :.6g format gives clean
values matching .sax file precision (22.0116, -19.9574, 175, 14).
Applied to both initial DOM build and COV update paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Opening a file while connected to a device prompts to disconnect first.
Connecting to a device while a file is open prompts to save unsaved
changes, then closes the file before showing the connect dialog.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Build root children directly under <app> instead of wrapping them in
a <comp name="app"> element. This matches the .sax DOM structure
where <app> is the container, so component paths like /AHU/TmpCont/X
match between link references and get_component_path.
- Fix _comp_path to produce /AHU/TmpCont/X format (skip root id 0,
leading /) matching the .sax link path convention.
- Format float property values to 4 decimal places instead of full
Python float precision (22.0116 vs 22.011558532714844).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add type_name_map on SoxClient so components loaded after connect
resolve proper names (e.g. control::And2) instead of kit::typeN.
This lets the slot registry and final_components_registry match,
so component boxes show their slots and property values.
- Load live schema gracefully: skip missing kit manifests instead of
aborting, compile the kits we have, and warn about missing ones in
the status bar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three fixes so live wiresheets render identically to offline .sax files:
1. Inherit sys::Component base slots (meta) into every type's slot
registry so component x/y positions are read from the device.
2. Place <link> elements under a <links> container element instead of
directly under <app>, matching the .sax structure that draw_links
and build_linked_slot_index expect.
3. Deduplicate links (each link appears on both endpoints in Sox) so
wires are not drawn twice.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>