Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions docs/buzz-help.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,10 @@ that agent in Buzz; they do not notify the external app through this connection.
lets them read; **Private** limits access to people added to the channel.
Changing this choice does not change existing members' roles. Ordinary
participants have no role caption; Owner, Admin, and Viewing only mark
exceptions. Use a person's actions menu to change their role when permitted.
exceptions. Use a member's actions menu to change their role when permitted.
**Agents** includes identities known to be agents, independently of channel
role. **Members** and **Others** do not certify that an identity is human.
Editing a profile cannot hide the relay's recorded agent classification.
- **Message search** treats the last word you type as the start of a word, so
it searches only once that word has at least three characters, even with
`from:` or `in:`. A shorter last word shows how many characters to type, not
Expand Down Expand Up @@ -183,25 +186,25 @@ that agent in Buzz; they do not notify the external app through this connection.
**Public (existing)**. This is not protocol enforcement. View-only visitors
cannot post; existing members keep their roles. Returning to Private warns
about retained viewers unless verified roles show no Guests, including while
roles load or fail to load; remove them in People to end their access. Public
roles load or fail to load; remove them in Members to end their access. Public
(existing) → Private also warns that joined members retain their access.
Public (existing) → View only confirms that existing
members keep their access. Agents the owner adds through
access search or a composer mention receive Bot access so they can reply,
including with a Guest admission default. Conversion to a channel offers
Private, View only and Public, starts from the chat's current access, and saves
the selected policy. Choosing Private during conversion also warns that people
who joined keep their access until removed in People. Opening a private chat to the community warns that its full
who joined keep their access until removed in Members. Opening a private chat to the community warns that its full
history and Canvas, including workspace folders and agent instructions, become
readable. View only retains Guest admission. Copying its link alone does not grant access. Check the intended people
and destination before changing access. If an older saved invitation granted
view-only access, remove that person in People and invite them again for
view-only access, remove that person in Members and invite them again for
participation. For an older unresolved invitation, use **Retry** in Outbox
or add the person again if the receipt is already Seen. After its 15-minute
delivery window, Buzz verifies the original command and current membership
before allowing a fresh participation invitation; failed checks keep the original.
If the original invitation was applied and the person was later removed, Retry
clears the old row without re-adding them. Use **Add** in People to invite them again.
clears the old row without re-adding them. Use **Add** to invite them again.
- **Open links in your browser**: on desktop, ⌘-click (Ctrl-click on Windows
and Linux) an HTTP(S) link in a message to open it in your default browser
instead of a side panel. You can also right-click the link and choose
Expand Down
11 changes: 11 additions & 0 deletions docs/channels.md
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,17 @@ another non-owner member:
| Bot | Admin / Guest, after explicit confirmation; Participating is already selected | Yes |
| Owner, self, unknown or inconsistent | No | No |

The selected relay-signed roster may carry two-field `agent` tags for active
members with a recorded community agent owner. These are positive identity evidence,
not ownership attribution or permission grants. Members combines them with locally
known agents and profile hints. Channel roles alone do not establish agent identity.
Absent evidence uses neutral **Members**/**Others** groups rather than asserting
human identity. Supporting relays refresh existing rosters when ownership is first
recorded. Older or failed-to-refresh snapshots lack traits until republished;
Refresh reads the latest published roster. Trusted
classification changes move affected keys between retained groups; optional name
and profile-hint enrichment preserves the current large-roster page and focus.

Agent identity is independent of channel role. Explicitly promoting a Bot to Admin grants authority to that agent’s own public key and preserves its verified agent identity; the human owner’s roles do not confer authority.

The menu offers **Viewing only** through a deliberate **Set to viewing only**
Expand Down
14 changes: 7 additions & 7 deletions docs/solo/sharing.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ owner's selectable agents are offered too and join as Bot, even with a Guest
admission default, without starting a run. Composer mentions use the same grant.
Where the viewer cannot add agents, a note says to mention an agent in the chat
instead.
Current access uses the channel groups (**People**, **Agents**) with Owner and
Current access uses the channel groups (**Members**, **Agents**) with Owner and
Admin tags, and each row has the channel members' actions menu, so the chat owner
can remove someone. The verified roster shows loading or failure, never treating a
failed read as an empty audience.

Invitations use the same groups and controls as channels: **Your agents** first,
then **People**, **Other agents** and **Your other agents**, omitting empty groups.
then **Others**, **Other agents** and **Your other agents**, omitting empty groups.
Solo still excludes agents the viewer cannot add directly. Each group uses shared
name, relationship and usage ranking, begins with four rows, and expands with its
own **Show more**. Only the viewer's client agents are suggested without typing,
Expand Down Expand Up @@ -76,14 +76,14 @@ relay's 15-minute intake window. After expiry, Retry fences local delivery and
checks the original event ID with a fresh strong read. Verified absence can
resolve it. If the command is recorded, a fresh signed roster reconciles its
result. If that person has since been removed, Outbox **Retry** durably clears
the old row without re-inviting them. An explicit **Add** from People still
the old row without re-inviting them. An explicit **Add** still
re-invites them in one action, including when the old receipt remains. Add finishes its fresh
membership check before atomically retiring the original and saving a new
explicit Member invitation. Closing or restarting keeps either original recovery
or the replacement receipt. Failed reads or storage, active delivery, and an echo
contradicting negative readback keep recovery. This uses the existing Outbox/member/share actions, with no
background retry or new control. If signed roles confirm that the earlier invite
admitted a Guest, remove that person in People and invite them again. Recovery
admitted a Guest, remove that person in Members and invite them again. Recovery
never silently promotes an existing Guest. A definitively failed invitation can
also be replaced after explicitly dismissing it in Outbox.

Expand All @@ -100,7 +100,7 @@ explains that people already in the chat keep their access. Moving from View onl
to Private warns that joined viewers remain members unless a verified role
snapshot establishes that no Guests exist; loading or failed verification keeps
the warning. Public (existing) → Private also warns that joined members retain
their access until removed in People. One signed kind-9002 edit sets
their access until removed in Members. One signed kind-9002 edit sets
`visibility=open` and `join_role=guest` together, through the existing
`channelDetails` save, conflict checks and exact readback.

Expand All @@ -121,7 +121,7 @@ Solo label, conversation ID and private workspace filing.

Changing back to Private stops nonmembers from reading; it does not remove
members or change their roles. Guests who joined through the CLI, MCP or another
client while the chat was View only keep read access until removed in People.
client while the chat was View only keep read access until removed in Members.
Reading without joining creates no membership to retain. Neither transition
changes invitations, creates a share message or adds notifications. Unconfirmed
publication uses **Check save status**, without automatically resending.
Expand Down Expand Up @@ -171,7 +171,7 @@ reading of its full history and Canvas, including workspace folders and agent
instructions. View only saves public visibility with Guest admission; Public saves
Member admission; Private keeps reading restricted to members and retains the
current admission default. Choosing Private for an open chat warns that joined
people retain access until removed in People, using the same warning as General
people retain access until removed in Members, using the same warning as General
access. A selected share channel is never granted by conversion.
Cancel writes nothing and returns focus to the retained initiating control.

Expand Down
6 changes: 3 additions & 3 deletions src/bundled/channels/ChannelGeneralAccess.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ it.each(["private", "view-only", "legacy-public"])(
expect(h.save).not.toHaveBeenCalled();
expect(screen.getByRole("alertdialog")).toHaveAccessibleDescription(
policy === "view-only"
? "Only chat members will have access. People who joined while it was View only are members and keep reading until you remove them in People."
? "Only chat members will have access. People who joined while it was View only are members and keep reading until you remove them in Members."
: `Everyone in this community will be able to read this chat’s full history and Canvas, including its workspace folders and agent instructions.${policy === "legacy-public" ? " People already in the chat keep their access." : ""}`,
);
h.load.mockResolvedValue(next);
Expand Down Expand Up @@ -289,7 +289,7 @@ it.each([
joinRole: "member",
option: "Private",
expected:
"Only chat members will have access. People who already joined remain members and keep their access until you remove them in People.",
"Only chat members will have access. People who already joined remain members and keep their access until you remove them in Members.",
},
] as const)(
"explains $visibility/$joinRole → $option with existing members",
Expand Down Expand Up @@ -510,7 +510,7 @@ it.each([true, false])(
const user = userEvent.setup();
await select(user, "General access", "Private");
expect(screen.getByRole("alertdialog")).toHaveAccessibleDescription(
"Only chat members will have access. People who already joined remain members and keep their access until you remove them in People.",
"Only chat members will have access. People who already joined remain members and keep their access until you remove them in Members.",
);
await user.click(screen.getByRole("button", { name: "Cancel" }));
expect(h.save).not.toHaveBeenCalled();
Expand Down
20 changes: 10 additions & 10 deletions src/bundled/channels/ChannelMembersDialog.solo.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ it.each(["guest", "member"] as const)(
await user.click(audience);
await user.click(await screen.findByRole("option", { name: "Private" }));
expect(screen.getByRole("alertdialog")).toHaveAccessibleDescription(
`Only chat members will have access.${role === "guest" ? " People who joined while it was View only are members and keep reading until you remove them in People." : ""}`,
`Only chat members will have access.${role === "guest" ? " People who joined while it was View only are members and keep reading until you remove them in Members." : ""}`,
);
expect(t.published).toEqual([]);
await user.click(screen.getByRole("button", { name: "Change access" }));
Expand Down Expand Up @@ -622,7 +622,7 @@ it.each(["guest", "member"] as const)(
await user.click(audience);
await user.click(await screen.findByRole("option", { name: "Private" }));
expect(screen.getByRole("alertdialog")).toHaveAccessibleDescription(
"Only chat members will have access. People who joined while it was View only are members and keep reading until you remove them in People.",
"Only chat members will have access. People who joined while it was View only are members and keep reading until you remove them in Members.",
);
await act(async () => {
reject(new Error("Roles unavailable"));
Expand All @@ -632,7 +632,7 @@ it.each(["guest", "member"] as const)(
"error",
);
expect(screen.getByRole("alertdialog")).toHaveAccessibleDescription(
"Only chat members will have access. People who joined while it was View only are members and keep reading until you remove them in People.",
"Only chat members will have access. People who joined while it was View only are members and keep reading until you remove them in Members.",
);
await user.keyboard("{Escape}");
expect(t.published).toEqual([]);
Expand Down Expand Up @@ -686,7 +686,7 @@ it("adds a person at once under the chat's share rule, by Add or Enter", async (
expect(await within(dialog).findByText("Aria is in the chat.")).toBeVisible();
await user.clear(search(dialog));
expect(
await within(dialog).findByRole("region", { name: "People" }),
await within(dialog).findByRole("region", { name: "Members" }),
).toHaveTextContent("Aria");
await user.type(search(dialog), "Dev");
await user.click(
Expand Down Expand Up @@ -853,7 +853,7 @@ it("shows loading and failed current access with refresh, never an empty roster"
expect(refresh).not.toHaveAttribute("aria-disabled", "true"),
);
await user.click(refresh);
await within(access).findByRole("region", { name: "People" });
await within(access).findByRole("region", { name: "Members" });
// General access has its own failed metadata read and explicit recovery.
const recovery = within(access).getByRole("group", {
name: "General access recovery",
Expand Down Expand Up @@ -908,7 +908,7 @@ it("groups agents and people by role while only verified additional humans mark
const agents = await within(dialog).findByRole("region", { name: "Agents" });
await within(agents).findByText("Helper Bot");
expect(within(agents).getByRole("heading")).toHaveTextContent("Agents · 1");
const people = within(dialog).getByRole("region", { name: "People" });
const people = within(dialog).getByRole("region", { name: "Members" });
expect(people).toHaveTextContent("(you)");
expect(people).not.toHaveTextContent("Helper Bot");

Expand All @@ -919,7 +919,7 @@ it("groups agents and people by role while only verified additional humans mark
await waitFor(() =>
expect(
within(
within(dialog).getByRole("region", { name: "People" }),
within(dialog).getByRole("region", { name: "Members" }),
).getAllByRole("listitem"),
).toHaveLength(2),
);
Expand All @@ -934,7 +934,7 @@ it("groups agents and people by role while only verified additional humans mark
await user.click(screen.getByRole("button", { name: "Access" }));
dialog = screen.getByRole("dialog", { name: "Share access" });
expect(
await within(dialog).findByRole("region", { name: "People" }),
await within(dialog).findByRole("region", { name: "Members" }),
).toHaveTextContent("Guest Human");
expect(t.publish).not.toHaveBeenCalled();

Expand Down Expand Up @@ -982,7 +982,7 @@ it("offers matching people and channels as labelled results without public keys"
await user.click(screen.getByRole("button", { name: "Access" }));
const dialog = screen.getByRole("dialog", { name: "Share access" });
expect(
await within(dialog).findByRole("region", { name: "People" }),
await within(dialog).findByRole("region", { name: "Members" }),
).toHaveTextContent("Aria");
await user.type(search(dialog), "Planning");
const people = await within(dialog).findByRole("region", {
Expand Down Expand Up @@ -1059,7 +1059,7 @@ it("lets the chat owner remove someone with access from the row menu", async ()
t.mount();
await user.click(screen.getByRole("button", { name: "Access" }));
const dialog = screen.getByRole("dialog", { name: "Share access" });
const people = await within(dialog).findByRole("region", { name: "People" });
const people = await within(dialog).findByRole("region", { name: "Members" });
await user.click(
await within(people).findByRole("button", { name: "Actions for Aria" }),
);
Expand Down
Loading