<feed xmlns='http://www.w3.org/2005/Atom'>
<title>labUI.git/core, branch main</title>
<subtitle>Data acquisition program written in Python for use with serial communication devices.
</subtitle>
<id>https://git.kolset.xyz/labUI.git/atom?h=main</id>
<link rel='self' href='https://git.kolset.xyz/labUI.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/'/>
<updated>2026-08-04T21:51:33Z</updated>
<entry>
<title>Fixed version number to be v0 for pre-release</title>
<updated>2026-08-04T21:51:33Z</updated>
<author>
<name>Christian Kolset</name>
<email>ckolset@colostate.edu</email>
</author>
<published>2026-08-04T21:51:33Z</published>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/commit/?id=4a77f5825efe2905e22998cd784983eb914c1fc2'/>
<id>urn:sha1:4a77f5825efe2905e22998cd784983eb914c1fc2</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Fix acquisition pipeline stutter with many channels</title>
<updated>2026-08-03T21:32:56Z</updated>
<author>
<name>Christian Kolset</name>
<email>ckolset@colostate.edu</email>
</author>
<published>2026-08-03T21:32:56Z</published>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/commit/?id=9bcad5d3af258eb72bc9c8ac87e3f1713e1b8b80'/>
<id>urn:sha1:9bcad5d3af258eb72bc9c8ac87e3f1713e1b8b80</id>
<content type='text'>
Batches per-tick device readings into one signal emit instead of one
per channel, evaluates derived channels once per tick instead of once
per raw sample, and makes ChannelBuffer.window() cost scale with the
window size instead of total buffer history. Strip chart X-range now
scrolls on an independent ~30fps timer instead of only snapping when
new data lands, which was the main visible cause of the stutter.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Merge branch 'feat/auto-updater'</title>
<updated>2026-08-02T17:05:48Z</updated>
<author>
<name>Christian Kolset</name>
<email>christian.kolset@gmail.com</email>
</author>
<published>2026-08-02T17:05:48Z</published>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/commit/?id=1c349a982026736f4c2a26951d9d0aefe7b963bc'/>
<id>urn:sha1:1c349a982026736f4c2a26951d9d0aefe7b963bc</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Remove Debug window feature</title>
<updated>2026-08-02T07:38:09Z</updated>
<author>
<name>Christian Kolset</name>
<email>christian.kolset@gmail.com</email>
</author>
<published>2026-08-02T07:38:09Z</published>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/commit/?id=fa0304793a4525db68cb53b527d695bfa2c65966'/>
<id>urn:sha1:fa0304793a4525db68cb53b527d695bfa2c65966</id>
<content type='text'>
Debug button, DebugWindow, and the stdout/stderr tee (core/debug_log.py)
weren't wanted. developer_mode toggle stays — still gates device
Simulation Mode checkboxes and verbose terminal logging.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Rename LabDAQ → LabUI and overhaul plugin system</title>
<updated>2026-08-02T01:06:36Z</updated>
<author>
<name>Christian Kolset</name>
<email>christian.kolset@gmail.com</email>
</author>
<published>2026-08-02T01:06:36Z</published>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/commit/?id=0aa59c13af65beeae104662593b21ba4d8a37789'/>
<id>urn:sha1:0aa59c13af65beeae104662593b21ba4d8a37789</id>
<content type='text'>
Branding:
- Rename app, window title, file extension (.labdaq → .labui), user data
  dirs (~/.labui/), spec file (labdaq.spec → labui.spec), and APP_NAME
  throughout all source, docs, and config files

Plugin system:
- Plugins no longer bundled in the PyInstaller build — installed at
  runtime by users via Settings → Plugins → Install Plugin (zip)
- PluginManager now takes user_dir + extra_scan_dirs; user plugins live
  in ~/.labui/plugins/, dev scan additionally covers project plugins/
- install_from_zip / uninstall / is_user_installed added to PluginManager
- vendor/ dir inside plugin zips: prepended to sys.path at load time so
  plugins can ship their own deps without requiring pip on end-user machine
- source_url field in manifest.json: shown as Download button in the
  missing-plugins dialog when a profile requires an absent plugin
- Frozen-app pip install now targets ~/.labui/plugin_packages/ using a
  real system Python (sys.executable is the exe in frozen builds)

Profile loading:
- Profile now stores plugins_manifest snapshot (id, name, version,
  source_url) alongside plugins_enabled
- On load, missing or dep-broken plugins trigger MissingPluginsDialog
  before the rest of the profile is applied; user can install from zip
  or download via source_url in-dialog, or cancel the load
- Plugin reconciliation only enables installed plugins — missing ones
  are not written to enabled.json

UI:
- Version label added to status bar (bottom-right, muted colour)
- Settings → Plugins tab: Install Plugin… button, per-plugin Remove
  button for user-installed plugins, live list refresh after install/remove

Docs:
- New docs/building.md covers the full release pipeline
- docs/plugin-development.md updated for new install flow, vendoring,
  source_url, profile behaviour, and distribution instructions

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Add PyInstaller packaging and tufup-based auto-update pipeline</title>
<updated>2026-07-31T21:52:28Z</updated>
<author>
<name>Christian Kolset</name>
<email>ckolset@colostate.edu</email>
</author>
<published>2026-07-31T21:52:28Z</published>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/commit/?id=bfbdd0c19910f464e779fa64cc0ec8590f8e37c1'/>
<id>urn:sha1:bfbdd0c19910f464e779fa64cc0ec8590f8e37c1</id>
<content type='text'>
Full pipeline, verified end-to-end against the real installed tufup
0.10.0 API (initial docs/summaries turned out inaccurate in places —
e.g. the apply method is download_and_apply_update, not update;
confirmed by inspecting installed package source directly rather than
trusting docs alone):

- core/version.py: single-source app version constant.
- labdaq.spec: PyInstaller onedir build (must be onedir, not onefile —
  tufup replaces individual files in the install dir on update).
  Bundles ui/*.qss, plugins/ (needed for runtime plugin discovery), and
  repository/metadata/root.json once repo_init.py has produced one.
  Built and smoke-tested: the frozen exe launches and stays running.
- scripts/release/{repo_init,repo_release}.py: maintainer-run release
  tooling using tufup.repo.Repository, manual local signing (keys never
  touch CI). Both actually run end-to-end during development of this
  feature against a real build, not just written and assumed correct.
  Longer expiration_days than tufup-example's CI-oriented defaults
  (targets/snapshot/timestamp 90d instead of 7d/7d/1d), since we're
  signing manually, not on an automated daily schedule — see
  scripts/release/README.md for the re-signing cadence this still
  requires even between releases.
- core/updater.py: thin Client wrapper. Refuses to run outside a
  frozen build (getattr(sys, "frozen", False)) since there's no
  installed bundle for tufup to update in `python main.py` dev mode.
  Bootstraps the bundled root.json into the metadata cache dir on
  first run — tuf.ngclient.Updater loads root.json from local disk on
  construction, it does not fetch it remotely by design (the root of
  trust can't come from the same server being verified).
- core/app_settings.py: factored out app_data_dir() (was inline in
  _settings_path()) so the updater's metadata/target cache dirs live
  in the same per-user location as settings.json, deliberately outside
  the install directory an update can replace/move.
- Settings &gt; General: version display + "Check for Updates" button,
  manual-only per discussion (no silent background network calls or
  surprise restarts for a lab-instrument-control app).

Metadata/targets are hosted on this repo's "updates" GitHub Release —
a fixed tag, not a normal per-version tag, because TUF's top-level
metadata needs a stable URL across app versions. No such GitHub-Releases-
hosting example exists in tufup or tufup-example; verified this by
fetching tufup-example's actual GitHub Actions workflow file directly
after a web search wrongly suggested one existed — the design here is
ours, not copied from upstream.

Not yet done, deliberately left for the user: running repo_init.py for
real (generates production signing keys), and creating the actual
"updates" GitHub Release. Both are irreversible-ish, security-sensitive,
externally-visible actions outside what should happen without the
user directly driving them.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Merge branch 'feat/device-naming'</title>
<updated>2026-07-29T19:41:29Z</updated>
<author>
<name>Christian Kolset</name>
<email>ckolset@colostate.edu</email>
</author>
<published>2026-07-29T19:41:29Z</published>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/commit/?id=934d3fc18bc4fc0e05d26a12d635915b00bd8c8d'/>
<id>urn:sha1:934d3fc18bc4fc0e05d26a12d635915b00bd8c8d</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Add Developer Mode setting, Debug window, and gate simulate behind it</title>
<updated>2026-07-29T19:28:24Z</updated>
<author>
<name>Christian Kolset</name>
<email>ckolset@colostate.edu</email>
</author>
<published>2026-07-29T19:28:24Z</published>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/commit/?id=cf33095f77e6ff902fb69932fa704eb6192b3418'/>
<id>urn:sha1:cf33095f77e6ff902fb69932fa704eb6192b3418</id>
<content type='text'>
New Settings &gt; General &gt; "Developer mode" checkbox (default on, so
this repo's simulate-by-default workflow is unaffected out of the box)
persisted through the existing core/app_settings.py load/save
functions. Exposed to code that can't easily receive the settings dict
via core.app_settings.is_developer_mode()/set_developer_mode(), a
small in-memory cache MainWindow keeps in sync whenever settings are
loaded or applied.

Debug window (ui/windows/debug_window.py): minimal first pass per the
plan — a live console. core/debug_log.py tees stdout/stderr into a
ring buffer + Qt signal (installed once in main.py, before anything
prints), so the window shows everything printed since app start,
including from background poll threads, not just what's printed while
it happens to be open. Its toolbar button is hidden unless developer
mode is on.

Simulate gating: every device's Simulation Mode checkbox/dropdown is
now hidden when developer mode is off — ui/add_device_dialog.py (also
covers the motion_capture camera panel, which shares this same
checkbox rather than having its own), and the per-device config
widgets in analog_input.py, arduino_device.py, digital_io.py,
nidaqmx_device.py, serial_device.py. Hiding rather than force-clearing
an already-simulating device's state — an existing simulated device
keeps working if developer mode is turned off later; the option is
just not offered again until it's back on.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Add editable device display name, persist it across profile save/load</title>
<updated>2026-07-29T19:03:24Z</updated>
<author>
<name>Christian Kolset</name>
<email>ckolset@colostate.edu</email>
</author>
<published>2026-07-29T19:03:24Z</published>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/commit/?id=d9ca58e48cf92438087ac24ee261c2c4911316b1'/>
<id>urn:sha1:d9ca58e48cf92438087ac24ee261c2c4911316b1</id>
<content type='text'>
DeviceInfo.name existed but was hardcoded per device type and never
operator-editable. Adds a Display Name field to Add Device and makes
the Info-tab Name field in the device config dialog editable, and
threads the name through get_save_config()/ProfileManager.apply() so
a custom name survives a .labdaq save/reload instead of reverting to
the type default. Name is applied post-construction rather than as a
constructor kwarg, since no device factory declares a "name" param.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Derived channels: signal display names as vars, auto-update template code, None op</title>
<updated>2026-06-10T05:18:43Z</updated>
<author>
<name>Christian Kolset</name>
<email>christian.kolset@gmail.com</email>
</author>
<published>2026-06-10T05:18:43Z</published>
<link rel='alternate' type='text/html' href='https://git.kolset.xyz/labUI.git/commit/?id=91b0b613f0b58498315ef91f6a541dd1089b88de'/>
<id>urn:sha1:91b0b613f0b58498315ef91f6a541dd1089b88de</id>
<content type='text'>
- Variable names derived from signal display name (e.g. "Acceleration X" → acceleration_x)
  instead of raw channel ID; all auto-names lowercase with underscores
- Template code auto-updates x[0]/x[1] refs to named vars as sources are added/changed;
  stops updating once user edits the code manually
- Derivative wrt-channel mode uses separate template with x[1] substituted to channel var
- Added "None" passthrough operation (result = x[0])
- source_names persisted in .labdaq profiles

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
