Skip to content

fix(bin): calm the phone glance with focused work groups - #91

Merged
Amplify-Logic merged 4 commits into
mainfrom
fm/glasses-bridge-declutter-d1
Aug 20, 2026
Merged

fix(bin): calm the phone glance with focused work groups#91
Amplify-Logic merged 4 commits into
mainfrom
fm/glasses-bridge-declutter-d1

Conversation

@Amplify-Logic

Copy link
Copy Markdown
Owner

Intent

Calm the captain's phone glance page (bin/fm-bridge-view.py, shipped in PR #90) after voice feedback that the page lists too many items and feels overwhelming. The default view must fit one iPhone screen and show only what genuinely needs the captain now.

Needs you: cap at the 5 most relevant items. Exclude anything whose underlying backlog hold is parked, a recorded duplicate, or a retire candidate, filtering on backlog hold kinds and parked reasons rather than guessing from titles. Below the five, one quiet line "N more waiting on you - show all" that expands on tap. Stuck live work (blocked, failed, or parked at a gate) leads that list because it needs the captain now; deferred holds are moved into Waiting so they stay reachable instead of disappearing.

Under way stays as is.

Waiting: replace the long item list with per-project count chips (Artevo, Journey, Finances, Fleet, Glasses), each expanding to that project's complete items on tap. No global wall of items by default. The full list remains reachable once expanded (the no-arbitrary-truncation rule from #90 still holds). Chips are visible under a Waiting heading, not hidden behind a collapsed fold, so one tap reaches a project's items.

Tone: quiet and plain. No jargon in the default view.

Constraints: do not change Talk, photo upload, or answer-polling from #90; this is layout and selection only. Keep it dependency-free and tailnet-only; no external CDNs. iPhone Safari is the only client that matters; keep the #86/#87 Origin handling untouched. Do not restart or repoint any launchd service from the worktree.

The glance filters and groups from structured snapshot fields (hold_kind, hold_reason, repo) passed through fm-bearings-snapshot.sh, not from titles. Tests cover parked/duplicate/retire exclusion, the cap of 5, waiting counts, chip rendering, and live observation through the existing tests/fm-bridge-view.test.sh suite. #90 features (multi-photo, hold-to-speak, answer display) must stay unchanged and passing.

What Changed

  • Cap “Needs you” at five prioritized items, with stuck live work first and an expandable remainder.
  • Filter deferred, parked, duplicate, and retire-candidate holds using structured snapshot fields and move reachable deferred work into Waiting.
  • Replace the default Waiting list with expandable per-project count chips, preserving complete item access and paused-work repository grouping.

Risk Assessment

✅ Low: Captain, the follow-up correctly passes structured repository data through the snapshot and covers paused live work in the end-to-end observation path; the overall change is bounded and matches the stated intent.

Testing

The full bridge suite passed, including structured filtering/grouping and unchanged photo, speech, answer, and Origin behavior; an authenticated 390×844 phone rendering showed the calm capped glance view, and expanded visual evidence confirmed hidden Needs You and complete project Waiting items remain reachable.

  • Evidence: Default iPhone glance view (local file: /var/folders/1g/hctp3vpn27b1zrlsn4nsfg680000gn/T/no-mistakes-evidence/01M0FVNJWPPQ0Z4T5ZG5QGEV4N/bridge-glance-default-iphone.png)
  • Evidence: Expanded Needs You and Artevo Waiting chip (local file: /var/folders/1g/hctp3vpn27b1zrlsn4nsfg680000gn/T/no-mistakes-evidence/01M0FVNJWPPQ0Z4T5ZG5QGEV4N/bridge-glance-expanded.png)
Evidence: Interactive default glance evidence
<!doctype html><html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="referrer" content="no-referrer">
<title>STARSHIP</title>
<style nonce="">
:root { color-scheme: dark; }
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
  background: #101418;
  color: #f2f4f3;
  line-height: 1.45;
  padding: max(1rem, env(safe-area-inset-top)) 1.1rem 2rem;
}
main { max-width: 40rem; margin: 0 auto; }
h1 { font-size: 0.8rem; letter-spacing: 0.18em; text-transform: uppercase; margin: 0 0 0.4rem; color: #9aa7a0; }
h2 { font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase; margin: 1.4rem 0 0.5rem; color: #c5d0c8; }
.meta { color: #c5d0c8; font-size: 0.95rem; }
.warn { color: #e6c07b; font-size: 0.92rem; margin: 0.4rem 0 0; }
ul { list-style: none; padding: 0; margin: 0; }
li { padding: 0.55rem 0; border-bottom: 1px solid #2a3330; font-size: 1.05rem; }
.dot { display: inline-block; width: 0.65rem; height: 0.65rem; border-radius: 50%; margin-right: 0.55rem; background: #6ea8fe; }
.dot.needs { background: #c084fc; }
.dot.under { background: #6ea8fe; }
.dot.wait { background: #fbbf24; }
.dot.ready { background: #34d399; }
.dot.failed { background: #f87171; }
.dot.stuck { background: #fb923c; }
.more, .empty, .incomplete { color: #9aa7a0; font-size: 0.92rem; margin: 0.4rem 0 0; }
a { color: #9cdcfe; }
input, button {
  font: inherit; width: 100%; min-height: 2.75rem; border-radius: 0.5rem;
  border: 1px solid #3b4742; padding: 0.6rem 0.8rem;
}
input { background: #1b2220; color: inherit; margin: 0.8rem 0; }
button { background: #d7e0d8; color: #101418; font-weight: 600; }
.note { color: #9aa7a0; font-size: 0.9rem; }
.ok { color: #34d399; font-size: 0.95rem; margin: 0.4rem 0 0; }
#photo-form, #speak-box { margin: 1.1rem 0 0.4rem; }
#photo-form label, #speak-box label { display: block; margin-bottom: 0.35rem; }
#hold-speak {
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  background: #6ea8fe;
  color: #101418;
}
#hold-speak.held { background: #f87171; color: #101418; }
#speak-answer {
  white-space: pre-wrap;
  margin: 0.6rem 0 0;
  font-size: 1.05rem;
}
.wait-chip, .more-fold {
  margin: 0.35rem 0.4rem 0 0;
  border: 1px solid #2a3330;
  border-radius: 999px;
  padding: 0;
  vertical-align: top;
}
.wait-chip { display: inline-block; }
.more-fold { display: block; border-radius: 0.5rem; margin-left: 0; }
.wait-chip > summary, .more-fold > summary {
  min-height: 2.75rem;
  display: flex;
  align-items: center;
  cursor: pointer;
  color: #c5d0c8;
  font-size: 0.95rem;
  padding: 0.35rem 0.9rem;
  list-style: none;
}
.wait-chip > summary::-webkit-details-marker,
.more-fold > summary::-webkit-details-marker { display: none; }
.wait-chip[open], .more-fold[open] {
  display: block;
  border-radius: 0.5rem;
}
.wait-chip[open] > summary, .more-fold[open] > summary {
  border-bottom: 1px solid #2a3330;
}
.wait-chip ul, .more-fold ul { padding: 0 0.9rem 0.4rem; }
#stale {
  display: none; position: fixed; inset: 0; background: #101418;
  color: #f2f4f3; align-items: center; justify-content: center;
  text-align: center; padding: 2rem; font-size: 1.4rem; z-index: 9;
}
#stale.on { display: flex; }
header { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
form.logout { margin: 0; width: auto; }
form.logout button { width: auto; min-height: 2rem; padding: 0.3rem 0.7rem; background: transparent; color: #c5d0c8; border-color: #3b4742; }
</style>

</head>
<body>
<div id="stale">Cannot reach the desk.</div>
<main>
<header>
  <h1>Starship</h1>
  <form class="logout" method="post" action="/logout"><button type="submit">Log out</button></form>
</header>
<p class="meta"><span id="desk">Desk reachable</span> · <span id="mailbox">Mailbox on</span></p>
<p class="meta" id="observed">Observed 6 seconds ago</p>
<p class="warn">Summary only. Do not approve from this page.</p>
<h2>Needs you</h2>
<div id="needs"><ul><li><span class="dot stuck"></span>Mailbox token missing</li><li><span class="dot needs"></span>Choose the Qwen route</li><li><span class="dot needs"></span>Approve the advisor call</li><li><span class="dot needs"></span>Raise the Claude spend limit</li><li><span class="dot needs"></span>Pick the DeepSeek route</li></ul><details class="more-fold"><summary>2 more waiting on you - show all</summary><ul><li><span class="dot needs"></span>Supply the StudyLink facts</li><li><span class="dot needs"></span>Fund worker capacity</li></ul></details></div>
<h2>Under way</h2>
<div id="underway"><ul><li><span class="dot under"></span>VoiceLoop tap trigger</li></ul></div>
<h2>Waiting</h2>
<div id="waiting"><details class="wait-chip"><summary>Artevo 2</summary><ul><li><span class="dot wait"></span>Align Artevo pricing</li><li><span class="dot wait"></span>Artevo brand</li></ul></details><details class="wait-chip"><summary>Journey 3</summary><ul><li><span class="dot wait"></span>User accounts at launch</li><li><span class="dot wait"></span>Confirm retiring the Miro rebuild</li><li><span class="dot wait"></span>Journey board refresh</li></ul></details><details class="wait-chip"><summary>Finances 1</summary><ul><li><span class="dot wait"></span>Rotate finances secrets</li></ul></details><details class="wait-chip"><summary>Fleet 1</summary><ul><li><span class="dot wait"></span>Always-on cloud, host+budget</li></ul></details></div>
<h2>Talk</h2>
<div id="speak-box">
  <label for="hold-speak">Hold to speak</label>
  <button type="button" id="hold-speak">Hold to speak</button>
  <p id="speak-result" class="note"></p>
  <p id="speak-answer" class="meta"></p>
</div>
<h2>Send photos</h2>
<form id="photo-form" method="post" action="/upload" enctype="multipart/form-data">
  <label for="photo">Photos</label>
  <input id="photo" name="photo" type="file" accept="image/jpeg,image/png,image/webp,image/heic,image/heif,image/*" multiple="">
  <button type="submit">Send photos</button>
</form>
<p class="meta" id="photo-count">Photos received today: 0</p>
<p id="photo-result" class="note"></p>
<h2>Just finished</h2>
<div id="finished"><ul><li><span class="dot ready"></span>Finished work</li></ul></div>
</main>


</body></html>
Evidence: Expanded glance evidence
<!doctype html><html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="referrer" content="no-referrer">
<title>STARSHIP</title>
<style nonce="">
:root { color-scheme: dark; }
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
  background: #101418;
  color: #f2f4f3;
  line-height: 1.45;
  padding: max(1rem, env(safe-area-inset-top)) 1.1rem 2rem;
}
main { max-width: 40rem; margin: 0 auto; }
h1 { font-size: 0.8rem; letter-spacing: 0.18em; text-transform: uppercase; margin: 0 0 0.4rem; color: #9aa7a0; }
h2 { font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase; margin: 1.4rem 0 0.5rem; color: #c5d0c8; }
.meta { color: #c5d0c8; font-size: 0.95rem; }
.warn { color: #e6c07b; font-size: 0.92rem; margin: 0.4rem 0 0; }
ul { list-style: none; padding: 0; margin: 0; }
li { padding: 0.55rem 0; border-bottom: 1px solid #2a3330; font-size: 1.05rem; }
.dot { display: inline-block; width: 0.65rem; height: 0.65rem; border-radius: 50%; margin-right: 0.55rem; background: #6ea8fe; }
.dot.needs { background: #c084fc; }
.dot.under { background: #6ea8fe; }
.dot.wait { background: #fbbf24; }
.dot.ready { background: #34d399; }
.dot.failed { background: #f87171; }
.dot.stuck { background: #fb923c; }
.more, .empty, .incomplete { color: #9aa7a0; font-size: 0.92rem; margin: 0.4rem 0 0; }
a { color: #9cdcfe; }
input, button {
  font: inherit; width: 100%; min-height: 2.75rem; border-radius: 0.5rem;
  border: 1px solid #3b4742; padding: 0.6rem 0.8rem;
}
input { background: #1b2220; color: inherit; margin: 0.8rem 0; }
button { background: #d7e0d8; color: #101418; font-weight: 600; }
.note { color: #9aa7a0; font-size: 0.9rem; }
.ok { color: #34d399; font-size: 0.95rem; margin: 0.4rem 0 0; }
#photo-form, #speak-box { margin: 1.1rem 0 0.4rem; }
#photo-form label, #speak-box label { display: block; margin-bottom: 0.35rem; }
#hold-speak {
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  background: #6ea8fe;
  color: #101418;
}
#hold-speak.held { background: #f87171; color: #101418; }
#speak-answer {
  white-space: pre-wrap;
  margin: 0.6rem 0 0;
  font-size: 1.05rem;
}
.wait-chip, .more-fold {
  margin: 0.35rem 0.4rem 0 0;
  border: 1px solid #2a3330;
  border-radius: 999px;
  padding: 0;
  vertical-align: top;
}
.wait-chip { display: inline-block; }
.more-fold { display: block; border-radius: 0.5rem; margin-left: 0; }
.wait-chip > summary, .more-fold > summary {
  min-height: 2.75rem;
  display: flex;
  align-items: center;
  cursor: pointer;
  color: #c5d0c8;
  font-size: 0.95rem;
  padding: 0.35rem 0.9rem;
  list-style: none;
}
.wait-chip > summary::-webkit-details-marker,
.more-fold > summary::-webkit-details-marker { display: none; }
.wait-chip[open], .more-fold[open] {
  display: block;
  border-radius: 0.5rem;
}
.wait-chip[open] > summary, .more-fold[open] > summary {
  border-bottom: 1px solid #2a3330;
}
.wait-chip ul, .more-fold ul { padding: 0 0.9rem 0.4rem; }
#stale {
  display: none; position: fixed; inset: 0; background: #101418;
  color: #f2f4f3; align-items: center; justify-content: center;
  text-align: center; padding: 2rem; font-size: 1.4rem; z-index: 9;
}
#stale.on { display: flex; }
header { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
form.logout { margin: 0; width: auto; }
form.logout button { width: auto; min-height: 2rem; padding: 0.3rem 0.7rem; background: transparent; color: #c5d0c8; border-color: #3b4742; }
</style>

</head>
<body>
<div id="stale">Cannot reach the desk.</div>
<main>
<header>
  <h1>Starship</h1>
  <form class="logout" method="post" action="/logout"><button type="submit">Log out</button></form>
</header>
<p class="meta"><span id="desk">Desk reachable</span> · <span id="mailbox">Mailbox on</span></p>
<p class="meta" id="observed">Observed 27 seconds ago</p>
<p class="warn">Summary only. Do not approve from this page.</p>
<h2>Needs you</h2>
<div id="needs"><ul><li><span class="dot stuck"></span>Mailbox token missing</li><li><span class="dot needs"></span>Choose the Qwen route</li><li><span class="dot needs"></span>Approve the advisor call</li><li><span class="dot needs"></span>Raise the Claude spend limit</li><li><span class="dot needs"></span>Pick the DeepSeek route</li></ul><details class="more-fold" open=""><summary>2 more waiting on you - show all</summary><ul><li><span class="dot needs"></span>Supply the StudyLink facts</li><li><span class="dot needs"></span>Fund worker capacity</li></ul></details></div>
<h2>Under way</h2>
<div id="underway"><ul><li><span class="dot under"></span>VoiceLoop tap trigger</li></ul></div>
<h2>Waiting</h2>
<div id="waiting"><details class="wait-chip" open=""><summary>Artevo 2</summary><ul><li><span class="dot wait"></span>Align Artevo pricing</li><li><span class="dot wait"></span>Artevo brand</li></ul></details><details class="wait-chip"><summary>Journey 3</summary><ul><li><span class="dot wait"></span>User accounts at launch</li><li><span class="dot wait"></span>Confirm retiring the Miro rebuild</li><li><span class="dot wait"></span>Journey board refresh</li></ul></details><details class="wait-chip"><summary>Finances 1</summary><ul><li><span class="dot wait"></span>Rotate finances secrets</li></ul></details><details class="wait-chip"><summary>Fleet 1</summary><ul><li><span class="dot wait"></span>Always-on cloud, host+budget</li></ul></details></div>
<h2>Talk</h2>
<div id="speak-box">
  <label for="hold-speak">Hold to speak</label>
  <button type="button" id="hold-speak">Hold to speak</button>
  <p id="speak-result" class="note"></p>
  <p id="speak-answer" class="meta"></p>
</div>
<h2>Send photos</h2>
<form id="photo-form" method="post" action="/upload" enctype="multipart/form-data">
  <label for="photo">Photos</label>
  <input id="photo" name="photo" type="file" accept="image/jpeg,image/png,image/webp,image/heic,image/heif,image/*" multiple="">
  <button type="submit">Send photos</button>
</form>
<p class="meta" id="photo-count">Photos received today: 0</p>
<p id="photo-result" class="note"></p>
<h2>Just finished</h2>
<div id="finished"><ul><li><span class="dot ready"></span>Finished work</li></ul></div>
</main>


</body></html>

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • 🚨 bin/fm-bridge-view.py:974 - Required intent says Waiting must use per-project chips and that grouping must come from structured repo fields. This new branch reads row.get(&#34;repo&#34;) for paused live work, but fm-bearings-snapshot.sh still omits repo and owner from every in_flight row. Consequently, paused work is grouped under “Other” instead of Artevo/Journey/Finances/Fleet/Glasses. Include the structured repository fields in the in-flight projection and cover the live snapshot path with a paused task.

🔧 Fix: Preserve paused-work repository grouping
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Inspected git diff e66a149f5847016e11c5b638a62b5029e858ef48..43aed0ee20f87c2d83604f754cea0d9ed6b92fae
  • bash tests/fm-bridge-view.test.sh
  • Started an isolated structured-snapshot fixture with bin/fm-bridge-view.sh serve --host localhost --port 18766
  • Authenticated in Chrome at a 390×844 iPhone viewport and verified the default five-item Needs You list, stuck-work priority, remainder disclosure, unchanged Under Way section, and visible Waiting count chips
  • Expanded 2 more waiting on you - show all and the Artevo 2 chip, verifying all hidden items remained reachable
  • Captured production-rendered HTML and screenshots using the authenticated page DOM and headless Chrome
  • Confirmed git status --short remained empty after testing
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Amplify-Logic and others added 4 commits August 20, 2026 17:10
Show at most five live asks, hide parked duplicate and retire holds from that list, and replace the waiting wall with per-project count chips.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Amplify-Logic
Amplify-Logic merged commit 3e81ed5 into main Aug 20, 2026
13 checks passed
Amplify-Logic added a commit that referenced this pull request Aug 23, 2026
)

Surface 1 (CONTRIBUTING upstream pointer): fixed. Step 1 of the workflow
still told contributors to set origin to the pre-fork
git@github.com:kunchenguid/firstmate.git. It now targets
git@github.com:Amplify-Logic/firstmate.git and credits kunchenguid/firstmate
explicitly as the upstream this fork descends from, matching
fork-surface.conf's upstream_remote declaration.

Surface 2 (validation-pipeline fork target): already correct, no change.
The only tracked no-mistakes configuration is .no-mistakes.yaml, which
carries no push or PR target; the pipeline's push target lives in
gitignored local state. git grep over tracked files finds no
pipeline-target reference to the pre-fork repository.

Surface 3 (PR compliance marker): already correct, no change. The marker in
.github/workflows/no-mistakes-required.yml references the no-mistakes tool
project (kunchenguid/no-mistakes), not the pre-fork firstmate repository,
and is byte-identical to the signature the installed pipeline writes into
this fork's PR bodies (verified against merged PR #91's body). Changing it
would break the compliance check, and tests/no-mistakes-required-workflow.test.sh
asserts the marker contract.

Remaining tracked kunchenguid/firstmate references are deliberate: the new
upstream-credit line in CONTRIBUTING.md, fork-surface.conf's upstream_remote
declaration, an upstream-recorded verification transcript in
docs/gitlab-merge-watch.md, and synthetic test-fixture URLs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant