From 95383948632814941948922a58cbfb3b24e678c9 Mon Sep 17 00:00:00 2001 From: Danyang Chen Date: Sun, 25 Oct 2026 18:21:35 +0800 Subject: [PATCH] Raise every control in the session dock to a phone touch target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The phone pass (#362) raised the held-reply card to 40px and left the rest of the same dock at its desktop sizes: `.fs-btn` 30px, `.fs-btn-sm` 26px, and `.fs-link` a 12px text run. On a 390px screen the card answers at 40px and the buttons beside it ask the same finger for half a touch target (#1149). They now sit in the single `@media (max-width:640px)` block the plugin already has, keyed off the dock's own container `.bt-flybot-session-dock`: buttons 40px, the two links a 32px hit area, and a bottom padding for `env(safe-area-inset-bottom)` — 0 on today's shell, which has no `viewport-fit=cover`, so the rule waits for one. Nothing outside that query moved. Fixes #1149 --- changelog.d/647-phone-dock.changed.md | 1 + docs/subsystems/social.md | 2 +- plugins/social/client.js | 2 +- plugins/social/test/social.test.js | 18 ++++++++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 changelog.d/647-phone-dock.changed.md diff --git a/changelog.d/647-phone-dock.changed.md b/changelog.d/647-phone-dock.changed.md new file mode 100644 index 000000000..30ee2dc6c --- /dev/null +++ b/changelog.d/647-phone-dock.changed.md @@ -0,0 +1 @@ +On a phone the whole session dock is now touch-sized, not only the held-reply card: every button in it (invite by link, approve all held replies, accept or decline a notice, close a room) is 40px tall instead of the desktop 26–30px, the Auto-send and Copy link links get a real hit area, and the dock clears the phone's home indicator, so a decision can be made one-handed (#647). diff --git a/docs/subsystems/social.md b/docs/subsystems/social.md index 9aea0688d..99b075707 100644 --- a/docs/subsystems/social.md +++ b/docs/subsystems/social.md @@ -123,7 +123,7 @@ Creating a group opens its Session with zero turns, and the host draws no sessio - Status chips on room details and Past rooms (`data-flybot=face-status`) read **Open** / **Closed** in sentence case with readable contrast, and the past row keeps its own spacing between avatars, title, time and chip (#170). - One card-placement rule (#418): a held message's decision sits where that message is. A Bot's reply to a friend has its buttons on the held reply bubble in place (no copy in the dock of that chat or the friend's chat); a message with no bubble of its own (a Bot's `message_friend_bot`, a typed composer line held by policy, a face-room line) has its card in the dock of the chat it belongs to, as below. - In-chat approval cards (`flybot.session.dock`) are right-aligned bubbles capped at ~460px: **Waiting for approval**, one `from → to` line, a word-wrapped preview with **Show more**, and **Approve** / **Reject** (one right-hand group) with a quiet **Always approve** link at the left of the same row, alone above them when the card is narrow (#166, #260, #271). One place per approval (#278): a Bot's direct message to a friend is drawn in the friend's chat once that chat exists (the sending Bot's own chat only until then), never in a Face-to-face room that has the friend in it; a Face-to-face room shows, counts as waiting, and gets a "Waiting for the owner's approval" notice for just the lines held for its own room id. The client also hides that old notice in a Face-to-face room's history. The held text shows once (#306): a Bot line held in a Face-to-face room keeps its text in the card, and the room draws no bubble for it until it is approved (`GroupMessage`, `core/src/client/cells.js`, matched by `room.heldLines`, #456, and by `room.pendingApprovals` as a fallback); a line the owner typed is never held and stays a plain bubble. A Bot's message to a friend (`message_friend_bot`) has no bubble in either chat, so the card is already its only place. #456: the held room line lives only on its card (greyed preview) until **Approve**: `onFaceLine` answers `{ held: true, approvalId }` for a Bot line Bot Social holds, `social/decide-approval` calls `flybot.rooms.settleHeld`, Approve makes it a room bubble and Reject a grey ` · Not sent` line, with no `Rejected.` note in the face room. A decided card leaves the dock at once (no **Approved** / **Rejected** stub, any card kind). A face card's `from` is the room's own #384 label for that local Bot (`Researcher (Alice) → the room`, `faceFromLabel`). `applyFaceSnapshot` posts a human or system event for a round only when the core room logged it now (`applyFaceLine` refuses an event id it has), so an event read twice starts no second round. -- On a phone-width screen (below 640px, #362) the hold card's **Approve** / **Reject** are 40px touch targets at the right of their row; wider screens keep the compact 28px buttons. +- On a phone-width screen (below 640px, #362) the hold card's **Approve** / **Reject** are 40px touch targets at the right of their row; wider screens keep the compact 28px buttons. The same media query carries the rest of the session dock (#647): every button in that slot is 40px — **Invite by link**, **Approve all**, the notice **Accept** / **Decline**, **Close** — because below 640px those are the only way to answer on a touch screen and they were left at the desktop 30px `.fs-btn` / 26px `.fs-btn-sm`; the **Auto-send** and **Copy link** links get a 32px hit area instead of their 12px text run, and the dock pads its bottom by `env(safe-area-inset-bottom)`. That value is 0 on today's shell — its viewport is `width=device-width, initial-scale=1` with no `viewport-fit=cover` (`dsh-web-frontend`, DSH's file, #647) — so the rule costs nothing until a shell sets it, and no other inset rule is worth adding before then. The rules key off the dock's container class `.bt-flybot-session-dock` (`core/src/client/flybot-slots.js`), so no component changed and nothing outside that query moved. The install half of #647 — a manifest of our own, raster icons, a service worker — stays DSH's (`core-client.md`): this page changes only what FlyBot draws. - The composer **Message {name}** label is held by core `HeaderPill` through `composer-label.js` (placeholder CSS var plus `aria-label` and `data-placeholder` on `[data-composer-input]`, from the first paint, restored on cleanup; #419, #499); chat-header tok jargon stays in pristine DSH / ds-bot. ### Face-room poll identity (#122) diff --git a/plugins/social/client.js b/plugins/social/client.js index fb98fd4ee..5a33ee8e8 100644 --- a/plugins/social/client.js +++ b/plugins/social/client.js @@ -255,7 +255,7 @@ window.__ModuleLoader__.load({ .fs-hold .fs-hold-decide{display:flex;gap:8px;flex:none} .fs-hold .fs-actions .fs-hold-always{order:-1;margin-right:auto;white-space:nowrap;flex:0 0 auto} .fs-hold[data-state] .fs-name{font-size:13px;line-height:18px;font-weight:500;color:var(--bt-ink-2,rgba(20,20,20,.6))} -@media (max-width:640px){.fs-hold .fs-btn{height:40px;padding:0 16px;font-size:14px}.fs-hold .fs-hold-decide{flex:1 1 auto;justify-content:flex-end}} +@media (max-width:640px){.fs-hold .fs-btn{height:40px;padding:0 16px;font-size:14px}.fs-hold .fs-hold-decide{flex:1 1 auto;justify-content:flex-end}.bt-flybot-session-dock{padding-bottom:calc(6px + env(safe-area-inset-bottom,0px))}.bt-flybot-session-dock .fs-btn,.bt-flybot-session-dock .fs-btn-sm{height:40px;padding:0 14px;font-size:14px}.bt-flybot-session-dock .fs-link{display:inline-flex;align-items:center;min-height:32px;padding:0 4px;font-size:13px}.fs-group-strip{margin:6px 8px}} .fs-row-wrap{position:relative;display:flex;align-items:center;gap:2px;min-width:0} .fs-row-wrap .fs-row,.fs-row-wrap > :first-child{flex:1 1 auto;min-width:0} .fs-remove{flex:none;width:20px;height:20px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--bt-ink-3,rgba(20,20,20,.36));font:inherit;font-size:14px;line-height:1;cursor:pointer;opacity:0} diff --git a/plugins/social/test/social.test.js b/plugins/social/test/social.test.js index d3edc39e2..3f1cafada 100644 --- a/plugins/social/test/social.test.js +++ b/plugins/social/test/social.test.js @@ -10301,6 +10301,24 @@ test('on a phone-width screen the hold card keeps Approve/Reject touch-sized (#3 assert.match(phone, /\.fs-hold \.fs-hold-decide\{[^}]*justify-content:flex-end/) }) +test('at phone width the rest of the session dock is touch-sized too, not only the held card (#647)', () => { + const src = readFileSync(new URL('../client.js', import.meta.url), 'utf8') + assert.equal((src.match(/@media \(max-width:640px\)\{/g) ?? []).length, 1, 'still one phone-width block') + const phone = src.match(/@media \(max-width:640px\)\{([^\n]*)\}\n/)?.[1] + assert.ok(phone, 'the block stays on one line, so it closes where it opened') + // The dock was left at desktop sizes: `.fs-btn` 30px and `.fs-btn-sm` 26px. On a phone those are + // the only way to invite by link, approve all held replies, accept a notice or close a room. + assert.match(phone, /\.bt-flybot-session-dock \.fs-btn,\.bt-flybot-session-dock \.fs-btn-sm\{height:40px;padding:0 14px;font-size:14px\}/) + // Auto-send and Copy link are 12px text runs with no box: give them a hit area. + assert.match(phone, /\.bt-flybot-session-dock \.fs-link\{[^}]*min-height:32px/) + // The dock is the last row above the composer, so it has to clear the phone's home indicator. + assert.match(phone, /\.bt-flybot-session-dock\{padding-bottom:calc\(6px \+ env\(safe-area-inset-bottom,0px\)\)\}/) + // Wider screens are unchanged: the desktop sizes stay outside the media query. + assert.match(src, /\n\.fs-btn\{flex:none;height:30px/) + assert.match(src, /\n\.fs-btn-sm\{height:26px/) + assert.match(src, /\n\.fs-group-strip\{[^}]*margin:6px 12px/) +}) + // Bot Social with the room lead (#476): POST /lead and PUT /roles answer the room, as the server does. function leadFaceServer(features) { const server = attestingFaceServer({ features })