Files
Ekaterina Broslavskaya 177f70141c chore: Recovery rework, steward-leave correctness, and UI rework (#58)
* feat(app/core): recovery-flow rework + user/core module reorg

Five-item recovery-flow rework making the bad path recoverable under
the deterministic-proposer model. Happy path unchanged.

- Buffer hygiene on join: skip buffering during PendingJoin, prune
  pending_updates on JoinedGroup, defensive member filter on apply,
  drop Add-for-present / Remove-for-absent in candidate build
- No self-accusation in censorship ECP: skip should_accuse when
  live_epoch_steward == self_identity
- Reelection exit paths: start_working on emergency resolve and on
  election accepted
- Election retry auto-fire: schedule try_initiate_steward_election
  after freeze_duration * 2 while retry_round <= max_retries
- Deterministic ECP proposer: responsible_ecp_proposer walks the
  rotation; non-proposers buffer evidence in pending_ecps with TTL

Reorganization alongside:
- User methods split into lifecycle / query / consensus_events
  (plus existing consensus / freeze / inbound / messaging / steward)
- Core: proposal_kind.rs replaces proposals.rs + proposal_priority.rs;
  election.rs folded into neighboring modules
- Tests: shared fixtures in tests/common; core_peer_scoring and
  core_steward_list moved to inline unit tests; selection_distribution
  moved to examples/; new tests/recovery_cascade.rs covers the
  concurrent-join + leave scenario

* refactor(logging): unified structured log format with fixed-column layout

Migrate tracing calls across the workspace to structured fields, strip
[function_name] prefixes (the module path already provides that),
collapse redundant per-layer logs, and add a fixed-column formatter
with colors for the desktop UI.

- `ShortId` wrapper in `mls_crypto` renders identities as 8-char hex
  prefixes instead of raw byte-array debug output
- Inline `"msg with {group} and {id}"` patterns replaced with
  `info!(group = ..., id = ..., "msg")` across src/{core,app} and
  crates/de_mls_gateway
- Dropped three-log-per-event patterns (e.g. removed
  `"Stored voting proposal: N"` — proposal_id already logged by
  `proposal opened` one line earlier)
- Custom `FormatEvent` + `FormatFields` in
  apps/de_mls_desktop_ui/src/logging.rs:
  timestamp (dim) | level (bold colored) | target (dim, pad 37) |
  message (pad 40) | key=value fields
- Colors only on stdout via `has_ansi_escapes()`; file sink stays
  plain text so logs/de_mls_ui.log remains grep-friendly

* fix(app/core): steward-leave correctness — rotation + consensus dedup + sync tolerance

Three related fixes for the self-leave path.

- `live_epoch_and_backup` on `StewardList` / `Group` resolves both slots
  in one pass and keeps them distinct when ≥2 stewards are eligible.
  The old `live_backup_steward` walked from the nominal backup index, so
  if the epoch-steward walk skipped the nominal epoch and landed on the
  nominal backup, both slots collapsed onto the same identity and the
  UI lost the Backup Steward role. `live_backup_steward` removed as
  dead code.
- `Group::{is,mark}_consensus_outcome_applied` + a guard at the top of
  `apply_consensus_outcome` drop re-emitted `ConsensusReached` events
  from the consensus library (timeout-path race). Previously the same
  outcome re-applied the steward list, re-fired UI events, and doubled
  logs ~3 s apart.
- `on_group_sync` relaxes the `all_present` check to `any_present`. A
  ghost steward (still in the list because no pruning happens on a
  member-removal commit) is filtered at query time by the `live_*`
  rotation helpers, so rejecting the whole sync was too strict. A sync
  with zero live stewards is still rejected. Proper list pruning on
  removal tracked in the roadmap.

* refactor(ui): redesign home layout with tabbed rail and structured vote cards

Replaces the four equal columns (Groups | Chat | Consensus | Members)
with a primary-chat layout: status strip on top, Groups rail (220),
Chat column (flex), and a tabbed details rail (400) for Members /
Proposals / History.

Votes surface as a scroll-capped banner at the top of the chat column
so YES/NO never falls below the fold. Cards are vertically stacked
(head, body, actions) with type-specific bodies — election stewards
render one per line in their own scrollable list for large groups.
History entries become outcome-colored cards (applied / rejected)
with explicit badges instead of run-on text.

State context (Freezing/Selection/Reelection/PendingJoin/Leaving)
collapses into a single strip above the composer, replacing the old
composer-notice. Min window size (1100x680) prevents shrinking into
broken layouts; #main is now a flex column so the sticky header does
not push panels below the fold. Login input width fixed.

* docs(core): drop private intra-doc link on live_epoch_and_backup

The link to Self::is_steward_eligible triggered the
rustdoc::private_intra_doc_links warning under
--document-private-items, which CI treats as an error.
2026-04-23 14:16:25 +03:00
..