Add Server section to the Inspector sidebar - #155
Open
NRohner wants to merge 14 commits into
Open
Conversation
Covers spawning TurboFieldfareServer as a sibling process using the app's existing model/runtime settings, start/stop state handling, and new port/queue-limit settings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
Task-by-task TDD plan covering AppServerState/AppServerController, the argument builder, MacAppSettings v3, ProcessServerController, AppModel wiring, the Inspector UI, and app-quit cleanup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
Wires AppServerController/AppServerState/AppServerArguments (Tasks 1-2) and the persisted serverPort/serverQueueLimit settings (Task 3) into AppModel: a serverController dependency, serverState/serverPort/ serverQueueLimit public state, canStartServer/canStopServer/ canEditServerSettings gating, and startServer/stopServer/ stopServerForTermination/setServerPort/setServerQueueLimit control methods. applyServerState mirrors the existing applyLoadState pattern, guarding a generation counter against a stale async callback from a start the model has since moved past. Also fixes a strict-concurrency compile error in the new test file's waitUntil helper (missing @mainactor on the function itself, matching the existing pattern in AppModelInstallTests.swift) that the plan's literal code did not have. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
Task 3 raised MacAppSettings.currentVersion to 3, which collided with this test's hardcoded version-3 fixture meant to simulate a schema newer than whatever this build currently understands. Derive the fixture version from currentVersion + 1 instead of a literal, so the test keeps testing "an unknown future version" regardless of where currentVersion moves next. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
… state AppModel computes installationStatus once in init from whichever directory is passed there (defaulting to AppModelLocation.defaultURL() when omitted). Reassigning modelPathText afterward doesn't recompute it, so the test passed only on a machine with no model installed at the default location. Construct AppModel with an explicit, guaranteed- missing directory instead, so the test is hermetic regardless of local scratch state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
…uards ProcessServerController reports .running from a stdout readability handler and .stopped/.failed from Process.terminationHandler, two independent unsynchronized callback paths. The prior generation-only guard let a late .running overwrite an already-applied terminal outcome depending on which callback's MainActor task ran last. Add a serverReachedTerminalState latch: once .stopped/.failed lands for the current generation, nothing else can change serverState until the next startServer() call. Also guard setServerPort/setServerQueueLimit against out-of-range values, matching MacAppSettings.isValid()'s bounds -- an invalid value previously got accepted into memory while persistSettings() silently no-op'd on every subsequent save, including unrelated settings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
State/Port/Queue limit rows and a Start/Stop button, wired to the AppModel server-control state machine added in prior tasks. Port and queue-limit fields bind through setServerPort/setServerQueueLimit (not directly to the raw properties) so their range validation stays in effect from the UI layer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
Bounds the stderr buffer ProcessServerController accumulates for the life of a server process and truncates the failure message it surfaces, since routine per-request logging on stderr could otherwise grow unbounded and get rendered whole in the Inspector sidebar on a crash. Also clears self.process in the termination handler so a crash right as Stop is clicked can't retain the old Pipes/buffer or terminate a reaped process. Adds direct unit tests for port(fromReadyOutput:), the only untested logic path that detects a successful server start, by exposing it as internal rather than private so the test target can reach it. Makes the "Stopping..." state explain itself when the stop was requested during .starting: SIGTERM is buffered by the server until the model finishes loading, so the UI could otherwise sit disabled for minutes with no visible reason why. Also: shows the double-memory-load caption during .starting (not just when canStartServer, which is false exactly then), stops a vision-pack download from blocking the unrelated Stop Server button, tightens serverPort/ serverQueueLimit to private(set), and gives three tests an explicit missing model directory instead of falling back to the real default location. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
stopServer() only ever set the flag to true, never cleared it, so a stop requested during .starting left it stuck true even after the buffered SIGTERM let the server finish loading and briefly report .running (the terminal-state latch doesn't cover .running, by design). A second Stop click after that flash then showed "waiting for the model to finish loading" for a server whose model had already loaded. Make the assignment unconditional instead of set-only-true. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TurboFieldfareServer, the local OpenAI-compatible inference server — previously only startable from a terminal.modelPathText,maxContextTokens,runtimeOptions) plus two new settings, port (default 8080) and queue limit (default 4).--vision-packis added automatically when a vision pack is installed.SIGTERM) when the app quits.MacAppSettingsschema bumped 2→3 for the two new persisted fields, backward compatible with older settings files.Full design spec:
docs/superpowers/specs/2026-08-25-server-ui-controls-design.mdFull implementation plan:
docs/superpowers/plans/2026-08-25-server-ui-controls.mdBuilt via subagent-driven development: 7 tasks, each independently reviewed for spec compliance and code quality, plus a final whole-branch review that caught and fixed an unbounded-memory issue in stderr capture, an untested code path, and a UX gap around the server's buffered-signal shutdown window.
Test plan
Scripts/test.sh)swift build -c debug(both the core library and theTurboFieldfareMacapp target)🤖 Generated with Claude Code
https://claude.ai/code/session_01BR7gXjo83uLkvj1HrDNEMQ