features(): the "Did you know?" tip window (same as eco.tip()); docs():
the docs site in the web browser, or a given page (docs("scanning")),
printing the URL where there is no display. Both are imported into the
session at startup, and the banner names them instead of showing links.
The full tip at every session start was too much. The banner now shows two
links: the docs site and its random "Did you know?" page
(did_you_know/random.html); eco.tip() still shows tips in a window.
- eco/utilities/tips.py reads docs/did_you_know/*.md (front matter, title,
body), prints a tip in the terminal and keeps the session's tip history:
Back/Next walk the tips already seen, and only past the end is a new
random one picked (unseen first).
- eco/widgets/tip_qt.py: the window behind eco.tip() (Back / Next / Open in
docs, arrow keys, one window per session). Without a display, eco.tip()
prints the next tip instead.
- eco/startup_inline.py prints one tip at session start, pointing at
eco.tip(); ECO_NO_TIP=1 turns it off.
- Links in tips point at the docs site (ECO_DOCS_URL, default
saresb-cons-04:8095).
A channel with more than max_bins (default 100000) events in the requested
range comes binned on the server (round bin width, average plus min/max/count
columns) instead of raw, the way databoard does it: 24 h of one 100 Hz BS
channel was 23 s and 3 GB raw, now about 5 s. Channels with fewer events stay
raw. A warning names the binned channels; bins= chooses the binning
explicitly, max_bins=None always fetches raw events.
Written after each html build (conf.py build-finished hook) from
_templates/random_tip.html with the current list of tips; ?tip=<name> shows
a given one. Meant for e.g.
google-chrome --app=http://<host>:8095/did_you_know/random.html --window-size=560,460
Also shorten the picker tip's code lines to fit such a window.
- docs/did_you_know/: one short tip per feature added since May 2026
(<date>-<slug>.md, YAML front matter: added/tags/details), listed
newest first; index.md describes the format for adding more.
- New pages for features that had none: launching, command_history,
tab_completion, live_gui, tweak, scanning, status_server,
channel_access, shared_files, schematic_panels. Wired into index.rst
together with the existing queues page.
- Fix archiver_stripchart/listening_monitor: the method is
archiver.strip_plot (strip_chart does not exist on DataHub); document
the per-device .strip_plot().
- NAMESPACE_MONITOR_FORMAT.md: scan monitoring is wired into Daq's scan
callbacks since 2026-09-09.
- scripts/eco-docs (+ -install-user-service): build the docs from an own
clone and serve them over HTTP as user systemd units, rebuilt by a
timer; a failed build never replaces the served site.
- shared eco.widgets.live_values: writes on a background thread with a
status dot (and working Stop during a move), reads off the GUI/kernel
thread, monitored items subscribed (trusted only once an event arrives,
demoted if a poll sees an unannounced change), tweak step from the IOC's
TWV for motor/SmarAct records.
- assembly_browser Set no longer falls back to mvr (a relative move with
an absolute value); runs off the kernel thread.
- fix process aborts: IOC finder worker destroyed while running, simple
gadget polling after close; tweak panel message timer guarded.
- eco.utilities.mainthread: a watchdog interrupts the main thread (SIGUSR2)
while a cell blocks, so Qt windows (camera viewers, plots, the desktop
workbench) stay live during umv/scans/sleep; call_soon/on_main for GUI
work from other threads. Camera viewers default to in-process again.
- eco.utilities.task_queue: named queues (queues[name], %queue magic,
scans.*(queue=...)) replacing scan_queue.py; every append is test-run
(names, signatures, scan build, limits); claims block moves of a running
task's adjustables from elsewhere (forced() overrides); output capture,
recording and an output window (queue_output_qt).
- eco.utilities.user_input: input() from a worker asks on the main thread;
input_timeout(escalate=False) takes the default at once inside a queue.
- scan: start_time %M -> %m bug fixed; GUI calls in scan callbacks routed
to the main thread.
With ECO_NO_X=1 or without X, the terminal tweak's detector recording
now plots as text (uniplot) just above the status line, redrawn in place
as points come in -- same layout as the Qt plot: detector(s) vs position
for one adjustable, detectors over positions vs step for several.
New eco.utilities.termplot, the wrapper planned in docs/terminal_plots.md:
panel_stack() for stacked plots over a shared x, and LiveRegion /
LivePlotter for in-place redraws that coexist with a '\r' status line
(prints a fresh copy if other output scrolled in between).
Records the decision to use uniplot (in sfb312) for no-display terminal
plots behind an eco wrapper, the gaps that could justify an own copy
later, and the alternatives considered.
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>
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>