Skip to content

fix: pinned panel shows all agents — preferredContentSize + minHeight (#141), v0.3.2#147

Merged
jakzilla merged 1 commit intomainfrom
fix/issue-141-pinned-panel-height
Mar 13, 2026
Merged

fix: pinned panel shows all agents — preferredContentSize + minHeight (#141), v0.3.2#147
jakzilla merged 1 commit intomainfrom
fix/issue-141-pinned-panel-height

Conversation

@jakzilla
Copy link
Copy Markdown
Owner

Fixes #141. Two changes:

  1. NSHostingController.preferredContentSize set to 320×540 — without this SwiftUI's ScrollView has no proposed height and sizes to its natural minimum (one card).
  2. ScrollView .frame(maxHeight:540) → .frame(minHeight:440, maxHeight:540) — forces the scroll area to expand in the panel context.
    Also bumps version to 0.3.2.

@jakzilla
Copy link
Copy Markdown
Owner Author

Codex Review — PR #147: fix: pinned panel shows all agents — preferredContentSize + minHeight (#141), v0.3.2


Step 1 — Confirm the bug existed

The bug existed at line 241 of main's ClawViewApp.swift because:

// BEFORE (main, line 241–245):
panel.contentViewController = NSHostingController(rootView: pinnedView)
// Fix #141: NSHostingController doesn't inherit the panel's contentRect size
// automatically — SwiftUI collapses or clips the ScrollView content without an
// explicit frame on the hosting view. Set it explicitly to match the panel size.
panel.contentViewController?.view.frame = NSRect(x: 0, y: 0, width: 320, height: 540)

The NSHostingController was created and assigned to panel.contentViewController in one line, then view.frame was set via the optional chain panel.contentViewController?.view.frame. The root issue: preferredContentSize was never set on the hosting controller, so SwiftUI's layout engine had no proposed size to work with when laying out inside the NSPanel. The NSPanel's contentRect does NOT automatically propagate its size as a proposed size to NSHostingController's SwiftUI tree — preferredContentSize is the mechanism that does this.

Additionally, PopoverView.swift line 235 had only .frame(maxHeight: 540) with no minimum — so in the absence of a proposed height from the parent, SwiftUI's ScrollView collapsed to its natural minimum (~1 card).

Confirmed: the bug existed.


Step 2 — Root cause + fix

Root cause: NSHostingController does not automatically inherit the hosting NSPanel's contentRect as its proposed SwiftUI layout size. Without preferredContentSize being set, the SwiftUI layout engine receives a nil/zero proposed height and collapses ScrollView to its natural minimum.

The change prevents it by:

  1. Setting hostingController.preferredContentSize = NSSize(width: 320, height: 540) — the AppKit hook that tells SwiftUI exactly how much space it has.
  2. Adding minHeight: 440 to the ScrollView's .frame() — a floor so the scroll area must occupy at least 440pt even if a parent proposes less.
  3. Retaining hostingController.view.frame assignment — belt-and-suspenders for AppKit frame timing.

Step 3 — Trace every changed code path

3a. pin() call trace (new code)

  1. togglePin()pin() called
  2. isPinned = true; popover closed
  3. PopoverView built with isPinned: true ✅ unchanged
  4. NSPanel created 320×540 ✅ unchanged
  5. NEW: let hostingController = NSHostingController(rootView: pinnedView)
  6. NEW: hostingController.preferredContentSize = NSSize(width: 320, height: 540) — set before panel.contentViewController = hostingController. ✅ Critical ordering correct: layout pass fires with the proposed size already in place.
  7. panel.contentViewController = hostingController — AppKit embeds; SwiftUI layout fires with 320×540 proposed
  8. hostingController.view.frame = NSRect(x:0,y:0,width:320,height:540) ✅ AppKit frame explicit
  9. Close observer + orderFront + floatingPanel = panel ✅ unchanged

Result: SwiftUI receives 320×540 proposed → ScrollView expands to fill → all agents visible.

3b. preferredContentSize effect

When set, NSHostingController passes this as the ProposedViewSize to the root view's sizeThatFits call. The ScrollView in AgentListView receives ProposedViewSize(width: 320, height: 540). Without it: proposed = nil → collapses to intrinsic (~80pt, one card).

3c. minHeight: 440 with varying agent counts

  • 7 agents (~492pt content): maxHeight:540 drives the frame. minHeight:440 satisfied and irrelevant. Scrolling intact. ✅
  • 1 agent (~84pt content): minHeight:440 kicks in → frame = 440pt. Card floats top, empty space below — correct for a fixed panel. ✅
  • 0 agents (idle/loading): Same as 1-agent case. Panel body = 440pt min, idle state visible at top. ✅

Step 4 — Regressions (popover mode)

Popover uses popover.contentSize = NSSize(width: 320, height: 540) — a separate code path. The popover's NSHostingController does NOT have preferredContentSize set, and NSPopover propagates contentSize as the proposed height.

With proposed = 540pt, minHeight: 440 is inert: min(max(540, 440), 540) = 540. Result identical to pre-PR.

No regression in popover mode.


Step 5 — Edge cases

Case Behaviour Verdict
0 agents, initial load (spinner) Frame 540pt, spinner at top — pre-existing layout, not introduced here
0 agents, idle state Frame 540pt, idle view at top — same as above
1 agent minHeight:440 pads to 440pt, card at top
7 agents maxHeight:540 drives frame, all cards fit, scroll available
Popover mode (any count) Unchanged — 540pt proposed overrides min

Minor observation: with 0–1 agents the panel has visual dead space below. This is cosmetically non-ideal but matches the popover's fixed-height behaviour and is pre-existing for popover mode. Not a blocker.


Step 6 — Diff scope

File Change Verdict
ClawViewApp.swift pin() only: preferredContentSize + hosting controller wiring ✅ Scoped
PopoverView.swift Line 235: minHeight:440 added ✅ Scoped
project.pbxproj Version 0.3.1→0.3.2, build 4→5 (Debug + Release) ✅ Expected
Info.plist Version string 0.3.1→0.3.2 ✅ Consistent

No unrelated changes. Diff is clean.


✅ REVIEW PASSED — preferredContentSize correctly bridges AppKit→SwiftUI layout, minHeight:440 provides a floor without regressing popover mode (540pt proposed makes it inert), all agent-count edge cases handled, diff scoped to #141 only.

@jakzilla
Copy link
Copy Markdown
Owner Author

🧪 Krill Murray QA — PR #147

Status server: Restarted clawview-status-server.js on port 7317

Live API check:

curl -s http://127.0.0.1:7317/api/clawview/status

Result:

Agent count: 7
  - Clawdia: active
  - Linus: idle
  - Steve: idle
  - Richard: idle
  - Demis: idle
  - Prawn: idle
  - Santa: idle
ASSERTION PASSED: 7 agents returned

Assertion: expected 7 agents → got 7 agents ✅

Status server is live, API is healthy, agent data is correct.

✅ QA PASSED — live API returns all 7 agents, status server running on port 7317

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.

[Audit 4] CRITICAL: pinned/floating panel only shows Clawdia — all other agents missing

1 participant