Skip to content

Commit bc0dcb3

Browse files
committed
Fix profile sidebar review feedback
1 parent f2db5bb commit bc0dcb3

9 files changed

Lines changed: 84 additions & 31 deletions

File tree

desktop/src/app/routes/channels.$channelId.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import * as React from "react";
22
import { createFileRoute } from "@tanstack/react-router";
33

4+
import {
5+
parseProfilePanelView,
6+
type ProfilePanelView,
7+
} from "@/features/profile/ui/UserProfilePanelUtils";
48
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
59

610
type ChannelRouteSearch = {
711
agentSession?: string;
812
messageId?: string;
913
profile?: string;
10-
profileView?: "memories" | "channels";
14+
profileView?: ProfilePanelView;
1115
thread?: string;
1216
threadRootId?: string;
1317
};
@@ -16,8 +20,8 @@ function nonEmptyString(value: unknown): string | undefined {
1620
return typeof value === "string" && value.length > 0 ? value : undefined;
1721
}
1822

19-
function profileViewValue(value: unknown): "memories" | "channels" | undefined {
20-
return value === "memories" || value === "channels" ? value : undefined;
23+
function profileViewValue(value: unknown): ProfilePanelView | undefined {
24+
return parseProfilePanelView(value) ?? undefined;
2125
}
2226

2327
function validateChannelSearch(

desktop/src/app/routes/pulse.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import * as React from "react";
22
import { createFileRoute } from "@tanstack/react-router";
33

4+
import {
5+
parseProfilePanelView,
6+
type ProfilePanelView,
7+
} from "@/features/profile/ui/UserProfilePanelUtils";
48
import { usePreviewFeatureWarning } from "@/shared/features";
59
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
610

@@ -11,7 +15,7 @@ const PulseScreen = React.lazy(async () => {
1115

1216
type PulseRouteSearch = {
1317
profile?: string;
14-
profileView?: "memories" | "channels";
18+
profileView?: ProfilePanelView;
1519
};
1620

1721
function validatePulseSearch(
@@ -22,10 +26,7 @@ function validatePulseSearch(
2226
typeof search.profile === "string" && search.profile.length > 0
2327
? search.profile
2428
: undefined,
25-
profileView:
26-
search.profileView === "memories" || search.profileView === "channels"
27-
? search.profileView
28-
: undefined,
29+
profileView: parseProfilePanelView(search.profileView) ?? undefined,
2930
};
3031
}
3132

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import assert from "node:assert/strict";
2+
import { beforeEach, describe, it } from "node:test";
3+
4+
import {
5+
isKnownAgentPubkey,
6+
registerKnownAgentPubkeys,
7+
resetAgentObserverStore,
8+
unregisterKnownAgentPubkeys,
9+
} from "./observerRelayStore.ts";
10+
11+
const AGENT_A =
12+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
13+
const AGENT_B =
14+
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
15+
const AGENT_C =
16+
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
17+
18+
describe("observerRelayStore known agent registrations", () => {
19+
beforeEach(() => {
20+
resetAgentObserverStore();
21+
});
22+
23+
it("unions known agents from multiple bridge registrations", () => {
24+
const agentsPage = Symbol("agents-page");
25+
const profilePanel = Symbol("profile-panel");
26+
27+
registerKnownAgentPubkeys(agentsPage, [AGENT_A, AGENT_B]);
28+
registerKnownAgentPubkeys(profilePanel, [AGENT_C]);
29+
30+
assert.equal(isKnownAgentPubkey(AGENT_A), true);
31+
assert.equal(isKnownAgentPubkey(AGENT_B), true);
32+
assert.equal(isKnownAgentPubkey(AGENT_C), true);
33+
34+
registerKnownAgentPubkeys(profilePanel, []);
35+
36+
assert.equal(isKnownAgentPubkey(AGENT_A), true);
37+
assert.equal(isKnownAgentPubkey(AGENT_B), true);
38+
assert.equal(isKnownAgentPubkey(AGENT_C), false);
39+
40+
unregisterKnownAgentPubkeys(agentsPage);
41+
42+
assert.equal(isKnownAgentPubkey(AGENT_A), false);
43+
assert.equal(isKnownAgentPubkey(AGENT_B), false);
44+
});
45+
});

desktop/src/features/agents/observerRelayStore.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const snapshotByAgent = new Map<string, ObserverSnapshot>();
4747
// We key each subscriber's contribution in `knownAgentsBySubscription` and
4848
// recompute the union, so co-mounted callers no longer clobber each other.
4949
const knownAgentPubkeys = new Set<string>();
50-
const knownAgentsBySubscription = new Map<string, Set<string>>();
50+
const knownAgentsBySubscription = new Map<string | symbol, Set<string>>();
5151

5252
function recomputeKnownAgentPubkeys() {
5353
knownAgentPubkeys.clear();
@@ -58,8 +58,8 @@ function recomputeKnownAgentPubkeys() {
5858
}
5959
}
6060

61-
function registerKnownAgents(
62-
subscriptionId: string,
61+
export function registerKnownAgentPubkeys(
62+
subscriptionId: string | symbol,
6363
pubkeys: readonly string[],
6464
) {
6565
knownAgentsBySubscription.set(
@@ -69,12 +69,16 @@ function registerKnownAgents(
6969
recomputeKnownAgentPubkeys();
7070
}
7171

72-
function unregisterKnownAgents(subscriptionId: string) {
72+
export function unregisterKnownAgentPubkeys(subscriptionId: string | symbol) {
7373
if (knownAgentsBySubscription.delete(subscriptionId)) {
7474
recomputeKnownAgentPubkeys();
7575
}
7676
}
7777

78+
export function isKnownAgentPubkey(pubkey: string) {
79+
return knownAgentPubkeys.has(normalizePubkey(pubkey));
80+
}
81+
7882
let connectionState: ConnectionState = "idle";
7983
let errorMessage: string | null = null;
8084
let unsubscribeRelay: (() => Promise<void>) | null = null;
@@ -176,7 +180,7 @@ async function handleRelayObserverEvent(
176180

177181
// Verify agent is known/trusted before decrypting.
178182
// Silently drop events from agents we are not managing.
179-
if (!knownAgentPubkeys.has(normalizePubkey(agentPubkey))) {
183+
if (!isKnownAgentPubkey(agentPubkey)) {
180184
return;
181185
}
182186

@@ -326,9 +330,9 @@ export function useManagedAgentObserverBridge(
326330
// own agent list. The store recomputes the union across all subscribers, so
327331
// a co-mounted caller no longer wipes out this caller's agents.
328332
React.useEffect(() => {
329-
registerKnownAgents(subscriptionId, agentPubkeys);
333+
registerKnownAgentPubkeys(subscriptionId, agentPubkeys);
330334
return () => {
331-
unregisterKnownAgents(subscriptionId);
335+
unregisterKnownAgentPubkeys(subscriptionId);
332336
};
333337
}, [subscriptionId, agentPubkeys]);
334338

desktop/src/features/channels/ui/useChannelPanelHistoryState.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import * as React from "react";
22

3-
import type { ProfilePanelView } from "@/features/profile/ui/UserProfilePanel";
3+
import {
4+
profilePanelViewFromSearch,
5+
type ProfilePanelView,
6+
} from "@/features/profile/ui/UserProfilePanelUtils";
47
import {
58
type HistorySearchSetterOptions,
69
useHistorySearchState,
@@ -36,10 +39,6 @@ const CHANNEL_SEARCH_KEYS = [
3639

3740
const CHANNEL_MANAGEMENT_OPEN_VALUE = "1";
3841

39-
function asProfilePanelView(value: string | null): ProfilePanelView {
40-
return value === "memories" || value === "channels" ? value : "summary";
41-
}
42-
4342
export function useChannelPanelHistoryState() {
4443
const { applyPatch, values } = useHistorySearchState(CHANNEL_SEARCH_KEYS);
4544

@@ -88,7 +87,7 @@ export function useChannelPanelHistoryState() {
8887
openAgentSessionPubkey: values.agentSession,
8988
openThreadHeadId: values.thread,
9089
profilePanelPubkey: values.profile,
91-
profilePanelView: asProfilePanelView(values.profileView),
90+
profilePanelView: profilePanelViewFromSearch(values.profileView),
9291
setChannelManagementOpen,
9392
setOpenAgentSessionPubkey,
9493
setOpenThreadHeadId,

desktop/src/features/profile/ui/UserProfilePanel.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,11 @@ export function UserProfilePanel({
735735
const headerActions = (
736736
<div className="ml-auto flex shrink-0 items-center gap-2">
737737
{view === "memories" && viewerIsOwner && effectivePubkey ? (
738-
<MemoryRefreshButton agentPubkey={effectivePubkey} variant="outline" />
738+
<MemoryRefreshButton
739+
agentPubkey={effectivePubkey}
740+
variant="outline"
741+
viewerIsOwner={viewerIsOwner}
742+
/>
739743
) : null}
740744
<Button
741745
aria-label="Close profile"

desktop/src/features/profile/ui/UserProfilePanelSections.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,7 @@ export function MemoryFocusedView({
734734

735735
return (
736736
<div className="pt-4">
737-
<MemorySection agentPubkey={agentPubkey} />
737+
<MemorySection agentPubkey={agentPubkey} viewerIsOwner={isOwner} />
738738
</div>
739739
);
740740
}

desktop/src/features/profile/ui/UserProfilePanelUtils.test.mjs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ test("personaManagedAgentUpdate syncs edited persona identity to linked agent",
8585
assert.deepEqual(personaManagedAgentUpdate(agent(), persona()), {
8686
pubkey: "deadbeef".repeat(8),
8787
name: "Fizz Prime",
88-
avatarUrl: null,
8988
systemPrompt: "New prompt",
9089
model: "new-model",
9190
envVars: { NEW_KEY: "2" },
@@ -121,7 +120,6 @@ test("personaManagedAgentUpdate maps changed persona runtime to linked agent com
121120
{
122121
pubkey: "deadbeef".repeat(8),
123122
name: "Fizz Prime",
124-
avatarUrl: null,
125123
systemPrompt: "New prompt",
126124
model: "new-model",
127125
envVars: { NEW_KEY: "2" },

desktop/src/features/pulse/ui/PulseScreen.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import * as React from "react";
33
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
44
import { useOpenDmMutation } from "@/features/channels/hooks";
55
import {
6+
profilePanelViewFromSearch,
67
type ProfilePanelView,
7-
UserProfilePanel,
8-
} from "@/features/profile/ui/UserProfilePanel";
8+
} from "@/features/profile/ui/UserProfilePanelUtils";
9+
import { UserProfilePanel } from "@/features/profile/ui/UserProfilePanel";
910
import { PulseView } from "@/features/pulse/ui/PulseView";
1011
import { useIdentityQuery } from "@/shared/api/hooks";
1112
import { ProfilePanelProvider } from "@/shared/context/ProfilePanelContext";
@@ -18,10 +19,7 @@ export function PulseScreen() {
1819
const identityQuery = useIdentityQuery();
1920
const { applyPatch, values } = useHistorySearchState(PULSE_PANEL_SEARCH_KEYS);
2021
const profilePanelPubkey = values.profile;
21-
const profilePanelView: ProfilePanelView =
22-
values.profileView === "memories" || values.profileView === "channels"
23-
? values.profileView
24-
: "summary";
22+
const profilePanelView = profilePanelViewFromSearch(values.profileView);
2523
const handleOpenProfilePanel = React.useCallback(
2624
(pubkey: string) => applyPatch({ profile: pubkey, profileView: null }),
2725
[applyPatch],

0 commit comments

Comments
 (0)