Skip to content

Make the dashboard's banner logic a pure, tested function - #315

Merged
okhsunrog merged 1 commit into
mainfrom
refactor/dashboard-issues
Aug 25, 2026
Merged

Make the dashboard's banner logic a pure, tested function#315
okhsunrog merged 1 commit into
mainfrom
refactor/dashboard-issues

Conversation

@okhsunrog

Copy link
Copy Markdown
Owner

loadDashboardState was 623 lines, ~290 of which were the guards deciding which banners a device gets. Each ended in a res.getString, which is what kept them untested — every branch needed a real Resources and this module has no Robolectric, so the most user-visible logic in the app had no coverage while the pure detectors feeding it had plenty.

The split follows the seam already used elsewhere in the codebase (classifyKmodProblem/renderKmodProblem): dashboardIssues() takes a DashboardFacts and returns List<DashboardIssue> with no Android types anywhere, and DashboardIssue.toMessage() words one. Both detekt suppressions come off loadDashboardState — it passes the gate on merit now, at 55 lines.

Behaviour is unchanged, verified emission by emission against the original: all 34 sites map one-to-one, in the same order, with the same severities. Two things that only looked like changes are called out in the commit body.

  • add DashboardIssue (28 cases, grouped into six sub-interfaces) and the pure dashboardIssues(DashboardFacts)
  • add DashboardIssueRender.kt — the only half that needs a Context
  • fold filesystemHidingDashboardMessage into the same two halves and delete its file
  • collapse the four near-identical module-diagnosis blocks into deriveModuleFact
  • split derivation into deriveModuleFacts / deriveLsposedFacts / deriveEnvironmentFacts / resolveProtectionFacts
  • add DashboardIssuesTest — 31 tests, including one pinning banner order
  • emit dashboard_issues_done, the mark measure-startup.py has always read and never received, and correct the stage spans around it
  • record the classify-then-render rule in AGENTS.md

…unction

loadDashboardState was 623 lines, and the ~25 guards that decide which banners
a device gets were ~290 of them, each ending in a res.getString. That shape is
what kept them untested: every branch needed a real Resources, this module has
no Robolectric, and so the single most user-visible piece of logic in the app
had zero coverage while the pure detectors feeding it had plenty.

Split along the seam the codebase already uses elsewhere — classifyKmodProblem
/renderKmodProblem, classifyKpmProblem/renderKpmProblem. dashboardIssues() takes
a DashboardFacts and returns List<DashboardIssue>, no Android types anywhere;
DashboardIssue.toMessage() words one. A case carries the data its wording needs
rather than a preformatted string, so the split is real and not cosmetic.
filesystemHidingDashboardMessage was the odd one out doing both halves itself;
it folds in, and its file goes.

Emission order is load-bearing — the screen groups by severity but keeps
emission order within a group, so this decides which error a user reads first.
It used to be implied by the physical layout of one long block, where inserting
a guard in the wrong place silently reordered banners. It is now eight named
calls in one place, pinned by a test.

The four backends' diagnosis blocks were near-identical eighteen-line copies
differing only in kind, activator path and classifier; they collapse into
deriveModuleFact. Derivation splits into deriveModuleFacts / deriveLsposedFacts
/ deriveEnvironmentFacts / resolveProtectionFacts, leaving an orchestrator that
reads as the four steps it always was. Both detekt suppressions come off — the
gate now passes on merit rather than by exemption.

Behaviour is unchanged, checked emission by emission against the original: all
34 sites map one-to-one, in the same order, with the same severities. Two things
that only looked like changes: the non-kmod module problems dropped their
downloadArtifact at the callsite, which was a no-op because only renderKmodProblem
ever sets one, and Active.version being null already meant no mismatch banner
because versionsMismatch returns false for it. One dead local (zygiskStatusRaw)
went with the rewrite.

Also fixes a mark the profiler has been asking for and never received:
measure-startup.py reads dashboard_issues_done, the code only ever emitted
dashboard_messages_done, so the "issues" stage has always measured nothing.
@okhsunrog
okhsunrog merged commit d4197f6 into main Aug 25, 2026
35 checks passed
@okhsunrog
okhsunrog deleted the refactor/dashboard-issues branch August 25, 2026 20:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant