diff --git a/apps/code/src/renderer/desktop-services.ts b/apps/code/src/renderer/desktop-services.ts index 140113505c..962d2cf3ed 100644 --- a/apps/code/src/renderer/desktop-services.ts +++ b/apps/code/src/renderer/desktop-services.ts @@ -70,6 +70,7 @@ import { type IAuthSideEffects, } from "@posthog/ui/features/auth/identifiers"; import { authKeys } from "@posthog/ui/features/auth/useCurrentUser"; +import { useThreadPanelStore } from "@posthog/ui/features/canvas/stores/threadPanelStore"; import { FEATURE_FLAGS, type FeatureFlags, @@ -85,6 +86,7 @@ import { } from "@posthog/ui/features/integrations/integrationsClientImpl"; import { NAVIGATION_TASK_BINDER } from "@posthog/ui/features/navigation/taskBinder"; import { navigationTaskBinder } from "@posthog/ui/features/navigation/taskBinderImpl"; +import { activeNotificationTarget } from "@posthog/ui/features/notifications/activeTarget"; import { ACTIVE_VIEW_PROVIDER, type IActiveView, @@ -324,28 +326,18 @@ container.bind(ACTIVE_VIEW_PROVIDER).toConstantValue({ hasFocus: () => document.hasFocus(), // Read the active leaf route directly: AppView collapses the channel routes // and drops channelId/dashboardId, which we need to identify a canvas target. + // What counts as "viewing" is decided in ui — a task can be on screen in a + // thread panel without owning the route. getActiveTarget: (): NotificationTarget | undefined => { const matches = getCurrentMatches(); const last = matches[matches.length - 1]; if (!last) return undefined; - const params = last.params as Record; - switch (last.routeId) { - case "/code/tasks/$taskId": - case "/website/$channelId/tasks/$taskId": - return params.taskId - ? { kind: "task", taskId: params.taskId } - : undefined; - case "/website/$channelId/dashboards/$dashboardId": - return params.channelId && params.dashboardId - ? { - kind: "canvas", - channelId: params.channelId, - dashboardId: params.dashboardId, - } - : undefined; - default: - return undefined; - } + const { openByChannel, collapsed } = useThreadPanelStore.getState(); + return activeNotificationTarget({ + routeId: last.routeId, + params: last.params as Record, + threadPanel: { openByChannel, collapsed }, + }); }, }); diff --git a/packages/core/src/canvas/mentionActivity.test.ts b/packages/core/src/canvas/mentionActivity.test.ts index 517dfca416..159024e19f 100644 --- a/packages/core/src/canvas/mentionActivity.test.ts +++ b/packages/core/src/canvas/mentionActivity.test.ts @@ -1,7 +1,10 @@ import type { TaskMention, UserBasic } from "@posthog/shared/domain-types"; import { describe, expect, it } from "vitest"; import { + countUnreadMentions, countUnseenActivity, + isMentionUnread, + type MentionActivityItem, mergeTaskMentions, toMentionActivityItems, } from "./mentionActivity"; @@ -133,3 +136,52 @@ describe("mergeTaskMentions", () => { expect(merged[0].message_id).toBe("newest"); }); }); + +describe("isMentionUnread", () => { + const item = (over: Partial = {}) => + ({ + messageId: "m1", + taskId: "t1", + taskTitle: "Task", + channelId: "c1", + channelName: "mobile", + author: null, + content: "@you", + createdAt: "2026-07-17T10:00:00.000Z", + ...over, + }) as MentionActivityItem; + + const none: ReadonlySet = new Set(); + + it("is unread until its thread is opened", () => { + expect(isMentionUnread(item(), null, none)).toBe(true); + expect(isMentionUnread(item(), null, new Set(["m1"]))).toBe(false); + }); + + it("reading one mention leaves the others unread", () => { + const read = new Set(["m1"]); + expect(isMentionUnread(item({ messageId: "m2" }), null, read)).toBe(true); + }); + + it("treats anything before the legacy seen watermark as read", () => { + const old = item({ createdAt: "2026-07-01T00:00:00.000Z" }); + expect(isMentionUnread(old, "2026-07-10T00:00:00.000Z", none)).toBe(false); + }); + + it("keeps mentions after the watermark unread until opened", () => { + const fresh = item({ createdAt: "2026-07-17T10:00:00.000Z" }); + const seen = "2026-07-10T00:00:00.000Z"; + expect(isMentionUnread(fresh, seen, none)).toBe(true); + expect(isMentionUnread(fresh, seen, new Set(["m1"]))).toBe(false); + }); + + it("counts only what's unread", () => { + const items = [ + item({ messageId: "a" }), + item({ messageId: "b" }), + item({ messageId: "c" }), + ]; + expect(countUnreadMentions(items, null, new Set(["b"]))).toBe(2); + expect(countUnreadMentions(items, null, new Set(["a", "b", "c"]))).toBe(0); + }); +}); diff --git a/packages/core/src/canvas/mentionActivity.ts b/packages/core/src/canvas/mentionActivity.ts index 049bd170ec..6451de6c26 100644 --- a/packages/core/src/canvas/mentionActivity.ts +++ b/packages/core/src/canvas/mentionActivity.ts @@ -44,6 +44,36 @@ export function countUnseenActivity( return items.filter((item) => item.createdAt > lastSeenAt).length; } +/** + * Has the viewer read this mention? + * + * Read is per mention: it's earned by opening that mention's thread, not by + * glancing at the list. `lastSeenAt` stays on as a historical watermark — + * everything from before the viewer's last "seen the page" sweep counts as + * read, so switching to per-mention tracking doesn't resurface years of old + * mentions as unread. Anything after it is unread until opened. + */ +export function isMentionUnread( + item: MentionActivityItem, + lastSeenAt: string | null, + readMessageIds: ReadonlySet, +): boolean { + if (readMessageIds.has(item.messageId)) return false; + if (!lastSeenAt) return true; + return item.createdAt > lastSeenAt; +} + +/** How many mentions the viewer hasn't read. Drives the sidebar's badge. */ +export function countUnreadMentions( + items: readonly MentionActivityItem[], + lastSeenAt: string | null, + readMessageIds: ReadonlySet, +): number { + return items.filter((item) => + isMentionUnread(item, lastSeenAt, readMessageIds), + ).length; +} + // Bounds the cache so a long-running session's accumulated feed can't grow // without limit. const MAX_CACHED_MENTIONS = 300; diff --git a/packages/core/src/canvas/threadTimeline.test.ts b/packages/core/src/canvas/threadTimeline.test.ts index a3b3b355f3..346bb8fa9d 100644 --- a/packages/core/src/canvas/threadTimeline.test.ts +++ b/packages/core/src/canvas/threadTimeline.test.ts @@ -5,6 +5,7 @@ import { hasAgentMention, normalizeAgentPromptText, shouldSuspendThreadSession, + visibleThreadMessages, } from "./threadTimeline"; describe("hasAgentMention", () => { @@ -167,3 +168,63 @@ describe("shouldSuspendThreadSession", () => { expect(shouldSuspendThreadSession(input)).toBe(false); }); }); + +describe("visibleThreadMessages", () => { + const human = { id: "h", author_kind: "human" as const }; + const turn = (runId: string, id = `turn-${runId}`) => ({ + id, + author_kind: "agent" as const, + event: "turn_complete", + payload: { run_id: runId }, + }); + + it("keeps everything when no run is streaming", () => { + expect( + visibleThreadMessages([human, turn("run-1")], undefined).map((m) => m.id), + ).toEqual(["h", "turn-run-1"]); + }); + + it("drops the durable turn for the run being streamed", () => { + expect( + visibleThreadMessages([human, turn("run-1")], "run-1").map((m) => m.id), + ).toEqual(["h"]); + }); + + it("keeps durable turns from other runs", () => { + expect( + visibleThreadMessages([turn("run-1"), turn("run-2")], "run-1").map( + (m) => m.id, + ), + ).toEqual(["turn-run-2"]); + }); + + it("never drops human messages", () => { + expect(visibleThreadMessages([human], "run-1").map((m) => m.id)).toEqual([ + "h", + ]); + }); + + it("keeps other agent rows — only turn_complete collides with a live turn", () => { + const ask = { + id: "ask", + author_kind: "agent" as const, + event: "permission_request", + payload: { run_id: "run-1" }, + }; + expect(visibleThreadMessages([ask], "run-1").map((m) => m.id)).toEqual([ + "ask", + ]); + }); + + it("keeps a turn row with no run id rather than dropping the only copy", () => { + const orphan = { + id: "orphan", + author_kind: "agent" as const, + event: "turn_complete", + payload: {}, + }; + expect(visibleThreadMessages([orphan], "run-1").map((m) => m.id)).toEqual([ + "orphan", + ]); + }); +}); diff --git a/packages/core/src/canvas/threadTimeline.ts b/packages/core/src/canvas/threadTimeline.ts index a440d4e08d..2ad5fbf3f0 100644 --- a/packages/core/src/canvas/threadTimeline.ts +++ b/packages/core/src/canvas/threadTimeline.ts @@ -142,3 +142,32 @@ export function shouldSuspendThreadSession({ }): boolean { return !isCloud && !hasRun && !hasSession; } + +/** + * The durable thread messages worth rendering, given the run whose turns the + * viewer is already watching stream. + * + * The backend posts every agent turn as a durable `turn_complete` row carrying + * `payload.run_id` so a client streaming that run can drop one copy. We drop + * the durable row rather than the live turn: the streamed one is what the + * reader watched arrive, and swapping it for the server's copy at the end would + * make the message jump. Anyone without that session — a teammate, or a run on + * another machine — has no live turn to collide with, so they keep the durable + * row and see the same conversation from the other side. + */ +export function visibleThreadMessages< + T extends { + author_kind?: string; + event?: string; + payload?: Record; + }, +>(messages: readonly T[], streamingRunId: string | undefined): T[] { + if (!streamingRunId) return [...messages]; + return messages.filter((message) => { + if (message.event !== "turn_complete") return true; + const runId = message.payload?.run_id; + // A row with no run id can't be matched to the live turn; keeping a + // possible duplicate beats silently dropping the only copy. + return typeof runId !== "string" || runId !== streamingRunId; + }); +} diff --git a/packages/shared/src/domain-types.ts b/packages/shared/src/domain-types.ts index 98f9d90ff9..840d5fb54f 100644 --- a/packages/shared/src/domain-types.ts +++ b/packages/shared/src/domain-types.ts @@ -100,9 +100,25 @@ export interface ChannelFeedMessage { created_at: string; } +/** Server-emitted agent rows in a task's thread. */ +export type TaskThreadMessageEvent = + /** The agent's final turn, made durable. `payload.run_id` identifies the run, + * so a client already streaming that run can drop this copy. */ + | "turn_complete" + /** The agent is asking to do something before it proceeds. */ + | "permission_request"; + /** - * One human message in a task's thread. Thread messages never reach the agent - * unless the task author forwards one, which stamps the forwarded_* fields. + * One message in a task's thread. Human messages never reach the agent unless + * the task author forwards one, which stamps the forwarded_* fields. + * + * Agent rows are authorless (`author_kind: "agent"`, no `author`) and carry a + * stable `event` + structured `payload`, so they can be rendered from those + * rather than by parsing `content` — which stays the rendered text, so a client + * that doesn't know the event still shows something sensible. + * + * `author_kind` is optional only so older payloads (and test fixtures) parse; + * absent means human, which is what every message was before agents could post. */ export interface TaskThreadMessage { id: string; @@ -110,7 +126,7 @@ export interface TaskThreadMessage { /** Who authored the row; agent rows are server-emitted announcements. Absent on older backends. */ author_kind?: "human" | "system" | "agent"; /** Stable event key for non-human rows (e.g. "canvas_created", "turn_complete"). */ - event?: string; + event?: TaskThreadMessageEvent | string; /** Structured event payload; turn_complete carries `{ run_id }` so a client rendering a run's live agent turns can dedupe the durable row. */ payload?: Record; content: string; diff --git a/packages/ui/src/features/canvas/components/ActivityView.tsx b/packages/ui/src/features/canvas/components/ActivityView.tsx index 17fe071e67..f3194d0639 100644 --- a/packages/ui/src/features/canvas/components/ActivityView.tsx +++ b/packages/ui/src/features/canvas/components/ActivityView.tsx @@ -1,26 +1,44 @@ import { AtIcon, LinkIcon } from "@phosphor-icons/react"; -import type { MentionActivityItem } from "@posthog/core/canvas/mentionActivity"; +import { + isMentionUnread, + type MentionActivityItem, +} from "@posthog/core/canvas/mentionActivity"; import { Avatar, AvatarFallback, - Button, + cn, Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Spinner, + ThreadItem, + ThreadItemAction, + ThreadItemActions, + ThreadItemAuthor, + ThreadItemBody, + ThreadItemContent, + ThreadItemGroup, + ThreadItemGutter, + ThreadItemHeader, } from "@posthog/quill"; -import { formatRelativeTimeShort } from "@posthog/shared"; import { ANALYTICS_EVENTS } from "@posthog/shared/analytics-events"; import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient"; import { useCurrentUser } from "@posthog/ui/features/auth/useCurrentUser"; import { getUserInitials } from "@posthog/ui/features/auth/userInitials"; import { MentionText } from "@posthog/ui/features/canvas/components/MentionText"; +import { ThreadScrollBody } from "@posthog/ui/features/canvas/components/ThreadScrollBody"; +import { ThreadSidebar } from "@posthog/ui/features/canvas/components/ThreadSidebar"; +import { ThreadTimestamp } from "@posthog/ui/features/canvas/components/ThreadTimestamp"; import { useChannels } from "@posthog/ui/features/canvas/hooks/useChannels"; import { useMentionActivity } from "@posthog/ui/features/canvas/hooks/useMentionActivity"; import { normalizeChannelName } from "@posthog/ui/features/canvas/hooks/useTaskChannels"; import { useActivitySeenStore } from "@posthog/ui/features/canvas/stores/activitySeenStore"; +import { + ACTIVITY_THREAD_KEY, + useThreadPanelStore, +} from "@posthog/ui/features/canvas/stores/threadPanelStore"; import { copyChannelLink } from "@posthog/ui/features/canvas/utils/copyChannelLink"; import { userDisplayName } from "@posthog/ui/features/canvas/utils/userDisplay"; import { @@ -35,14 +53,20 @@ function ActivityRow({ item, folderChannelId, isNew, + isOpen, currentUserEmail, + onOpen, }: { item: MentionActivityItem; /** Desktop folder channel id (the /website route param); null when unmapped. */ folderChannelId: string | null; - /** Arrived since the viewer last opened this page. */ + /** Unread: its thread hasn't been opened. */ isNew: boolean; + /** Its thread is the one open beside the list. */ + isOpen: boolean; currentUserEmail?: string | null; + /** Reads the mention: opens its thread beside the list. */ + onOpen: () => void; }) { const openThread = () => { track(ANALYTICS_EVENTS.CHANNEL_ACTION, { @@ -51,82 +75,79 @@ function ActivityRow({ channel_id: folderChannelId ?? undefined, task_id: item.taskId, }); - // The channel thread route is the deep-link target; tasks whose channel - // folder is gone fall back to the plain task view. - if (folderChannelId) { - navigateToChannelTask(folderChannelId, item.taskId); - } else { - navigateToTaskDetail(item.taskId); - } + onOpen(); }; return ( -
- + + + + {item.taskTitle} + + + {/* The same window the thread panel gives an agent turn: a mention + can be a whole essay, and one of those shouldn't push the next + mention off the page. */} + + + + + {folderChannelId && ( - + + + void copyChannelLink(folderChannelId, "activity", item.taskId) + } + > + + + )} -
+ ); } // The Activity page: every channel-thread message that @-mentions the viewer, -// newest first. Opening it clears the sidebar badge. +// newest first. Reading one means opening its thread; the page itself marks +// nothing, so the badge survives a glance at the list. export function ActivityView() { const client = useOptionalAuthenticatedClient(); const { data: currentUser } = useCurrentUser({ client }); @@ -149,11 +170,32 @@ export function ActivityView() { channelName ? (folderIdByName.get(normalizeChannelName(channelName)) ?? null) : null; - const markSeen = useActivitySeenStore((s) => s.markSeen); - // Snapshot before marking seen so rows that were new on arrival keep their - // dot for this visit. - const [seenAtOpen] = useState( - () => useActivitySeenStore.getState().lastSeenAt, + const threadTaskId = useThreadPanelStore( + (s) => s.openByChannel[ACTIVITY_THREAD_KEY] ?? null, + ); + // Which *mention* is on the right, not just which task. The panel only tracks + // a task, and a task can be mentioned in several rows — keying the highlight + // off it alone would light all of them for one click. Paired with the panel's + // own state below, so closing the thread clears the highlight for free. + const [openMessageId, setOpenMessageId] = useState(null); + const openThread = useThreadPanelStore((s) => s.openThread); + const closeThread = useThreadPanelStore((s) => s.closeThread); + // The panel's task card links into /website/$channelId, so a thread can only + // open beside the list for a mention whose channel folder still resolves. + const threadChannelId = threadTaskId + ? folderChannelIdFor( + items.find((item) => item.taskId === threadTaskId)?.channelName ?? null, + ) + : null; + + const lastSeenAt = useActivitySeenStore((s) => s.lastSeenAt); + const markMessageRead = useActivitySeenStore((s) => s.markMessageRead); + // Snapshot the read set on arrival rather than subscribing: a mention you + // open keeps its dot for the rest of the visit instead of vanishing under the + // cursor, and the list doesn't re-render every time one is read. The sidebar + // badge reads the live set, so it still drops immediately. + const [readAtOpen] = useState( + () => useActivitySeenStore.getState().readMessageIds, ); useEffect(() => { @@ -163,54 +205,82 @@ export function ActivityView() { }); }, []); - // Re-mark as items stream in so the badge stays cleared while reading. - // biome-ignore lint/correctness/useExhaustiveDependencies: re-run per new item - useEffect(() => { - markSeen(); - }, [markSeen, items.length]); - return ( -
-
- - Activity - - - Mentions of you across channels. - -
- {isLoading && items.length === 0 ? ( -
- -
- ) : items.length === 0 ? ( - - - - - - No mentions yet - - When a teammate tags you with @ in a channel thread, it lands - here. - - - - ) : ( -
- {items.map((item) => ( - seenAtOpen} - currentUserEmail={currentUser?.email} - /> - ))} -
- )} +
+
+
+ + Activity + + + Mentions of you across channels. + +
+ {isLoading && items.length === 0 ? ( +
+ +
+ ) : items.length === 0 ? ( + + + + + + No mentions yet + + When a teammate tags you with @ in a channel thread, it + lands here. + + + + ) : ( + + {items.map((item) => { + const folderChannelId = folderChannelIdFor(item.channelName); + return ( + { + // Opening the thread is what reads the mention — the + // list itself never marks anything. + markMessageRead(item.messageId); + // Reading a mention shouldn't cost you the list. Without + // a channel folder there's no thread route to host, so + // those still fall through to the plain task view. + if (folderChannelId) { + setOpenMessageId(item.messageId); + openThread(ACTIVITY_THREAD_KEY, item.taskId); + } else { + navigateToTaskDetail(item.taskId); + } + }} + /> + ); + })} + + )} +
+ + {threadTaskId && threadChannelId && ( + closeThread(ACTIVITY_THREAD_KEY)} + onOpenFull={() => + navigateToChannelTask(threadChannelId, threadTaskId) + } + /> + )}
); } diff --git a/packages/ui/src/features/canvas/components/ThreadPanel.tsx b/packages/ui/src/features/canvas/components/ThreadPanel.tsx index fba801b8f1..7909c9cbfc 100644 --- a/packages/ui/src/features/canvas/components/ThreadPanel.tsx +++ b/packages/ui/src/features/canvas/components/ThreadPanel.tsx @@ -16,6 +16,7 @@ import { type ThreadAgentMessage, type ThreadAgentStatus, type ThreadTimelineRow, + visibleThreadMessages, } from "@posthog/core/canvas/threadTimeline"; import { Avatar, @@ -60,6 +61,7 @@ import { MentionText, mentionChipClass, } from "@posthog/ui/features/canvas/components/MentionText"; +import { ThreadScrollBody } from "@posthog/ui/features/canvas/components/ThreadScrollBody"; import { ThreadTimestamp } from "@posthog/ui/features/canvas/components/ThreadTimestamp"; import { agentTurns } from "@posthog/ui/features/canvas/components/threadAgentTurns"; import { useOrgMembers } from "@posthog/ui/features/canvas/hooks/useOrgMembers"; @@ -216,12 +218,15 @@ export function AgentStatusLine({ status }: { status: ThreadAgentStatus }) { export function AgentTurnRow({ message, streaming, + onOpenTask, }: { message: ThreadAgentMessage; streaming: boolean; + /** Opens the task behind the turn; omitted where the task is already open. */ + onOpenTask?: () => void; }) { return ( - + @@ -240,13 +245,13 @@ export function AgentTurnRow({ {message.text && ( -
+ {streaming ? ( ) : ( )} -
+
)} @@ -264,7 +269,7 @@ export function UserPromptRow({ const promptText = normalizeAgentPromptText(message.text); return ( - + {getUserInitials(author)} @@ -360,6 +365,7 @@ function ThreadTimeline({ agentActive, onSendToAgent, onDelete, + onOpenTask, }: { timeline: ThreadTimelineRow[]; isReady: boolean; @@ -372,6 +378,7 @@ function ThreadTimeline({ agentActive: boolean; onSendToAgent: (messageId: string) => void; onDelete: (messageId: string) => void; + onOpenTask?: () => void; }) { if (!isReady) return ; if (timeline.length === 0) { @@ -420,6 +427,7 @@ function ThreadTimeline({ key={row.message.id} message={row.message} streaming={row.message.id === lastAgentId && agentActive} + onOpenTask={onOpenTask} /> ), )} @@ -554,12 +562,20 @@ function ThreadConversation({ ], ); + // The backend makes every agent turn durable, so a run we're streaming would + // otherwise show each turn twice — once live, once from the server. + const streamingRunId = session?.taskRunId; + const durableMessages = useMemo( + () => visibleThreadMessages(messages, streamingRunId), + [messages, streamingRunId], + ); + const timeline = useMemo( () => buildThreadTimeline({ prompts: promptMsgs, agentMessages: agentMsgs, - humanMessages: messages.map((message) => ({ + humanMessages: durableMessages.map((message) => ({ id: message.id, content: message.content, createdAt: message.created_at, @@ -567,7 +583,7 @@ function ThreadConversation({ value: message, })), }), - [promptMsgs, messages, agentMsgs], + [promptMsgs, durableMessages, agentMsgs], ); const lastAgentId = agentMsgs[agentMsgs.length - 1]?.id; @@ -660,7 +676,7 @@ function ThreadConversation({ /> {showTaskSummary && ( -
+
)} @@ -675,6 +691,7 @@ function ThreadConversation({ canForward={canForward} lastAgentId={lastAgentId} agentActive={agentStatus?.phase === "active"} + onOpenTask={onOpenFull} onSendToAgent={handleSendToAgent} onDelete={handleDelete} /> diff --git a/packages/ui/src/features/canvas/components/ThreadScrollBody.tsx b/packages/ui/src/features/canvas/components/ThreadScrollBody.tsx new file mode 100644 index 0000000000..658a4ba541 --- /dev/null +++ b/packages/ui/src/features/canvas/components/ThreadScrollBody.tsx @@ -0,0 +1,68 @@ +import { ChatStream, cn } from "@posthog/quill"; +import { type ReactNode, useCallback } from "react"; + +/** + * The framed, capped window a thread row's content sits in — an agent's turn in + * the thread panel, a mention's body in Activity. Both are "something long the + * agent said", so both are read the same way: a box tall enough to show the + * shape of it, scrolled for the rest, rather than a wall that pushes the next + * row off screen. + * + * The frame is this wrapper, not the ChatStream inside it. ChatStream masks its + * own top and bottom edges to fade the content as it scrolls, and that mask + * would eat a border drawn on the same element — the frame would dissolve at + * the corners exactly when there's more to read. Keeping them separate lets the + * content fade while the box stays put. (ChatStream expects this: it "brings no + * chrome and no rail — let the container frame it".) + * + * With `onClick` the frame is the click target and highlights on hover. The + * stream still scrolls inside it; only a drag started on the scrollbar itself + * would land on the button, which is the same trade every clickable card makes. + */ +export function ThreadScrollBody({ + pinned = false, + onClick, + className, + children, +}: { + /** Follow live output. Off for anything already finished. */ + pinned?: boolean; + /** Makes the frame pressable — e.g. an agent turn opening its task. */ + onClick?: () => void; + className?: string; + children: ReactNode; +}) { + // Open on the last line, not the first: the end of a turn is what the reader + // wants, and scrolling up for the rest is the natural way back. + // + // A ref callback is the whole of it — it lands once, on mount, before paint, + // with no state, no effect and nothing to re-run. The obvious CSS answer, + // `flex-direction: column-reverse`, does start at the bottom but flips the + // scroll origin negative, and ChatStream's fade logic reads raw `scrollTop`: + // under the flip its two flags stick at constant values and the fades stop + // tracking the content. Keeping the normal origin keeps them honest. + // + // While pinned, ChatStream is already following the newest line, and hands + // over parked at the bottom when it unpins — so this is only the mount case. + const scrollToEnd = useCallback((frame: HTMLElement | null) => { + const stream = frame?.querySelector('[data-slot="stream"]'); + if (stream) stream.scrollTop = stream.scrollHeight; + }, []); + + const frame = cn( + "w-full rounded-md border border-border bg-muted px-2 py-1.5 text-left", + onClick && "cursor-default hover:border-primary/50 hover:bg-fill-hover", + className, + ); + const stream = {children}; + + return onClick ? ( + + ) : ( +
+ {stream} +
+ ); +} diff --git a/packages/ui/src/features/canvas/components/threadAgentTurns.test.ts b/packages/ui/src/features/canvas/components/threadAgentTurns.test.ts new file mode 100644 index 0000000000..5876b9883a --- /dev/null +++ b/packages/ui/src/features/canvas/components/threadAgentTurns.test.ts @@ -0,0 +1,111 @@ +import { agentTurns } from "@posthog/ui/features/canvas/components/threadAgentTurns"; +import type { ConversationItem } from "@posthog/ui/features/sessions/components/buildConversationItems"; +import { describe, expect, it } from "vitest"; + +let seq = 0; + +function chunk(text: string, timestamp = 1): ConversationItem { + seq += 1; + return { + type: "session_update", + id: `chunk-${seq}`, + timestamp, + update: { + sessionUpdate: "agent_message_chunk", + content: { type: "text", text }, + }, + turnContext: {}, + } as unknown as ConversationItem; +} + +function toolCall(): ConversationItem { + seq += 1; + return { + type: "session_update", + id: `tool-${seq}`, + update: { + sessionUpdate: "tool_call", + toolCallId: `t${seq}`, + status: "in_progress", + }, + turnContext: {}, + } as unknown as ConversationItem; +} + +function userMessage(): ConversationItem { + seq += 1; + return { + type: "user_message", + id: `user-${seq}`, + content: "go", + timestamp: 0, + } as unknown as ConversationItem; +} + +describe("agentTurns", () => { + it("joins streamed chunks of one sentence without inventing gaps", () => { + expect(agentTurns([chunk("I'll prep"), chunk("are a branch.")])).toEqual([ + expect.objectContaining({ text: "I'll prepare a branch." }), + ]); + }); + + it("breaks a paragraph where the agent stopped to run a tool", () => { + const turns = agentTurns([ + chunk("…before committing."), + toolCall(), + chunk("The patch rebased cleanly."), + ]); + expect(turns).toHaveLength(1); + expect(turns[0].text).toBe( + "…before committing.\n\nThe patch rebased cleanly.", + ); + }); + + it("breaks once for a run of tool calls, not once each", () => { + const turns = agentTurns([ + chunk("Working."), + toolCall(), + toolCall(), + toolCall(), + chunk("Done."), + ]); + expect(turns[0].text).toBe("Working.\n\nDone."); + }); + + it("leaves no dangling break when a turn ends on a tool call", () => { + const turns = agentTurns([chunk("Checking."), toolCall()]); + expect(turns[0].text).toBe("Checking."); + }); + + it("adds no leading break when a turn opens with a tool call", () => { + const turns = agentTurns([toolCall(), chunk("Found it.")]); + expect(turns[0].text).toBe("Found it."); + }); + + it("starts a new turn at each user message", () => { + const turns = agentTurns([ + chunk("First."), + userMessage(), + chunk("Second."), + toolCall(), + chunk("Third."), + ]); + expect(turns.map((t) => t.text)).toEqual(["First.", "Second.\n\nThird."]); + }); + + it("doesn't carry a pending break across a turn boundary", () => { + const turns = agentTurns([ + chunk("First."), + toolCall(), + userMessage(), + chunk("Second."), + ]); + expect(turns.map((t) => t.text)).toEqual(["First.", "Second."]); + }); + + it("ignores whitespace-only chunks", () => { + expect(agentTurns([chunk(" "), chunk("Real.")])).toEqual([ + expect.objectContaining({ text: "Real." }), + ]); + }); +}); diff --git a/packages/ui/src/features/canvas/components/threadAgentTurns.ts b/packages/ui/src/features/canvas/components/threadAgentTurns.ts index ddcc58ec80..57178b4611 100644 --- a/packages/ui/src/features/canvas/components/threadAgentTurns.ts +++ b/packages/ui/src/features/canvas/components/threadAgentTurns.ts @@ -1,23 +1,53 @@ import type { ThreadAgentMessage } from "@posthog/core/canvas/threadTimeline"; import type { ConversationItem } from "@posthog/ui/features/sessions/components/buildConversationItems"; +/** + * The agent's prose for each turn, as one thread message per turn. + * + * Text arrives as a stream of chunks, so consecutive chunks are joined raw — + * they're mid-sentence fragments, not sentences. What does separate them is a + * tool call: the agent says something, goes and does it, then comes back and + * says something else. Those are two paragraphs, and gluing them together runs + * "…before committing." straight into "The patch rebased cleanly." Rather than + * render the call itself (the full task view does that), the thread keeps just + * its shape: a break where the work happened. + */ export function agentTurns(items: ConversationItem[]): ThreadAgentMessage[] { const turns: ThreadAgentMessage[] = []; let current: ThreadAgentMessage | null = null; + // Set when a tool call lands mid-turn, and spent by the next chunk of prose — + // so trailing calls add no dangling break, and a run of calls adds only one. + let brokenByToolCall = false; + for (const item of items) { if (item.type === "user_message") { if (current) turns.push(current); current = null; + brokenByToolCall = false; continue; } + if (item.type !== "session_update") continue; + + if (item.update.sessionUpdate === "tool_call") { + // Only meaningful between two pieces of prose; a call before the agent + // has said anything has nothing to break away from. + if (current) brokenByToolCall = true; + continue; + } + if ( - item.type === "session_update" && item.update.sessionUpdate === "agent_message_chunk" && "content" in item.update && item.update.content.type === "text" && item.update.content.text.trim() ) { if (current) { + // A blank line, not a newline: the body renders as markdown, where a + // single newline is just a space. + if (brokenByToolCall) { + current.text += "\n\n"; + brokenByToolCall = false; + } current.text += item.update.content.text; } else { current = { diff --git a/packages/ui/src/features/canvas/stores/activitySeenStore.ts b/packages/ui/src/features/canvas/stores/activitySeenStore.ts index 687068deb1..5f215b7788 100644 --- a/packages/ui/src/features/canvas/stores/activitySeenStore.ts +++ b/packages/ui/src/features/canvas/stores/activitySeenStore.ts @@ -2,22 +2,66 @@ import { electronStorage } from "@posthog/ui/shell/rendererStorage"; import { create } from "zustand"; import { persist } from "zustand/middleware"; -// When the viewer last opened the Activity page; mentions newer than this -// count toward the sidebar's unread badge. +/** + * Which mentions the viewer has read. + * + * A mention is read by opening its thread — not by opening the Activity page, + * which is a list of things you haven't dealt with yet, not a receipt for them. + * + * `lastSeenAt` is the watermark from when the page itself marked everything + * seen. It's no longer written, only read: it keeps mentions from before the + * switch to per-mention tracking from resurfacing as unread. New mentions are + * unread until their thread is opened. + */ interface ActivitySeenState { lastSeenAt: string | null; - markSeen: () => void; + readMessageIds: Set; + markMessageRead: (messageId: string) => void; } +const MAX_READ_IDS = 500; + export const useActivitySeenStore = create()( persist( (set) => ({ lastSeenAt: null, - markSeen: () => set({ lastSeenAt: new Date().toISOString() }), + readMessageIds: new Set(), + markMessageRead: (messageId) => + set((state) => { + if (state.readMessageIds.has(messageId)) return state; + const next = new Set(state.readMessageIds); + next.add(messageId); + // The mentions feed itself is capped, so unbounded read ids would + // outlive anything that could reference them. Oldest out first. + if (next.size > MAX_READ_IDS) { + const excess = next.size - MAX_READ_IDS; + const ids = next.values(); + for (let i = 0; i < excess; i++) { + const oldest = ids.next().value; + if (oldest !== undefined) next.delete(oldest); + } + } + return { readMessageIds: next }; + }), }), { name: "channels-activity-seen", storage: electronStorage, + // Sets don't survive JSON; store the ids as an array and rebuild on load. + partialize: (state) => ({ + lastSeenAt: state.lastSeenAt, + readMessageIds: [...state.readMessageIds], + }), + merge: (persisted, current) => { + const saved = persisted as + | { lastSeenAt?: string | null; readMessageIds?: string[] } + | undefined; + return { + ...current, + lastSeenAt: saved?.lastSeenAt ?? current.lastSeenAt, + readMessageIds: new Set(saved?.readMessageIds ?? []), + }; + }, }, ), ); diff --git a/packages/ui/src/features/canvas/stores/threadPanelStore.ts b/packages/ui/src/features/canvas/stores/threadPanelStore.ts index ee2c66427e..869150d5a1 100644 --- a/packages/ui/src/features/canvas/stores/threadPanelStore.ts +++ b/packages/ui/src/features/canvas/stores/threadPanelStore.ts @@ -4,6 +4,14 @@ import { persist } from "zustand/middleware"; const DEFAULT_PANEL_WIDTH = 360; +/** + * The Activity page's key in `openByChannel`. Threads are keyed by channel, but + * Activity spans every channel, and borrowing a real channel's key would mean + * reading a mention there silently changed which thread that channel shows. + * Channel ids are UUIDs, so this can't collide with one. + */ +export const ACTIVITY_THREAD_KEY = "activity"; + interface ThreadPanelState { openByChannel: Record; collapsed: boolean; diff --git a/packages/ui/src/features/notifications/activeTarget.test.ts b/packages/ui/src/features/notifications/activeTarget.test.ts new file mode 100644 index 0000000000..4f4f62edd7 --- /dev/null +++ b/packages/ui/src/features/notifications/activeTarget.test.ts @@ -0,0 +1,106 @@ +import { + activeNotificationTarget, + type ThreadPanelSnapshot, +} from "@posthog/ui/features/notifications/activeTarget"; +import { describe, expect, it } from "vitest"; + +const noThread: ThreadPanelSnapshot = { openByChannel: {}, collapsed: false }; + +describe("activeNotificationTarget", () => { + it("targets the task on a task route", () => { + expect( + activeNotificationTarget({ + routeId: "/code/tasks/$taskId", + params: { taskId: "t1" }, + threadPanel: noThread, + }), + ).toEqual({ kind: "task", taskId: "t1" }); + }); + + it("targets the canvas on a dashboard route", () => { + expect( + activeNotificationTarget({ + routeId: "/website/$channelId/dashboards/$dashboardId", + params: { channelId: "c1", dashboardId: "d1" }, + threadPanel: noThread, + }), + ).toEqual({ kind: "canvas", channelId: "c1", dashboardId: "d1" }); + }); + + it("targets the task whose thread is open beside the channel feed", () => { + expect( + activeNotificationTarget({ + routeId: "/website/$channelId", + params: { channelId: "c1" }, + threadPanel: { openByChannel: { c1: "t9" }, collapsed: false }, + }), + ).toEqual({ kind: "task", taskId: "t9" }); + }); + + it("targets the task whose thread is open beside the Activity list", () => { + expect( + activeNotificationTarget({ + routeId: "/website/activity", + params: {}, + threadPanel: { openByChannel: { activity: "t9" }, collapsed: false }, + }), + ).toEqual({ kind: "task", taskId: "t9" }); + }); + + it("ignores a thread open on another channel's surface", () => { + expect( + activeNotificationTarget({ + routeId: "/website/$channelId", + params: { channelId: "c1" }, + threadPanel: { openByChannel: { c2: "t9" }, collapsed: false }, + }), + ).toBeUndefined(); + }); + + it("is nothing when the panel is collapsed — a rail shows no conversation", () => { + expect( + activeNotificationTarget({ + routeId: "/website/activity", + params: {}, + threadPanel: { openByChannel: { activity: "t9" }, collapsed: true }, + }), + ).toBeUndefined(); + }); + + it("is nothing on a feed with no thread open", () => { + expect( + activeNotificationTarget({ + routeId: "/website/$channelId", + params: { channelId: "c1" }, + threadPanel: noThread, + }), + ).toBeUndefined(); + }); + + it("is nothing on an unrelated route, thread or not", () => { + expect( + activeNotificationTarget({ + routeId: "/website/skills", + params: {}, + threadPanel: { openByChannel: { activity: "t9" }, collapsed: false }, + }), + ).toBeUndefined(); + expect( + activeNotificationTarget({ + routeId: undefined, + params: {}, + threadPanel: noThread, + }), + ).toBeUndefined(); + }); + + it("treats a closed thread (null) as nothing open", () => { + expect( + activeNotificationTarget({ + routeId: "/website/activity", + params: {}, + threadPanel: { openByChannel: { activity: null }, collapsed: false }, + }), + ).toBeUndefined(); + }); +}); diff --git a/packages/ui/src/features/notifications/activeTarget.ts b/packages/ui/src/features/notifications/activeTarget.ts new file mode 100644 index 0000000000..0ac3e91d3f --- /dev/null +++ b/packages/ui/src/features/notifications/activeTarget.ts @@ -0,0 +1,65 @@ +import type { NotificationTarget } from "@posthog/platform/notifications"; +import { ACTIVITY_THREAD_KEY } from "@posthog/ui/features/canvas/stores/threadPanelStore"; + +/** The open thread panel, as much of it as deciding "am I looking at this?" needs. */ +export interface ThreadPanelSnapshot { + /** Surface key → the task whose thread is open there. */ + openByChannel: Record; + /** Collapsed to a rail: the thread is no longer on screen. */ + collapsed: boolean; +} + +/** + * What the viewer is looking at, for deciding whether a notification about it + * would be telling them something they can already see. + * + * The route alone isn't the answer. A task's thread opens *beside* the channel + * feed and the Activity list without changing the route, so keying off the + * route would announce "needs your input" for a task filling half the screen. + * The open thread panel is as much "viewing the task" as its own route is. + */ +export function activeNotificationTarget({ + routeId, + params, + threadPanel, +}: { + routeId: string | undefined; + params: Record; + threadPanel: ThreadPanelSnapshot; +}): NotificationTarget | undefined { + // A collapsed panel is a rail with nothing legible in it, so it isn't viewing. + const openThreadTaskId = (key: string | undefined): string | undefined => + !key || threadPanel.collapsed + ? undefined + : (threadPanel.openByChannel[key] ?? undefined); + + switch (routeId) { + case "/code/tasks/$taskId": + case "/website/$channelId/tasks/$taskId": + return params.taskId + ? { kind: "task", taskId: params.taskId } + : undefined; + + case "/website/$channelId/dashboards/$dashboardId": + return params.channelId && params.dashboardId + ? { + kind: "canvas", + channelId: params.channelId, + dashboardId: params.dashboardId, + } + : undefined; + + // The channel feed and the Activity list both host a thread beside them. + case "/website/$channelId": { + const taskId = openThreadTaskId(params.channelId); + return taskId ? { kind: "task", taskId } : undefined; + } + case "/website/activity": { + const taskId = openThreadTaskId(ACTIVITY_THREAD_KEY); + return taskId ? { kind: "task", taskId } : undefined; + } + + default: + return undefined; + } +} diff --git a/packages/ui/src/features/sidebar/components/items/ActivityItem.tsx b/packages/ui/src/features/sidebar/components/items/ActivityItem.tsx index 5ccd296933..f0ab9b1133 100644 --- a/packages/ui/src/features/sidebar/components/items/ActivityItem.tsx +++ b/packages/ui/src/features/sidebar/components/items/ActivityItem.tsx @@ -1,5 +1,5 @@ import { BellIcon } from "@phosphor-icons/react"; -import { countUnseenActivity } from "@posthog/core/canvas/mentionActivity"; +import { countUnreadMentions } from "@posthog/core/canvas/mentionActivity"; import { useMentionActivity } from "@posthog/ui/features/canvas/hooks/useMentionActivity"; import { useActivitySeenStore } from "@posthog/ui/features/canvas/stores/activitySeenStore"; import { useMemo } from "react"; @@ -12,14 +12,15 @@ interface ActivityItemProps { } // The Activity nav row with its unread-mentions dot. Owns the mentions -// subscription so the query mounts once here; the badge counts thread mentions -// newer than the last time the Activity page was opened. +// subscription so the query mounts once here; the badge counts mentions whose +// thread the viewer hasn't opened — visiting the page doesn't clear it. export function ActivityItem({ isActive, onClick }: ActivityItemProps) { const { items } = useMentionActivity(); const lastSeenAt = useActivitySeenStore((s) => s.lastSeenAt); + const readMessageIds = useActivitySeenStore((s) => s.readMessageIds); const unseen = useMemo( - () => countUnseenActivity(items, lastSeenAt), - [items, lastSeenAt], + () => countUnreadMentions(items, lastSeenAt, readMessageIds), + [items, lastSeenAt, readMessageIds], ); return ( =16} @@ -5647,8 +5662,8 @@ packages: react: 19.2.6 react-dom: 19.2.6 - '@posthog/quill@0.3.0-beta.24': - resolution: {integrity: sha512-lBnnFqX3aVNXPPc5j8pO2cGr99IeClIr2ByVTdote477Bnqwt8HDX7jbFxCwiUr8ARnuSTvhDrqeagZzplwE9Q==} + '@posthog/quill@0.3.0-beta.25': + resolution: {integrity: sha512-lkrsQI1sXonjWb6/uhzTjErTN7vqOyv7DtSzwOPVuDQenfOKMZHRDVknHAMnS0tc03Y7WOKyYD/SbAHEg+H0sg==} engines: {node: '>=20'} peerDependencies: '@base-ui/react': ^1.3.0 @@ -7843,6 +7858,9 @@ packages: '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -8771,7 +8789,7 @@ packages: bippy@0.5.43: resolution: {integrity: sha512-Tvu7b1M7+d8b9/YHaCeODEsi2CgbuoBql+dWSBrNnCuqJ1gMUeY3i0r+319hvjjl5GVBP6FFWxrKnq3fhZER0w==} peerDependencies: - react: '>=17.0.1' + react: 19.2.6 bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -9502,8 +9520,8 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - deslop-js@0.7.1: - resolution: {integrity: sha512-HsEoRI/bzuD0o2OVczYz42SXTCl5of3ax6eiojZbC/7gJsPNxxjPRvBysP88LXsHujYrIGJnGtFRnHwCmKWuxQ==} + deslop-js@0.7.4: + resolution: {integrity: sha512-OKhLEBDFk3wYgfSUz65O/1SP2L/jcMOYym5EB9wvEuDUrJrg+32X3tnUHHvlB/2sSGDU6wCSMqy1006EToOptA==} destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} @@ -11758,12 +11776,6 @@ packages: lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} - lightningcss-android-arm64@1.31.1: - resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -11776,12 +11788,6 @@ packages: cpu: [arm64] os: [darwin] - lightningcss-darwin-arm64@1.31.1: - resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - lightningcss-darwin-arm64@1.32.0: resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} @@ -11794,12 +11800,6 @@ packages: cpu: [x64] os: [darwin] - lightningcss-darwin-x64@1.31.1: - resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - lightningcss-darwin-x64@1.32.0: resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} @@ -11812,12 +11812,6 @@ packages: cpu: [x64] os: [freebsd] - lightningcss-freebsd-x64@1.31.1: - resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - lightningcss-freebsd-x64@1.32.0: resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} @@ -11830,12 +11824,6 @@ packages: cpu: [arm] os: [linux] - lightningcss-linux-arm-gnueabihf@1.31.1: - resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - lightningcss-linux-arm-gnueabihf@1.32.0: resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} @@ -11849,13 +11837,6 @@ packages: os: [linux] libc: [glibc] - lightningcss-linux-arm64-gnu@1.31.1: - resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} @@ -11870,13 +11851,6 @@ packages: os: [linux] libc: [musl] - lightningcss-linux-arm64-musl@1.31.1: - resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} @@ -11891,13 +11865,6 @@ packages: os: [linux] libc: [glibc] - lightningcss-linux-x64-gnu@1.31.1: - resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} @@ -11912,13 +11879,6 @@ packages: os: [linux] libc: [musl] - lightningcss-linux-x64-musl@1.31.1: - resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} @@ -11932,12 +11892,6 @@ packages: cpu: [arm64] os: [win32] - lightningcss-win32-arm64-msvc@1.31.1: - resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} @@ -11950,12 +11904,6 @@ packages: cpu: [x64] os: [win32] - lightningcss-win32-x64-msvc@1.31.1: - resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - lightningcss-win32-x64-msvc@1.32.0: resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} @@ -11966,10 +11914,6 @@ packages: resolution: {integrity: sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==} engines: {node: '>= 12.0.0'} - lightningcss@1.31.1: - resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} - engines: {node: '>= 12.0.0'} - lightningcss@1.32.0: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} @@ -12873,8 +12817,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - oxlint-plugin-react-doctor@0.7.1: - resolution: {integrity: sha512-fvARsCESDZYvDIlhuB/JlDeUhTOLHYstoDJCKm0pzh4HQQJVVV6gcrQajBjYo/hdHC1ukl7btKTK3rk4uZwuew==} + oxlint-plugin-react-doctor@0.7.4: + resolution: {integrity: sha512-0JK+5KdT3maDvcXH9Qe79z5wVbWHax6dpqzdEM83e5uMLLZfqI6WVpCJvdtVoknMYLvJY0hdyFTf3adpzZlrQw==} engines: {node: ^20.19.0 || >=22.13.0} oxlint@1.66.0: @@ -13520,8 +13464,8 @@ packages: resolution: {integrity: sha512-+NRMYs2DyTP4/tqWz371Oo50JqmWltR1h2gcdgUMAWZJIAvrd0/SqlCfx7tpzpl/s36rzw6qH2MjoNrxtRNYhA==} engines: {node: ^20.9.0 || >=22} - react-doctor@0.7.1: - resolution: {integrity: sha512-Gmty7Enyrh6GPlz6Paq+UoL2O7YkTzNeHdflbqdp6fspX1UbUem5ejPyIUgo1jf77D6kB+INqsi2K+Mk/K8uBQ==} + react-doctor@0.7.4: + resolution: {integrity: sha512-OcNqh3joJ6ihycni2d/IgZq/aJBgn5XXsztwRpt9bvb195jM76PyYgK5M2LjWnzIScvPFZu3GzQjHqzoKmjLaA==} engines: {node: ^20.19.0 || >=22.13.0} hasBin: true @@ -17296,6 +17240,12 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -17313,6 +17263,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 @@ -17333,6 +17288,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild-kit/core-utils@3.3.2': dependencies: esbuild: 0.18.20 @@ -17868,7 +17828,7 @@ snapshots: glob: 13.0.1 hermes-parser: 0.29.1 jsc-safe-url: 0.2.4 - lightningcss: 1.31.1 + lightningcss: 1.32.0 minimatch: 9.0.5 postcss: 8.4.49 resolve-from: 5.0.0 @@ -18694,11 +18654,11 @@ snapshots: '@joplin/turndown-plugin-gfm@1.0.67': {} - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(typescript@5.9.3)': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(typescript@5.9.3)': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@5.9.3) - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) optionalDependencies: typescript: 5.9.3 @@ -19119,6 +19079,13 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.2 + optional: true + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: '@emnapi/core': 1.9.2 @@ -19126,6 +19093,20 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 + optional: true + '@noble/ciphers@1.3.0': {} '@noble/curves@1.9.7': @@ -19489,9 +19470,9 @@ snapshots: '@oxc-parser/binding-openharmony-arm64@0.135.0': optional: true - '@oxc-parser/binding-wasm32-wasi@0.120.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@oxc-parser/binding-wasm32-wasi@0.120.0(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': dependencies: - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -19508,14 +19489,14 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@oxc-parser/binding-wasm32-wasi@0.135.0': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@oxc-parser/binding-win32-arm64-msvc@0.120.0': @@ -19984,7 +19965,7 @@ snapshots: react-dom: 19.2.6(react@19.2.6) simple-statistics: 7.8.9 - '@posthog/quill@0.3.0-beta.24(@base-ui/react@1.3.0(@types/react@19.2.17)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(tailwindcss@4.2.2)': + '@posthog/quill@0.3.0-beta.25(@base-ui/react@1.3.0(@types/react@19.2.17)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(tailwindcss@4.2.2)': dependencies: '@base-ui/react': 1.3.0(@types/react@19.2.17)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) class-variance-authority: 0.7.1 @@ -19996,7 +19977,7 @@ snapshots: tailwind-merge: 2.6.1 tailwindcss: 4.2.2 - '@posthog/quill@0.3.0-beta.24(@base-ui/react@1.3.0(@types/react@19.2.17)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(tailwindcss@4.3.1)': + '@posthog/quill@0.3.0-beta.25(@base-ui/react@1.3.0(@types/react@19.2.17)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(tailwindcss@4.3.1)': dependencies: '@base-ui/react': 1.3.0(@types/react@19.2.17)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) class-variance-authority: 0.7.1 @@ -21075,9 +21056,9 @@ snapshots: '@rolldown/binding-openharmony-arm64@1.0.0-beta.53': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.53(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.53(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': dependencies: - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -21371,10 +21352,10 @@ snapshots: axe-core: 4.11.1 storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@storybook/addon-docs@10.4.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2))': + '@storybook/addon-docs@10.4.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@19.2.6) - '@storybook/csf-plugin': 10.4.1(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2)) + '@storybook/csf-plugin': 10.4.1(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2)) '@storybook/icons': 2.0.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@storybook/react-dom-shim': 10.4.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) react: 19.2.6 @@ -21390,25 +21371,25 @@ snapshots: - vite - webpack - '@storybook/builder-vite@10.4.1(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2))': + '@storybook/builder-vite@10.4.1(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2))': dependencies: - '@storybook/csf-plugin': 10.4.1(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2)) + '@storybook/csf-plugin': 10.4.1(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2)) storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) ts-dedent: 2.2.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.4.1(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2))': + '@storybook/csf-plugin@10.4.1(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2))': dependencies: storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.2 rollup: 4.57.1 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) webpack: 5.105.0(@swc/core@1.15.43)(esbuild@0.27.2) '@storybook/global@5.0.0': {} @@ -21427,11 +21408,11 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@storybook/react-vite@10.4.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.27.2)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@5.9.3)(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2))': + '@storybook/react-vite@10.4.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.27.2)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@5.9.3)(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(typescript@5.9.3) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(typescript@5.9.3) '@rollup/pluginutils': 5.3.0(rollup@4.57.1) - '@storybook/builder-vite': 10.4.1(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2)) + '@storybook/builder-vite': 10.4.1(esbuild@0.27.2)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(rollup@4.57.1)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2)) '@storybook/react': 10.4.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@5.9.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -21441,7 +21422,7 @@ snapshots: resolve: 1.22.11 storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) tsconfig-paths: 4.2.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -21773,19 +21754,19 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.1 '@tailwindcss/oxide-win32-x64-msvc': 4.3.1 - '@tailwindcss/vite@4.2.2(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': + '@tailwindcss/vite@4.2.2(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.2.2 '@tailwindcss/oxide': 4.2.2 tailwindcss: 4.2.2 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) - '@tailwindcss/vite@4.3.1(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': + '@tailwindcss/vite@4.3.1(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.3.1 '@tailwindcss/oxide': 4.3.1 tailwindcss: 4.3.1 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) '@tanstack/devtools-client@0.0.8': dependencies: @@ -21800,16 +21781,16 @@ snapshots: '@tanstack/devtools-event-client@0.5.0': {} - '@tanstack/devtools-vite@0.8.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': + '@tanstack/devtools-vite@0.8.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@tanstack/devtools-client': 0.0.8 '@tanstack/devtools-event-bus': 0.4.2 chalk: 5.6.2 launch-editor: 2.14.1 magic-string: 0.30.21 - oxc-parser: 0.120.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + oxc-parser: 0.120.0(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) picomatch: 4.0.3 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -21893,7 +21874,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.168.18(@tanstack/react-router@1.170.15(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2))': + '@tanstack/router-plugin@1.168.18(@tanstack/react-router@1.170.15(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(webpack@5.105.0(@swc/core@1.15.43)(esbuild@0.27.2))': dependencies: '@babel/core': 7.29.0 '@babel/template': 7.28.6 @@ -21906,7 +21887,7 @@ snapshots: zod: 4.4.3 optionalDependencies: '@tanstack/react-router': 1.170.15(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) webpack: 5.105.0(@swc/core@1.15.43)(esbuild@0.27.2) transitivePeerDependencies: - supports-color @@ -22237,6 +22218,11 @@ snapshots: tslib: 2.8.1 optional: true + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -22521,7 +22507,7 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': @@ -22545,7 +22531,7 @@ snapshots: '@urql/core': 5.2.0(graphql@16.12.0) wonka: 6.3.5 - '@vitejs/plugin-react@4.7.0(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitejs/plugin-react@4.7.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -22553,11 +22539,11 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.7.0(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitejs/plugin-react@4.7.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -22565,7 +22551,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -22581,7 +22567,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@5.2.0(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitejs/plugin-react@5.2.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -22589,7 +22575,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -22605,7 +22591,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/expect@3.2.4': dependencies: @@ -22633,14 +22619,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.6(msw@2.12.8(@types/node@25.2.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.1.6(msw@2.12.8(@types/node@25.2.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.12.8(@types/node@25.2.0)(typescript@5.9.3) - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) '@vitest/mocker@4.1.8(msw@2.12.8(@types/node@20.19.41)(typescript@5.9.3))(vite@7.3.5(@types/node@20.19.41)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: @@ -22660,14 +22646,14 @@ snapshots: msw: 2.12.8(@types/node@22.20.0)(typescript@5.9.3) vite: 7.3.5(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) - '@vitest/mocker@4.1.8(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.1.8(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.12.8(@types/node@24.12.0)(typescript@5.9.3) - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) '@vitest/mocker@4.1.8(msw@2.12.8(@types/node@25.2.0)(typescript@5.9.3))(vite@7.3.5(@types/node@25.2.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))': dependencies: @@ -22744,7 +22730,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/utils@3.2.4': dependencies: @@ -24097,7 +24083,7 @@ snapshots: dequal@2.0.3: {} - deslop-js@0.7.1: + deslop-js@0.7.4: dependencies: '@oxc-project/types': 0.132.0 fast-glob: 3.3.3 @@ -24332,7 +24318,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron-vite@4.0.1(@swc/core@1.15.43)(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): + electron-vite@4.0.1(@swc/core@1.15.43)(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) @@ -24340,7 +24326,7 @@ snapshots: esbuild: 0.25.12 magic-string: 0.30.21 picocolors: 1.1.1 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) optionalDependencies: '@swc/core': 1.15.43 transitivePeerDependencies: @@ -26885,99 +26871,66 @@ snapshots: transitivePeerDependencies: - supports-color - lightningcss-android-arm64@1.31.1: - optional: true - lightningcss-android-arm64@1.32.0: optional: true lightningcss-darwin-arm64@1.27.0: optional: true - lightningcss-darwin-arm64@1.31.1: - optional: true - lightningcss-darwin-arm64@1.32.0: optional: true lightningcss-darwin-x64@1.27.0: optional: true - lightningcss-darwin-x64@1.31.1: - optional: true - lightningcss-darwin-x64@1.32.0: optional: true lightningcss-freebsd-x64@1.27.0: optional: true - lightningcss-freebsd-x64@1.31.1: - optional: true - lightningcss-freebsd-x64@1.32.0: optional: true lightningcss-linux-arm-gnueabihf@1.27.0: optional: true - lightningcss-linux-arm-gnueabihf@1.31.1: - optional: true - lightningcss-linux-arm-gnueabihf@1.32.0: optional: true lightningcss-linux-arm64-gnu@1.27.0: optional: true - lightningcss-linux-arm64-gnu@1.31.1: - optional: true - lightningcss-linux-arm64-gnu@1.32.0: optional: true lightningcss-linux-arm64-musl@1.27.0: optional: true - lightningcss-linux-arm64-musl@1.31.1: - optional: true - lightningcss-linux-arm64-musl@1.32.0: optional: true lightningcss-linux-x64-gnu@1.27.0: optional: true - lightningcss-linux-x64-gnu@1.31.1: - optional: true - lightningcss-linux-x64-gnu@1.32.0: optional: true lightningcss-linux-x64-musl@1.27.0: optional: true - lightningcss-linux-x64-musl@1.31.1: - optional: true - lightningcss-linux-x64-musl@1.32.0: optional: true lightningcss-win32-arm64-msvc@1.27.0: optional: true - lightningcss-win32-arm64-msvc@1.31.1: - optional: true - lightningcss-win32-arm64-msvc@1.32.0: optional: true lightningcss-win32-x64-msvc@1.27.0: optional: true - lightningcss-win32-x64-msvc@1.31.1: - optional: true - lightningcss-win32-x64-msvc@1.32.0: optional: true @@ -26996,22 +26949,6 @@ snapshots: lightningcss-win32-arm64-msvc: 1.27.0 lightningcss-win32-x64-msvc: 1.27.0 - lightningcss@1.31.1: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.31.1 - lightningcss-darwin-arm64: 1.31.1 - lightningcss-darwin-x64: 1.31.1 - lightningcss-freebsd-x64: 1.31.1 - lightningcss-linux-arm-gnueabihf: 1.31.1 - lightningcss-linux-arm64-gnu: 1.31.1 - lightningcss-linux-arm64-musl: 1.31.1 - lightningcss-linux-x64-gnu: 1.31.1 - lightningcss-linux-x64-musl: 1.31.1 - lightningcss-win32-arm64-msvc: 1.31.1 - lightningcss-win32-x64-msvc: 1.31.1 - lightningcss@1.32.0: dependencies: detect-libc: 2.1.2 @@ -28305,7 +28242,7 @@ snapshots: outvariant@1.4.3: {} - oxc-parser@0.120.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): + oxc-parser@0.120.0(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2): dependencies: '@oxc-project/types': 0.120.0 optionalDependencies: @@ -28325,7 +28262,7 @@ snapshots: '@oxc-parser/binding-linux-x64-gnu': 0.120.0 '@oxc-parser/binding-linux-x64-musl': 0.120.0 '@oxc-parser/binding-openharmony-arm64': 0.120.0 - '@oxc-parser/binding-wasm32-wasi': 0.120.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@oxc-parser/binding-wasm32-wasi': 0.120.0(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) '@oxc-parser/binding-win32-arm64-msvc': 0.120.0 '@oxc-parser/binding-win32-ia32-msvc': 0.120.0 '@oxc-parser/binding-win32-x64-msvc': 0.120.0 @@ -28477,7 +28414,7 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.45.0 '@oxfmt/binding-win32-x64-msvc': 0.45.0 - oxlint-plugin-react-doctor@0.7.1: + oxlint-plugin-react-doctor@0.7.4: dependencies: '@typescript-eslint/types': 8.62.0 eslint-scope: 9.1.2 @@ -29230,19 +29167,19 @@ snapshots: transitivePeerDependencies: - supports-color - react-doctor@0.7.1(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(eslint@10.5.0(jiti@2.7.0)): + react-doctor@0.7.4(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(eslint@10.5.0(jiti@2.7.0)): dependencies: '@babel/code-frame': 7.29.0 '@sentry/node': 10.61.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1)) agent-install: 0.0.5 conf: 15.1.0 confbox: 0.2.4 - deslop-js: 0.7.1 + deslop-js: 0.7.4 eslint-plugin-react-hooks: 7.1.1(eslint@10.5.0(jiti@2.7.0)) jiti: 2.7.0 magicast: 0.5.3 oxlint: 1.66.0 - oxlint-plugin-react-doctor: 0.7.1 + oxlint-plugin-react-doctor: 0.7.4 prompts: 2.4.2 typescript: 5.9.3 vscode-languageserver: 9.0.1 @@ -29512,7 +29449,7 @@ snapshots: preact: 10.29.2 prompts: 2.4.2 react: 19.2.6 - react-doctor: 0.7.1(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(eslint@10.5.0(jiti@2.7.0)) + react-doctor: 0.7.4(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(eslint@10.5.0(jiti@2.7.0)) react-dom: 19.2.6(react@19.2.6) react-grab: 0.1.48(react@19.2.6) optionalDependencies: @@ -29789,14 +29726,14 @@ snapshots: sprintf-js: 1.1.3 optional: true - rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0): + rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: '@oxc-project/runtime': 0.101.0 fdir: 6.5.0(picomatch@4.0.3) lightningcss: 1.32.0 picomatch: 4.0.3 postcss: 8.5.15 - rolldown: 1.0.0-beta.53(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + rolldown: 1.0.0-beta.53(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.12.0 @@ -29810,14 +29747,14 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0): + rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: '@oxc-project/runtime': 0.101.0 fdir: 6.5.0(picomatch@4.0.3) lightningcss: 1.32.0 picomatch: 4.0.3 postcss: 8.5.15 - rolldown: 1.0.0-beta.53(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + rolldown: 1.0.0-beta.53(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.2.0 @@ -29831,14 +29768,14 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0): + rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: '@oxc-project/runtime': 0.101.0 fdir: 6.5.0(picomatch@4.0.3) lightningcss: 1.32.0 picomatch: 4.0.3 postcss: 8.5.15 - rolldown: 1.0.0-beta.53(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + rolldown: 1.0.0-beta.53(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.2.0 @@ -29852,7 +29789,7 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - rolldown@1.0.0-beta.53(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): + rolldown@1.0.0-beta.53(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2): dependencies: '@oxc-project/types': 0.101.0 '@rolldown/pluginutils': 1.0.0-beta.53 @@ -29867,7 +29804,7 @@ snapshots: '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.53 '@rolldown/binding-linux-x64-musl': 1.0.0-beta.53 '@rolldown/binding-openharmony-arm64': 1.0.0-beta.53 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.53(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.53(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.53 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.53 transitivePeerDependencies: @@ -31201,12 +31138,12 @@ snapshots: react-dom: 19.2.6(react@19.2.6) solid-js: 1.9.13 - vite-tsconfig-paths@6.1.1(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(typescript@5.9.3): + vite-tsconfig-paths@6.1.1(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))(typescript@5.9.3): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - supports-color - typescript @@ -31279,10 +31216,10 @@ snapshots: tsx: 4.22.4 yaml: 2.9.0 - vitest@4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.2.0)(jsdom@26.1.0)(msw@2.12.8(@types/node@25.2.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): + vitest@4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.2.0)(jsdom@26.1.0)(msw@2.12.8(@types/node@25.2.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.6 - '@vitest/mocker': 4.1.6(msw@2.12.8(@types/node@25.2.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/mocker': 4.1.6(msw@2.12.8(@types/node@25.2.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.6 '@vitest/runner': 4.1.6 '@vitest/snapshot': 4.1.6 @@ -31299,7 +31236,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.2.0)(esbuild@0.27.2)(jiti@1.21.7)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 @@ -31368,10 +31305,10 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): + vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/mocker': 4.1.8(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -31388,7 +31325,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 @@ -31399,10 +31336,10 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): + vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(@vitest/ui@4.1.8)(jsdom@26.1.0)(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/mocker': 4.1.8(msw@2.12.8(@types/node@24.12.0)(typescript@5.9.3))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -31419,7 +31356,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: rolldown-vite@7.3.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) + vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a0bac050d7..0c59399bc0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,7 +12,7 @@ catalog: '@earendil-works/pi-tui': 0.80.6 '@parcel/watcher': ^2.5.6 '@phosphor-icons/react': ^2.1.10 - '@posthog/quill': 0.3.0-beta.24 + '@posthog/quill': 0.3.0-beta.25 '@radix-ui/themes': ^3.2.1 '@tanstack/react-query': ^5.100.14 '@tanstack/react-router': ^1.170.10