exlab_wizard.ui.pages.settings#

Settings dialog (Frontend Spec §7).

Two-pane modal with a left vertical-nav and a right content area; setup-incomplete mode auto-selects the first incomplete one.

Functions

build_settings_draft(config)

Return the editable deep-copy draft the settings dialog mutates.

finalize_settings_draft(draft)

Re-validate a mutated draft into a clean Config.

first_incomplete_section(incomplete)

Return the first section ID in canonical order that's incomplete.

lims_credential_initial_state(*, present)

Return the credential-row state seeding the LIMS password field.

render_settings_page(*[, config, state, ...])

Render the settings dialog.

save_button_label(state)

Compute the Save all button label, including the badge count.

section_has_warning(state, section)

Return True when the sidebar should decorate section.

section_is_dirty(state, section)

Return True when section has uncommitted edits.

settings_sections_for(config)

Return the visible section ids for config.

Classes

SettingsState([active_section, ...])

Mutable state for the dialog.

class exlab_wizard.ui.pages.settings.SettingsState(active_section='paths', incomplete_sections=(), dirty_sections=<factory>, pending_change_count=0)[source]#

Bases: object

Mutable state for the dialog.

Parameters:
active_section: str = 'paths'#
dirty_sections: set[str]#
incomplete_sections: tuple[str, ...] = ()#
pending_change_count: int = 0#
exlab_wizard.ui.pages.settings.build_settings_draft(config)[source]#

Return the editable deep-copy draft the settings dialog mutates.

None (a fresh install with no config.yaml) yields a Config() carrying the §9 defaults so every field still has a sensible starting value.

Parameters:

config (Config | None)

Return type:

Config

exlab_wizard.ui.pages.settings.finalize_settings_draft(draft)[source]#

Re-validate a mutated draft into a clean Config.

The dialog’s two-way bindings mutate the draft without running Pydantic validation (assignment validation is off on the model), so the Save handler round-trips model_dump -> model_validate to coerce widget types (e.g. ui.number floats back to ints) and enforce the §9 cross-field invariants. Raises ValidationError when the edited values do not form a valid config.

warnings=False on the dump silences Pydantic’s “expected int, got float” notice – the float is an artefact of ui.number and model_validate coerces it back to int on the next line.

Parameters:

draft (Config)

Return type:

Config

exlab_wizard.ui.pages.settings.first_incomplete_section(incomplete)[source]#

Return the first section ID in canonical order that’s incomplete.

The dynamic NAS-remote section is not part of the static SETTINGS_SECTIONS tuple, so it is folded into the canonical order here (right after equipment) – otherwise an INCOMPLETE_NO_NAS_REMOTE install would auto-select nothing and land the operator on the default section (rclone.conf migration).

Parameters:

incomplete (tuple[str, ...])

Return type:

str | None

exlab_wizard.ui.pages.settings.lims_credential_initial_state(*, present)[source]#

Return the credential-row state seeding the LIMS password field.

A password already in the OS keyring opens the row in the Set resting state ([Replace] / [Clear]); an empty keyring opens it in Not set ([Set]). resting matches state so a cancelled Replace collapses back to where it started.

Parameters:

present (bool)

Return type:

CredentialState

exlab_wizard.ui.pages.settings.render_settings_page(*, config=None, state=None, on_save=None, on_discard=None, on_select_section=None, on_save_lims_password=None, on_clear_lims_password=None, lims_password_present=False, nas_remote_available=None, on_test_connection=None, nas_remotes=(), list_remotes=None, autostart_registered=False, on_set_autostart=None, on_quit=None, tray_available=False)[source]#

Render the settings dialog.

config is the live config.yaml model (or None on a fresh install). The dialog edits an in-memory deep copy – the draft – so cancelling discards the edits; on_save receives the validated Config built from the draft when the operator clicks Save.

on_select_section is invoked when the operator clicks a sidebar nav row. The Phase 12 cut bound this to a no-op (the selection cycle is handled by the host page); the e2e harness wires it to a navigation hook so each section’s body becomes assertable.

on_save_lims_password / on_clear_lims_password back the LIMS section’s credential field. Per Frontend Spec §7.3 credentials are independent of Save – these write straight to the OS keyring at click time, so the host wires them to a KeyringStore rather than to the draft. lims_password_present seeds the credential row’s resting state from whether the keyring already holds one.

The NAS-remote section (rclone.conf migration) is editable: the operator picks the remote, base root, and optional --config path. nas_remotes seeds the remote dropdown (the boot-time rclone listremotes snapshot) and list_remotes(config_path) re-lists on demand from the typed config path. nas_remote_available(name) answers whether nas.remote is present in the operator’s rclone.conf (found / not-found badge), and on_test_connection(remote, config_path) runs the rclone probe against the typed values, returning a TestConnectionResult (or an awaitable of one) for the inline panel. All are optional so unit tests can render the section without a wired rclone driver.

Parameters:
Return type:

Any

exlab_wizard.ui.pages.settings.save_button_label(state)[source]#

Compute the Save all button label, including the badge count.

Parameters:

state (SettingsState)

Return type:

str

exlab_wizard.ui.pages.settings.section_has_warning(state, section)[source]#

Return True when the sidebar should decorate section.

Parameters:
Return type:

bool

exlab_wizard.ui.pages.settings.section_is_dirty(state, section)[source]#

Return True when section has uncommitted edits.

Parameters:
Return type:

bool

exlab_wizard.ui.pages.settings.settings_sections_for(config)[source]#

Return the visible section ids for config.

The NAS-remote section is always inserted right after equipment so the operator can configure and test the single nas: remote even before (or independent of) adding nas-mode equipment. config is accepted for signature symmetry with the other section helpers but no longer gates visibility.

Parameters:

config (Config | None)

Return type:

tuple[str, ...]