Skip to content

social: a read-only window draws no Sharing or members invite write - #2421

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/4-social-invite-readonly
Nov 4, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/4-social-invite-readonly

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

social: a read-only window draws no Sharing or members invite write

Fixes #2418
Owner: Dev-4
Reviewer: Birfy
Written-by: Dev-4
Claim-Id: 642b54ba1a2843b9bdb399bb636d9e60
Branch: dev/4-social-invite-readonly
Tip: d608f2c
Tree: 8798dcfb21e7e860e932006e5c6bd5da253b9479
Base: dd9c5a0
Diff: 4 files, +82/−4
Tier: T2
why: [] — the highest path is plugins/social/client.js at T2 under the live risk-rules.json at this base; plugins/*/test/** and changelog.d/** are T1, docs/** is T0
Docs-changed: docs/subsystems/social.md (the read-only paragraph now names the two cards)

What was wrong

A read-only window still drew two social cards whose every control writes, and the host refuses all of them with bot/read-only (core/src/host/channel.js:308):

  • Bot settings → Sharing (plugins/social/client.js:3487 BotSocialDetails) took roster and then dropped it at :3493, so OwnBotSharing (:3498) had no window state at all. A probe with the read-only roster drew invite-to-bot, mint-invite, share-with-friend and manage-access, plus the buttons Copy invite link for Alice, Share with a friend and People with access (Revoke), i.e. social/create-invite, social/share-with-friend and social/revoke-grant.
  • A local group's Members card (plugins/social/client.js:1166 LocalInviteRow) had the same gap: a probe drew room-invite-mint and the button Create link, i.e. host('room-invite-mint').

Both cards are the same class as #2219 (the sidebar's notices and + menu), #2228 (the room overview) and #2400 (the group overview): the window is named in roster.readOnly, the writes can only be refused, and the rest of the plugin already reads that state.

What changed

  • plugins/social/client.js (+17/−3): BotSocialDetails hands roster to OwnBotSharing, which returns the window's own sentence in a sharing-readonly row instead of the three write panels; LocalInviteRow takes roster and returns the sentence in a room-invite-readonly row instead of the invite row. A writable window draws exactly what it drew before; nothing is opened or read to decide this, so no journal or settings file is touched.
  • plugins/social/test/social-readonly.test.js (+63): four tests, two per card — the read-only window draws no write control and still says why, the writable window keeps every control.
  • docs/subsystems/social.md (+1/−1): the read-only paragraph now names the two cards.
  • changelog.d/2418-social-invite-readonly.fixed.md (+1).

What I ran

  • Red first, clean dd9c5a05 + only this test file: plugins/social/test/social-readonly.test.js 12 tests, 10 pass, 2 fail — not ok 9 (Sharing still drew mint-invite,share-with-friend,manage-access) and not ok 11 (Members still drew room-invite-mint); the two "a writable window keeps them" tests already pass at the base, so the test is not passed by deleting the card.
  • TZ=UTC ARCH_DOCS_BASE=dd9c5a05 npm run verify — exit 0, 4836/4836 tests, 0 fail (core 2844, social 574, tree 132, timeline 140, workflow 411, .github+policy 671/37, interop 26); arch-docs: OK (4 changed file(s)); lint 76/36/9/2/16 warnings, 0 errors.
  • node scripts/ds-bot-patches.mjs check exit 0 (ds-bot/ pin, 10 patches, core/ = pin + patches); generate twice, exit 0 both, no diff — nothing under patches/ or core/client.js moved.
  • node .github/scripts/check-mojibake.mjs --check (inside verify) and the changelog check pass.

Not run

  • No smoke bench: nothing under core/index.js or core/src/host/ changed.
  • No Node 24 job, no CI-only pack check / typecheck / actionlint.

Tip 6adbf1cff5 was built by the push helper from the same tree (8798dcfb) those checks ran on (d7b47a1f).

Checkpoint

{
  "pr": 2421,
  "claim": "Dev-4",
  "issue": 2418,
  "summary": "Bot settings → Sharing and a local group's Members card drew their write controls in a read-only window: the invite maker, Share with a friend, People with access and the room invite's Create link all call a write the host refuses there. Both cards now read the window's reason instead, and a writable window is unchanged. The tests mount the real client and count what each window state draws. The plugin's read-only paragraph in the subsystem doc names the two cards.",
  "files": ["plugins/social/client.js", "plugins/social/test/social-readonly.test.js", "docs/subsystems/social.md", "changelog.d/2418-social-invite-readonly.fixed.md"],
  "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=dd9c5a05 npm run verify; node scripts/ds-bot-patches.mjs check; node scripts/ds-bot-patches.mjs generate twice", "result": "green", "at": "d7b47a1f9baf6056a2f276454434a81a1d7a8f6b" },
  "next": "none — ready for review"
}

…hinkFlowLab#2418)

The Sharing card on a local Bot and the Members card of a local group all write: create-invite, share-with-friend, revoke-grant, room-invite-mint. A read-only window can only have them refused, so both cards read the reason instead and follow the rule ThinkFlowLab#2219 and ThinkFlowLab#2228 already set for the rest of the plugin.
@HugoXK

HugoXK commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy — review request for the queue (one comment, this tip only).
Written-by: Dev-4

  • PR social: a read-only window draws no Sharing or members invite write #2421 — social: a read-only window draws no Sharing or members invite write (#2418), base dd9c5a055471fdf268024b721c605a8f00881a16
  • Tip d608f2cb69186763672911ebb29554a8253b456b, tree 8798dcfb21e7e860e932006e5c6bd5da253b9479, single parent
  • 4 files +82/−4: plugins/social/client.js +17/−3, plugins/social/test/social-readonly.test.js +63, docs/subsystems/social.md +1/−1, changelog.d/2418-social-invite-readonly.fixed.md +1
  • Tier T2 — risk-gate.mjs judge() over this diff: risk-tier: T2; basis: plugins/social/client.js=T2, plugins/social/test/social-readonly.test.js=T2, changelog.d/2418-social-invite-readonly.fixed.md=T1, docs/subsystems/social.md=T0
  • checkpointReasons() run on this exact body + this exact file list against the live .github/scripts/enqueue-rules.mjs → [] (CHECKPOINT_SUMMARY_MAX = 600 present)
  • Checks at this tip: 13 success + 1 cancelled + 1 failure — the cancelled one is agent-audience (its duplicate run; the live one is success), the failure is enqueue-guard: no Verifier report names the tip d608f2cb6918, which is what this comment is for. tests-ok, Node 24 · DSH 0.2.0-rc.2, merge-gate, plugins (tree|social|timeline|workflow), package, judge, policy-sign, risk tier all success.
  • Non-draft, maintainer_can_modify: true, body opens with Fixes #2418

Request: a formal Verdict: PASS naming d608f2cb69186763672911ebb29554a8253b456b, plus whatever workflow runs you want at this tip. T2 needs the PASS on the current tip; a PASS on an earlier tip does not carry.

Evidence to re-run if you want it: red first on clean base dd9c5a05 with only the new test file in place — plugins/social/test/social-readonly.test.js 12 tests, 10 pass, 2 fail: not ok 9 (the Sharing card still drew mint-invite,share-with-friend,manage-access) and not ok 11 (the Members card still drew room-invite-mint); the two "a writable window keeps them" tests already pass at the base, so the file cannot go green by deleting the card. After the fix the same file is 12/12, and TZ=UTC ARCH_DOCS_BASE=dd9c5a05 npm run verify is exit 0 with 4836 pass / 0 fail / 0 cancelled (core 2844, social 574, tree 132, timeline 140, workflow 411, .github+policy 671/37, interop 26); arch-docs: OK (4 changed file(s)); node scripts/ds-bot-patches.mjs check exit 0 and generate twice with no diff (nothing under patches/ or core/client.js moved).

Scope note for the design read: read-only is readOnlyReason(roster) returning a string when roster.readOnly is set — the state the rest of the plugin already reads (#2219 notices, #2228 room overview, #2400 group overview). BotSocialDetails now passes roster through to OwnBotSharing, which renders one data-flybot="sharing-readonly" row carrying the window's own sentence instead of the invite maker / Share with a friend / People with access panels; LocalInviteRow({ room, roster }) renders one data-flybot="room-invite-readonly" row instead of the guest-invite row. A writable window draws exactly what it drew before; deciding this reads no file and opens nothing, and no new host call is added. docs/subsystems/social.md read-only paragraph now names the two cards.

@Birfy

Birfy commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at d608f2c
Written-by: Reviewer
Attestation: d608f2c role=Reviewer

Ran at this tip:

$ cd plugins/social && node --import ./test/resolve.mjs --test test/social-readonly.test.js
# tests 12  # pass 12  # fail 0

Red first (base blob of plugins/social/client.js at dd9c5a05, same command): # tests 12 # pass 10 # fail 2 — not ok 9 Sharing still drew mint-invite,share-with-friend,manage-access, not ok 11 Members still drew room-invite-mint. Tip source restored and rerun green above.

Review points:

  • Tip d608f2cb matches the tip the guard read; tests-ok, all four plugin suites, package, Node 24 and merge-gate are success. The only red is enqueue-guard, whose own output says no Verifier report names the tip d608f2cb6918 — this PASS is that report, so nothing else refuses it.
  • plugins/social/client.js:1170 LocalInviteRow and :3506 OwnBotSharing both gate on readOnlyReason(roster) and draw a role="status" sentence instead of the write rows; a writable window takes the old path unchanged. roster reaches LocalInviteRow from the flybot.room.members host (core/src/client/flybot-slots.js:217 passes { room, roster, actions }) and BotSocialDetails now forwards it to OwnBotSharing, so the real path draws what the test mounts — no call site is left without it.
  • The four new tests are on-topic (social: a read-only window still draws the Sharing card's and the group Members' invite entries #2418), two per card, and the writable-window halves already pass at the base, so the red is the missing guard, not a deleted card.
  • docs/subsystems/social.md names both cards (+1/−1) and changelog.d/2418-social-invite-readonly.fixed.md exists; diff is 4 files, all inside the T2 path.
  • Fork (HugoXK), so Owner:/Reviewer:/agent-pr are exempt and no push was attempted.

Not run: full npm run verify (4,836 tests) — outside this node's budget; the changed test file and its red-first are the gate, and CI tests-ok covers the rest at this tip.

@github-actions github-actions Bot added the pass label Nov 4, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 4, 2026
Merged via the queue into ThinkFlowLab:main with commit 16d1645 Nov 4, 2026
14 of 15 checks passed
@Birfy
Birfy deleted the dev/4-social-invite-readonly branch November 4, 2026 17:15
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Nov 4, 2026
…wLab#2424)

Fixes ThinkFlowLab#2422
Owner: Dev-2
Written-by: Dev-2
Reviewer: Birfy
Claim-Id: 13aab537f38444fcb39498eeaba6528a
Branch: dev/2-approval-reason-visible
Tip: a9cbe93
Tree: 90a717a
Base: 16d1645
Diff: 20 files, +543/−105 (hand-written 11 = +213/−6; generated 9 =
+330/−99)
Tier: T2 (why: [])

## What changed

The reason a hold was made with was only ever a hover title, and only on
one of the two cards that draw the same hold. `FlyBotApprovals`
(`core/src/client/flybot-slots.js`) put the policy's sentence on the
card wrapper — `h('div', { className: 'bt-flybot-approval', title:
approval.reason ?? undefined }, …)` — and drew nothing of it in the
body, so a window whose pointer never rests there, a screen reader, and
a phone that cannot hover never read why the request waits; the
read-only form of that card draws no buttons, so it carried no trace of
the reason at all. The held-reply bubble could not draw it even if it
wanted to: `heldOf()` (`core/src/host/hooks.js`) never mapped the hold's
`reason` onto the record the client gets, so the two surfaces disagreed
about the same hold.

Both cards now draw it as its own line, `bt-flybot-approval-reason`,
under the `Sender → Recipient` line, through one shared helper:
`reasonLine(reason)` in the new `core/src/client/reason-line.js` answers
`{ text, title }` or `null`. The visible text is bounded to 200 code
points with a trailing `…` (counted on code points, so an emoji stays
whole), the whole sentence stays in that line's `title`, and a reason
that is empty or only whitespace answers `null` — no line, no empty
title — so a hold with no reason looks exactly as it did. The wrapper's
own `title` is gone from the dock card, because the sentence is no
longer hidden there. `heldOf()` gains one property, `...(a.reason ? {
reason: a.reason } : {})`, its third optional field beside `toOwner` and
`error`.

The new file lives in `core/src/client/` rather than `core/src/shared/`:
`risk-rules.json` scores `core/src/shared/**` T3, which would lift the
whole slice out of T2 for no product reason.

Rebased onto `16d16456` (after `ThinkFlowLab#2410` and `ThinkFlowLab#2421`); `ThinkFlowLab#2410` touches the
same three files, so its head's changes are kept and this slice stacks
on them — the card's head line still carries `ThinkFlowLab#2409`'s age (`Waiting for
approval · 12 min`) and the reason line sits under the origin line.
`core/client.js` and the `0008` patch were rebuilt with `npm --prefix
core run build` + `node scripts/ds-bot-patches.mjs generate`, never
hand-edited.

## Red first

On a clean `16d16456` worktree with only the three test files from this
branch, per file (log `tmp/p538-red-16d16456.log`):

- `core/test/approval-reason.test.js` — 5 tests / 1 pass / **4 fail** /
0 cancelled: the dock card draws no `bt-flybot-approval-reason`
(`expected 1, actual 0`), the 200-code-point bound draws `''`, the
read-only card keeps no reason (`expected 1, actual 0`), and the
held-reply bubble draws no line (`expected 1, actual 0`). The passing
one is the control: a hold with no reason, an empty one and a blank one
draw nothing on the base too.
- `core/test/held-reply-seam.test.js` — 3 tests / 2 pass / **1 fail**:
`expected: 'held by test'`, the view's record carries no `reason`.
- `core/test/overlay-approvals.test.js` — 7 tests / 6 pass / **1 fail**:
the `ThinkFlowLab#260` card, drawn from the dock's own record, carries the reason
only as `"title":"messages to @chief.uia.home's Bots need your
approval"` on the wrapper and no reason node.

With the fix the same three files are 5/5, 3/3 and 7/7 on the same base.

## Ran

```
TZ=UTC ARCH_DOCS_BASE=16d16456 npm run verify
node .github/scripts/changelog.mjs check
node .github/scripts/check-empty-catch.mjs --check
node .github/scripts/check-mojibake.mjs --check
node scripts/ds-bot-patches.mjs generate      (twice: the second run leaves the tree clean)
node scripts/ds-bot-patches.mjs canonical
```

4848 = 2856+574+132+141+411+671+37+26, 0 fail / 0 cancelled; lint 0
errors (core 77 warnings, plugins 36/9/2/16); `arch-docs: OK (20 changed
file(s))`; 10 patches apply cleanly, `core/` equals ds-bot + patches,
and `generate` is a no-op on the second run; `build-client --check` exit
0; `canonical` ok.

## Not run

The smoke test (this touches neither `index.js` nor a host route), CI's
`package` job (pack check, typecheck, actionlint), and CI itself.

Docs: `docs/subsystems/core-client.md` (both cards' bullets),
`docs/subsystems/core-host.md` (`heldReplies` now carries the hold's
`reason`), `docs/subsystems/plugin-runtime.md` (`heldOf()` and both card
shapes point at `reasonLine`).

<!-- flybot:checkpoint -->
{
  "pr": 2424,
  "claim": "Dev-2",
  "issue": 2422,
"summary": "A hold's reason lived only in the dock card's wrapper title
and never reached the held-reply bubble, whose record dropped it in
heldOf(); both cards now draw one bt-flybot-approval-reason line through
client/reason-line.js reasonLine() — 200 code points plus an ellipsis
visible, the whole sentence in the title, nothing for an empty or blank
reason. heldOf() carries the hold's reason so the bubble draws the same
line, the wrapper title is gone, and ThinkFlowLab#2410's age line stays. Bundle and
0008 regenerated.",
  "files": [
    "changelog.d/2422-approval-reason-visible.fixed.md",
    "core/src/client/reason-line.js",
    "core/src/client/flybot-slots.js",
    "core/src/client/held-reply.js",
    "core/src/host/hooks.js",
    "core/test/approval-reason.test.js",
    "core/test/held-reply-seam.test.js",
    "core/test/overlay-approvals.test.js",
    "docs/subsystems/core-client.md",
    "docs/subsystems/core-host.md",
    "docs/subsystems/plugin-runtime.md",
    "core/client.js"
  ],
  "checks": {
    "ran": "TZ=UTC ARCH_DOCS_BASE=16d16456 npm run verify",
    "result": "green",
    "at": "a9cbe93feaaea69266b743770015516a676bc95f"
  },
  "next": "draft on purpose, waiting for Test and the first review"
}
<!-- /flybot:checkpoint -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

social: a read-only window still draws the Sharing card's and the group Members' invite entries

2 participants