Skip to content

feat(macos): per-port custom names, descriptions, folders, and type overrides - #115

Open
StreamlinedStartup wants to merge 13 commits into
productdevbook:mainfrom
StreamlinedStartup:feature/macos-port-customizations
Open

feat(macos): per-port custom names, descriptions, folders, and type overrides#115
StreamlinedStartup wants to merge 13 commits into
productdevbook:mainfrom
StreamlinedStartup:feature/macos-port-customizations

Conversation

@StreamlinedStartup

Copy link
Copy Markdown

Scope

This PR changes the macOS app only. It does not change the Windows app, the Linux app, or the Rust core. I can not test Windows or Linux.

What this PR adds

Users can customize each port. The app stores one record per port. The record holds a name, a description, a folder, and a process type.

  • Custom name. The custom name replaces the process name in the port rows. The real process name stays visible in the detail view.
  • Description. The description shows in the detail view. It also shows as a tooltip on table rows. Search matches custom names and descriptions.
  • Folder. The scanner reads the working directory of each process with proc_pidinfo. This adds no process spawns. The detail view shows the folder. A click reveals the folder in Finder. Users can also select a folder by hand.
  • Type override per port. Users can correct the process type for one port. Before, one override applied to all processes with the same name.
  • Inline editing. Users edit all fields in the detail view. A field saves when it loses focus. The context menu has a "Customize…" item that opens the detail view.

Data migration

  • The app merges the old port labels and notes into the new records one time.
  • The app does not delete the old data. A downgrade to an old release keeps its labels and notes.
  • The app still reads old per-name type overrides as a fallback. The "Automatic" option removes the old override.

Bug fixes in this PR

  • Edits now show immediately. Before, views read the stored labels without observation. The UI waited for the next scan, up to 5 seconds.
  • The filter caches now invalidate when any port changes. Before, the cache keys sampled only the first port.
  • killPort now rejects inactive ports and PID 0. kill(0, …) signals the whole process group.

Tests

  • swift test passes: 80 tests, 21 of them new.
  • I tested the app by hand on macOS: rename, description, folder detection, folder override, type override, migration, search, menu bar, and context menu.

How this was built

  • Claude Code with the Fable 5 model wrote the code.
  • Codex Sol ran an adversarial review of the branch. It reported four issues. This branch fixes all four: cache invalidation, legacy type override clearing, "Customize…" navigation, and rollback-safe migration.

🤖 Generated with Claude Code

StreamlinedStartup and others added 13 commits August 10, 2026 07:26
Introduces the unified per-port customization record (name, description,
folder, type) and routes process type resolution through it: per-port
override, then legacy per-name override, then automatic detection. No
behavior change while no records exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-port customizations live in an @observable state object so edits
re-render views immediately instead of waiting for the next scan. Legacy
portLabels/portNotes are merged into the new record on launch (existing
fields win) and then cleared; the merge is idempotent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reads each scanned PID's cwd via proc_pidinfo(PROC_PIDVNODEPATHINFO) --
one syscall per PID, no process spawns, matching the sysctl command
lookup. The detected folder backs the port's effective folder unless a
manual override is set. Inaccessible PIDs yield nil.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Main-window filtering passes each port's customization into
PortFilter.matches, and the menu-bar quick search matches the display
name. Both filter caches now key on the customizations hash so renames
invalidate stale results.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The custom name replaces the process name in the table and menu-bar
rows (real name stays in the detail view), nested tree rows keep it
next to the port number, and table rows get a description tooltip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The detail header becomes an editable name field (placeholder = real
process name), the notes section becomes a description editor backed by
the customization record, and the details grid gains a per-port type
picker plus a folder row with reveal-in-Finder, folder picking, and
override clearing. Editors persist on focus loss. Extracting the
components brings PortDetailView from 356 to 282 lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the label/note NSAlert prompts with a Customize... item that
opens the main window on the port's detail editor, and rewires the type
submenu to the per-port override. Deletes the now-unused label/note/
type-override AppState extensions; their Defaults keys remain for
migration and the legacy per-name type fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The overlay padding inherited from the old notes editor sat ~8px below
the TextEditor's insertion point, making the placeholder appear on the
second line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
updatePorts diffed scans by port-pid keys only, so a rescan after a
type override produced an identical key set and the guard discarded the
fresh PortInfo values. The row type pill and the sidebar type sections
kept reading the stale processType. Comparing full PortInfo values
keeps the no-op-scan optimization while picking up per-port changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both filter cache keys sampled only the first port's hash, so a rescan
that replaced a non-first PortInfo (e.g. after a type override) served
stale cached results indefinitely. A portsRevision counter bumped in
updatePorts now keys both caches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge-and-clear destroyed legacy portLabels/portNotes, so downgrading
to a release that only reads those keys lost all names and notes. The
migration now runs once behind a hasMigratedCustomizations flag and
leaves the legacy keys intact; the flag also prevents re-merging from
resurrecting values the user has since deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A nil per-port override fell through to the legacy per-name dictionary,
so the picker showed Automatic while the legacy type silently applied,
with no remaining UI path to restore detection. Choosing Automatic now
also removes the legacy per-name entry, and the picker and context menu
display the effective override (legacy included) instead of lying.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Customize... now navigates to a sidebar state that can actually show
the port detail (favorites/watched for inactive placeholders, All Ports
when a special view is selected), and selectedPort falls back to
filteredPorts so synthesized inactive placeholders resolve instead of
showing No Port Selected. The detail view hides Kill for inactive
ports, and killPort/killPortDeep guard pid > 0 because an inactive
placeholder's pid 0 would signal the entire process group.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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