Skip to content

Face room invites: previewInvite has three layers and no client, so a join never shows the room first #798

Closed
Birfy/FlyBot
#35

Description

@Birfy

What already exists on main (fd205c40)

Three layers of the invite preview are written and tested, and no fourth layer uses them:

  • plugins/social/src/face-rooms.js:680 — previewInvite(ticket); GET /invites/<ticket>, and at :686-693 it returns {roomId, name, lead, members, maxMembers, expiresAt}.
  • plugins/social/src/social.js:369 — SOCIAL_UI_ENDPOINTS; :376 already lists 'social/face-invite-preview' next to face-invite and face-invite-revoke; :386 registers every name in that array; :3529 binds the handler and gates it on serverInvites(), so an older Bot Social answers {unsupported: true} instead of being called.
  • plugins/social/test/social.test.js covers the api method.

What is missing

The client. plugins/social/client.js calls social/face-join-invite (:1247, in joinLink), social/face-invite and social/face-invite-revoke; it never calls the preview. So today a person who pastes an invite link or scans one joins a room they have not seen: no name, no lead, no member count, no expiry. The three layers above are unreachable from the UI, which is why this issue exists rather than a silent deletion.

The follow-up

In the join dialog, after a link is pasted or a code is scanned and before any Bot joins, show the room: name, lead, members (members/maxMembers when both are numbers), and expiry when the server sends one, with the same two ways out the dialog already has (join, or clear the field).

Two things the wiring must get right

  1. name is the server's raw string. face-rooms.js:688 returns String(body.name) as-is while :689 runs lead through cleanFaceName. A remote room controls that string; rendering it before the same cleaning gives a stranger's text a place in our dialog. Either clean name in previewInvite the way lead is cleaned, or clean at render time — but not "somewhere later".
  2. The preview must not block a join that could work. A 404 or a timeout falls through to the join that works today. {unsupported: true} is the exception: the preview and face-join-invite sit behind the same face_room_invites switch, so on such a server the join fails with the same sentence anyway — show Invites need a newer Bot Social. The 6-digit number still works. and do not call join (owner decision, 2026-10-23).

Out of scope

  • Changing joinInvite, the ticket format, or the scan path.
  • Showing the preview for a 6-digit room number (that is a different route, social/face-join).

Done when

  • Pasting or scanning an invite in the join dialog shows room name, lead and member count before a Bot joins; the name is cleaned as above; a missing/older-server preview is skipped and the existing join path keeps its current sentence.
  • Two tests: a preview fixture whose name contains markup renders cleaned text; a 404/timeout fixture still joins and shows no error, and an unsupported fixture shows the sentence and makes no join call.

Why it is not agent-ready yet

Chief scoped this out of #774 on purpose ("keep it, do not wire it into any screen this version"), and it changes what a person sees at the moment of joining. It becomes ready when the owner asks for the wiring.

Activity

  1. added
    enhancementNew feature or request
    area:socialFriends, invites, grants, Bot Social
    area:uiWeb/desktop UI and copy
    on Oct 23, 2026
  2. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Opened per Chief's decision on #774: keep previewInvite, do not wire it into any screen this version, and record the wiring here so the three finished layers do not sit as unattributed dead code.

    Label: enhancement | area:social | area:ui and no status label on purpose. Nothing is blocked on an answer — the answer is already "not this version". It becomes agent-ready when the owner asks for the wiring; until then it is backlog, and marking it needs-human would put it in the queue of things waiting for a word that has already been given.

    Checked while writing it, on main fd205c40: plugins/social/src/social.js:369 SOCIAL_UI_ENDPOINTS, :376 already lists social/face-invite-preview, :386 registers it, :3529 binds the handler behind serverInvites(); plugins/social/src/face-rooms.js:680 is the api method and :686-693 its answer. plugins/social/client.js never calls it — the only invite route it uses is social/face-join-invite at :1247. Two details worth fixing at wiring time are in the body: name is returned raw at :688 while lead is cleaned at :689, and the preview must never be able to block a join.

  3. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Triage: agent-ready. The three backend layers are on 6f9b60c7 — previewInvite (plugins/social/src/face-rooms.js:680), the endpoint id in SOCIAL_UI_ENDPOINTS (social.js:390) and the handler with its {unsupported: true} fallback for older servers (social.js:3550). The client still has none of it: plugins/social/client.js contains no previewInvite and no face-invite-preview, and the join goes straight from the parsed ticket to the server at :1247.

    The scan path is worse than the paste path and is worth naming: beginScan hands a recognised link to void joinLink(raw) (:1271), so a scan can put a Bot in a room on the same click that decoded the code, with nothing shown in between.

    Acceptance

    After faceInviteTicket(text) succeeds — on the paste path (joinLink, :1232) and on the scan path (:1271) — and before any call to social/face-join-invite, call social/face-invite-preview with the ticket and render a confirmation card. The join happens only on an explicit confirm.

    The card shows, in this order, and only what the server actually returned:

    • name — preview.name is a plain string (face-rooms.js:690). It is rendered as text, never as markup: a room named <b>x</b> must show those characters. An empty string omits the title; it must not print undefined.
    • lead — shown when non-empty, as text.
    • members / maxMembers — the host already normalises these to a number or undefined (:691-692), so the client must show N / M only when both are finite numbers, N alone when only one is, and nothing at all when neither is. undefined, NaN, or a fabricated 0 is a failure of this issue, not a cosmetic bug.
    • expiresAt — when present, shown; when it is in the past, the confirm action is disabled and the card says the link has expired instead of attempting a join.
    • which Bot will join — the Bot already selected in the join dialog; confirm is disabled while none is picked, as today (:1240).

    {unsupported: true} from the preview (an older Bot Social, social.js:3550) shows INVITE_UNSUPPORTED and stops without calling join — the same sentence the join path already produces at :1248, so nothing changes for old servers.

    Cancelling the card leaves no trace: no social/face-join-invite, no social/face-leave, no session opened, no room state.

    Done when the client cases in plugins/social/test/social.test.js (the file that already exercises social/face-join-invite and loads client.js) cover: preview called once and join not called before confirm; join called once after confirm with the same ticket and the picked Bot; a name containing markup rendered literally; members present with maxMembers undefined → one number, no slash, no undefined; both undefined → the line is absent; expired expiresAt → confirm disabled and no join; {unsupported: true} → INVITE_UNSUPPORTED and no join; scan path → the same card, no automatic join.

    Kill conditions

    • A new endpoint, a new field in previewInvite's return shape, or a change to face-rooms.js / social.js beyond what the client needs.
    • name reaching the DOM as markup (innerHTML, dangerouslySetInnerHTML, or an attribute built by string concatenation).
    • Any path where social/face-join-invite fires before the card is confirmed — including the scan path.
    • undefined/NaN reaching the card, or 0 substituted for a count the server did not send.
    • Blocking the join when the preview fails for a reason other than unsupported (a network error must not lock the user out of a room they can still enter).

    Out of scope

    The backend preview, its normalisation, and the endpoint list; the mint/revoke UI; the QR and camera code (startFaceScan); how a room is left after joining; the room card in the session header.

  4. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 23, 2026
  5. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev2

    Claiming this. Branch: feat/798-invite-preview on Birfy/FlyBot, cut from fork main at 6f9b60c7; the pull request will reference this issue as Fixes ThinkFlowLab/FlyBot#798.

    Scope as written: one social/face-invite-preview call inside joinLink and a confirmation card in the join dialog. No new endpoints, no change to the backend response shape, joinInvite/ticket format/scan path untouched.

  6. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    and removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 23, 2026
  7. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 23, 2026
  8. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Acceptance amended after the merge (owner decision, 2026-10-23).

    Original item 2: “The preview must never block the join. A server that answers 404 / {unsupported: true} / a timeout falls through to the join that works today.”

    Now: 404 and timeouts still fall through to today's join. {unsupported: true} does not, because face-invite-preview and face-join-invite are gated on the same face_room_invites switch — the join would fail with the same sentence and only cost a round trip. It shows Invites need a newer Bot Social. The 6-digit number still works. and makes no join call. The Done when line was updated the same way.

    This matches what shipped in #35 (plugin/social/client.js merged at 3d062b9f: previewLink returns on unsupported, and joinTicket keeps its defensive unsupported branch).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:socialFriends, invites, grants, Bot Socialarea:uiWeb/desktop UI and copyenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions