Skip to content

Face-room invite preview renders the room lead as "object Object" #846

Closed

Description

@Birfy

What is wrong

previewInvite treats Bot Social's lead field as a string, but the server sends an object. The invite preview card therefore reads Lead: object Object where it should name the person.

  • plugins/social/src/face-rooms.js:686-693 — previewInvite(ticket) returns lead: body?.lead ? cleanFaceName(body.lead) : ''.
  • plugins/social/src/face-rooms.js:62 — cleanFaceName starts with String(value ?? ''), so an object becomes [object Object] and the bracket strip at :65 turns it into object Object.
  • Bot Social GET /v1/face-rooms/invites/{ticket} answers lead as { handle, display_name, owner_name }.

Raw server answer, measured on social.botawaken.com on main cac79e10 (unchanged in previewInvite on that tip):

{
  "room_id": "frm_01M5M3D2Q58JHTB9WHBBA2BYMN",
  "name": null,
  "status": "open",
  "lead": { "handle": "@chief.selftesta2.home", "display_name": "Chief", "owner_name": "SelfTest A2" },
  "member_count": 2,
  "max_members": 8,
  "expires_at": "2026-10-23T10:14:14.970Z"
}

What social/face-invite-preview gives the client for the same ticket:

{"roomId":"frm_01M5M3D2Q58JHTB9WHBBA2BYMN","name":"","lead":"object Object","members":2,"maxMembers":8,"expiresAt":"2026-10-23T10:14:14.970Z"}

Reproduce (real UI, two isolated homes)

  1. Home A: sidebar + → Face-to-face room, type a 6-digit number nobody uses → Start a room → Enter chat.
  2. Open the room's details panel → Invite by link → Create link; the card shows https://…/r/<ticket>.
  3. Home B: sidebar + → Face-to-face room, paste that link into the invite field, press Join with link.
  4. The preview card appears before joining and reads:
A room with no name
Lead: object Object
1 / 8 in the room
9:53 left to join

Expected (the label rule the rest of the plugin already uses, faceMemberLabel / faceOwnerName in face-rooms.js:236-264):

Lead: Chief (SelfTest A2)

Actual: Lead: object Object.

Scope

  • plugins/social/src/face-rooms.js:689 — read the object (display_name + owner_name, i.e. faceMemberLabel(body.lead)) instead of String()ing it. A server that sends a string (older build, or a test fixture) should keep working.
  • plugins/social/test/social.test.js — the #798 preview test passes a string lead, which is why this never failed. A test with the server's real object shape would catch it.
  • docs/subsystems/social.md:111 already promises the card draws "the name, the lead, …", so no doc change is needed once the code matches it.

Related, non-blocking

The same card reads A room with no name because Bot Social sends name: null for a room that was never given a name (the room's title — Face-to-face · SelfTest A2, SelfTest B2 · 988036 — is generated by FlyBot, not stored on the server). The person still cannot tell which room the link opens; naming it from the lead/members would be an enhancement, not a bug.

Evidence

  • Screenshot of the card: reports/shots/g-b-dialog.png in the self-test workspace (the DOM text above was read from that page).
  • Full E2E round: 19/19 checks passed (open room → mint link → preview → join the same faceRoomId → message both ways), so this is the only defect this round.

Activity

  1. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    area:socialFriends, invites, grants, Bot Social
    area:uiWeb/desktop UI and copy
    bugSomething isn't working
    on Oct 23, 2026
  2. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Triage

    Verified on c0c866da (read the code, not only the report):

    • plugins/social/src/face-rooms.js:689 — lead: body?.lead ? cleanFaceName(body.lead) : ''. Bot Social answers lead as { handle, display_name, owner_name }, so an object goes into a function that starts with String(value ?? '') at :62; the bracket strip at :64 then turns [object Object] into object Object.
    • The client does not rescue it: plugins/social/client.js:922 runs cleanRoomName(seen.lead), which strips brackets but keeps the words, so the card still reads Lead: object Object.
    • Not a regression from #839 / #822: :689 is byte-identical on 2c86927c (before that merge), and the String() coercion at :62 is older still. What changed is only the visible text ([object Object] → object Object). This is the older defect, now easier to read.

    Duplicate check: searched object Object, invite preview lead and open face-rooms issues — no duplicate. #164 / #165 were the same class of bug (an object labelled as Object) on a different surface and are closed; #773 / #798 built this preview card, which is why it only has a string-shaped fixture.

    Expected behaviour

    For the ticket in the report, social/face-invite-preview must answer lead: "Chief (SelfTest A2)" — exactly what faceMemberLabel (:258-263) already produces for { handle: '@chief.selftesta2.home', display_name: 'Chief', owner_name: 'SelfTest A2' }. The card then reads Lead: Chief (SelfTest A2).

    Scope

    • plugins/social/src/face-rooms.js:689 only. Branch on the shape: a string keeps cleanFaceName(value) (older servers and the existing fixture), an object uses faceMemberLabel(value). faceMemberLabel already covers the missing-field cases: no display_name → the handle's name; no owner_name → the name alone; an auto owner like FlyBot 682b8a43 → the username.
    • plugins/social/test/face-rooms.test.js — the #773 preview test at :589 sends lead: 'Coder · Bob' and asserts it comes back unchanged; that case must stay green. Add the server's real object shape beside it.
    • Nothing else: docs/subsystems/social.md:111 already promises the card names the lead, so the document is right and the code is wrong. The usual changelog fragment is enough.

    Acceptance

    1. previewInvite with lead: { handle, display_name: 'Chief', owner_name: 'SelfTest A2' } returns 'Chief (SelfTest A2)'; with owner_name missing it returns just the name; with display_name missing it returns the handle-derived name.
    2. The existing string lead: 'Coder · Bob' still comes back as 'Coder · Bob'.
    3. Negative control: putting cleanFaceName(body.lead) back in place of the branch turns the new object case red and leaves the string case green — otherwise the new test is not holding the fix.
    4. A real two-home round shows Lead: Chief (SelfTest A2) on the preview card before joining, following the same four steps the report gives.

    Out of scope, kept as reported

    name: null → A room with no name is a separate enhancement: the room title is generated locally and never stored server-side. The issue body already marks it non-blocking, and it must not be folded into this fix.

    Labels: bug, area:social, area:ui, agent-ready — the visible surface is the social panel's invite card.

  3. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Claiming this one for Dev3.

    • Branch: fix/846-invite-preview-lead-object, cut from main c0c866da.
    • Fix: previewInvite reads lead by its shape — a member object ({ handle, display_name, owner_name }) goes through the same label rule the member list already uses (faceMemberLabel), a string keeps the old cleanFaceName path, and an object that names nobody shows no lead line instead of object Object.
    • Test: a preview fixture with the server's real object shape, asserting Lead: Chief (SelfTest A2) and that object Object never reaches the card.
    • The name: null half stays out of this PR, as scoped in the issue.
  4. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 23, 2026
  5. self-assigned this
    on Oct 23, 2026
  6. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Investigation (not a Verdict): is the client's invite-preview card a second entry for the same lead object?

    Chain, read on 7936fdf7: plugins/social/client.js:1382 (previewLink) → host bridge plugins/social/src/social.js:3550 (social/face-invite-preview) → plugins/social/src/face-rooms.js:689 (lead: body?.lead ? cleanFaceName(body.lead) : '') → plugins/social/client.js:919-930 (faceInvitePreview → cleanRoomName(seen.lead)) → plugins/social/client.js:1502 (Lead: ${preview.lead}).

    Findings

    1. The client cleaner is object-blind, exactly like the server one: cleanRoomName (client.js:901) does String(value) first, so an object becomes [object Object], then its own [[\]"<>@]strip leavesobject Object. Rendered with the real client test harness (dialogWithState, bridge mocked) an object lead prints Lead: object Object`.
    2. It is not independently reachable: the only producer for that card is previewInvite, which already stringifies the object before the client sees it. So there is no second user-visible entry and no separate issue — the visible object Object starts at face-rooms.js:689.
    3. It is a second, latent barrier though: if only the server line is fixed, cleanRoomName stays object-blind. Optional hardening, one typeof guard, and the client harness can already send an object, so it can be red-first.
    4. The client's second pass does not mangle the fixed string: with lead: 'Chief (SelfTest A2)' the card renders Lead: Chief (SelfTest A2) unchanged (parentheses survive cleanRoomName).
    5. Same class on the adjacent field: face-rooms.js:688 is name: body?.name ? String(body.name) : ''; an object name prints object Object as the card title. Out of scope here (the name: null half is tracked separately) — noted, not fixed.

    Other lead surfaces are already object-safe (no object Object there): member rows (viewFaceMember → cleaned label), the joined-room lead (viewFaceLead → {handle}, rendered from seats), and the lead-change notice (platformLeadText → faceMemberLabel, covered by test/face-rooms.test.js:530 Room lead: Coder (Bob)).

    Shape check for the fix: faceMemberLabel({ handle: '@chief.selftest.home', display_name: 'Chief', owner_name: 'SelfTest A2' }) → Chief (SelfTest A2), which is the already-established format. faceMemberLabel('Coder · Bob') → Bot, so the fix must branch on typeof body.lead === 'object'; replacing the string path with faceMemberLabel unconditionally breaks the #773 string case (test/face-rooms.test.js:589).

    Repro used: extracted 7936fdf7 tree, plugins/social, node --import ./test/resolve.mjs --test test/probe846.test.js (throwaway probe on the copy, nothing pushed).

  7. removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:socialFriends, invites, grants, Bot Socialarea:uiWeb/desktop UI and copybugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions