Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d8ebeb7
feat(bestie): pin the saved Bestie conversation
delkc Oct 29, 2026
3340bed
fix(bestie): preserve pinned conversation states
delkc Oct 29, 2026
ad7e43b
fix(bestie): keep a stable search identity
delkc Oct 29, 2026
7bdd456
test(bestie): cover pinned recovery states
delkc Oct 29, 2026
e6891be
fix(bestie): retain DM promotion through inventory failure
delkc Oct 29, 2026
2f877db
fix(bestie): recover account choice reads
delkc Oct 29, 2026
744b808
fix(bestie): support server-rendered selection
delkc Oct 29, 2026
63e1896
fix(bestie): identify pinned search results
delkc Oct 29, 2026
7dca86d
fix(bestie): wait for an account-scoped session
delkc Oct 30, 2026
bf0df38
fix(solo): fence stale Bestie account reads
delkc Oct 30, 2026
83c339f
fix(bestie): route thread navigation through history
delkc Oct 30, 2026
64a999b
fix(bestie): preserve focus across DM retries
delkc Oct 30, 2026
c716a38
fix(solo): settle failed first Bestie saves
delkc Oct 30, 2026
a0f4350
fix(bestie): retire repeated thread visits
delkc Oct 30, 2026
f789630
feat(bestie): choose an existing agent or create one
delkc Oct 30, 2026
ce67347
Revert "feat(bestie): choose an existing agent or create one"
delkc Oct 30, 2026
1ffa2e5
Merge #1935 (clay/bestie-pinned-nav) into the Bestie stack
rileycrane Oct 30, 2026
eb45012
feat(bestie): choose an existing agent or create one
rileycrane Oct 30, 2026
6c2e161
feat(bestie): offer local agents in place, and remember from the sidebar
rileycrane Oct 30, 2026
f5a7c87
feat(bestie): deleting your Bestie saves none to the account
rileycrane Oct 30, 2026
cd653c6
fix(bestie): clear a deleted Bestie once the account is read, and tur…
rileycrane Oct 30, 2026
b024c3c
test(bestie): expect Choose your bestie on the empty Bestie page
rileycrane Oct 30, 2026
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
7 changes: 6 additions & 1 deletion docs/shell-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,12 @@ avatar. Search, sidebar and history controls use unfilled ghost icon buttons wit
Their hover fills remain translucent over the colored backdrop.
Pressed fills are slightly stronger; icons keep full opacity. Plugin launchers
use the shared glass style.
Bestie is available in the Messages sidebar when its plugin is enabled.
Bestie is available in the Messages sidebar when its plugin is enabled. An
account-saved Bestie decorates that row with the agent's identity and promotes
its exact one-to-one DM there. Search Buzz shows the same agent identity with
**Bestie** as supporting text and matches either name. See
[`solo/README.md`](solo/README.md) and the bundled Bestie README for selection,
DM promotion and hosted-setup fallback ownership.
`ProfileButton.tsx` uses the selected community's profile when connected and
local defaults in Personal space. Its anchored account menu groups profile and
status controls above Appearance, Plugins and Settings. The avatar inside the
Expand Down
25 changes: 19 additions & 6 deletions docs/solo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,25 @@ behavior on the label, so a rename touches copy only.
archive read has settled. A chosen agent missing for another reason is stood in
for without replacing the choice. Until an agent is explicitly saved as the
bestie, including for an automatic pick, a stand-in or a failed save, **Make my
bestie** stays offered. Nothing is picked until the account record has been read
and its archive read has settled, so a new chat is never tagged for a stale
pick. A new chat tags the current default once each time it is shown; the tag
isn't saved until the draft is edited, so showing an untouched new chat again
uses the current default. Archive rereads, failed rereads and reconnects keep
the last known archives, so a known deletion is never forgotten and a live
bestie** stays offered. When the Bestie plugin is enabled, the explicit
account-saved choice also owns its pinned sidebar destination; automatic and
stand-in defaults do not. The pinned destination reuses the newest verified
one-to-one DM whose only other member is that exact agent, opening one through
the shared DM service only when none exists. It is an ordinary DM with the same
history, draft, threads and unread state—not a Solo chat or a second message
store. While promoted, that DM is omitted from the ordinary Messages inventory;
replacing or deleting the Bestie, disabling the plugin, or losing the exact
roster match restores the ordinary row. The pinned navigation row adopts the
agent's name and avatar. Global search shows the same identity with **Bestie**
as supporting text and matches either name; the separate agent result remains
a **New chat** action rather than another route to the pinned DM.

Nothing is picked until the account record has been read and its archive read
has settled, so a new chat is never tagged for a stale pick. A new chat tags the
current default once each time it is shown; the tag isn't saved until the draft
is edited, so showing an untouched new chat again uses the current default.
Archive rereads, failed rereads and reconnects keep the last known archives, so
a known deletion is never forgotten and a live
choice never disappears.
Follow-ups keep all exact agents notified by the previous send, just like
Team threads, including the shared Remember mentioned agents preference.
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages.integration.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ test("the app runtime exposes ready bundled pages and removes them on disable",
assert.equal(bestiePage.primary, true);
assert.match(
renderToStaticMarkup(createElement(bestiePage.component)),
/Meet your Bestie/,
/Choose a community to continue with your Bestie\./,
);
await services.plugins.change("disable", "buzz.bestie");
assert.equal(
Expand Down
15 changes: 14 additions & 1 deletion src/app/shell/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
useMemo,
useRef,
useState,
type ComponentProps,
type ReactNode,
type Ref,
} from "react";
Expand All @@ -25,6 +26,7 @@ import type { Appearance } from "../../shared/theme/service";
import { PageSearch, type SearchServices } from "./PageSearch";
import { headerPageSelection, pagePlacement, orderPages } from "./presentation";
import { PageNavigation } from "./PageNavigation";
import { BestieNavigationProvider } from "./BestieNavigation";
import type { NavigationOpenOptions } from "../../features/navigation/controller";
import {
PopoverRoot,
Expand All @@ -38,7 +40,18 @@ import { hasIntegratedWindowControls, WindowControls } from "./WindowControls";
const macDesktop = isTauri() && /Mac/i.test(navigator.platform);
const titleBarDragProps = macDesktop ? macTitleBarDragHandlers : {};

export function AppShell({
export function AppShell(props: ComponentProps<typeof AppShellContent>) {
return (
<BestieNavigationProvider
pages={props.pages}
relay={props.communities.relay}
>
<AppShellContent {...props} />
</BestieNavigationProvider>
);
}

function AppShellContent({
pages,
selected,
navigationAttempt,
Expand Down
7 changes: 7 additions & 0 deletions src/app/shell/BestieNavigation.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.label[data-bestie-unread] {
color: var(--text-navigation-unread);
font-weight: var(--type-weight-semibold);
}
[data-selected] .label[data-bestie-unread] {
color: var(--text-row-selected);
}
Loading
Loading