Skip to content

Commit e988f5a

Browse files
authored
Merge PR #112: promote staging → main — per-credential agent usage + sharing Phase 2
Per-credential Claude Code/Codex usage (grouped by agent, daily+weekly) + system_prompt parity; sharing Phase 2 editor collaboration + polish. CI green, dry-run merge clean.
2 parents baebd78 + 77369e7 commit e988f5a

36 files changed

Lines changed: 3386 additions & 255 deletions

apps/web/src/components/DefaultCatchBoundary.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import {
66
useMatch,
77
useRouter,
88
} from "@tanstack/react-router";
9+
import { useEffect } from "react";
10+
import {
11+
isChunkLoadError,
12+
reloadOnceForStaleChunk,
13+
} from "../lib/handle-stale-chunk";
914

1015
export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
1116
const router = useRouter();
@@ -14,8 +19,33 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
1419
select: (state) => state.id === rootRouteId,
1520
});
1621

22+
// Safety net for the case where the stale-chunk rejection reaches the
23+
// boundary (e.g. `vite:preloadError` didn't fire): one guarded hard reload
24+
// picks up the current build, since router.invalidate() can't refetch a 404.
25+
const chunkError = isChunkLoadError(error);
26+
useEffect(() => {
27+
if (chunkError) reloadOnceForStaleChunk();
28+
}, [chunkError]);
29+
1730
console.error(error);
1831

32+
if (chunkError) {
33+
return (
34+
<div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
35+
<p className="text-sm text-muted-foreground">
36+
Updating to the latest version…
37+
</p>
38+
<button
39+
type="button"
40+
onClick={() => window.location.reload()}
41+
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
42+
>
43+
Reload
44+
</button>
45+
</div>
46+
);
47+
}
48+
1949
return (
2050
<div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
2151
<ErrorComponent error={error} />

apps/web/src/components/chat/chat-messages.tsx

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Id } from "@harness/convex-backend/convex/_generated/dataModel";
2+
import { useNavigate } from "@tanstack/react-router";
23
import { ChevronDown, ChevronRight, Sparkles } from "lucide-react";
34
import { motion } from "motion/react";
45
import React, {
@@ -24,7 +25,7 @@ import { type DisplayMode, MessageActions } from "../message-actions";
2425
import { MessageAttachments } from "../message-attachments";
2526
import { PendingResponseIndicator } from "../pending-response-indicator";
2627
import { RoseCurveSpinner } from "../rose-curve-spinner";
27-
import { Avatar, AvatarFallback } from "../ui/avatar";
28+
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
2829
import { StreamingUsage, ThinkingBlock, ToolCallBlock } from "./message-blocks";
2930

3031
/** Superset of stream parts and persisted Convex parts. */
@@ -258,6 +259,7 @@ export function ChatMessages({
258259
activeConversation,
259260
forkedFromConversationId,
260261
forkedFromConversationTitle,
262+
forkedFromShareToken,
261263
forkedAtMessageCount,
262264
onNavigateToConversation,
263265
isStreaming,
@@ -295,6 +297,10 @@ export function ChatMessages({
295297
};
296298
model?: string;
297299
interrupted?: boolean;
300+
// Author attribution snapshot for collaborator-sent messages (name +
301+
// avatar only). Absent on the owner's own messages.
302+
authorName?: string;
303+
authorImageUrl?: string;
298304
attachments?: Array<{
299305
storageId: Id<"_storage">;
300306
mimeType: string;
@@ -340,6 +346,7 @@ export function ChatMessages({
340346
| undefined;
341347
forkedFromConversationId?: Id<"conversations">;
342348
forkedFromConversationTitle?: string;
349+
forkedFromShareToken?: string;
343350
forkedAtMessageCount?: number;
344351
onNavigateToConversation: (convoId: Id<"conversations"> | null) => void;
345352
isStreaming: boolean;
@@ -351,6 +358,7 @@ export function ChatMessages({
351358
/** Shared view: resolve attachment URLs through the token-scoped query. */
352359
shareToken?: string;
353360
}) {
361+
const navigate = useNavigate();
354362
const scrollRef = useRef<HTMLDivElement>(null);
355363
const highlightTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
356364
// "Pinned to bottom" — when true, auto-scroll follows new content.
@@ -850,7 +858,12 @@ export function ChatMessages({
850858
: undefined
851859
}
852860
onEditPrompt={
853-
msg.role === "user" && i === lastUserMsgIdx
861+
// Editing a prompt forks-and-resends, which is owner-
862+
// centric; collaborators (shareToken set) send and
863+
// regenerate but don't rewrite the owner's prompts.
864+
msg.role === "user" &&
865+
i === lastUserMsgIdx &&
866+
!shareToken
854867
? () => onStartEditPrompt(msg._id, msg.content)
855868
: undefined
856869
}
@@ -900,8 +913,16 @@ export function ChatMessages({
900913
</div>
901914
{msg.role === "user" && (
902915
<Avatar className="h-7 w-7 shrink-0">
916+
{msg.authorImageUrl && (
917+
<AvatarImage
918+
src={msg.authorImageUrl}
919+
alt={msg.authorName ?? "User"}
920+
/>
921+
)}
903922
<AvatarFallback className="bg-muted text-foreground text-[10px]">
904-
U
923+
{msg.authorName
924+
? msg.authorName.charAt(0).toUpperCase()
925+
: "U"}
905926
</AvatarFallback>
906927
</Avatar>
907928
)}
@@ -913,11 +934,23 @@ export function ChatMessages({
913934
Branched from{" "}
914935
<button
915936
type="button"
916-
onClick={() =>
917-
onNavigateToConversation(
918-
forkedFromConversationId ?? null,
919-
)
920-
}
937+
onClick={() => {
938+
// A fork can ONLY come from a share link, so the
939+
// forker never owns the original — route them back to
940+
// the shared page, not an empty owner-gated /chat. (A
941+
// since-revoked link lands on the neutral "not
942+
// available" page, which is the correct UX.)
943+
if (forkedFromShareToken) {
944+
navigate({
945+
to: "/share/$token",
946+
params: { token: forkedFromShareToken },
947+
});
948+
} else {
949+
onNavigateToConversation(
950+
forkedFromConversationId ?? null,
951+
);
952+
}
953+
}}
921954
className="font-medium text-foreground underline underline-offset-2 hover:text-foreground/80"
922955
>
923956
{forkedFromConversationTitle}

0 commit comments

Comments
 (0)