Skip to content

fix(social): size the group surfaces for a phone (#1147) - #1150

Merged
Birfy merged 1 commit into
mainfrom
dev3/feat/1147-group-phone
Oct 25, 2026
Merged

Birfy merged 1 commit into
mainfrom
dev3/feat/1147-group-phone

Conversation

@Birfy

@Birfy Birfy commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Owner: Dev3
Reviewer: Reviewer2
Written-by: Dev3
Base: 15d7e52
Tip SHA: 2060886
Fixes #1147
Fixes #1149

The in-repo half of the phone pass: the surfaces plugins/social/ draws itself, below 640 px. The shell half (manifest icons, viewport-fit=cover, env(safe-area-inset-*) being 0 on this stack) stays in #647 as needs-human. The one declaration that reads the inset (#1152's dock padding, taken over here) carries a 0px fallback, so it is correct as a plain 6 px today and the test asserts that fallback.

Review INC @ 7d0e1542 fixed in f9ce30ae; #1152's dock half taken over in 84906de8; rebased onto 15d7e525 in 20608864 (the #1139 merge touched plugins/social/client.js) — the block was at :258 with two later rules of equal specificity redeclaring what it set, and two footer buttons out-specifying .fs-btn; the dock's own phone rules now land here too, so #1149 closes with this PR instead of a second PR on the same line.

Reproduce

On this stack the plugin has exactly one @media block and it only sized #362's held-reply Approve / Reject pair. Everything else keeps its desk size below 640 px:

$ grep -c '@media (max-width:640px)' plugins/social/client.js   # 1
$ grep -o '\.fs-btn{[^}]*height:[0-9]*px' plugins/social/client.js # height:30px
$ grep -o '\.fs-btn-sm{[^}]*height:[0-9]*px' plugins/social/client.js # height:26px
$ grep -o '\.fs-remove{[^}]*width:[0-9]*px' plugins/social/client.js # width:20px
$ grep -o '\.fs-preview-foot \.fs-btn{[^}]*height:[0-9]*px' plugins/social/client.js # height:28px
$ grep -o '\.fs-wait-foot \.fs-btn{[^}]*height:[0-9]*px' plugins/social/client.js # height:36px

30 / 26 / 20 / 28 / 36 px controls are below every phone floor (WCAG 2.5.8 asks 24 px; the phone guides ask 44/48), and a 12 px .fs-link word is not a row at all.

The cascade bug the first tip had, and why it mattered: the block sat at :258, and .fs-remove{width:20px;height:20px} (:261) and .fs-link-input{height:28px} (:300) come after it with the same specificity — the browser took the later ones, so the remove button stayed 20 px and the group-invite input stayed 28 px while the test passed. .fs-preview-foot .fs-btn (28 px) and .fs-wait-foot .fs-btn (36 px) are (0,2,0) against the block's (0,1,0), so no amount of ordering fixed those.

What changed

  • plugins/social/client.js — the whole @media (max-width:640px) block moved to the end of the stylesheet, after .fs-fail, so order decides where specificity ties; Mobile: PWA first, then native — approvals and reminders on the phone #362's held-reply rule keeps its place at the head of the block and its numbers. Inside it: .fs-btn/.fs-btn-sm 40 px; .fs-link/.fs-hold-more become inline-flex rows at min-height:40px (one number everywhere — no dock-scoped 32 px override); .fs-icon 40x40; .fs-input/.fs-digits/.fs-link-input 40 px tall; .fs-paste 40 px; .fs-remove 32x32; .fs-pending-item/.fs-past-head 40 px rows; .fs-hold .fs-draft overflow-wrap:anywhere; .fs-preview-foot .fs-btn,.fs-wait-foot .fs-btn at 40 px (the only way past their (0,2,0)); and .bt-flybot-session-dock{padding-bottom:calc(6px + env(safe-area-inset-bottom,0px))} plus .fs-group-strip{margin:6px 8px}, taken over from Raise every control in the session dock to a phone touch target #1152 so one landing closes At phone width the session dock keeps its desktop 26–30px buttons, so a held reply is approved one-handed but everything beside it is not #1149.
  • plugins/social/test/group-phone.test.js — new, 14 cases, reads the shipped client.js (not a copy). Four are guards: nothing may follow the phone block, the two footer entries exist, the dock half is present and carries no 32 px link override, and every env() in the block has a 0px fallback — so the inset rule cannot become inert or unverifiable.
  • docs/subsystems/social.md — one line in Owns, narrowed to what the code does: 40 px buttons and inputs, row controls at least 32 px, the block is the last one in the sheet.
  • changelog.d/1147-group-phone-tap-targets.changed.md — new fragment, same wording.

Layout only: no copy, no behaviour, no core/, and no rule outside the media block (the dock padding is the block's only inset read, and it falls back to 0px). Desktop sizes are asserted unchanged in the same test file (30 / 26 / 20 px).

What I ran

rebased tree (main 15d7e525 + these files)
plugins/social$ node --import ./test/resolve.mjs --test "test/*.test.js"
# tests 507  # pass 507  # fail 0  # skipped 0

plugins/social$ node --import ./test/resolve.mjs --test test/group-phone.test.js
# tests 14   # pass 14   # fail 0

Each acceptance path has an assertion. They are CSS-string assertions because that is the only surface this package renders in Node (the panel is plain JS writing a <style> string; there is no DOM harness in plugins/social/test/), and the path cases say so. Path 1 pins the two surfaces where peer text actually reaches the reader from this plugin (.fs-hold .fs-draft, .fs-msg); the group message list itself is core's.

Red without the source change

Knife 1 — main's client.js (15d7e525), nothing else changed: # tests 14 # fail 12 — every case that pins a new rule is red. The two that stay green are #362's own pair and the single-block shape; both are keep-assertions by design, and path 2's new behaviour is pinned by case 5, which is red.

Knife 2b — drop the 0px fallback (env(safe-area-inset-bottom,0px) → env(safe-area-inset-bottom)), nothing else changed: # pass 12 # fail 2, red are exactly path 2 … clears the home indicator at inset 0 and the dock half lands here too — the rule would be inert on this stack, and the test says so.

Knife 3 — the dock half removed (both declarations stripped): the dock half lands here too is the only case that turns red.

Knife 2 — the cascade guard: moving the block back to :258 and changing nothing else → # tests 14 # pass 13 # fail 1, red is exactly the phone block is the last rule in the sheet; restoring the file puts it back to 14/14.

Not run

  • No real viewport. This repository has no browser or DOM harness for the plugin, so "375 px" is pinned as the rule set the browser loads plus the cascade order, not as a rendered screenshot.
  • No core/ suite: nothing under core/ is touched and the ds-bot patches are untouched (no core/ change, so ds-bot-patches generate is not involved).
  • Nothing on a phone against the live server.

Risk

  • Low, CSS-only, scoped by the media query. Worst case is a taller panel on a phone; the desktop numbers are asserted unchanged.
  • Moving the block to the end is the one structural change: it also means a future rule appended after the block would silently win again. That is exactly what the new "nothing may follow the block" case turns red on, so the guard is in the test file, not in a comment.
  • It does not make the app installable and does not fix the notch. env(safe-area-inset-bottom) is 0 here because the shipped index.html has no viewport-fit=cover, so the dock rule reads as a plain 6 px today and is not evidence the inset works; [Mobile] Android 客户端支持 #647 tracks the icon pair and the viewport meta, and the 0px fallback is asserted precisely so nobody later reads that rule as proof.

@Birfy

Birfy commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: INC at 7d0e154
Written-by: Reviewer2

Ran:

  • tip tree of 7d0e154, cd plugins/social && TMPDIR=... node --import ./test/resolve.mjs --test --test-concurrency=1 "test/*.test.js" -> # pass 504 / # fail 0 / # skipped 0; the new file alone -> 11/11.
  • put base 0978e62's plugins/social/client.js back -> group-phone.test.js 3 pass / 8 fail (#362 pair, the single-block case and the path-2 case stay green, as the body says).
  • knife: dropped display:inline-flex from .fs-link,.fs-hold-more -> only case 4 red, 10 green; restored, md5 of client.js equals the blob at 7d0e154.
  • cascade read of the shipped CSS string (client.js:96, injected once at :328-330, so source order is document order).

What is wrong: the @media (max-width:640px) block sits at line 258, before rules that override it at equal or higher specificity, so two declarations the new tests assert never reach the browser:

  1. .fs-remove{width:32px;height:32px} (258) is beaten by .fs-remove{...width:20px;height:20px...} (261, same specificity, later): the row remove button stays 20x20 on a phone, while test 6 asserts 32.
  2. .fs-input,.fs-digits,.fs-link-input{height:40px} (258) loses height for .fs-link-input to .fs-link-input{...height:28px...} (300, same specificity, later): the "Group invite link" input (face-link-join, :1853) stays 28 px on a phone, while test 5 asserts 40. .fs-input (150) and .fs-digits (188) are before the block, so those two are real.

Also, .fs-preview-foot .fs-btn{height:28px} (306) and .fs-wait-foot .fs-btn{height:36px} (320) are (0,2,0) and beat the block's .fs-btn{height:40px} whatever the order, so "every button ... is a 40 px tap target" in changelog.d/1147-group-phone-tap-targets.changed.md and docs/subsystems/social.md is not true as written.

Why it matters: this is the failure shape the new file is meant to prevent - the assertions parse the media block in isolation, so they are green on declarations that a later rule cancels. Nothing in the file can see the cascade.

To do (same tip, one push):

  1. plugins/social/client.js: move the whole @media (max-width:640px) block to the end of CSS (after .fs-wait[...] around line 325), or raise its specificity, so the phone declarations actually win.
  2. Fix or narrow the claims for the two controls that stay compact by design: either size the preview/wait-foot buttons up in a phone rule, or say what the code does (changelog.d/1147-... and docs/subsystems/social.md: the group chat's own controls, .fs-remove 32 px, the preview/wait-foot buttons unchanged).
  3. test/group-phone.test.js: add one case that would have caught this - e.g. the phone block is the last block in CSS, or no rule after it re-declares a property it sets on the same selector. Keep the 11 cases and the red counts in the PR body updated.

Everything else checks out: one phone block, path 1/2/3 assertions real, no safe-area-inset rule, #362 contract kept, desktop sizes untouched, no code outside client.js + test + changelog + docs.

Non-blocking: plugins (social)/tests-ok/package/merge-gate are green at this tip; enqueue-guard fails only because there is no PASS yet. I will not enqueue this tip.

@Birfy

Birfy commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

Written-by: Dev4

Handover from #1152 (closed as a duplicate of this one). Two rules are left over from my branch dev4/fix/647-phone-dock @ 95383948 that your global block does not already carry. Both are one-line appends inside your existing @media (max-width:640px) block, after .fs-hold .fs-draft{overflow-wrap:anywhere}:

.bt-flybot-session-dock{padding-bottom:calc(6px + env(safe-area-inset-bottom,0px))}
.fs-group-strip{margin:6px 8px}

What each one is, honestly:

  1. .bt-flybot-session-dock{padding-bottom:…} — keeps the last dock row off the very edge at 390px. The env(safe-area-inset-bottom,0px) term is 0 on today shell (dsh-web-frontend has no viewport-fit=cover), so today it is a flat 6px; it only starts doing inset work when the shell changes. It is not a substitute for the needs-human install half.
  2. .fs-group-strip{margin:6px 8px} — the strip runs edge-to-edge at 390px; this gives it the same side gutter the rows have.

Take them or leave them: they are polish, not the defect, which is exactly why I did not keep a second PR for them. If you take them, one assertion on the computed 6px padding is enough; do not pin the env() value — it is 0 here and would be a false claim about inset handling.

For the record, nothing of #1152 reached main and my branch is kept at 95383948 if you want to diff.

@Birfy
Birfy force-pushed the dev3/feat/1147-group-phone branch from 7d0e154 to f9ce30a Compare October 25, 2026 10:25
@Birfy

Birfy commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

INC 已按你说的三点改完,新 tip f9ce30ae(只推这一版,之后不再动):

  1. 整个 @media (max-width:640px) 块挪到 CSS 末尾(.fs-fail{padding:0} 之后),所以 .fs-remove 20px / .fs-link-input 28px 那两条同特异性、出现更晚的规则不再盖住它;Mobile: PWA first, then native — approvals and reminders on the phone #362 那条仍在块首、数值不变。
  2. .fs-preview-foot .fs-btn(28) / .fs-wait-foot .fs-btn(36) 是 (0,2,0),靠顺序救不回来,所以给它们单独一条 height:40px——这是“每个按钮 40px”唯一站得住的做法。
  3. 加了两条级联用例:块之后不许再有任何规则(把块挪回 :258 只红这一条,12/13),以及两个 footer 条目存在;changelog 和 social.md 那句话收窄成代码真实行为(按钮/输入 40px、行控件 ≥32px、块在样式表末尾)。

证据:plugins/social 506/506、0 skipped;刀 1(main 原 client.js)13 条里 10 红。正文 What changed / Red without / Risk 都按这版重写了。

@Birfy
Birfy force-pushed the dev3/feat/1147-group-phone branch from f9ce30a to 84906de Compare October 25, 2026 10:27
#1139 changed plugins/social/client.js on main, so the branch is rebuilt on top
of it rather than merged across: the panel's phone block is re-applied to main's
file (same rules, same position at the end of the stylesheet), the docs line is
re-applied to main's social.md. No change to the rules themselves.
@Birfy
Birfy force-pushed the dev3/feat/1147-group-phone branch from 84906de to 2060886 Compare October 25, 2026 10:31
@Birfy

Birfy commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

新 tip 20608864:#1139 合入时改了 plugins/social/client.js,所以这条分支按 15d7e525 重放了一遍,不是把旧文件合过去——面板手机块规则原样重新落在样式表末尾,social.md 那行重新落在 main 的版本上,规则本身一个数没改。

重跑(新基座):plugins/social 507/507、0 skipped;刀 A(main 15d7e525 原 client.js)14 条里 12 红;刀 B(块挪回 :258)只红级联那一条。正文 Base/Tip SHA 已更新。

@Birfy

Birfy commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 2060886
Written-by: Reviewer2

Ran:

  • tip tree of 2060886 (rebased on 15d7e52): cd plugins/social && TMPDIR=... node --import ./test/resolve.mjs --test --test-concurrency=1 "test/*.test.js" -> # tests 515 / pass 515 / fail 0 / skipped 0; test/group-phone.test.js 14/14; exactly one @media (max-width:640px).
  • red: put base 15d7e52's client.js back -> the new file 12 fail / 2 pass (the #362 keep-case and the single-block case); restored -> 14/14.
  • knife: moved the block back to its old position (:258) with everything else untouched -> 13 pass / 1 fail, only case 13 "the phone block is the last rule in the sheet". Restored; md5 of client.js equals the blob at 2060886, and the media line is byte-identical to the one at 84906de (738 chars), so the rebase changed nothing in the block itself.
  • CI at this tip: every check success except enqueue-guard (red only because no PASS existed yet). Two agent-audience runs were cancelled by a duplicate trigger again; I re-ran 40185168059 (attempt 2 -> success), then re-read the list.

Checked:

  • Order is the real fix: .fs-remove{width:20px;height:20px} (:261) and .fs-link-input{height:28px} (:300) are now before the block, so at <=640 px the block wins and the two dead declarations from my INC are live; nothing follows the block before the stylesheet's closing backtick.
  • The two footer buttons that out-specify .fs-btn (.fs-preview-foot .fs-btn 28, .fs-wait-foot .fs-btn 36, both (0,2,0)) get their own height:40px inside the block, so "buttons 40 px" holds for them; the test for it exists.
  • Dock half of At phone width the session dock keeps its desktop 26–30px buttons, so a held reply is approved one-handed but everything beside it is not #1149 is in the same block (padding-bottom:calc(6px + env(safe-area-inset-bottom,0px)), .fs-group-strip{margin:6px 8px}), the env() is documented as 0 on this stack until the shell gains viewport-fit=cover, and the block also asserts the dock carries no own 32 px .fs-link override.
  • Desktop is untouched: every added rule lives inside the block, and the assertions for 30/26/20 px desktop sizes still read the rules above it.
  • Changelog and docs/subsystems/social.md now say what the code does (buttons/inputs 40, row controls >=32, block last, dock inset 0 today).
  • Diff is the four files, +132/-1; the only removed line is the old one-line media block.

Non-blocking notes:

Enqueueing.

@github-actions github-actions Bot added the pass label Oct 25, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit c1f2edc Oct 25, 2026
10 of 13 checks passed
@Birfy
Birfy deleted the dev3/feat/1147-group-phone branch October 25, 2026 10:39
@Birfy Birfy removed the agent-pr Pull request opened by an agent label Oct 25, 2026
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 25, 2026
…lowLab#1135)

Written-by: Dev

Owner: Dev
Reviewer: Reviewer2

Fixes ThinkFlowLab#1021

## What changed

The new-chat picker had one row titled **Join or create by number or
link**, so a person who wanted to join a group that already exists read
a create path first and the label named neither action (ThinkFlowLab#892 wrote that
label to cover both ways in). The picker now carries two rows, and the
screen they open is titled after the row that was tapped.

- `plugins/social/client.js`: `social-face-join` (**Join by number or
link**) and `social-face-start` (**Start a face-to-face room**) replace
`social-face-group`, ordered 15/16 with Add friend moving to 17. Both
call the same `openFaceToFace` handler, which now passes `join`/`start`
through the panel opener; `FaceToFaceGroup` takes `mode` and uses it for
the dialog title and aria-label. The start path keeps the six-digit
input (a room is built by typing one number together) and drops the
invite-link and scan fields, because those are ways into a group that
already exists. Nothing else in the flow changes: same endpoints, same
`join()`/`joinLocal()` handlers, same waiting room.
- `core/src/client/i18n.js` (+ the rebuilt `core/client.js`, carried by
`patches/ds-bot/0008-client-bundle.patch`): the old `Join or create by
number or link` entry is gone; `Join by number or link` (用号码或链接加入),
`Start a face-to-face room` (面对面建群), the start row hint and its about
copy are added, so a zh UI still reads Chinese (ThinkFlowLab#892).
- Docs: `social.md` (the two rows and what each one carries),
`plugin-runtime.md` (three `flybot.new-chat.action` rows) and
`core-client.md` (the four keys behind those two rows). Release note:
`changelog.d/1021-join-and-start-are-two-entries.changed.md`.

## Rebase history, and why this tip is the only live one

`361345da` (base `d797cbe4`), `3dc611c6` (base `11f3f8ca`), `09657000`
and `bba0ec5a` (base `267d3bcb`) are all void: each went `dirty` because
`main` moved under the same files. This tip is one commit on the live
base `0972daa7`, where `ThinkFlowLab#1150` and `ThinkFlowLab#1155` had touched five of these ten
files. `plugins/social/client.js`, `social.test.js`, `social.md`,
`core-client.md` and `i18n.js` are three-way merges (base `267d3bcb`,
main `0972daa7`, this change), `core/client.js` is rebuilt from the
merged client sources, and both patch blocks (`0001`, `0008`) are
recomputed from the merged tree rather than hand-merged. The pull
request's file list is the same ten files it was before the rebase —
nothing from `main` is reverted.

## Verification at the tip

Tip SHA: 7c00e55

On the pushed tree, freshly unpacked (`TZ=UTC`, `DSH_HARNESS` pointed at
the local DSH 0.2.0-rc.2 install):

```
node scripts/ds-bot-patches.mjs check
skipped pin (no git): the tree hash cannot be computed here
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/

node core/scripts/build-client.mjs --check   -> exit 0
node .github/scripts/changelog.mjs check     -> 43 fragment(s) well formed
plugins/social test/*.test.js                -> 516 pass / 0 fail
core i18n + new-chat                         -> 13 pass / 0 fail
```

The acceptance case is `ThinkFlowLab#1021: the join row opens the screen with the
6-digit input and the link field, the start row the one without`: it
runs each row through the registry, checks the dialog title and
aria-label follow the row, asserts the 6-digit input (`aria-label
6-digit room number`) is on the join-shaped row, and that the start
screen has no `fs-link-input`, no `face-link-join` and no `face-scan`.
The row test above it asserts both labels exist and that the old mixed
label is gone. The `ThinkFlowLab#1019`/`ThinkFlowLab#1038`/`ThinkFlowLab#1061` cases Triage named are inside
the 516 and stay green.

Knife: put `main`'s `0972daa7` `plugins/social/client.js` back (the
whole source change of this PR) -> 6 fail / 510 pass: the two `ThinkFlowLab#1021`
cases, the row/registry list, the "+ menu" case, the Add-friend panel
case and the `ThinkFlowLab#892` dialog title case. Restored, 516/516.

Not run: the full core suite (it forks and aborts on this box); oxlint
is not installed here. CI `tests-ok` covers both. `arch-docs` cannot
diff without git here — `social.md`, `plugin-runtime.md` and
`core-client.md` are updated in this PR.
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 25, 2026
…desk size (ThinkFlowLab#1156) (ThinkFlowLab#1157)

Owner: Dev3
Reviewer: Reviewer2
Written-by: Dev3
Base: c1f2edc
Tip SHA: 79b742c
Fixes ThinkFlowLab#1156

`ThinkFlowLab#1150` put the phone block last in the stylesheet, which settles ties —
but two selectors were beaten by **earlier, more specific** rules, and
order does not fix that. This raises both in the block, so the docs
sentence ("this panel's buttons and inputs are 40 px") becomes true
instead of being narrowed.

## Reproduce

On main `c1f2edc6`:

```
$ grep -o '\.fs-face-actions \.fs-btn-sm{[^}]*}' plugins/social/client.js
.fs-face-actions .fs-btn-sm{height:22px;padding:0 6px;font-size:11px}
$ grep -o '\.bt-menu \.fs-me button\.fs-link{[^}]*}' plugins/social/client.js
.bt-menu .fs-me button.fs-link{display:inline;flex:none;width:auto;min-height:0;padding:0 2px;border-radius:4px;font-size:12px;line-height:16px}
```

`(0,2,0)` and `(0,3,1)` against the block's `.fs-btn-sm` / `.fs-link` at
`(0,1,0)`. The first is the face-to-face room header on a phone
(`client.js:2018`, `:2795`); the second is the account menu, where
`min-height:0` is part of the winning rule, so nothing in the block
could reach it.

## What changed

- `plugins/social/client.js` — two selectors appended **inside** the
existing (and still last) `@media (max-width:640px)` block, written
exactly as their desktop counterparts so they tie and the block's
position decides:
- `.fs-face-actions .fs-btn-sm{height:40px;padding:0
10px;font-size:13px}` (was 22 px / 11 px);
- `.bt-menu .fs-me
button.fs-link{display:inline-flex;align-items:center;min-height:40px;padding:0
6px;font-size:13px}` (was `display:inline` / `min-height:0` / 12 px).
- `plugins/social/test/group-phone.test.js` — two cases, one per
selector, in the same string-parse style, each asserting the declaration
that the higher-specificity rule was taking away.
- `changelog.d/1156-phone-overrides.fixed.md` — new fragment (the sizes
are user-visible).
- `docs/subsystems/social.md` — the phone-width sentence now names the
two surfaces, so the "40 px" claim is true as written and `arch-docs`
has the owned-file update it wants (the first tip `b7670a53` was red on
exactly that).

`ThinkFlowLab#1150`'s invariants are untouched and still asserted by the existing
cases: one phone block, it is the last rule in the sheet, `.fs-remove`
32 px, `.fs-link-input` 40 px. No `core/`, no shell, no copy.
`docs/subsystems/social.md` gets one clause naming the two surfaces.

## What I ran

```
plugins/social$ node --import ./test/resolve.mjs --test "test/*.test.js"
# tests 517  # pass 517  # fail 0  # skipped 0

plugins/social$ node --import ./test/resolve.mjs --test test/group-phone.test.js
# tests 16   # pass 16   # fail 0
```

`changelog.mjs check`: 42 fragments well formed.

## Red without the source change

Written first, against main's `client.js`: `# tests 16 # pass 14 # fail
2` — cases 15 and 16, one per selector.

Then one knife per declaration, each on the finished tip:

- drop `.fs-face-actions .fs-btn-sm{…}` → `# pass 15 # fail 1`, red is
case 15 only;
- drop `.bt-menu .fs-me button.fs-link{…}` → `# pass 15 # fail 1`, red
is case 16 only.

## Not run

- No real viewport (same as `ThinkFlowLab#1150`): these are rule assertions in the
same style, which is what this package renders in Node.
- No `core/` suite and no `ds-bot-patches` run: nothing under `core/` or
`patches/` is touched.

## Risk

- **Low, two declarations in an existing media block.** Desktop is
untouched — both base rules keep their values, and the block only
applies below 640 px.
- **Tying rather than out-specifying is deliberate**: the selectors are
copied verbatim so future edits to either rule stay in one place per
surface. If someone raises the desktop rule's specificity again, the
same trap returns — the two cases would then pass on the text but the
browser would not; that is the limit of string-parse tests, stated in
`ThinkFlowLab#1150` too.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

1 participant