Skip to content

feat(ui): sidebar nav rewrite — mockup-spec filter rail#14

Merged
DaxxSec merged 1 commit into
mainfrom
feat/sidebar-filter-nav
May 13, 2026
Merged

feat(ui): sidebar nav rewrite — mockup-spec filter rail#14
DaxxSec merged 1 commit into
mainfrom
feat/sidebar-filter-nav

Conversation

@DaxxSec
Copy link
Copy Markdown
Owner

@DaxxSec DaxxSec commented May 13, 2026

Why

You called out lots of drift between the mockup's sidebar and what shipped:

the left navigation items are not at all like your mockup projected it would be

Side-by-side, here's the gap I was closing:

Mockup Shipped (pre-PR)
Compact brand: small hex mark + "SecVF" wordmark + small "Security Virtualization" tag 170×120 hex logo + 26pt heavy "Sec/VF" title + "Security Virtualization Framework" subtitle
Filters section: ▣ All VMs / ● Running / ◐ Paused / ○ Stopped + live counts Nothing — became a toolbar "Focus Running" button instead
Operating System section: 🐧 Linux / ⌘ macOS / ▩ Windows + counts Replaced with marketing bullets ("▸ Malware Analysis / Isolated Sandbox / Virtual Networking")
Network section: ⊘ Isolated / ⇄ Virtual / 🌐 NAT + counts Not built at all
Framework info in bottom status bar Pinned at sidebar bottom
(none) A LOGS button group I added on my own

The current sidebar is marketing real estate; the mockup's is the primary table filter UI. This PR closes that.

What's new

TacticalSidebarSection — reusable view: section label + selectable rows with leading glyph and trailing count. Single-select radio within a section; sections are independent. Hover + selection states use the tactical palette. Click fires onSelect(id).

Sidebar layout, top → bottom:

  1. Brand block (88pt vs 225pt before): 28pt hex mark + 16pt SecVF wordmark + 8pt tag
  2. Filters (status): All VMs / ● Running / ◐ Paused / ○ Stopped with counts
  3. Operating System: All / 🐧 Linux / ⌘ macOS / ▩ Windows with counts
  4. Network: All / ⊘ Isolated / ⇄ Virtual / 🌐 NAT with counts
  5. Logs: ◆ Security / ◆ Network / ◆ ISO Cache — same visual style, dispatches through responder chain to existing AppDelegate handlers, selection pops after the action (logs aren't a persistent filter)

The framework-info footer ("Built on Apple Virtualization Framework") is gone from the sidebar — the bottom status bar already shows that info.

Filter composition

The three sidebar filters AND-compose with each other and with the existing `runningFilterIDs` (Focus Running toolbar):

```
displayedStandardVMs =
master.list
|> ids in runningFilterIDs (if any)
|> status matches statusFilter (if any)
|> osType matches osFilter (if any)
|> networkConfig matches networkFilter (if any)
```

Pure predicates live as static funcs on `VMLibraryWindowController` (`matchesStatusFilter` / `matchesOSFilter` / `matchesNetworkFilter`) so the rules are testable without touching the window.

"Isolated" in the Network section means virtual-mode VMs with no router relationship — not a router, not a guest. That matches "this VM can't talk to anything outside its own L2".

Tests

New `SidebarFilterTests` — 11 passing:

  • Each predicate's positive + negative cases
  • Paused covers both `.starting` and `.stopping` (the two transient framework states)
  • OS matching is case-insensitive substring (handles "Kali Linux", "Apple Mac OS", etc.)
  • Isolated excludes both routers and guests, even in virtual mode

Full suite: 261/261 green on this branch (one pre-existing flaky network test skipped as before).

Test plan

  • `xcodebuild build` succeeds
  • `xcodebuild test` — 261/261 passing
  • Launch app — sidebar shows brand + 4 sections (Filters / OS / Network / Logs) with count badges
  • Click ● Running in Filters — table narrows to only running VMs
  • Click 🐧 Linux in OS — table narrows further to running Linux VMs (combined filter works)
  • Click All VMs to reset Filters section — OS filter still active
  • Click ◆ Security under Logs — Security Log window opens; row selection pops
  • Resize window — sidebar sections stay anchored, no overlap with table

Out of scope

  • The Filter section's chevron-toggleable collapse from the mockup (▾/▸) — sections always expanded for now; collapse can come as a follow-up if the rail feels too tall
  • Snapshot tests of the sidebar at different VM counts — would catch a future count-badge regression, filing as a separate follow-up

🤖 Generated with Claude Code

… / Network / Logs)

The merged redesign kept the original SecVF marketing sidebar (big
hex logo + 26pt "Sec/VF" title + framework subtitle + ▸ Malware
Analysis / Isolated Sandbox / Virtual Networking bullets) and added
a small "LOGS" button group at the bottom. The mockup specced
something fundamentally different: a *functional* navigation rail
that drives the table content.

This PR closes that drift.

Before
------
- 170×120 hex logo (huge — eats top third of the rail)
- 26pt heavy mono "Sec/VF" title
- "Security Virtualization Framework" subtitle
- 3-line marketing bullet list
- LOGS button group (3 entries)
- Framework info pinned to bottom ("Built on Apple Virtualization
  Framework / github.com/DaxxSec/SecVF")

After
-----
- Compact brand block: 28pt hex mark + 16pt "SecVF" wordmark + 8pt
  "Security Virtualization" tag (~88pt total vs ~225pt before)
- **Filters** section: All VMs / ● Running / ◐ Paused / ○ Stopped
  with live count badges, single-select, drives the table
- **Operating System** section: All / 🐧 Linux / ⌘ macOS / ▩ Windows
  with live count badges, single-select
- **Network** section: All / ⊘ Isolated / ⇄ Virtual / 🌐 NAT
  with live count badges, single-select
- **Logs** section: ◆ Security / ◆ Network / ◆ ISO Cache —
  re-implemented in the same TacticalSidebarSection style, dispatches
  through the responder chain to AppDelegate's existing handlers,
  pops selection after action (logs aren't a persistent filter)
- Framework info dropped from the sidebar — bottom status bar
  already carries that information

New component: `TacticalSidebarSection`
---------------------------------------
Reusable view for "section label + selectable rows with leading
glyph and trailing count". Single-select radio within a section;
sections are independent. Hover + selection states match the
tactical palette (OD-green selection band, accent glyph). Click
fires an `onSelect(id)` closure with the row's identifier.

`refreshCounts(_:)` lets the controller push new badge values in
place without rebuilding the layout — cheaper for the common
"VM list changed, counts shifted" path.

Filter composition
------------------
The three sidebar filters AND-compose with each other and with
the existing `runningFilterIDs` set (Focus Running toolbar button):

```
displayedStandardVMs =
  master.list
    |> ids in runningFilterIDs (if any)
    |> status matches sidebarStatusFilter (if any)
    |> osType matches sidebarOSFilter (if any)
    |> networkConfig matches sidebarNetworkFilter (if any)
```

Pure predicates live as static functions on VMLibraryWindowController
(matchesStatusFilter / matchesOSFilter / matchesNetworkFilter) so
the rules are testable without touching the window.

"Isolated" in the Network section means virtual-mode VMs with no
router relationship at all — not a router, not a guest. That
matches the operator's mental model of "this VM can't talk to
anything outside its own L2".

Tests
-----
New `SidebarFilterTests`: 11 tests covering each predicate's
positive + negative cases, including:
- Paused covers both .starting and .stopping (the two transient
  framework states)
- OS matching is case-insensitive substring (handles "Kali Linux",
  "Apple Mac OS", etc.)
- Isolated excludes both routers and guests, even in virtual mode

Full suite: 261/261 passing on this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@DaxxSec DaxxSec merged commit 3dfcece into main May 13, 2026
1 check passed
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