✨ Add menubar app support with server registry & auto-port allocation#200
Conversation
This comment has been minimized.
This comment has been minimized.
Code ReviewSolid implementation of the menubar support infrastructure. The auto-port allocation and server registry are well-designed. I've identified a few bugs and potential improvements below. Critical Issues1. Race Condition in Port Allocation (src/commands/tdd-daemon.js:67-81)Issue: There's a TOCTOU (time-of-check-time-of-use) race condition between finding an available port and actually starting the server. // Auto-allocate an available port
port = await registry.findAvailablePort();
autoAllocated = port !== 47392;
// ... later, server starts on this port
// But another process could grab it in between!If two Fix: Consider one of these approaches:
2. Top-level Await Without Error Handling (src/cli.js:758-759)Issue: The // Save user's PATH for menubar app before parsing commands
// This auto-configures the menubar app so it can find npx/node
await saveUserPath().catch(() => {});If Fix:
saveUserPath()
.catch(err => {
// Only log in verbose mode to avoid noise
if (program.opts().verbose) {
output.debug('cli', 'Failed to save PATH for menubar:', err.message);
}
});Bugs3. Incorrect osascript Syntax (src/tdd/server-registry.js:166)Issue: The osascript command for posting notifications is incorrect: execSync(
`osascript -e 'tell application "System Events" to post notification with name "dev.vizzly.serverChanged"'`,
{ stdio: 'ignore', timeout: 1000 }
);This won't work - System Events doesn't have a Fix: Either:
Since the menubar app needs to watch 4. Registry Cleanup Logic Inconsistency (src/commands/tdd-daemon.js:53-54)Issue: When a stale server is detected, it's removed from the registry but the local } else {
// Stale entry - clean it up
registry.unregister({ directory: process.cwd() });
}The local Fix: Also clean up the local files when detecting stale entries: } else {
// Stale entry - clean it up
registry.unregister({ directory: process.cwd() });
const vizzlyDir = join(process.cwd(), '.vizzly');
const pidFile = join(vizzlyDir, 'server.pid');
const serverFile = join(vizzlyDir, 'server.json');
if (existsSync(pidFile)) unlinkSync(pidFile);
if (existsSync(serverFile)) unlinkSync(serverFile);
}Code Quality5. Missing Input Validation (src/tdd/server-registry.js:57-80)Issue: The register(serverInfo) {
// No validation that pid, port, directory are provided
registry.servers.push({
id: serverInfo.id || randomBytes(8).toString('hex'),
port: Number(serverInfo.port), // Number(undefined) = NaN
pid: Number(serverInfo.pid), // Number(undefined) = NaN
// ...
});
}Fix: Add validation: register(serverInfo) {
if (!serverInfo.pid || !serverInfo.port || !serverInfo.directory) {
throw new Error('Missing required fields: pid, port, directory');
}
const port = Number(serverInfo.port);
const pid = Number(serverInfo.pid);
if (isNaN(port) || isNaN(pid)) {
throw new Error('Invalid port or pid - must be numbers');
}
// ...
}6. Unregister Should Match Both Port AND Directory (src/tdd/server-registry.js:86-97)Issue: The current logic uses OR when both are provided: unregister({ port, directory }) {
if (port) {
registry.servers = registry.servers.filter(s => s.port !== port);
}
if (directory) {
registry.servers = registry.servers.filter(
s => s.directory !== directory
);
}
}If you call Fix: Use AND logic when both are provided: unregister({ port, directory }) {
if (port && directory) {
// Both specified - match both
registry.servers = registry.servers.filter(
s => !(s.port === port && s.directory === directory)
);
} else if (port) {
registry.servers = registry.servers.filter(s => s.port !== port);
} else if (directory) {
registry.servers = registry.servers.filter(s => s.directory !== directory);
}
}7. Hardcoded Console.warn (src/tdd/server-registry.js:41)Issue: Uses console.warn('Warning: Could not read server registry, starting fresh');This bypasses the CLI's output configuration (JSON mode, color settings, etc.). Fix: Use the output module (you'll need to import it): import * as output from '../utils/output.js';
// In read():
} catch (_err) {
output.warn('Could not read server registry, starting fresh');
}8. Platform-specific Code Without Tests (src/tdd/server-registry.js:160-172)Issue: The Fix: Add a comment indicating this is untested or needs macOS verification: /**
* Notify the menubar app that the registry changed
* Uses macOS DistributedNotificationCenter via osascript
*
* NOTE: This implementation is experimental and may not work as expected.
* Consider file watching as a more reliable alternative.
*/Performance Considerations9. Synchronous File I/O in Request Path (src/tdd/server-registry.js)Issue: All registry operations use synchronous file I/O ( Recommendation: Consider async I/O for better responsiveness, especially since the daemon startup is already async. Not critical for this PR, but worth noting for future optimization. Test CoverageExcellent Test Coverage ✅The test suite in
Minor gap: No tests for the macOS notification code (understandable since it's platform-specific). Security Considerations10. Directory Traversal ProtectionThe registry stores absolute paths in SummarySeverity Breakdown:
Strengths:
Recommendations:
Great work on the auto-port allocation feature - this will be a huge UX improvement for running multiple projects simultaneously! |
Addresses feedback from PR #200 code review: **Critical fixes:** - Make saveUserPath() non-blocking to avoid CLI startup delays - Add comment documenting the race condition mitigation strategy **Bug fixes:** - Remove broken osascript notification (file watching is primary) - Clean up local .vizzly files when detecting stale registry entries **Code quality:** - Add input validation to register() (required fields, number checks) - Fix unregister() to use AND logic when both port and directory provided - Fix flaky test by using high port range (48500+) to avoid conflicts
- Add ServerRegistry class to manage running servers at ~/.vizzly/servers.json - Register/unregister servers on tdd start/stop - Add `tdd list` command to show running servers - Save user's PATH to config.json for menubar app to use - Notify menubar via DistributedNotification when servers change
When starting a TDD server without --port, automatically find an available port instead of failing if 47392 is busy. This enables running multiple projects simultaneously without manual port config. - Add findAvailablePort() to ServerRegistry - Check both registry and actual TCP binding - Show "Auto-assigned port :47393" when using non-default - Add 17 tests for server registry functionality
TDD daemon now writes JSON logs to .vizzly/server.log that the menubar app can read and display. Log format is NDJSON with timestamp, level, message, and contextual data. - Configure output.logFile in daemon child process - Register logFile path in server registry - Include logFile in server.json for local discovery
Addresses feedback from PR #200 code review: **Critical fixes:** - Make saveUserPath() non-blocking to avoid CLI startup delays - Add comment documenting the race condition mitigation strategy **Bug fixes:** - Remove broken osascript notification (file watching is primary) - Clean up local .vizzly files when detecting stale registry entries **Code quality:** - Add input validation to register() (required fields, number checks) - Fix unregister() to use AND logic when both port and directory provided - Fix flaky test by using high port range (48500+) to avoid conflicts
b435510 to
8fc16bd
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Addresses feedback from PR #200 code review: **Critical fixes:** - Make saveUserPath() non-blocking to avoid CLI startup delays - Add comment documenting the race condition mitigation strategy **Bug fixes:** - Remove broken osascript notification (file watching is primary) - Clean up local .vizzly files when detecting stale registry entries **Code quality:** - Add input validation to register() (required fields, number checks) - Fix unregister() to use AND logic when both port and directory provided - Fix flaky test by using high port range (48500+) to avoid conflicts
Vizzly - Visual Test ResultsCLI Reporter - 5 changes need review
Changes needing review (5)fullscreen-viewer · Firefox · 1920×1080 · 5.8% diff filter-failed-only · Firefox · 1920×1080 · 0.1% diff fullscreen-viewer · Firefox · 375×667 · 100.6% diff bulk-accept-dialog · Firefox · 375×667 · 175.2% diff viewer-zoomed-100 · Firefox · 1920×1080 · 0.5% diff CLI TUI - 2 changes need review
Changes needing review (2)vizzly-help · 1202×1430 · 165.4% diff vizzly-tdd-help · 1202×578 · 270.7% diff
|







Summary
Adds CLI-side infrastructure for the macOS menubar companion app (VIZ-110):
~/.vizzly/servers.jsontracks all running TDD serverstdd startfinds an available port when default is busy.vizzly/server.logfor menubar to displaynpx/nodetdd listcommand - Lists all running servers with port, uptime, directoryAuto-port allocation
When starting multiple projects, ports are automatically assigned:
Server registry
The menubar app watches
~/.vizzly/servers.jsonfor real-time updates:{ "version": 1, "servers": [ { "id": "abc123", "port": 47392, "pid": 12345, "directory": "/projects/my-app", "name": "my-app", "logFile": "/projects/my-app/.vizzly/server.log", "startedAt": "2024-02-02T..." } ] }Test plan
vizzly tdd startin one project, verify registry updatedvizzly tdd startin second project, verify auto-port allocationvizzly tdd listshows both serversvizzly tdd stopremoves from registry.vizzly/server.loghas JSON lines after starting daemon