Source code for exlab_wizard.ui.components.sync_rollup

"""Folder/run-level sync-status rollup (two-icon presence model, 2026-05-30).

When a run row (left tree) or a folder (metadata pane) is summarised, it carries
a single "worst-of" rollup icon reduced from its children's per-file sync
discriminators. This module owns that reduction as a pure, NiceGUI-free function
so the severity ordering is testable in isolation.

The reduction is over :class:`FileSyncView` (the UI presentation view), not the
schema-committed ``SyncStatus`` enum: severity is a presentation concern, so it
lives here beside its only consumer rather than on the wire enum.
"""

from __future__ import annotations

from collections.abc import Iterable

from exlab_wizard.ui.components.sync_status_icon import FileSyncView, file_sync_view

# Most-attention-worthy first. A folder/run is summarised by the highest-ranked
# view any child carries.
_SEVERITY_ORDER: tuple[FileSyncView, ...] = (
    FileSyncView.MISSING,
    FileSyncView.UPLOAD_FAILED,
    FileSyncView.BLOCKED,
    FileSyncView.LOCAL_ONLY,
    FileSyncView.SYNCED,
    FileSyncView.ON_NAS,
)
_SEVERITY_RANK: dict[FileSyncView, int] = {v: i for i, v in enumerate(_SEVERITY_ORDER)}


[docs] def sync_rollup(statuses: Iterable[str | None]) -> str | None: """Reduce per-file sync discriminators to a single worst-of view value. Returns the highest-severity recognised view's value string (``missing > upload_failed > blocked > local_only > synced > on_nas``), or ``None`` when there is nothing to roll up (empty, or only ``None`` / unrecognised values map to :attr:`FileSyncView.NONE`, which is ignored). """ best: FileSyncView | None = None best_rank = len(_SEVERITY_ORDER) for status in statuses: view = file_sync_view(status) rank = _SEVERITY_RANK.get(view) if rank is None: # NONE continue if rank < best_rank: best_rank = rank best = view return best.value if best is not None else None