Fix profile sidebar review feedback

This commit is contained in:
klopez4212
2026-06-25 14:06:51 +01:00
parent f2db5bbb50
commit bc0dcb3ed0
9 changed files with 84 additions and 31 deletions
@@ -1,13 +1,17 @@
import * as React from "react";
import { createFileRoute } from "@tanstack/react-router";
import {
parseProfilePanelView,
type ProfilePanelView,
} from "@/features/profile/ui/UserProfilePanelUtils";
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
type ChannelRouteSearch = {
agentSession?: string;
messageId?: string;
profile?: string;
profileView?: "memories" | "channels";
profileView?: ProfilePanelView;
thread?: string;
threadRootId?: string;
};
@@ -16,8 +20,8 @@ function nonEmptyString(value: unknown): string | undefined {
return typeof value === "string" && value.length > 0 ? value : undefined;
}
function profileViewValue(value: unknown): "memories" | "channels" | undefined {
return value === "memories" || value === "channels" ? value : undefined;
function profileViewValue(value: unknown): ProfilePanelView | undefined {
return parseProfilePanelView(value) ?? undefined;
}
function validateChannelSearch(
+6 -5
View File
@@ -1,6 +1,10 @@
import * as React from "react";
import { createFileRoute } from "@tanstack/react-router";
import {
parseProfilePanelView,
type ProfilePanelView,
} from "@/features/profile/ui/UserProfilePanelUtils";
import { usePreviewFeatureWarning } from "@/shared/features";
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
@@ -11,7 +15,7 @@ const PulseScreen = React.lazy(async () => {
type PulseRouteSearch = {
profile?: string;
profileView?: "memories" | "channels";
profileView?: ProfilePanelView;
};
function validatePulseSearch(
@@ -22,10 +26,7 @@ function validatePulseSearch(
typeof search.profile === "string" && search.profile.length > 0
? search.profile
: undefined,
profileView:
search.profileView === "memories" || search.profileView === "channels"
? search.profileView
: undefined,
profileView: parseProfilePanelView(search.profileView) ?? undefined,
};
}
@@ -0,0 +1,45 @@
import assert from "node:assert/strict";
import { beforeEach, describe, it } from "node:test";
import {
isKnownAgentPubkey,
registerKnownAgentPubkeys,
resetAgentObserverStore,
unregisterKnownAgentPubkeys,
} from "./observerRelayStore.ts";
const AGENT_A =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const AGENT_B =
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const AGENT_C =
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
describe("observerRelayStore known agent registrations", () => {
beforeEach(() => {
resetAgentObserverStore();
});
it("unions known agents from multiple bridge registrations", () => {
const agentsPage = Symbol("agents-page");
const profilePanel = Symbol("profile-panel");
registerKnownAgentPubkeys(agentsPage, [AGENT_A, AGENT_B]);
registerKnownAgentPubkeys(profilePanel, [AGENT_C]);
assert.equal(isKnownAgentPubkey(AGENT_A), true);
assert.equal(isKnownAgentPubkey(AGENT_B), true);
assert.equal(isKnownAgentPubkey(AGENT_C), true);
registerKnownAgentPubkeys(profilePanel, []);
assert.equal(isKnownAgentPubkey(AGENT_A), true);
assert.equal(isKnownAgentPubkey(AGENT_B), true);
assert.equal(isKnownAgentPubkey(AGENT_C), false);
unregisterKnownAgentPubkeys(agentsPage);
assert.equal(isKnownAgentPubkey(AGENT_A), false);
assert.equal(isKnownAgentPubkey(AGENT_B), false);
});
});
@@ -47,7 +47,7 @@ const snapshotByAgent = new Map<string, ObserverSnapshot>();
// We key each subscriber's contribution in `knownAgentsBySubscription` and
// recompute the union, so co-mounted callers no longer clobber each other.
const knownAgentPubkeys = new Set<string>();
const knownAgentsBySubscription = new Map<string, Set<string>>();
const knownAgentsBySubscription = new Map<string | symbol, Set<string>>();
function recomputeKnownAgentPubkeys() {
knownAgentPubkeys.clear();
@@ -58,8 +58,8 @@ function recomputeKnownAgentPubkeys() {
}
}
function registerKnownAgents(
subscriptionId: string,
export function registerKnownAgentPubkeys(
subscriptionId: string | symbol,
pubkeys: readonly string[],
) {
knownAgentsBySubscription.set(
@@ -69,12 +69,16 @@ function registerKnownAgents(
recomputeKnownAgentPubkeys();
}
function unregisterKnownAgents(subscriptionId: string) {
export function unregisterKnownAgentPubkeys(subscriptionId: string | symbol) {
if (knownAgentsBySubscription.delete(subscriptionId)) {
recomputeKnownAgentPubkeys();
}
}
export function isKnownAgentPubkey(pubkey: string) {
return knownAgentPubkeys.has(normalizePubkey(pubkey));
}
let connectionState: ConnectionState = "idle";
let errorMessage: string | null = null;
let unsubscribeRelay: (() => Promise<void>) | null = null;
@@ -176,7 +180,7 @@ async function handleRelayObserverEvent(
// Verify agent is known/trusted before decrypting.
// Silently drop events from agents we are not managing.
if (!knownAgentPubkeys.has(normalizePubkey(agentPubkey))) {
if (!isKnownAgentPubkey(agentPubkey)) {
return;
}
@@ -326,9 +330,9 @@ export function useManagedAgentObserverBridge(
// own agent list. The store recomputes the union across all subscribers, so
// a co-mounted caller no longer wipes out this caller's agents.
React.useEffect(() => {
registerKnownAgents(subscriptionId, agentPubkeys);
registerKnownAgentPubkeys(subscriptionId, agentPubkeys);
return () => {
unregisterKnownAgents(subscriptionId);
unregisterKnownAgentPubkeys(subscriptionId);
};
}, [subscriptionId, agentPubkeys]);
@@ -1,6 +1,9 @@
import * as React from "react";
import type { ProfilePanelView } from "@/features/profile/ui/UserProfilePanel";
import {
profilePanelViewFromSearch,
type ProfilePanelView,
} from "@/features/profile/ui/UserProfilePanelUtils";
import {
type HistorySearchSetterOptions,
useHistorySearchState,
@@ -36,10 +39,6 @@ const CHANNEL_SEARCH_KEYS = [
const CHANNEL_MANAGEMENT_OPEN_VALUE = "1";
function asProfilePanelView(value: string | null): ProfilePanelView {
return value === "memories" || value === "channels" ? value : "summary";
}
export function useChannelPanelHistoryState() {
const { applyPatch, values } = useHistorySearchState(CHANNEL_SEARCH_KEYS);
@@ -88,7 +87,7 @@ export function useChannelPanelHistoryState() {
openAgentSessionPubkey: values.agentSession,
openThreadHeadId: values.thread,
profilePanelPubkey: values.profile,
profilePanelView: asProfilePanelView(values.profileView),
profilePanelView: profilePanelViewFromSearch(values.profileView),
setChannelManagementOpen,
setOpenAgentSessionPubkey,
setOpenThreadHeadId,
@@ -735,7 +735,11 @@ export function UserProfilePanel({
const headerActions = (
<div className="ml-auto flex shrink-0 items-center gap-2">
{view === "memories" && viewerIsOwner && effectivePubkey ? (
<MemoryRefreshButton agentPubkey={effectivePubkey} variant="outline" />
<MemoryRefreshButton
agentPubkey={effectivePubkey}
variant="outline"
viewerIsOwner={viewerIsOwner}
/>
) : null}
<Button
aria-label="Close profile"
@@ -734,7 +734,7 @@ export function MemoryFocusedView({
return (
<div className="pt-4">
<MemorySection agentPubkey={agentPubkey} />
<MemorySection agentPubkey={agentPubkey} viewerIsOwner={isOwner} />
</div>
);
}
@@ -85,7 +85,6 @@ test("personaManagedAgentUpdate syncs edited persona identity to linked agent",
assert.deepEqual(personaManagedAgentUpdate(agent(), persona()), {
pubkey: "deadbeef".repeat(8),
name: "Fizz Prime",
avatarUrl: null,
systemPrompt: "New prompt",
model: "new-model",
envVars: { NEW_KEY: "2" },
@@ -121,7 +120,6 @@ test("personaManagedAgentUpdate maps changed persona runtime to linked agent com
{
pubkey: "deadbeef".repeat(8),
name: "Fizz Prime",
avatarUrl: null,
systemPrompt: "New prompt",
model: "new-model",
envVars: { NEW_KEY: "2" },
@@ -3,9 +3,10 @@ import * as React from "react";
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
import { useOpenDmMutation } from "@/features/channels/hooks";
import {
profilePanelViewFromSearch,
type ProfilePanelView,
UserProfilePanel,
} from "@/features/profile/ui/UserProfilePanel";
} from "@/features/profile/ui/UserProfilePanelUtils";
import { UserProfilePanel } from "@/features/profile/ui/UserProfilePanel";
import { PulseView } from "@/features/pulse/ui/PulseView";
import { useIdentityQuery } from "@/shared/api/hooks";
import { ProfilePanelProvider } from "@/shared/context/ProfilePanelContext";
@@ -18,10 +19,7 @@ export function PulseScreen() {
const identityQuery = useIdentityQuery();
const { applyPatch, values } = useHistorySearchState(PULSE_PANEL_SEARCH_KEYS);
const profilePanelPubkey = values.profile;
const profilePanelView: ProfilePanelView =
values.profileView === "memories" || values.profileView === "channels"
? values.profileView
: "summary";
const profilePanelView = profilePanelViewFromSearch(values.profileView);
const handleOpenProfilePanel = React.useCallback(
(pubkey: string) => applyPatch({ profile: pubkey, profileView: null }),
[applyPatch],