Skip to content

Design pr 3#374

Open
eonist wants to merge 189 commits into
mainfrom
fix/issue-294-popover-redesign-design-branch-3
Open

Design pr 3#374
eonist wants to merge 189 commits into
mainfrom
fix/issue-294-popover-redesign-design-branch-3

Conversation

@eonist
Copy link
Copy Markdown
Collaborator

@eonist eonist commented May 10, 2026

User description

Closes #294

Re-opening of #309 (which was merged then reverted in #312) so it can be reviewed and merged when ready.

Summary

Redesigns PopoverMainView to match the new dynamic layout specified in #178 / #294.

Changes

  • Header row — system stats (CPU · MEM · DISK) merged inline left; settings + ✕ close button right
  • Auth indicator simplified — previous header showed "RunnerBar v0.34 / Authenticated / Sign in with GitHub"; replaced with a compact 7pt dot (green = authenticated, orange = unauthenticated) + inline "Sign in" caption when token is missing. Functionally equivalent; removes version string from popover header.
  • Unified scrollable actions list — replaces the separate System / Actions / Active Jobs sections
  • Pie/radial progress dotPieProgressDot replaces plain colored dots on action rows and inline job rows. Shows radial fill fraction (concluded jobs / total jobs for groups; concluded steps / total steps for jobs). Color semantics unchanged.
  • "Started X ago" timestamp — compact relative timestamp (e.g. "3m ago", "1h ago") added to action row trailing meta via RelativeTimeFormatter.
  • Inline ↳ job expansion — in-progress action groups can be expanded to show running job rows with step name + progress + elapsed
  • "Load more jobs" affordance — expanded groups with > 4 jobs show a tappable "Load more jobs…" button (reveals +4 at a time) or "+ N more…" caption for small overflows. State is per-group via inlineJobsLimit dictionary in PopoverMainView.
  • Typography parity (⭐ Maybe show action items in a more dynamic way. #178) — status chip font weight raised to .bold; consistent 6pt horizontal spacing around pie dot.
  • Removed standalone "Active Jobs" section — job context lives under its parent action row
  • Conditional Local Runners row — visible only when at least one runner is online; hidden when all idle/offline (Phase 6 stub)
  • Overflow indicators — + N more… caption shown in runner rows (capped at 3) when entries are hidden
  • Pagination — "Load N more actions…" button; label and increment use the same computed nextBatch constant
  • Close button (✕) — hides the app (calls NSApplication.shared.hide)
  • All regression-guard frame/padding rules preserved

Self-review fixes (vs original #309)

Issue #315 fixes (committed on this branch)

  • SystemStatsViewModel.start() / stop() added; timer no longer auto-starts in init() (2b97131)
  • Completed group status uses group.conclusion == "success" instead of runs.allSatisfy — fixes mis-labeled red groups (2bd231a)
  • Action group store/display cap lifted from 5 → 30; UI pagination now reachable (c30b18f)

Checks (on previous merged HEAD 4e91c7b)

  • ✅ SwiftLint
  • ✅ Octopus Review
  • ✅ SonarQubeCloud

Note

Not merging on the agent's behalf. Awaiting your explicit approval before merge.


CodeAnt-AI Description

Redesign the popover with a wider layout, richer job details, and clearer controls

What Changed

  • The main popover now uses a wider, dynamic layout that shows system stats in the header, recent actions in a single list, and inline job details under running actions
  • Action and job rows now show clearer progress, timing, and status information, including a pie-style progress indicator and relative “started ago” text
  • Job and step detail screens now include repo, branch, and GitHub links, plus start/end timestamps and step numbers
  • The settings screen now shows all sections without internal scrolling, with clearer toggle placement and updated notification defaults
  • Local runner detection now avoids broad home-folder scans, reducing permission prompts when finding installed runners

Impact

✅ Clearer run status at a glance
✅ Faster access to GitHub details
✅ Fewer permission prompts during runner setup

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

eonist added 30 commits May 8, 2026 12:11
)

- Collapse System/Actions/ActiveJobs into a single scrollable list
- System stats merged into header row (CPU · MEM · DISK inline)
- Action rows show inline ↳ job expansion for in-progress groups
- Remove standalone "Active Jobs" section (jobs shown under action rows)
- Add conditional Local Runners row (hidden when all runners idle/offline)
- Add "Load 10 more actions…" pagination button stub
- Close button (×) added to header alongside settings gear
- All regression-guard frame/padding rules preserved (ref #52 #54 #57)
…h (issue #294)

- Extract ActionRowView and InlineJobRowView into file-private structs
  to bring PopoverMainView under the 400-line file_length limit
- Replace all trailing-closure Button{} with explicit Button(action:label:)
  to satisfy multiple_closures_with_trailing_closure rule
- Fix #1: ActionRowView — expand chevron moved outside outer Button;
  now composed as HStack(tappable body | expand button) so onToggleExpand
  fires reliably on macOS SwiftUI without being swallowed by the row tap.
- Fix #2: double Divider — removed the unconditional Divider() between
  headerRow and localRunnerRow; localRunnerRow owns its leading + trailing
  Divider inside its @ViewBuilder guard, so no adjacent dividers appear.
- Fix #3: Phase 4 follow-up — TODO comment added referencing #310 / #182;
  follow-up issue #310 opened to track the pie-dot radial progress work.
- Fix #4: systemStats timer leak — added .onDisappear { systemStats.stop() }
  so the repeating timer/publisher is torn down when the popover closes.
…ody_length

SwiftLint reported two violations on commit f126705:
  - file_length: 442 > 400 (warning)
  - type_body_length: PopoverMainView body 202 > 200 (warning)

Fix: extract header, local-runner row, action row, and inline job rows
into PopoverMainViewSubviews.swift. PopoverMainView.swift now contains
only the root struct (~115 lines, body ~40 lines). Subviews file is
~265 lines. No behaviour change; all 4 prior fixes remain intact.
1. loadMoreButton overshoot: clamp visibleCount to store.actions.count
   so tapping 'Load more' never sets visibleCount beyond the total.
2. authIndicator: restore 'Sign in' caption next to orange dot so
   unauthenticated state is discoverable without tooltip-only affordance.
3. InlineJobRowsView: add TODO comment above prefix(4) cap noting that
   a 'load more' affordance is needed when activeJobs.count > 4.
4. expandedGroupIDs stale entries: add .onChange(of: store.actions)
   to prune IDs of groups that are no longer in the actions list.
Replace runs.allSatisfy { $0.conclusion == "success" } with
group.conclusion == "success" in both statusLabel and dotColor.

group.conclusion is nil while any run is still live (returns nil
unless every run has concluded), so completed groups that arrive
before the API marks all runs done were previously mis-labeled red.
The new check reads the already-computed group-level conclusion
string directly, matching RunnerStore's own priority logic
(failure > cancelled > skipped > success). Empty-runs groups
return "success" vacuously via the allSatisfy base case, which is
consistent with the previous behaviour.
Raise trimGroupCache limit and buildGroupDisplay loop guards from 5 to 30.
This makes the UI "Load more" pagination in PopoverMainView reachable in
practice (visibleCount starts at 10; store now serves up to 30 groups).
The cache trim at 30 keeps memory bounded while covering realistic
workloads (many repos with concurrent in-progress + queued + recent runs).
The label said min(10, store.actions.count - visibleCount) but the action
clamped with min(visibleCount + 10, store.actions.count). Both now use
the same computed nextBatch constant so label and increment are always
consistent: shows the exact number of groups that will appear on tap.
- InlineJobRowsView: append "+ N more…" caption below the 4-row cap
  when activeJobs.count > 4, so users know jobs are hidden.
- PopoverLocalRunnerRow: append "+ N more…" caption below the 3-row cap
  when active.count > 3, so users know runners are hidden.
…ypography parity (#294 / #178)

- PieProgressDot: small radial fill view replacing plain colored dot on
  action rows and inline job rows. Progress fraction from group/job
  elapsed vs estimated; color semantics unchanged.
- RelativeTimeFormatter: Date → short string ("3m ago", "1h ago", "2d ago")
  used for started-ago label on action rows.
- ActionRowView: pie dot replaces circle dot; "Xm ago" compact timestamp
  added alongside elapsed. Typography parity: status chip font weight
  raised to .bold, consistent spacing.
- InlineJobRowsView: pie dot on job rows; tappable "Load more jobs"
  affordance added when jobs exceed the 4-row cap. Limit state
  passed in as binding (inlineJobsLimit) from PopoverMainView.
- PopoverMainView: inlineJobsLimit state dictionary added; passed to
  InlineJobRowsView; pruned alongside expandedGroupIDs on store change.

All new types documented with ///. Files stay under SwiftLint
file_length 400 / type_body_length 200 limits.
InlineJobRowsView now takes @binding var jobLimit. PopoverMainView
maintains a @State jobLimits dict keyed by group.id so each expanded
group has its own independent limit (starts at 4, increments by 4 via
the Load more jobs affordance).
…WithGitHub docs

- Always render Divider() after PopoverHeaderView so separator is visible
  even when store.isRateLimited==false and all runners are offline (#294)
- Drop redundant leading Divider inside PopoverLocalRunnerRow.runnerList
  (parent now always provides one; trailing Divider preserved)
- loadMoreButton: @ViewBuilder guard nextBatch > 0 else EmptyView()
- Restore multi-line doc comment on signInWithGitHub (refs #221 #246)
…e dot, scrollview cap, simplify expansion

1. Inline job rows always visible for in-progress groups (no expand/collapse)
2. Remove chevron from inline job rows (non-tappable children)
3. PieProgressDot: filled wedge via GeometryReader+Path
4. ScrollView maxHeight: 400 cap
5. InlineJobRowsView: drop jobLimit Binding, fixed cap 4 + passive '+ N more…'
6. PopoverLocalRunnerRow: refresh LocalRunnerStore on appear
7. Reset visibleCount to 10 in onChange(of: store.actions)
…ter (#178)

- statChip: add .lineLimit(1) to label and value Text views to prevent
  fittingSize height changes in AppDelegate.openPopover() (load-bearing)
- jobRow: move current step name between job title and step counter,
  separated by middle-dot ·; falls back gracefully when no in_progress step
… add block-bar chips

- Gap 1: remove cosmetic chevron.right from PopoverLocalRunnerRow (no navigation wired)
- Gap 2: remove Button wrapper from InlineJobRowsView job rows (read-only per spec)
- Gap 3: add blockBar() helper; prepend 3-char block bar to each stat chip value
- Gap 4: replace passive '+ N more…' Text with tappable load-more Button (+4 per tap)

Fixes #324
InlineJobRowsView no longer accepts onSelectJob (Gap 2 — rows are read-only).
Update PopoverMainView.actionsSection accordingly.

Fixes #324
…) for load-more

- identifier_name: `f` renamed to `filledCount` (>=3 chars)
- multiple_closures_with_trailing_closure: Button(action:label:) explicit form
  used for the Gap 4 load-more button in InlineJobRowsView.body
…ble, resolvingSymlinksInPath, RunnerStoreObservable missing, enrichGroupJobs missing, LocalRunnerScanner dict literal, StepLogView optional)
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented May 12, 2026

CodeAnt AI is running Incremental review

@codeant-ai codeant-ai Bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels May 12, 2026
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented May 12, 2026

CodeAnt AI Incremental review completed.

eonist added 8 commits May 12, 2026 21:31
- Repo label → https://github.com/{owner}/{repo}
- Branch label → https://github.com/{owner}/{repo}/tree/{branch}
- SHA/PR origin label → PR URL (https://github.com/{owner}/{repo}/pull/{N})
  or commit URL (https://github.com/{owner}/{repo}/commit/{sha})
  derived from ActionGroup.label ("#1270" → PR, else short-sha → commit)
- All three use Button(.plain) + .help() tooltip
- ActionGroup passed as new `group` prop; all call sites must supply it
detailViewFromAction(job:group:) and detailView(job:) now supply the
required `group` parameter to JobDetailView so the metadata row
(repo / branch / SHA-origin chips) has the data it needs.

detailView(job:) constructs a minimal ActionGroup from the job's
htmlUrl so the chips still render correctly on the Jobs (non-Actions)
navigation path.
…jump on menu bar hide

ResizeAndRepositionPanel() was recomputing Y from the live
statusItemRect.minY on every KVO fire. When macOS auto-hides the menu
bar, that rect shifts down (or to 0) — pulling the panel under the
camera notch.

Fix: capture panelOriginY once in openPanel() from the initial
statusItemRect, and use that stored Y in every subsequent
resizeAndRepositionPanel() call. Only X is re-derived live (to keep
horizontal centering correct on width changes). Y is immutable for the
lifetime of the panel session.

Reset panelOriginY to nil in closePanel().
… correctly

Previous fix locked panelOriginY = statusItemRect.minY - initH - gap
(bottom-left corner), but resizeAndRepositionPanel() sets totalH from
the real content size — growing the panel downward from a stale bottom
coord broke alignment entirely.

Fix: store panelTopY = statusItemRect.minY - gap (the top edge, just
below the status bar). resizeAndRepositionPanel() then computes
y = panelTopY - totalH on every resize, so the panel always grows
downward from the correct top anchor regardless of content height.
Y origin never tracks live statusItemRect movement.
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented May 12, 2026

CodeAnt AI is running Incremental review

@codeant-ai codeant-ai Bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels May 12, 2026
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented May 12, 2026

CodeAnt AI Incremental review completed.

eonist added 5 commits May 12, 2026 22:43
… start()

Two issues caused blank stats on open:
1. start() called sample() synchronously on main thread. First call always
   returns CPU=0 (prevTicks all zero → dTotal=0 guard). Real values only
   appeared after the 2s timer tick.
2. No pre-warm on close: prevTicks reset to zero between opens, so every
   re-open had the same blank-for-2s problem.

Fix:
- start() dispatches sample() to background queue immediately so the first
  real values publish to SwiftUI before the first timer tick.
- stop() calls sample() synchronously on whatever queue it is on (background)
  to capture a fresh prevTicks snapshot. Next start() delta is valid instantly.
…just non-dimmed groups

isDimmed filter was excluding groups whose jobs were still in-progress,
meaning only 1-2 jobs from the non-dimmed group were counted while 8+
in-progress jobs in dimmed groups were invisible to the icon progress.

Fix: include any group that has at least one incomplete job (conclusion==nil),
regardless of isDimmed. Dimmed groups with ALL jobs concluded are still
excluded — they are truly done and should not affect progress fraction.
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🎨 Redesign main popover view (issue #178)

1 participant