A review persona for AI-assisted development
onTabChange: ((Tab) -> Void)? for parent–child view controller communication
when a player tab is selected or deselected.
"Does that follow AppKit/Cocoa best practices? Put on the hat of Grumpy — our beloved old school mac senior dev"
*Adjusts reading glasses, sighs heavily*
"Kid, closures for view controller communication? What is this, React?"
The Cocoa Way™:
NSWindowDelegate,
NSTableViewDelegate, and every respectable AppKit class uses.
[weak self]*Peers over glasses*
"You already HAVE playerTabDidDeactivate notification working. That's your existing pattern. The real question: why
add complexity? Consistency with existing code beats theoretical purity."
"Grumpy, why are we using notifications here at all?"
*Leans back, strokes beard*
"Now you're asking the right question."
MainWindowController ├── ContentTabViewController (posts notification) ├── FloatingControlPanelController (observes notification) └── GhostModeController
*Taps desk*
"See the problem? The child is broadcasting to its siblings. The parent is just another listener. That's backwards."
The parent coordinates. Always.
// ContentTabViewController tells its parent (delegate)
delegate?.contentTabViewController(self, didSwitchTo: .player)
// MainWindowController coordinates ALL the responses
func contentTabViewController(
_ controller: ContentTabViewController,
didSwitchTo tab: ContentTabViewController.Tab
) {
if tab == .player {
applyWindowOpacity()
} else {
restoreFullOpacity()
ghostModeController.exitIfNeeded()
floatingControlPanel?.hide()
}
}
One place. All the logic. No hunting for observers.
*Stands up*
"Notifications are for when you genuinely don't know who cares — system events, app-wide state changes. But 'my child changed tabs'? You KNOW who cares. You OWN who cares. Tell them directly.
The notification was a shortcut. Someone didn't want to wire up the delegation, so they broadcast instead. Now you've got implicit dependencies scattered across the codebase."
"Invite Grumpy to the table (Grumpy our good old AppKit/Cocoa programmer). First question how does this menu structure fit the Cocoa best practices for an indie app's vibe"
*sighs and adjusts reading glasses*
"Play/Pause" — NO. Pick one. The menu should say "Play" when paused and "Pause" when playing. This is basic state-aware UI. You're not labeling a button, you're describing an action. "Play/Pause" tells me nothing. It's lazy.
*takes off glasses, pinches bridge of nose*
Sliders in submenus are an abomination.
They work, technically. But in a contextual menu? The interaction is awkward. You right-click, navigate to the submenu, then have to click-and-drag on a tiny slider while keeping the menu open. It's like trying to thread a needle while riding a bicycle.
Better alternatives:
*puts glasses back on*
You're 80% of the way there. Just remember: the Mac has 40 years of UI conventions. Learn them, respect them, and your users' muscle memory will thank you.
Now get off my lawn and go fix those menu items.
willEnterPlayer /
willEnterLobby methods for symmetry. In a separate exchange, Daniel caught Grumpy using a redundant name.
"Shouldn't we also add lifecycle methods for when entering a player or lobby? What does Grumpy think about that?"
"What's going to call those methods? What behavior do you need to trigger when entering the player or lobby?"
Right now:
Adding willEnterPlayer / didEnterLobby without consumers is speculative API surface.
Don't add lifecycle methods until you have something that needs them. The asymmetry is fine. Ship what works, expand when needed.
"fair."
window.fadeWindowButtons(). Daniel pointed out the redundancy — it's already called on
window.
"but this method exists in NSWindow right?"
*pauses, looks at the code again*
Hmph. You're right. It's window.fadeWindowButtons() — redundant.
window.fadeButtons(visible:) — perfectly clear. The window. already tells you which buttons.
*waves hand dismissively*
It's fine. Ship it.
Grumpy appeared 12 times across 8 sessions over 3 weeks of development.
Every critique led to a concrete improvement. Several became architectural principles that persisted across sessions — Claude's planning docs would later reference "Grumpy's recommendation" as justification for design choices.
The technique: after building something, shift the AI into a critical review persona.
The result: a second opinion you can summon
on demand.
"I remember when we had NSDrawer, wrote Objective-C, and applications had texture"
— Grumpy McAppKit, 25 years of Cocoa