Commit Graph
12 Commits
Author SHA1 Message Date
lemke_h 45be1f3b72 gui fixes 2026-09-10 19:31:54 +02:00
lemke_h c52ce03d02 eco-box 2026-09-07 17:11:28 +02:00
lemke_h 9c87c23d18 fixes 2026-08-28 14:25:39 +02:00
gac bernina c7f653ba7c Log activity from terminal console and JupyterLab/Voila kernels too, merge multi-stream logs with separable filters
Previously only eco desktop's console had an activity log (via
eco.widgets.console_kernel's ZMQ-message interception, which only works
for a kernel with a real iopub channel a Qt widget attaches to) -- a
plain terminal `eco console` session and JupyterLab-native/Voila kernels
had none. eco.widgets.kernel_registry.install_shell_logger() closes that
gap using IPython's pre_run_cell/post_run_cell events instead, which work
identically for any real InteractiveShell. Wired into startup_inline.py
(terminal), eco_cli.py's generated kernel profile (JupyterLab-native), and
voila_app.ipynb/jupyterlab_app.ipynb (Voila / bare `eco jupyterlab`).
Narrower than console_kernel's approach by design: only input plus the
resulting value/error are captured, not raw stdout/stderr streaming.

Also proactively set use_jedi=False for the terminal's `eco console`
startup script, matching the reasoning already applied to desktop's
console (see console_kernel.build_console_widget's docstring) -- Jedi's
introspection was measured slower and less correct against eco's dynamic,
lazily-resolving namespace.

Every kernel log's entries are now tagged with which stream they came
from (TimelineEntry.session, a "kind:label · HHMMSS" label derived from
the log's own session_start record) so eco.logs's timeline keeps merging
every session by default but LogTimelineQt gained a second, AND-combined
"Stream:" filter row alongside the existing "Show:" kind row to separate
them back out on demand. Fixed a same-second label collision the first
version of the label had (two sessions of the same kind+label started
within the same second, e.g. under test, produced identical labels) by
falling back to a trailing id fragment from the log filename.

Cross-user session browsing (the log_dir parameter already supports it)
is deliberately deferred, not built this pass.
2026-08-22 09:30:28 +02:00
gac berninaandClaude Sonnet 5 4116d4afb6 Make eco desktop's scope default follow .ecorc like every other subcommand
Real bug: p_desktop hardcoded scope_default=None regardless of .ecorc, so
`.ecorc`'s scope= was silently ignored for `eco desktop` specifically --
even when .ecorc's own command= is something else (e.g. console), running
`eco desktop` on its own should still pick up .ecorc's scope, same as
`eco console`/`eco webapp`/`eco jupyterlab` already do. Now
scope_default=defaults["scope"], matching every other subcommand; the
"plain console, no Namespace panel" case (see eco.widgets.desktop_app) is
still reachable, just needs an explicit `-s ""` or a blank `scope =` in
.ecorc instead of being the unconditional default.

Verified via eco-dev desktop --help with an ECORC pointed at a scope=alvra
file (and command=console, to confirm it isn't gated on command matching)
-- correctly showed "(default: alvra)".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 23:06:07 +02:00
gac berninaandClaude Sonnet 5 91be28b04b Use real JupyterLab Sidecar panels for eco jupyterlab, not Voila's inline layout
Real bug reported after live testing: `eco jupyterlab` rendered the
Namespace launcher as a large inline block (full notebook width, not a
narrow sidebar like eco desktop's dock), and opening a device apparently
popped a Qt window instead of an ipywidgets view. Root cause: eco jupyterlab
was pointed at the same voila_app.ipynb used by `eco webapp`, which renders
everything via eco.widgets.widget_tray.make_namespace_dashboard -- built
for Voila, which (per eco.widgets.jupyter_sidecar's own existing docstring)
has no Lumino shell to dock into at all, so there was never a "narrow
sidebar" option available there in the first place. JupyterLab DOES have a
Lumino shell, and eco already had eco.widgets.jupyter_sidecar.open_in_sidecar
for exactly this -- it just wasn't wired up to the CLI.

- NamespaceLauncherWidget now takes a generic on_open(obj, label) callback
  instead of a hardcoded WidgetTray, so it can drive either a WidgetTray
  panel (Voila/webapp, unchanged) or a Sidecar panel (JupyterLab, new).
- New eco.widgets.jupyter_sidecar.open_namespace_dashboard(): docks the
  Namespace launcher in its own Sidecar panel, and routes each opened
  device into its own Sidecar panel too -- the closest browser-side
  analogue to eco desktop's dockable Namespace panel + tiled device docks.
- New eco/jupyterlab_app.ipynb (packaged; added to package-data, learned
  that lesson the hard way earlier this session) uses it, with its code
  cell collapsed by default (standard nbformat source_hidden metadata) --
  JupyterLab has no server-side route to auto-open a real Console tab (it's
  a frontend-only command, confirmed by grepping jupyterlab's own source
  for any matching handler), so a notebook is still the minimal anchor tab
  needed for Sidecar/ipywidgets to have something to render into; this
  keeps its visible footprint to just the dashboard.
- New _write_jupyterlab_notebook(): opening an *existing* .ipynb always
  uses the kernel named in its own file, not JupyterLab's server-wide
  default -- so eco_cli.py now regenerates a copy with its kernelspec
  pointed at the just-registered eco-<scope> kernel, same as
  _register_eco_kernel's other generated files.

Voila (`eco webapp`) is unchanged -- make_namespace_dashboard/WidgetTray
remains correct there, confirmed working earlier this session.

Verified: the notebook's cell code executes without error against a real
eco-bernina kernel (same mechanism used to verify the kernel registration
earlier), a scripted Sidecar dashboard construction + simulated click
against a fake namespace, and the full test suite (338/340 -- the 2
failures are pre-existing in test_config_lazy_init.py, unrelated).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 22:51:38 +02:00
gac berninaandClaude Sonnet 5 166a5ef44b Fix desktop console/launcher lag, add startup-script export, default to dark theme
Real bug found via manual testing ("even not closing, desktop is super
unresponsive and laggy... the qt console, when entering something takes
ages to update, 30s ish"): eco.utilities.config.Proxy (the lazy device
proxy) only special-cases __class__ to avoid resolving while unresolved --
__dir__ isn't one of lazy_object_proxy's protected operations, so it
forwards straight to __wrapped__, fully constructing the device (real
EPICS calls) the moment anything calls dir() on it. Confirmed for real:
dir() on a still-lazy complex device took 62.7s. Since eco desktop's
console now has ~180 bare device names in its namespace (many still lazy),
and any completion mechanism scanning that namespace calls dir() on
candidates, typing in the console could trigger this on nearly every
keystroke. Added Proxy.__dir__, mirroring __class__'s existing "stay shy
to introspection" pattern exactly. Also measured Jedi-based completion as
independently slower (5.8s vs 0.001s) and less correct (0 matches vs 63)
than the classic completer for this dynamic a namespace -- now disabled
for every eco console (build_console_widget, so it applies uniformly to
both in-process and subprocess kernels).

Second, separate perf issue in the Namespace launcher panel: its
live-refresh timer (every 2s) fully rebuilds the table, and the Required
column (added last session) used a real QCheckBox+QWidget+QHBoxLayout per
row -- constructing/destroying ~120 native Qt widgets twice a second at
bernina's scale is real, visible GUI-thread cost. Switched to checkable
QTableWidgetItems (blockSignals()'d during the rebuild so programmatic
setCheckState() calls don't themselves trigger spurious required_names()
writes) -- same functionality, far cheaper to rebuild.

New: "Save Startup Script..." in the desktop window's Workspace menu --
writes a standalone, executable .sh (paired with a .json workspace file,
same format as Save Workspace) that relaunches `eco desktop` with just
this session's open widgets reopened. Lazy loading means nothing else in
the namespace gets touched, so this is a fast, minimal per-task dashboard
instead of the full namespace. New --workspace PATH flag on both eco_cli's
desktop subcommand and eco.widgets.desktop_app's own CLI loads a workspace
on startup; desktop_app._main() now builds the window and loads it before
entering run()'s blocking loop, instead of auto-starting straight into
that block.

Also: the Namespace launcher's lazy (not actively loading) entries no
longer show a spinner-adjacent hourglass -- gray text alone marks
"not built yet"; the animated spinner is now reserved for "actually
initializing right now". And the desktop window's Material dark theme
(qt-material's dark_teal.xml) is now the default -- it already existed as
an opt-in --theme dark, but wasn's what a first `eco desktop` showed; added
'none' as an explicit choice for anyone who wants native OS style back,
and qt-material to eco[gui]'s extras so pip installs get the real theme
too, not just the built-in approximation.

All confirmed via scripted offscreen reproductions of each bug before
writing its fix (not just after), plus the full test suite (336/338 --
the 2 failures are pre-existing in test_config_lazy_init.py, confirmed
via git stash, unrelated to any of this).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 22:17:40 +02:00
gac berninaandClaude Sonnet 5 cadfcd2667 Restructure eco CLI into subcommands; fix namespace resolution and add required-checkbox launchers
CLI: eco --ui {shell,lab,voila,desktop} -> subcommands eco {console,desktop,
webapp,jupyterlab}, console being the default (bare `eco` == `eco console`).
desktop's -s/--scope has no forced default any more: without it you get a
plain embedded Qt console with no Namespace launcher panel, instead of
silently defaulting to bernina. Both desktop and jupyterlab get a
--console/--no-console flag (console on by default). jupyterlab, when given
-s, now registers a real eco-<scope> Jupyter kernel (IPython profile startup
file + kernelspec, regenerated each launch) and makes it JupyterLab's
default kernel, then opens a real `jupyter console` on it in the launching
terminal -- so a fresh Console/Notebook opened from JupyterLab's own
launcher gets eco.<scope> preloaded too, not just one pre-built notebook.
Also added --set-rcfile [PATH] to persist the current invocation into an
.ecorc instead of launching.

Real bug found and fixed: Namespace.append_obj writes constructed/lazy
device objects onto sys.modules[root_module] (the scope module, e.g.
eco.bernina), not onto the Namespace instance itself -- the instance only
tracks name -> state bookkeeping (lazy_items/failed_items/initialized_items).
eco.widgets.desktop_app's launcher panel and console-namespace-population
both assumed `getattr(namespace, name)` worked, which silently raised
AttributeError for every single namespace entry -- opening or initializing
anything from the desktop launcher failed outright. Added
Namespace.resolve_item() as the canonical fix (the lazy-or-failed-or-
initialized dict chain reinitialize() itself already used internally) and
build_namespace_vars() (mirrors `from eco.<scope> import *` -- bare names
like mono/att, not just a `namespace` variable) for both the in-process and
subprocess console-kernel paths.

Also ported this same fix to eco.widgets.widget_tray (the ipywidgets
dashboard backing webapp/jupyterlab), replacing the old status_collection-
based member dropdown (initialized-only, no lazy/failed visibility) with a
NamespaceLauncherWidget matching desktop's launcher: browse every
registered name including lazy/failed ones, click to initialize+open.

New: a Required column/checkbox (Namespace.required_names()) in both
launchers -- desktop's Qt _NamespaceLauncher (QListWidget -> QTableWidget,
Name + Required columns) and the new ipywidgets NamespaceLauncherWidget.

Verified via eco-dev across all four subcommands against the real bernina
scope (shell/console, desktop with and without -s, webapp, jupyterlab
including the registered-kernel mechanism via a live jupyter_client
KernelManager), plus the full test suite (44/44 in test_desktop_app.py,
323/325 overall -- the 2 failures are pre-existing in
test_config_lazy_init.py, unrelated to any of this).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 20:01:05 +02:00
gac berninaandClaude Sonnet 5 779a93cfa6 Rename eco.epics -> eco.epics_utils to stop shadowing pyepics; rework eco --help
eco shipped its own subpackage literally named eco/epics/, colliding with
the third-party `epics` (pyepics) package. Whenever eco's own package
directory landed on sys.path, `import epics.pv` resolved to eco's shadow
instead of pyepics. The previous commit patched one trigger (eco_cli.py's
shell UI), but --ui lab/voila hit the identical collision via a different
route: voila_app.ipynb lives inside eco/, and Jupyter/Voila kernels start
with the notebook's own directory as sys.path[0]. Confirmed via eco-dev:
both lab and voila's dashboard execution failed with the exact same
ModuleNotFoundError: No module named 'epics.pv'.

Renaming the subpackage (eco.epics -> eco.epics_utils, ~90 mechanical
import-line updates) removes the collision at the root, regardless of
trigger. Verified via eco-dev across all four --ui modes (shell, lab,
voila, desktop) with no more epics.pv errors anywhere.

Also reworked eco_cli.py's --help: shows [default] on -l/--lazy, adds a
full description of all four --ui front-ends including desktop, documents
.ecorc configuration, and adds --set-rcfile [PATH] to persist the current
invocation's scope/profile/lazy/ui into an .ecorc instead of launching.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 16:17:33 +02:00
gac berninaandClaude Sonnet 5 82cebdcce8 Fix eco shell UI shadowing pyepics with eco's own eco.epics subpackage
_run_shell launched IPython with file-mode `%run <path>`, which prepends
the run script's own directory to sys.path. Since startup_inline.py lives
inside the eco package, that put eco's own install dir on sys.path, and
its eco/epics/ subpackage then shadowed the real third-party epics
(pyepics) package -- breaking `import epics.pv` in assembly.py with
ModuleNotFoundError. Switching to module-mode `%run -m eco.startup_inline`
resolves it through the normal import system instead, avoiding the extra
sys.path entry entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 15:34:45 +02:00
gac bernina 97a9020149 updates to run 2026-08-21 15:10:25 +02:00
gac bernina a01556e584 big blast of changes. 2026-08-18 21:56:17 +02:00