Skip to content
Merged
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
4 changes: 3 additions & 1 deletion docs/buzz-help.md
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,9 @@ and are asked to sign in again in Buzz.
starred channel in Starred until you unstar it.
- **Plugin channel rows**: a plugin can open a channel from its Solo row and keep
that row selected. The sidebar then keeps its scope, scroll position, and open
sections. Opening the channel from Team follows the usual Team navigation.
sections. When a plugin is explicitly disabled, its existing chats appear as
ordinary Solo chats; enabling it again returns them to the plugin's rows when
available. Opening the channel from Team follows the usual Team navigation.
- **Side panels stay with their conversation**: a tab or panel you open beside a
conversation, such as a thread, **Canvas**, a huddle, or Todos, stays open for
that conversation when you go to another conversation or page and come back,
Expand Down
24 changes: 15 additions & 9 deletions docs/solo-regions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ implementation. The public types are exported from `@buzz/author`. The service i
created with the relay in `src/app/services.ts`; plugins register sources through
their tracked Cordis context.

The host derives `buzz.region:<pluginId>:<sourceId>` from the caller. Labels own
placement, even when a plugin is off, loading, unavailable, or failed. The host
uses one projection for the live list, recovery list, inventory exclusion, header
chip, and personal filing guards. Unknown channel types do not imply Solo.
Existing personal workspace assignments remain stored while they are dormant.
Label edits preserve region labels. There is no public remove operation.
The host derives `buzz.region:<pluginId>:<sourceId>` from the caller. A ready
catalog that explicitly disables a plugin shows its eligible labeled chats in
the ordinary Solo inventory while retaining their labels and filing guards.
Their region associations stay dormant and are reclaimed if the plugin is
enabled again. A missing source alone does not imply that a plugin is disabled:
loading, unavailable, failed, not-installed, and unknown states remain in the
recovery placement. The host uses one projection for region presentation,
inventory exclusion, and personal filing guards. Unknown channel types do not
imply Solo. Existing personal workspace assignments remain stored while those
assignments are dormant. Label edits preserve region labels. There is no public
remove operation.

Each bind has a fresh incarnation. Reconnect and Retry abort the previous bind;
old handles, menus, state writes, and reveal subscribers cannot act in the new
Expand All @@ -23,9 +28,10 @@ host draws no heading, rows, header actions, or "In" chip for it, and it neither
names the Solo tab nor composes its workspaces. The region stays in the shared
projection, so its labeled chats stay out of the Solo list, its filing guard and
workspace still apply, and its drafts still start; the chats remain reachable by
links, notifications, and search, and the source presents them in its own UI. Any
other state (off, loading, unavailable, failed) shows the recovery region as
usual, so the chats never vanish when the source cannot present them.
links, notifications, and search, and the source presents them in its own UI. If the
plugin is explicitly disabled, its chats appear in ordinary Solo instead. Other
non-ready states (off, loading, unavailable, failed) show the recovery region,
so the chats remain reachable when the source cannot present them.

The source owns its content, presentation, and disclosure controls. It receives
the stable `ui.Conversation` component. Conversation renders only live channels
Expand Down
4 changes: 2 additions & 2 deletions src/bundled/channels/SoloChatMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function SoloChatMenuItems({
Chat settings
</MenuItem>
{active &&
!regions.excluded.has(channel.id) &&
!regions.managed.has(channel.id) &&
onMove &&
onNewWorkspace && (
<MenuSubmenu>
Expand Down Expand Up @@ -159,7 +159,7 @@ export function SoloChatMenuItems({
)}
<ChannelCopyLinkMenuItem channelId={channel.id} />
{active &&
!regions.excluded.has(channel.id) &&
!regions.managed.has(channel.id) &&
onConvert &&
canConvertToChannel(session, channel) && (
<MenuItem onClick={onConvert}>
Expand Down
6 changes: 4 additions & 2 deletions src/bundled/channels/SoloHeading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ export function SoloToolbar({
() => chatMenus?.snapshot(),
);
const regions = useSoloNavigation(session);
const region = regions.regions.find((region) =>
region.channels.some((item) => item.id === channel.id),
const region = regions.regions.find(
(region) =>
region.reason !== "disabled" &&
region.channels.some((item) => item.id === channel.id),
);
const actionsTrigger = useRef<HTMLButtonElement>(null);
// Each dialog returns focus to the control that opened it.
Expand Down
16 changes: 13 additions & 3 deletions src/bundled/channels/SoloWorkspaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export function SoloWorkspaces({
navigator: navigationController,
scope,
chats,
dormantAssignments,
renderChat,
renderChatMenu,
onNew,
Expand All @@ -105,6 +106,8 @@ export function SoloWorkspaces({
navigator?: Navigation | undefined;
scope: string;
chats: readonly ChatListItem[];
/** Existing assignments stay stored but do not place source-managed chats. */
dormantAssignments?: ReadonlySet<string> | undefined;
sidebarRows?: readonly RegisteredSidebarRows[] | undefined;
renderChatMenu?: ((channelId: string) => ReactNode) | undefined;
/** A stable function lets a chat's row skip renders when nothing it shows changed. */
Expand Down Expand Up @@ -240,6 +243,8 @@ export function SoloWorkspaces({
}>();
const groups = snapshot.data?.sections ?? [];
const assigned = snapshot.data?.assignments ?? {};
const assignmentFor = (item: ChatListItem) =>
dormantAssignments?.has(item.id) ? undefined : assigned[item.id];
const ids = new Set(groups.map((group) => group.id));
const draftProject = draft
? ids.has(draft.projectId ?? "")
Expand All @@ -258,12 +263,12 @@ export function SoloWorkspaces({
const projects = [
...groups.map((group) => ({
...group,
rows: chats.filter((item) => assigned[item.id] === group.id),
rows: chats.filter((item) => assignmentFor(item) === group.id),
})),
{
id: "",
name: "Chats",
rows: chats.filter((item) => !ids.has(assigned[item.id] ?? "")),
rows: chats.filter((item) => !ids.has(assignmentFor(item) ?? "")),
},
];
// A collapse request takes effect after the editor closes. Keep its draft and
Expand All @@ -272,7 +277,12 @@ export function SoloWorkspaces({
project.rows.some((item) => item.id === renaming),
)?.id;
const move = (id: string, projectId?: string) => {
if (prepareSoloMove(session, id)) return;
const refusal = prepareSoloMove(session, id);
if (refusal) {
setProjectError(refusal);
return;
}
setProjectError("");
const saving = preferences.assign(id, projectId);
toggle(projectId ?? "", true);
hold([id]);
Expand Down
6 changes: 3 additions & 3 deletions src/bundled/claude-code/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ it("keeps the copy in use when a new release fails its test", async () => {

it("says why a new release quit during its test", async () => {
const { spawn, request, state } = releases("2.1.300", {
current: "2.1.200",
current: "2.1.280",
quits: {
version: "2.1.300",
stderr: "Starting Claude\nError: bypassPermissions is disabled\n",
Expand All @@ -719,8 +719,8 @@ it("says why a new release quit during its test", async () => {
expect(state.used).toEqual([]);
expect(setup.snapshot()).toMatchObject({
notice:
"Claude 2.1.300 failed its test: Error: bypassPermissions is disabled. Buzz keeps using 2.1.200.",
status: { state: "ready", version: "2.1.200" },
"Claude 2.1.300 failed its test: Error: bypassPermissions is disabled. Buzz keeps using 2.1.280.",
status: { state: "ready", version: "2.1.280" },
});
});

Expand Down
3 changes: 2 additions & 1 deletion src/features/channel-navigation/SoloInventory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export function SoloInventory({
JSON.stringify(conversations.map((item) => item.id)),
);
const composing = !!regions.regions.find(
(region) => region.source && !region.hidden,
(region) => region.source && !region.hidden && region.reason !== "disabled",
)?.source?.composeWorkspaces;
const empty =
list.status === "ready" &&
Expand Down Expand Up @@ -347,6 +347,7 @@ export function SoloInventory({
)}
<SoloWorkspaces
sidebarRows={rowEntries}
dormantAssignments={regions.managed}
compose={compose}
renderChatMenu={renderChatMenu}
navigator={navigator}
Expand Down
Loading
Loading