Conversation
bin/fm-inbox-view.sh writes one self-contained HTML board from live fleet state, so every open captain decision, review-ready pull request and stopped task is answerable from a single surface instead of arriving one chat interrupt at a time. It is a read-only projection in the same shape as fm-fleet-view.sh: it shells out to fm-fleet-snapshot.sh --json and `tasks-axi show --full`, and writes nothing but the board file. A test proves state/ and data/ stay byte-identical across a generation. Three selection defects the existing aggregated surface has are fixed here. Decisions are selected on the captain hold alone, because the snapshot's own captain_actionable flag also requires kind == "captain" and therefore hides every thread gated with `tasks-axi hold --kind captain`, which is where the oldest waiting decisions live. Decision text is read through tasks-axi, because the snapshot's metadata capture stops at the first comma and silently drops the options from any hold reason that lists them. A recorded pull request renders as unverified unless --verify-prs live-checks it, because locally recorded metadata cannot know a pull request was closed and a closed one must never read as ready to merge. Decision cards lead with plain English. An optional captain-private cards file supplies the plain question, what the captain is actually choosing, where it came from, firstmate's recommendation, a link to research a product or service, and a collapsed technical section. An item that is firstmate's own assumption rather than a choice the captain made is flagged as one. A decision with no card still renders and says so rather than looking complete. Answers use the Lavish input contract: native radios plus a free-text note, with exactly one queued prompt per question on submit. This change delivers the surface only; regenerating on a cadence, relaying answers back, and any escalation-routing change remain separate captain decisions.
…flow fix Fold in the captain's hard requirements from live use of the board. Free-form answers are now first-class. A card's predefined options are optional quick-picks with a clear-selection reset; the answer submits with just free text and no option selected. Previously the submit required picking an offered option, so a genuine free-form answer could not be sent at all. Each card gains a discuss path that sends a question back (DISCUSS <id>: ...) instead of an answer (DECISION <id>: ...), so a decision can be clarified rather than forced. Delivery is made reliable end to end. bin/fm-inbox-serve.sh is one operator command that regenerates the board, arms the answer relay, serves it on the Tailscale address, and verifies the link. bin/fm-inbox-arm.sh writes and registers state/inbox.check.sh, a bounded lavish poll bound by fm-check-register, so firstmate's supervision cycle relays answers on its normal check cadence without anyone remembering to poll. The relay fails closed: when the board is not served the poll errors fast and it stays silent. Submitting also sends immediately with a visible confirmation, closing the window where an answer looked silently eaten. Long titles and ids now wrap instead of overflowing horizontally, which Lavish's own layout audit flags as an error. No-project items get readable area names derived from their origin or id instead of a generic badge. Behavior is covered by tests/fm-inbox-view.test.sh (generator, read-only, free-form, discuss, immediate send, area names, wrapping) and tests/fm-inbox-arm.test.sh (the relay is a valid registered fail-closed check, silent while unserved, rejected after tampering). docs/inbox-board.md records the design and verification evidence.
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.
Intent
Ship the captain's decision-and-review board (firstmate shared tracked material). The board is one Lavish HTML surface that collects everything waiting on the captain (decisions to make, PRs to approve, stopped work) so it arrives in one place instead of scattered chat interruptions. Deliverables: bin/fm-inbox-view.sh (a pure READ-ONLY generator projecting bin/fm-fleet-snapshot.sh --json plus 'tasks-axi show --full'; must mutate nothing under state/ or data/, proven by a test), bin/fm-inbox-render.py (HTML renderer), bin/fm-inbox-arm.sh and bin/fm-inbox-serve.sh (serve + arm the answer relay), tests, and docs/inbox-board.md.
The captain approved the board shape yesterday and, from live use, stressed five HARD requirements folded into this change: (1) FREE-FORM ANSWER IS FIRST-CLASS - every decision card must accept a standalone free-text answer with NO forced option selection; predefined options are optional quick-picks with a clear-selection reset. (2) A per-card discuss/clarify path that sends a question back (DISCUSS : ...) instead of an answer (DECISION : ...). (3) RELIABLE DELIVERY - his prior answers never reached firstmate because nothing polled the board; fm-inbox-serve.sh now serves AND arms a registered watcher check (state/inbox.check.sh via fm-check-register.sh, same pattern as the Workflowy @go channel) that relays answers on the normal supervision cadence with no manual polling, and the submit sends immediately with visible confirmation. The relay fails closed: silent when the board is unserved. (4) Fix horizontal overflow so long titles wrap (Lavish's layout audit flagged it error-severity). (5) Clearer names for no-project items, derived from origin/id.
Deliberate design decisions a reviewer should know: The generator selects open captain decisions on hold_kind==captain ALONE, intentionally wider than the snapshot's captain_actionable flag (which also requires kind==captain and thus hides the captain's oldest holds). Full decision text is read via tasks-axi show --full because the snapshot truncates hold reasons at the first comma. Recorded PRs render as UNVERIFIED unless --verify-prs live-checks them, because local metadata cannot know a PR was closed. Plain-English card copy lives in an optional captain-private cards file (data/inbox-cards.md), not in tracked code. fm-inbox-view stays a pure read-only generator; the stateful arming lives in the separate fm-inbox-serve/fm-inbox-arm so the no-mutation guarantee and its test stay clean. This touches firstmate shared tracked material, so firstmate-coding-guidelines was followed (one sentence per line in Markdown, plain dashes, no agent co-author, shellcheck-clean scripts, colocated tests, evidence-backed doc). A pre-existing unrelated failure in tests/fm-bearings-snapshot.test.sh (an SSHHIP-child warning) exists on the base commit and is out of scope.
What Changed
state/inbox.check.shrelay, and deliver submitted answers through the normal supervision cadence while staying silent when unserved.Risk Assessment
✅ Low: The follow-up is narrow, source-verifiable, and addresses the prior relay-spawn and wake-count defects without introducing a material remaining risk I can substantiate.
Testing
After fixing an initial changed-test selector gap, I reran the runner and inbox behavior tests, generated and served a fixture board, verified read-only manifests and relay registration, captured desktop/mobile/interaction screenshots, and confirmed free-text answers, discuss prompts, immediate send confirmation, and no horizontal overflow all worked.
/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/inbox-board-rendered.png)/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/inbox-board-mobile-wrap.png)/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/inbox-board-interaction.png)Evidence: Generated inbox board HTML
Evidence: Browser interaction payload and layout check
Evidence: Serve and arm output
armed: state/inbox.check.sh relays answers for /var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/inbox-board.html board: /var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/inbox-board.html link: http://127.0.0.1:4891/session/31e87e96a7fed7be reachable: yes (HTTP 200 over Tailscale)Evidence: Relay registration check
registered=yes mode=700 relay=/var/folders/tc/llfmckm54ls5tzxm08q758lm0000gn/T/no-mistakes-evidence/01KYB60J8J5SM9F7Z527TA80M4/fixture-home/state/inbox.check.shEvidence: Read-only manifest check
state/ and data/ manifests are byte-identical after fm-inbox-view.shPipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 4 issues found → auto-fixed (2) ✅
bin/fm-inbox-render.py:321- Whengh pr viewfails,fm-inbox-view.shrecords the state asunknown, but this branch treats every verified non-OPENstate as stale. With--verify-prs, a transient auth/network failure can move an open PR out of “Review and merge” and label it “already unknown”; keepunknownin the review list as unchecked instead.bin/fm-inbox-arm.sh:68- The registered relay runs barelavish-axi poll, so a board served with a non-defaultLAVISH_AXI_PORTis verified byfm-inbox-serve.shbut later polled on the watcher's default port unless that environment happens to match. Capture/export the served port in the generated check so non-default-port boards do not silently miss answers.bin/fm-inbox-arm.sh:58-cat > "$CHECK"opens the predictablestate/inbox.check.shpath before destination validation, so an existing symlink or hard link can be overwritten outside the intended state file beforefm-check-register.shrejects it. Publish through a private temp file instate/and a validated rename, matching the existing check-publication pattern.bin/fm-inbox-arm.sh:65- The generated check embedsBOARDandANSWERSinside single quotes without shell escaping. A board/home path containing a single quote or newline can register a syntactically broken or injectable check, making the relay fail silently; generate shell-escaped assignments instead.🔧 Fix: Fix inbox relay safety and PR verification
2 warnings still open:
bin/fm-inbox-arm.sh:106- The generated check callslavish-axi polleven when the board is unserved. Current LavishpollcallsensureServer()first, so an idle watcher can start a Lavish server itself; because the shim does not setLAVISH_AXI_HOST, that server may be localhost-bound and laterfm-inbox-serve.shcan reuse it instead of serving on Tailscale. Gate polling on an already-running served session before invokingpoll.bin/fm-inbox-arm.sh:111- The answer-count grep does not match Lavish's prompt rows, which are serialized as rows likedecision,"DECISION ...", not lines beginning with two spaces and a quote. With zero matches,grep -cprints0and exits 1, so the fallback appends another0; real answer wakes can say0\n0 captain answer(s)even after writing an answer file. Count prompt rows correctly or emit a fixed single-line wake message.🔧 Fix: Gate inbox relay before polling
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bin/fm-test-run.sh --list --changed --base 2b7cd66ad09722fec4cf11acdbee4fd8046f06e7initially exposed the unmapped renderer; after the fix it succeeded and wrotechanged-selection-after-fix.txt.bin/fm-test-run.sh tests/fm-test-run.test.shbin/fm-test-run.sh tests/fm-inbox-view.test.sh tests/fm-inbox-arm.test.shGenerated a fixture board withPATH="$FAKEBIN:$PATH" FM_HOME="$EVIDENCE/fixture-home" bin/fm-inbox-view.sh "$EVIDENCE/inbox-board.html"and comparedstate/+data/manifests before/after.Served and armed the fixture board withLAVISH_AXI_PORT=4891 FM_HOME="$EVIDENCE/fixture-home" bin/fm-inbox-serve.sh --no-generate --link-host 127.0.0.1 "$EVIDENCE/inbox-board.html".Captured rendered UI withplaywright screenshotat desktop and 390px mobile widths.Rannode "$EVIDENCE/inbox-board-interaction.cjs"to clear a quick-pick, submit a free-text-onlyDECISION, submit aDISCUSSquestion, verify immediate send calls, and assert no horizontal overflow.Checked relay trust withfm_custom_check_registered "$STATE" inboxand verifiedstate/inbox.check.shmode700.Cleaned up the temporary Lavish server withLAVISH_AXI_PORT=4891 lavish-axi stopand confirmed the port no longer answered.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.