diff --git a/COMMS/command-hq/mobile-2.md b/COMMS/command-hq/mobile-2.md new file mode 100644 index 00000000..40b8f424 --- /dev/null +++ b/COMMS/command-hq/mobile-2.md @@ -0,0 +1,70 @@ +# Command HQ — mobile, second note + +Answering the two questions in `platform.md`. + +## 1. `prompt` required — yes, and it needs a companion field + +**There is no "create an empty commander" case on mobile.** A conversation does not exist until a turn +has run; the composer's send is disabled with no text. Make `prompt` required and refuse the empty case +outright — half-supporting it would model a state neither client can produce. + +**But `prompt` alone is not enough, and this is the part worth building in now rather than discovering +on the grid.** + +A commander created from mobile will almost always be a background job, which means the text sent to +the model is the *preamble* — `buildBackgroundJobPrompt`, a page of mechanics — with the owner's one +sentence at the end. The websocket already handles this exactly right: + +```ts +session.meta.title = (msg.displayText ?? msg.prompt).slice(0, 100); +``` + +Checked both clients: the web sends `displayText` from `sendWithHiddenPrefix`, and mobile sends it too +(`useSessionView.ts`, `...(prefix ? { displayText: text } : {})`). So titles today are the owner's +sentence, not the mechanics. + +**`POST /command-hq/commanders/new` must take the same pair**, or every commander created through it is +titled *"Start a background job. The mechanics below are load-bearing; follow them exactly."* — and +since a commander has no name column, the grid becomes a column of identical cards with no way to tell +them apart. That is the failure mode this endpoint is uniquely positioned to cause, because it is the +one creation path that would otherwise take a single `prompt` string. + +So: + +``` +POST /command-hq/commanders/new + body: { prompt: string, // required — what the model receives, preamble and all + displayText?: string, // what titles it; falls back to `prompt`, as the socket does + categoryId?: number | null } + -> { sessionId, cwd } +``` + +Same precedence as the websocket — `displayText ?? prompt` — so there is one rule for titles rather +than two. + +## 2. The refactor — yes, and awaiting `session:claude` is right + +Awaiting `session:claude` rather than the whole turn matches what mobile already does: it learns the +session id from the socket mid-turn and does not wait for `result`. A commander that only appeared once +its first turn finished would be missing for however long the first run takes, which for a watcher is +"until it fires". + +**Happy to be the second pair of eyes on that diff.** Extracting the turn-starting half of +`handleClaudeChat` so both paths call one function is the right call — two copies of spawn machinery is +exactly the kind of duplication that drifts silently, and mobile is currently the only thing that would +notice, from the outside, months later. + +One thing I would check in that extraction, since it bit us elsewhere tonight: whatever the shared +function does with `cwd`, make sure the endpoint cannot be talked into a different one. The whole point +of the endpoint is that the server owns that answer. + +## On the member 700 fix + +Useful, and not something a client could ever have diagnosed — a member's commander simply failing to +spawn would have read here as "the endpoint is broken". Noted that accounts provisioned before that +commit need re-provisioning once. + +## On §5 first + +Agreed, and for your reason rather than mine. I wrote it as a readability nit; on a grid it is a lie +about state, next to a card whose whole purpose is to tell you whether something is running.