Files
eco/STARTUP_MODES.md
T
2026-08-21 15:10:25 +02:00

188 lines
10 KiB
Markdown

# eco startup modes
eco has four front-ends, selected with `--ui` (see `eco_cli.py`). **The
default remains `shell` (the traditional terminal session) and nothing
about it changes** — `/sf/bernina/bin/eco`, the script everyone actually
runs today, is untouched and keeps working exactly as before. Everything
below is additive and opt-in.
| `--ui` | What it is | Status |
|-----------|----------------------------------------------------------------|--------|
| `shell` | Traditional interactive IPython session (default) | live, unchanged |
| `lab` | JupyterLab on the packaged notebook (`eco/voila_app.ipynb`) | existing, not yet wired into `/sf/bernina/bin/eco` |
| `voila` | That notebook served as a standalone Voila dashboard | existing, not yet wired |
| `desktop` | A Spyder/MATLAB-like Qt workbench window | new (this round), not yet wired |
None of `lab`/`voila`/`desktop` are reachable from `/sf/bernina/bin/eco`
yet — `eco_cli.py` (the module that implements all four) isn't installed
as the live `eco` command in `bpy312`. Try them directly for now:
```bash
source /sf/bernina/bin/bpy-env
cd /sf/bernina/code/gac-bernina/eco
python3 eco_cli.py --ui desktop # the new Qt workbench
python3 eco_cli.py --ui desktop --theme dark --lazy
python3 eco_cli.py --ui lab # existing JupyterLab notebook
python3 eco_cli.py --ui voila # existing Voila dashboard
```
Wiring `eco_cli.py` up as the actual `eco` command (so `eco --ui desktop`
just works) is a separate, deliberately-not-yet-taken step — it touches
the live launcher script everyone uses, so it wants its own explicit
go-ahead rather than happening as a side effect of this work.
## `desktop` — the Qt workbench
`eco/widgets/desktop_app.py`. One window:
- **Center**: a live IPython console (`qtconsole`) with `namespace`
already available. Its kernel is in-process (same Python process as the
window itself, so it can literally share the calling terminal's own
namespace dict) whenever that's safe — but *not* when this process
already has its own running IPython shell (the normal case for calling
`eco.start_desktop()` from an `ipython` terminal session): a second
in-process kernel can't coexist with one already running (IPython's
`InteractiveShell` is a process-wide singleton), so the console falls
back to a real subprocess kernel with its own, freshly-rebuilt
`namespace` instead. See `eco.widgets.console_kernel` for the full
explanation — the banner shown when the console opens says which case
you're in. Pass `with_console=False` to skip this entirely: no console,
no kernel, no MultipleInstanceError/PYTHONPATH subtleties to worry
about — the calling terminal stays the one and only "master" session
(see the "New independent console(s)" section below for the opposite:
a genuinely separate second session).
- **Left dock, "Namespace"**: every registered name in the namespace,
filterable. Names already initialized are opened immediately on click.
Lazy (not-yet-built) names are shown grayed out; clicking one triggers
initialization in the background (a spinner shows on that row while it
runs) and opens it automatically once ready. Failed names are shown in
red and are not retried automatically.
- "Opening" a name calls `<name>.widget()` directly (not through the
console — see `EcoDesktopApp._open_widget`'s docstring for why calling
it straight from the launcher's own Qt code already gets a real QWidget
back, the same as typing it at the calling terminal's own prompt), so
this works identically whether or not `with_console` is on. **Known v1
limitation**: each opened widget is still its own top-level window, not
its own dock panel — true `QDockWidget` embedding is a reasonable next
step, not attempted in this first pass.
- A per-class hook lets a device supply a purpose-built view instead of
the generic property grid: set `_default_widget = "some_method_name"`
on the `Assembly` subclass (see `eco.elements.assembly.Assembly.widget`).
`AxisPTZ` already does this — `_default_widget = "viewer"` opens its
live-video viewer instead of a plain slider grid.
- A modern theme is available: pass `theme="dark"`/`"light"` — uses the
real `qt-material` package (now installed) if present, else a
hand-built approximation of its default teal-on-charcoal look; see
`eco.widgets.qt_theme`. Same switch used by
`eco.widgets.camserver_panel_qt`/`camserver_stream_qt`.
- **Workspace menu**: "Save Workspace Now" writes the dock layout plus
which namespace entries are currently open to
`~/.eco/desktop_workspace.json` (also happens automatically when the
window closes); "Reload Last Workspace" restores both — reopening each
widget the same way clicking it would (lazy entries get re-initialized
first). A fresh window always starts empty; reloading is only ever an
explicit action, never automatic on startup. "New (Empty) Workspace"
just forgets the tracked open-widget list for the current session.
## Testing any of this from a running terminal session, without leaving it
Once eco has started (`namespace` exists in your session — the normal
case), three functions are available directly as `eco.X()`:
```python
eco.start_desktop(theme="dark") # opens the Qt workbench, non-blocking;
# its console shares *this* session's own
# namespace when that's possible (see above)
eco.start_desktop(with_console=False) # no embedded console/kernel at all --
# this terminal stays the only "master"
# session; the Namespace panel still
# works fully either way (see below)
eco.start_console() # a brand-new, INDEPENDENT console/kernel --
# a second working session, not another view
# onto this one. kind="qt" (default) or
# kind="jupyterlab"
eco.start_jupyterlab() # attaches to a running JupyterLab server if
# there is one, else starts a background one
eco.start_webapp() # starts (or reuses) a background Voila server
```
None of these block your terminal — the whole point is to try any of the
other front-ends side by side with the one you're already in. See
`eco/widgets/app_launchers.py` for the full signatures.
### Every kernel is tracked, with its full input/output history
Whether opened via `start_desktop()` or `start_console()`, every kernel's
activity — everything typed or run, everything that came back (results,
stdout/stderr, errors) — is logged as it happens, tagged with which kernel
it belongs to, so a sequence of commands across several
consoles/widgets can be reconstructed after the fact instead of only being
visible in whichever window happened to be open at the time:
```python
from eco.widgets import kernel_registry
kernel_registry.sessions_summary() # what's running right now, and where its log is
kernel_registry.find_all_logs() # every session's log on disk, including past ones
session.tail(20) # last 20 events for one KernelSession
```
Each session's log is a plain JSONL file under `~/.eco/kernel_logs/`, one
line per event (`session_start`, `input`, `result`, `stream`, `error`),
timestamped. See `eco.widgets.kernel_registry` and
`eco.widgets.console_kernel.LoggingJupyterWidget` (the console widget both
`start_desktop()` and `start_console()` build on, which is what actually
does the logging).
Try it from Python directly instead of the CLI:
```python
from eco.widgets.desktop_app import EcoDesktopApp, build_namespace
namespace = build_namespace(scope="bernina", lazy=True)
app = EcoDesktopApp(namespace, theme="dark") # blocks until the window closes
```
## `lab`/`voila` — the browser/ipywidgets side
Already built (a prior round, not this one) in `eco/widgets/widget_tray.py`
(`WidgetTray`, `make_namespace_dashboard`) and `eco/voila_app.ipynb`. This
is the Voila-compatible answer to the same "browse the namespace, open
and close widgets without leaking EPICS polling" problem — it works in
Voila (no JupyterLab shell needed) as well as JupyterLab/Notebook, which
`sidecar` (below) cannot.
New this round: `eco/widgets/jupyter_sidecar.py`'s `open_in_sidecar(obj,
anchor=...)` — a thin wrapper around the `sidecar` package
(`pip install sidecar`) for **JupyterLab specifically**: it opens
`obj.widget()` in a real Lumino dock panel that you can then freely
drag/tab/tile via JupyterLab's own UI, the closest browser-side analogue
to the Qt side's dockable panels. It does **not** work in Voila or
classic Notebook (no Lumino shell to dock into there) — use
`widget_tray`/`voila_app.ipynb` for those. Degrades to a clear
`RuntimeError` (not a traceback) if `sidecar` isn't installed.
JupyterLab's own **Workspaces** feature (`jupyter lab workspaces export/
import`, also in the command palette) is the closest browser-side
equivalent of the Qt side's window-layout persistence — not eco code,
just worth knowing it exists if a sidecar-heavy layout is worth saving.
## What's genuinely new vs. what already existed
New in this round: `eco/widgets/desktop_app.py` (the whole Qt workbench),
`eco/widgets/jupyter_sidecar.py`, the `_default_widget` hook on
`Assembly.widget()` (`eco/elements/assembly.py`) plus `AxisPTZ` using it,
the `desktop` choice wired into `eco_cli.py`, and this file. Everything
under `lab`/`voila` (`widget_tray.py`, `voila_app.ipynb`, `eco_cli.py`
itself) already existed going into this round and is described here only
for the full picture.
Newer still: `eco/widgets/console_kernel.py` (the in-process/subprocess
kernel-building + input/output logging shared by every console widget),
`eco/widgets/kernel_registry.py` (the cross-kernel activity log described
above), `eco/widgets/console_window_qt.py` and `eco.start_console()` (a
genuinely independent second console/kernel). These replaced
`desktop_app.py`'s original always-in-process kernel, which crashed
(`ipykernel`'s `MultipleInstanceError`) the moment `eco.start_desktop()`
was called from an already-running IPython terminal session — the single
most common real way to actually use it. See
`eco.widgets.console_kernel`'s module docstring for the full explanation.