argos.ui

Modules

Shell — the Argos main window. NINA-style: the night happens in ONE screen.

The main workflow is:

  • Equipment — connect the Seestar devices and Stellarium server.

  • Capture — live image, camera, mount, focus and photometry workspace.

  • Plan — target lookup, altitude preview and observing sequences.

  • Analyze — reload, vet and export saved photometry.

  • Settings — observer, site, data paths and appearance.

The session layer owns the hardware (WS5): DeviceSession holds the device handles, pollers and the Stellarium server; AcquisitionEngine holds the camera-ownership state machine and the capture/solve/photometry workers. The Capture page (ImagingPage) is a view over the two. The Equipment page emits connect/disconnect intents that the Shell routes to the DeviceSession; device-state updates flow back to the status bar and the Equipment page. Targeting is driven by Stellarium (select an object, Ctrl+1) over the TCP telescope-control protocol, or by the mount dock’s goto fields.

Left navigation sidebar — switches between the workflow phases.

Vertical toolbar pinned to the left of the Shell. Each entry is a crisp, vector line icon above a wrapped label; clicking it emits mode_changed(mode_id) which the Shell uses to swap the central QStackedWidget page.

Icons are inline SVG (Feather, MIT) rendered with QtSvg — no emoji, no shipped asset files — so they stay sharp at any DPI and recolour with the theme (muted → hover → accent) by injecting the stroke colour.

Each entry carries a small state dot (set_mode_state): green = the phase’s prerequisites are met, accent = the phase is actively running (e.g. a sequence on Capture), amber = blocked (a required device is missing). No dot = neutral.

Preset colour palettes for the Argos theme system (WS9c).

Each palette is a Palette instance. Import PALETTES (an ordered dict of name → Palette) and pass an entry to argos.ui.theme.apply_palette() to switch themes at runtime.

Naming rules

  • bg / bg2 — main window background / sub-surface (inputs, log)

  • surface — raised elements (buttons, tab backgrounds)

  • border / border_soft — hard and subtle separators

  • fg / fg_muted / fg_disabled — text hierarchy

  • accent / accent_hover / accent_deep — primary action colour

  • cyan — secondary info accent (coords, mount status)

  • success / warning / danger — semantic colours

  • variable — VSX variable-star markers

  • font_ui / font_mono — CSS font-family stacks

argos.ui.palettes.PALETTES: dict[str, Palette] = {'Charcoal': Palette(name='Charcoal', bg='#232529', bg2='#181a1d', surface='#2c3035', border='#545c65', border_soft='#3d444c', fg='#ece9e2', fg_muted='#b0aea7', fg_disabled='#73777b', accent='#c49a3c', accent_hover='#d8b75f', accent_deep='#977331', cyan='#86a9af', success='#8ca58c', warning='#d1aa5e', danger='#c57b76', variable='#9abfcd', font_ui='"Helvetica Neue", "Segoe UI", "Noto Sans", sans-serif', font_mono='"SF Mono", "Cascadia Mono", "JetBrains Mono", "Noto Sans Mono", "Menlo", "Consolas", monospace'), 'Night (Red)': Palette(name='Night (Red)', bg='#1a0304', bg2='#100102', surface='#2b080a', border='#501719', border_soft='#350c0e', fg='#d88f8f', fg_muted='#a65758', fg_disabled='#5a2829', accent='#c14d50', accent_hover='#df6264', accent_deep='#7d2327', cyan='#bb6667', success='#8b3437', warning='#ad494c', danger='#e05e61', variable='#985055', font_ui='"Helvetica Neue", "Segoe UI", "Noto Sans", sans-serif', font_mono='"SF Mono", "Cascadia Mono", "JetBrains Mono", "Noto Sans Mono", "Menlo", "Consolas", monospace'), 'Observatory': Palette(name='Observatory', bg='#191b1f', bg2='#121417', surface='#22262c', border='#47515c', border_soft='#343c45', fg='#e8e5df', fg_muted='#aaa8a2', fg_disabled='#6d7278', accent='#c49a3c', accent_hover='#d6b25d', accent_deep='#8d6b2b', cyan='#7da8b2', success='#83a68b', warning='#d0a85a', danger='#bf7772', variable='#8fb7ca', font_ui='"Helvetica Neue", "Segoe UI", "Noto Sans", sans-serif', font_mono='"SF Mono", "Cascadia Mono", "JetBrains Mono", "Noto Sans Mono", "Menlo", "Consolas", monospace')}

All available preset palettes, in display order.

class argos.ui.palettes.Palette(name: str, bg: str, bg2: str, surface: str, border: str, border_soft: str, fg: str, fg_muted: str, fg_disabled: str, accent: str, accent_hover: str, accent_deep: str, cyan: str, success: str, warning: str, danger: str, variable: str, font_ui: str, font_mono: str)[source]

Bases: object

All colour tokens that parameterise the global QSS and widget stylesheets.

accent: str
accent_deep: str
accent_hover: str
bg: str
bg2: str
border: str
border_soft: str
cyan: str
danger: str
fg: str
fg_disabled: str
fg_muted: str
font_mono: str
font_ui: str
name: str
success: str
surface: str
variable: str
warning: str

Startup splash — logo, version, licence and honest loading progress.

Argos takes a couple of seconds to reach a usable window: the scientific imports (numpy, astropy, pyqtgraph, alpyca) and then twenty-odd docks being built. That used to happen behind a blank screen.

Two rules this module keeps:

It never shows a percentage it cannot justify. The stages below are real checkpoints in main.main(), each marked as it completes. Anything that cannot be instrumented would get an indeterminate bar rather than a number invented to look busy.

It always closes. Splash.fail() is wired to the exception path in main.py — a splash left on screen after a crash hides the traceback behind a frameless always-on-top window with no way to dismiss it.

argos.ui.splash.STAGES: tuple[tuple[str, str, float], ...] = (('config', 'Reading configuration…', 0.05), ('imports', 'Loading scientific libraries…', 0.35), ('profile', 'Resolving telescope profile…', 0.45), ('theme', 'Applying theme…', 0.55), ('shell', 'Building the workspace…', 0.9), ('layout', 'Restoring layout…', 1.0))

(key, label, fraction complete). The fractions are rough but monotonic and honest about ordering — the Shell build genuinely dominates once imports are warm.

Type:

Ordered loading checkpoints

Permanent top status strip — devices, capture progress, tracking, last action.

Sits between the toolbar and the mode workspace. Always visible across all modes so the user never wonders “am I still connected?” or “is the mount tracking?” — and, during a run, never loses sight of the sequence. Updated via slots called by the Shell from device/sequence signals.

Argos visual theme — parameterized around Palette.

Single source of truth for all colors and the global Qt stylesheet.

Usage

At startup, call apply_palette() with the desired preset:

from argos.ui import theme
from argos.ui.palettes import EQUILUX
theme.apply_palette(EQUILUX)           # or load from config

All module-level constants (theme.BG, theme.ACCENT, …) are rebound by apply_palette() so every consumer that does theme.BG at call time (not import time) picks up the new value automatically. Widgets that embedded a color string into a local stylesheet at construction time (sidebar icons, dock_host QSS, statusbar) are not retroactively updated — see live-vs-restart note in the WS9c spec.

get_stylesheet returns a freshly generated QSS from the current palette. Call it after apply_palette() to regenerate the global stylesheet.

argos.ui.theme.apply_palette(palette: Palette) → None[source]

Rebind every module-level color constant to the new palette values.

Also regenerates LOG_COLORS from the new palette so log-panel rendering picks up the change at the next message. The caller is responsible for calling app.setStyleSheet(get_stylesheet()) afterwards.

argos.ui.theme.get_stylesheet() → str[source]

Return the global Qt stylesheet for the application.

Always reads the current module-level constants, so calling this after apply_palette() yields a stylesheet for the new theme.

Argos design system — single source of truth for layout primitives.

Every page and widget should compose with the elements below instead of re-implementing spacing, typography, button styling, or card chrome. The goal is that a new screen built three months from now still looks like the rest of the app without anyone having to remember a stylesheet snippet.

Conventions:

  • Spacing — use the constants SPACING_* not raw pixel values.

  • Cards — use Card (with card_layout()) for every dock / right-rail group. Never instantiate a bare QGroupBox.

  • Buttons — use PrimaryButton, SuccessButton, DangerButton, or SecondaryButton rather than QPushButton(...).setProperty("class", "primary").

  • Labels — use MutedLabel for form keys, MetricLabel for monospace values (RA/Dec/HFD), and SectionLabel for sub-section titles inside a card.

If something looks “off” in the UI, fix it here — every page picks the change up automatically.

argos.ui.design.button_row(*buttons: <MagicMock name = 'mock.QtWidgets.QPushButton' id='139720786454992'>, stretch_each: bool = True) → <MagicMock name='mock.QtWidgets.QHBoxLayout' id='139720796166480'>[source]

Lay out buttons horizontally with the standard inter-button spacing.

Pass stretch_each=False to keep buttons at their min size (useful when there are 3+ small action buttons and you don’t want them to grow to fill the row).

argos.ui.design.card_layout(card: Card) → QVBoxLayout[source]

Return the standard outer QVBoxLayout for a Card.

Sets the project-wide card padding and the medium inter-row spacing in one call so callers can’t accidentally drift to different values.

argos.ui.design.horizontal_divider(parent: <MagicMock name = 'mock.QtWidgets.QWidget.__or__()' id='139720779805072'> = None) → <MagicMock name='mock.QtWidgets.QFrame' id='139720782972176'>[source]

Clear but quiet horizontal separator shared by every form/card.

argos.ui.design.scroll_page(max_width: int = 880) → tuple[<MagicMock name='mock.QtWidgets.QScrollArea' id='139720782973008'>, <MagicMock name='mock.QtWidgets.QVBoxLayout' id='139720789247120'>][source]

Build a vertically-scrolling page body, centered and width-capped.

Returns (scroll_area, content_layout): add the scroll area to the page root layout and fill content_layout with cards/sections. Capping the width stops sparse, form-style pages from stretching edge-to-edge on wide windows (which makes them look empty and unstructured).

argos.ui.design.stat_color(value: float, *, ok_below: float, warn_below: float) → str[source]

Pick a colour for a metric based on threshold ranges.

Used by HFD / drift / SNR readouts.

Parameters:
  • value – Current metric value.

  • ok_below – Strictly below this the value is “good” (green).

  • warn_below – Strictly below this (but at or above ok_below) the value is “warning” (amber). At or above warn_below the value is “danger” (red).

argos.ui.design.two_columns(spacing: int = 12) → tuple[<MagicMock name='mock.QtWidgets.QHBoxLayout' id='139720796166480'>, <MagicMock name='mock.QtWidgets.QVBoxLayout' id='139720789247120'>, <MagicMock name='mock.QtWidgets.QVBoxLayout' id='139720789247120'>][source]

Return (row, left, right) — two equal side-by-side column layouts.

Add the row to a content layout, then drop cards into left / right to put several sections next to each other instead of one tall column.

Analysis window — standalone viewer for inspecting a single FITS frame.

Opened via the “Open FITS” button or from a Review curve point. It uses the same solved-field identification layers as Observe (Gaia, VSX, VSP, SIMBAD, NASA and the bundled deep-sky catalogue), while remaining read-only: target management and photometry setup still belong to the Observe workspace.

argos.ui.analysis_window.read_fits_2d(path: str) → ndarray | None[source]

Read a FITS file into a 2-D float32 array (NaNs zeroed, cubes collapsed).

argos.ui.analysis_window.read_fits_header_text(path: str) → str[source]

Return the complete primary FITS header for the read-only inspector.

argos.ui.analysis_window.read_fits_meta(path: str) → dict[source]

Return a dict of useful header keywords from a FITS file.

Pages

Connection mode — connect the Seestar devices and the Stellarium server.

The user comes here once at the start of a session. The page emits intents (connect_requested, disconnect_requested, discover_requested) and the Shell routes them to the device session living on the Acquisition page. State updates flow back via set_device_state. The Stellarium telescope- control server is started/stopped from the embedded StellariumCard.

The Shell uses its discovery/connect/disconnect signals, the set_device_state and set_discovered_address slots, and the stellarium_card property.

Acquisition mode — the work surface where most session time is spent.

Layout (WS9a — dockable workspace):

┌─ ImageToolbar (View · Open FITS · "display ≠ data") ───────────┐
├─ Panel-toggle strip: Camera · Mount · Focuser · Filters · … ───┤
├─ OverlayBar: Grid · Stars · Variables · … ────────────────────┤
│  ┌──────────────────────────────────┬─────────────────────┐   │
│  │  FitsViewer (hero, central) +    │  Camera dock (right, │   │
│  │  crosshair + pixel readout       │  Mount/Focuser/Filter│   │
│  │                                  │  tabbed behind it)   │   │
│  │  Stats bar: HFD·Stars·Sky·…      │                      │   │
│  ├──────────────────────────────────┴─────────────────────┤   │
│  │  Sequence dock (bottom, Log tabbed with it)            │   │
│  └────────────────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────────────┘

Every panel is a real QDockWidget (movable / resizable / closable / floatable to a 2nd monitor), hosted in an internal QMainWindow used as a plain widget. The central widget is the viewer + the always-visible stats strip. Layout is persisted via QMainWindow.saveState() into ui.imaging.layout.

The page is a view over the session layer (WS5): the DeviceSession owns the device handles and pollers, the AcquisitionEngine owns the camera-ownership state machine and the capture/solve/photometry workers. The page builds the docks, routes their intents into session/engine methods, and renders the typed signals coming back.

Upward signals the Shell wires into the global status bar + Connection page (relays of the session/engine signals, kept here so the Shell wiring and the tests keep one stable surface):

device_state_changed(device, state, info) tracking_changed(bool | None) action_changed(text) log_message(level, message)

Sequencer mode — plan and run a capture sequence.

The SequencePanel is a dockable workspace: the table stays central, while target search, visibility and controls are movable. This page owns panel↔ engine wiring; frames, the log and live light curve stay on Capture.

Analyze phase — vet the light curve and export AAVSO; inspect frames.

Two companion windows do the work (second-monitor friendly, so a finished night can be vetted while a new run continues on Capture):

  • Light curve — reload a session’s measurements into the PhotometryWindow, then export target-only AAVSO Extended Format stamped with the observer code + band from Settings.

  • Frame inspector — open any FITS in the AnalysisWindow.

The screen surfaces the observer code so it is obvious what an export will carry, and warns when it is unset (AAVSO submissions need a real code).

Configuration mode — software settings (observer, site, paths, appearance).

Persists everything into Config (~/.argos/config.json). The observer/site fields feed the FITS headers (OBSERVER, SITELAT/LONG/ELEV, and the AIRMASS/MOON computations) written by every frame.

Public interface (used by the Shell): just the constructor ConfigurationPage(config).

Panels

Session log panel — colored, timestamped, scrollable log display.

Manual mount control dialog — Alpaca MoveAxis jogging.

Uses ASCOM Alpaca MoveAxis (port 32323) which is confirmed working on Seestar S30 Pro firmware 7.18+.

Axis mapping (Alt-Az mount):

Axis 0 (Primary) = Azimuth → East/West Axis 1 (Secondary) = Altitude → North/South

The mount keeps moving at the commanded rate until Rate=0 is sent. Button pressed → MoveAxis(rate), button released → MoveAxis(0). No periodic resend needed (unlike native scope_speed_move).

Floating Photometry window (notes/photometry_plan.md §6 C5/C6).

Hosts the live differential light curve + the session-metrics panel in tabs. A separate top-level window (like the analysis window) so it can sit on a second monitor during a run. Display only — the page feeds it points; this window owns no acquisition state.

Stellarium integration card — TCP telescope-control server toggle.

Embedded in the Connection panel as a Siril-style card (QGroupBox). Argos runs the standard Stellarium Telescope Control TCP server: Stellarium connects to it as a telescope, and a slew command (select an object, press Ctrl+1 / ⌘+1) sends a goto that Argos forwards to the mount. The card does not own the worker; it emits intents (start/stop) and accepts state updates.

Widgets

FITS / raw image viewer (PyQtGraph) — display stretch + measurement tools.

Receives a linear display array (2-D uint16 plane or 3-D uint16 RGB from debayer.render_view) and maps it to the screen through the stretch (black/white/midtones + linear/log/asinh). The linear array is kept for measurement (§4): pixel readout on hover, a region-stats ROI, and a saturation/clipping overlay. None of this touches the data written to FITS.

Signals include levels_changed(black, white) after an auto-stretch, pixel_info(str) for the cursor readout, and region_info(dict | None) for the ROI statistics.

argos.ui.widgets.fits_viewer.ROTATION_MODES = ('auto', '0', '90', '180', '270')

Display-rotation modes. “auto” turns portrait frames 90° CW so the wide Seestar sensor reads landscape; the fixed angles are clockwise. Rotation is display-only: the public API (star_clicked, mark_selection, set_*_markers) keeps speaking un-rotated frame coordinates, and saved FITS are untouched.

Small observer-facing toolbar above the FITS image.

Compact controls for scientific overlays on a solved image.

The first row is organised by physical object type. Catalogue providers are data-provenance details and live in the catalogue settings instead of being presented as object types in the observer’s primary workflow.

Display panel — per-channel histogram, stretch controls, measurement readouts.

The control surface for the display pipeline (capture_panel.md §3/§4). It owns nothing about the data: it shows per-channel (R/G/B) histograms of the raw CFA, drives the viewer’s stretch (black/white/midtones + linear/log/asinh), and shows the pixel readout + ROI region stats coming back from the viewer.

Signals:

stretch_changed(black, white, midtones, mode) auto_requested() saturation_toggled(bool) roi_toggled(bool)

Camera dock — single-shot capture controls for the Capture tab.

The Capture tab is where you frame and test-shoot while focusing; the full multi-step acquisition plan lives in the Sequence tab. So this dock owns the live/single-frame parameters (type, object, filter, exposure, gain) and a “Shot” button. Numeric parameters use the SliderSpin composite (slider + value box), the idiom from NINA / SharpCap; exposure runs on a log slider.

UI-only — the ImagingPage wires the signal to the camera/worker.

Public surface:
Signals

take_shot_clicked() live_start_requested() # user pressed ▶ Live live_stop_requested() # user pressed ■ Stop live filter_selected(name: str) # user picked a filter → move the wheel offset_changed(value: int) # user edited the (driver-backed) offset binning_changed(value: int) # user edited the (driver-backed) binning preview_scale_changed(scale: int) # preview quality: 1 = full, 2 = half

Methods (called by ImagingPage)

params() -> CaptureParams preview_scale() -> int set_enabled(connected: bool) set_live_running(running: bool) set_gain_range(lo: int, hi: int) set_exposure_range(lo: float, hi: float) set_offset_support(lo: int, hi: int, value: int) set_binning_support(max_bin: int, value: int) reset_camera_limits() # back to defaults, hide optional rows set_filter_options(names: list[str]) set_current_filter(name: str) set_hfd(value: float | None) set_temperature(temp: float | None)

class argos.ui.widgets.camera_dock.CaptureParams(frame_type: str, object_name: str, filter_name: str, exposure_s: float, gain: int)[source]

Bases: object

Snapshot of the live capture form values.

exposure_s: float
filter_name: str
frame_type: str
gain: int
object_name: str
argos.ui.widgets.camera_dock.DEFAULT_GAIN_RANGE = (0, 600)

Fallback limits when no camera is connected (the historical hardcodes).

Focuser dock — compact right-rail focuser control for the Imaging page.

Public surface:
Signals

step_requested(int) # ±N steps halt_requested() autofocus_requested() move_to_requested(int) # absolute position

Methods (called by ImagingPage)

set_enabled(connected: bool) set_position(pos: int) set_temperature(temp: float | None) set_moving(moving: bool) set_autofocus_running(running: bool)

Mount dock — right-side mount control for the Imaging mode.

UI-only. ImagingPage wires this widget’s signals to Telescope and feeds back live coordinates via set_position(). Manual jog stays in the existing ManualControlDialog which is launched from here.

Public surface:
Signals

goto_clicked(ra_hours, dec_degrees) sync_to_current_clicked() center_target_clicked() tracking_toggled(enabled: bool) tracking_rate_changed(rate: int) # 0=Sidereal, 1=Lunar, 2=Solar abort_clicked() park_clicked() manual_control_requested()

Methods (called by ImagingPage)

set_enabled(connected: bool) set_position(ra_h, dec_d, alt_d, az_d, tracking, slewing) set_goto_fields(ra_h, dec_d) # so an external pull can pre-fill

Sequence panel — dockable multi-step acquisition planning workspace.

The sequence table is the stable centre. Target search, plan options, visibility, presets and controls are independent docks which can be moved, tabbed, resized, or floated. UI-only: builds a SequencePlan from the editable table and emits start/pause/resume/stop intents; the AcquisitionEngine drives the SequenceWorker and feeds progress back.

Presets: a plan (steps + options) saves/loads as JSON via the sequencer’s plan_to_dict / plan_from_dict round-trip.

Astrometry + catalog settings — a popup to tune solving & catalog queries.

A convenience dialog launched from the Shell’s Astrometry menu (and from the analysis window) so the user can adjust the plate-solve and AAVSO-catalog parameters without hunting through the Settings page. It reads and writes the same config keys as the main Configuration page, so the two stay in sync. On save it emits saved — the Shell asks the engine to re-query the catalog so changes (e.g. a brighter magnitude limit) apply on the live field immediately.

Why two tabs, not two dialogs

The owner’s mental model is two menu entries — “Plate solving…” and “Catalog…”. Rather than duplicate a form (and its load/save plumbing) across two dialogs, we keep one dialog with two tabs and let the caller pick which tab opens first via section. One save button writes every key at once, so the two related parameter sets can be tuned together without closing and reopening.

argos.ui.widgets.astrometry_settings.SECTION_ASTROMETRY = 'astrometry'

Selectable initial tab (also the config-namespace each tab writes into).

On-image star-info card (notes/photometry_plan.md §5 B2, confirmed UX).

A compact overlay card that shows what the user clicked — catalog identity + RA/Dec + mags + measured FWHM/SNR — and offers role buttons to build the night’s target set. It starts bottom-left but can be dragged by its title and resized from its lower-right corner. The page owns the hit-test and TargetSet; this widget is display + buttons only.

Target-set management table (notes/photometry_plan.md §5 B4, P5).

Review the night’s saved stars (role / name / RA / Dec / mags), remove one, or copy the lot as TSV. Display only — the page owns the TargetSet and handles removal (persist + re-project) on the remove_requested signal.

Comparison-star ensemble table (WS7 revival).

ComparisonEnsembleTable — the comp ensemble actually in use for the night, read off the target set (role == comparison), with their catalog mags and a remove button. Lives as a tab of the Photometry window so the user can see and prune the ensemble that forms the differential zero-point.

Live differential-photometry plots.

The scientific target/check light curves and the comparison-star diagnostics serve different jobs. They deliberately live in separate, X-linked plots: the target keeps its calibrated magnitude scale while each comparison is shown as a residual about its own median. This prevents an ensemble spanning several magnitudes from making the science curve unreadable.

Session metrics over time (notes/photometry_plan.md §6 C6).

A single plot with a metric selector — sensor temperature, airmass, sky level, FWHM, HFD, star count vs elapsed time. Each series stores its own samples (fed at whatever cadence is available), so a missing value simply doesn’t extend its line.

HFD History dock — the per-frame focus trend, promoted to its own panel (WS9b).

NINA’s HFR History: the HFD of successive frames as a running sparkline so the observer watches focus drift (temperature, flexure) at a glance. Here it also overlays the detected #Stars on a twinned right axis — a sudden star drop with a rising HFD is cloud, a rising HFD with steady stars is defocus.

Promoted out of the focuser dock (which keeps the V-curve): the “Focus quality” section — trend + HFD / #Stars / Sky read-outs — lives here now. Fed from the same per-frame metrics path the focuser dock used (push_metrics).

Statistics dock — the per-frame numbers as a compact key/value grid (WS9b).

NINA’s Statistics panel: a dense 2-column grid of the current frame’s whole-image statistics (Mean / Median / StdDev / MAD / Min / Max / bit depth) plus the green-plane star metrics (#Stars / HFD / FWHM / eccentricity). The thin always-visible stats strip under the image shows only six of these; this dock is where the user reads the full set on demand.

All values are computed off the UI thread by build_processed_frame() (whole-frame median / MAD / std) and argos.core.imaging.metrics (stars); the dock only formats and displays. It fills from the same per-frame payload the Imaging page already dispatches to the other docks (see _on_processed).

Selectable single-comparison diagnostic for the Review workspace.

Themed QDockWidget factory for the dockable Imaging workspace (WS9a).

NINA-inspired docking: every control panel becomes a real QDockWidget the user can move, resize, close and float to a second monitor. This module keeps the title bars consistent — a small title, a hairline border, the equilux palette — so the docks read as one sober cockpit rather than a pile of windows.

make_dock wraps a content widget in a scroll area (so a stacked rail of Fixed-height cards scrolls instead of stretching) and returns a styled dock. style_toggle_action restyles the toolbar chips driven by QDockWidget.toggleViewAction() (which — unlike visibilityChanged — is already immune to tab-switch spurious toggles).

argos.ui.widgets.dock_host.make_dock(title: str, content: <MagicMock name = 'mock.QtWidgets.QWidget' id='139720784877328'>, *, object_name: str, scroll: bool = True, features: <MagicMock name = 'mock.QtWidgets.QDockWidget.DockWidgetFeature.__or__()' id='139720779067216'> = None) → <MagicMock name='mock.QtWidgets.QDockWidget' id='139720783684432'>[source]

Wrap content in a themed, movable/closable/floatable dock.

object_name is required — QMainWindow.saveState()/restoreState() identify docks by it, so it must be stable across launches.

When scroll is true the content is placed in a top-aligned, no-frame scroll area (the right-rail control cards keep their natural height and the dock scrolls if shorter than the stack). Wide panels that manage their own scrolling (Sequence, Log) pass scroll=False.

argos.ui.widgets.dock_host.panel_toolbar_qss() → str[source]

Return the shared, neutral toolbar treatment for dock workspaces.

A visible dock is not an active command. Styling every checked toggleViewAction with the primary accent made a normal default layout look as if every control were simultaneously selected. Keep these buttons neutral; reserve Argos brass for focus, deliberate actions and real state. Menu arrows are rendered as text by the caller, avoiding platform-native indicators which were vertically misplaced on compact toolbars.

argos.ui.widgets.dock_host.style_toggle_action(action: <MagicMock name = 'mock.QtGui.QAction' id='139720788628688'>, label: str) → <MagicMock name='mock.QtGui.QAction' id='139720788628688'>[source]

Restyle a dock’s toggleViewAction() for the panel-toggle toolbar.

toggleViewAction() already keeps the checkbox in sync with the dock’s real visibility (and, unlike visibilityChanged, does not fire on tab switches). We only override the label so the strip reads compactly.

Offline quality plots for a finished Argos observing session.

Selectable target light curve for the Review workspace.

Field variable-star table — pick the photometry target from a list.

Every VSX variable of the solved field, one row each (same order as the engine’s cached list, brightest first), with a ● marking stars already in the target set. Clicking a variable on the image and picking it here are the same action: the page routes target_requested through the exact code path as the star-card’s Target button (auto-comparisons included).

Compact HFD V-curve — the canonical autofocus visual.

Plots the sweep samples (HFD against focuser position), the fitted parabola and its vertex (best focus). Lives inside the FocuserDock so focus is watched where capture happens; stays verifiable headless through the public add_sample / set_best / set_samples API (mirrors AutofocusWorker.step_done / best_found).