On macOS 26 SwiftUI's Table retained AutoLayout and view-graph objects on
every data update, on top of an NSHostingView per cell: tens of MB an hour
while the window was visible. Measured side by side (6 min, window
visible, scrolling), heap growth fell from ~600 KB/min to ~35 KB/min and
the starting footprint from 67 MB to 49 MB.
Cells are reused AppKit views and an update rewrites only the changed
fields of rows on screen. Column widths and order are remembered; the
context menu, double-click, Delete key and sort directions match the old
table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Power & Storage only had macOS's thermal state (a level, no degrees) and
the battery temperature. The CPU die sensors are now read through
IOHIDEventSystemClient (no root or entitlement needed outside the
sandbox): "PMU tdie*" from M2 on, "pACC/eACC MTR Temp Sensor*" on M1.
System Status shows the hottest one, with the average and sensor count in
the tooltip, coloured from 80/90/100 °C. Intel Macs have no such sensors
and the row stays hidden.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
project.yml declared `info: path: TopManager/Info.plist` without
properties, so every `xcodegen generate` rewrote Info.plist with
XcodeGen's defaults, 1.0 (1). That silently undid the version bump in
cf88044: its message says 1.1.1 (3), but the commit actually set
Info.plist back to 1.0 (1).
The version now lives in MARKETING_VERSION / CURRENT_PROJECT_VERSION in
project.yml, and the generated Info.plist references them. Regenerating
is idempotent and the built app reports 1.1.1 (3).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
RAM exceeding Memory is normal for any SwiftUI/AppKit app — resident size
includes system framework pages shared by every process, while the
footprint counts only the process's own modified pages (in RAM,
compressed or swapped) — but it reads like a bug without context.
The inspector now shows a short "About memory" note under the figures,
plus a line saying memory figures are unavailable when the process
belongs to another user or the system (they read as zero otherwise).
Bump version to 1.1.1 (3).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First version change since v1.0.0: adds the RAM and Compressed memory
columns and ships the memory-leak, pause-while-hidden and zero-value
flicker fixes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds two columns to the Processes table (and rows to the inspector) next
to Memory, which is the physical footprint:
- RAM: pages currently in physical memory (ri_resident_size, from the
proc_pid_rusage call that was already being made).
- Compressed: the original size of the process's pages held by the
compressor, via task_name_for_pid + TASK_VM_INFO. This is also the
closest per-process swap figure: macOS pages out compressor segments,
not processes, and those stay counted here. Activity Monitor's
"Compressed Memory" and top's CMPRS report the same value.
Both need no privileges for the user's own processes; other users' and
system processes report 0, as the Memory column already did. Values match
top/ps within sampling noise; a full refresh of ~650 processes including
the extra call takes ~5 ms.
Memory, RAM and Compressed live in one @TableColumnBuilder property:
Table takes at most 10 columns per block, and inlining them also
exceeded the type checker's time limit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The window left open all day grew the app by ~360 MB in 24 h even after
the .tabItem fix. Isolating each piece in lab builds showed the growth is
the sum of several small framework leaks on macOS 26.6 that fire on every
view update: SwiftUI Table cell updates, AppKit text-cell KVO
dependencies (NSKeyValueDependency) and an ObservationRegistrar created
inside DesignLibrary. With the window closed every counter stayed flat;
minimized or fully covered, SwiftUI kept updating and kept leaking.
- WindowVisibility tracks whether any TopManager window is on screen
(occlusion state; the status item's host window is ignored).
- SystemMonitor records into an always-current LiveState and only
mirrors it into the @Published properties while something is visible.
Resuming publishes everything at once, so views catch up immediately.
Metrics history, alerts and the menu bar keep running while paused.
- MenuBarStatus carries the pre-formatted menu-bar strings, republished
only when the visible text changes.
- AlertCenter assigns healthScore/diagnosis only when they change.
- Monitoring now starts in applicationDidFinishLaunching instead of the
main window's onAppear, so it also runs when the app launches with no
window (state restoration after the window was closed).
Also fixes a visible bug: ProcessMonitor's lightweight refresh (2 of
every 3 fetches) emitted 0 for memory, CPU and threads instead of the
last known values, so ~90% of rows showed "Zero KB" most of the time.
Verified in an isolated lab instance: minimized or covered, all leak
counters stay flat while the menu-bar title keeps updating; a partially
visible window updates every tick. Growth while the window is actually
visible (~190 KB/min) is unchanged — those leaks are in Apple's
frameworks. 58/58 tests pass, including a regression test that fails on
the old code (472 of 487 own processes reported 0 memory).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The existing */build/* pattern only matches build/ inside a subdirectory,
so the xcodebuild -derivedDataPath output at the repo root kept showing
up as untracked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ContentView declared an @EnvironmentObject it never read, so every
SystemMonitor publish (3 s by default) re-evaluated its body. On macOS
the AppKit tab bridge permanently retains the AnyView(Label(...)) behind
each .tabItem, leaking ~6 KB per tick — 519 MB after three days of
uptime, >90% of the process footprint.
Keep the TabView shells free of observable dependencies:
- ContentView: drop the unused @EnvironmentObject, so the body is
evaluated exactly once.
- SettingsView: split the shell from its two tabs, which now own the
AppSettings observation (dragging a threshold slider leaked 2 labels
per step).
- PerformanceView: drop the write-only selection binding (3 labels per
sub-tab switch).
Two smaller wins while in here:
- GPUMonitor leaked one io_object per GPU poll: leaving a
`defer { release; IOIteratorNext }` loop via break/return hands back an
object nobody releases. Replaced with firstMatch(in:), and removed a
never-released iterator in getAppleSiliconChipName.
- MetricsStore re-encoded the whole 20k-sample history to JSON every
30 s; saveEvery 10 -> 40 (~2 min). flush() on quit and on background
is unchanged.
Measured over 10 minutes (~200 refresh cycles): footprint 86-123 MB with
no upward trend, leaked Label views pinned at 4 — the tab items
themselves. Was 519 MB and climbing ~170 MB/day. 55/55 unit tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Refresh processes, performance-cpu, performance-memory and power-storage shots to
show the new columns, System Health score, battery panel and time-range trends.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
macOS suppresses an app's own notifications while it is the frontmost app unless
the UNUserNotificationCenter delegate opts in. Set the delegate in AppDelegate and
return [.banner, .sound, .list] from willPresent so High-CPU/health alerts pop as
banners even with the window focused. (Also confirmed notifications require a real
code signature, not the ad-hoc CODE_SIGNING_ALLOWED=NO test build.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Full XCTest suite green (55 tests)
- Visual verification of all tabs (screenshots): Processes (Energy/Disk columns +
live data), Performance (range picker + P/E core charts), Power & Storage
(Health score 55/Fair + battery 97%/114 cycles + preserved System/GPU)
- Ground-truth cross-checks: memory 12.31GB vs 12.53GB reported (16KB pages),
battery vs ioreg/pmset, per-process CPU vs ps, disk vs df — all consistent
- README updated to document new capabilities; testing docs referenced
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- HealthModel (pure): 0-100 score + plain-language diagnosis + rating
- SystemAlert/AlertKind/AlertThresholds + pure AlertEvaluator (sustained-breach)
- AlertCenter (@MainActor): per-cycle health + alert evaluation, de-dup by kind,
inbox (capped), native UserNotifications on transition into a condition
- Covers: sustained high CPU, critical memory pressure, disk full, thermal,
runaway process, low battery
- Wire evaluate() into SystemMonitor; request notif auth on launch
- HealthCardView (score ring + diagnosis) + AlertsInboxView in Power & Storage;
health score + alert badge in menu bar
- Renamed model Alert->SystemAlert to avoid SwiftUI.Alert shadowing
- 10 new tests incl. AlertCenter de-dup integration (52 total green)
- Visual smoke: Energy + Disk I/O columns render with live data under load
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ProcessItem: disk read/write rate + cumulative bytes, energy-impact score,
executable path; equality now tracks energy/disk so the table refreshes
- ProcessMonitor: disk I/O + idle-wakeup rates from rusage_info_v4; full path
capture; on-demand KERN_PROCARGS2 args + PROC_PIDLISTFDS open-file count
- EnergyModel heuristic + pure ProcArgs parser (both unit-tested)
- ProcessView: sortable Energy + Disk I/O columns (icon folded into Name to stay
within SwiftUI's 10-column limit); Get Info (double-click / ⌘I) opens inspector
- ProcessInspectorSheet: live stats, path, args, open files, Reveal/Copy Path
- 10 new tests (32 total green); smoke: stable under CPU+disk load
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- MetricsStore: thread-safe on-disk ring buffer (Application Support), throttled
background saves, flush on quit/background, 24h/20k cap
- MetricsMath: pure trim + downsample (unit-tested)
- HistoryRange (Live/5m/30m/1h/24h) + HistoryRangePicker + MetricTrendChart
- Wire recording into SystemMonitor; range picker in CPU/Memory/Network tabs
- Fix durability: flush history on applicationWillTerminate + scenePhase.background
- 8 new tests (30 total green); runtime smoke: no crash, 7 samples persisted
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- DEVELOPMENT_TEAM beállítva a céges fiókra (InstaSoft, P3663392R2).
- A project.yml-ből kivéve a target `entitlements:` blokk, amely
`properties` nélkül üres entitlements fájlt generált és felülírta a
kézzel karbantartott TopManager.entitlements-et (app-sandbox=false).
A CODE_SIGN_ENTITLEMENTS beállítás marad. Release build ellenőrizve.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Security improvements:
- Force Kill (SIGKILL) now always requires confirmation - removed "Don't ask
again" option for this destructive operation
- Added protection for critical system processes (kernel_task, launchd,
WindowServer, loginwindow) - UI prevents force-killing these
- Terminate (SIGTERM) retains the "Don't ask again" option as it's a gentler
operation that allows processes to save data
UX improvements:
- Clear error message when attempting to force-kill protected processes
- ConfirmationSheet now supports optional "Don't ask again" toggle via
nullable skipPreferenceKey parameter
This prevents accidental system crashes from force-killing critical processes
while maintaining power-user convenience for regular termination.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Defensive coding improvements to mitigate "kernel as messenger" attack vector
where a local malicious process could craft extreme process names/paths:
- fetchProcessName: Create string from explicit length instead of relying
on null terminator. Protects against edge cases where path buffer might
not be null-terminated at exactly maxPathSize.
- BSD name extraction: Copy to safe buffer with guaranteed null termination
before converting to String. Protects against pbi_name being exactly
MAXCOMLEN bytes without null terminator.
- fetchBasicProcessInfo: Same safe pattern for p_comm field extraction.
- Strip control characters and whitespace from process names to prevent
display/injection issues from maliciously crafted names.
Security: Addresses potential buffer over-read in unsafe Swift pointer
operations when parsing attacker-influenced kernel data structures.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Security improvements:
- Validate process still exists before sending signals
- Verify process start time matches to detect PID reuse
- Add ProcessControlError enum with descriptive error messages
- Show error alerts when operations fail (permission denied, process not found, PID reused)
- Pass expectedStartTime to all process control operations
This prevents accidentally terminating the wrong process when a PID
gets reused between user selection and confirmation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add cpuUsageTotal property to ProcessItem (100% = all cores)
- Keep cpuUsage as per-core percentage (100% = 1 core)
- Display both columns: CPU/Core and CPU/Total
- CPU/Total values now sum to match the total system CPU
This clarifies the difference between per-core and system-wide CPU
measurements that was causing confusion.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Major performance improvements to reduce CPU usage from 25-30% to ~0.5%:
- Move all data fetching to dedicated background queue
- Prime CPU and process monitors at startup with 1-second delay for accurate delta calculation
- Implement smart refresh cycles: processes fetched every other cycle after initial warmup
- Add lightweight refresh that skips expensive calls for idle processes (CPU < 0.1%)
- Increase refresh interval from 2s to 3s
- Add caching for processes with no icons to avoid repeated lookups
- Use sysctl fallback to get correct user ownership for system processes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
A native SwiftUI app for monitoring macOS system resources:
- Processes tab with sorting, filtering, and process control
- Apps tab for running applications
- Performance tab with CPU, memory, and network charts
- Power & Storage tab with system info, GPU, storage, and network
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>