Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit c4be25c

Browse files
authored
fix(canvas): review cleanups — head-failure toolbar gate, derived pin state
Gate the view-mode toolbar on currentHeadBuildFailure too, matching the fix already applied in CanvasBuildStatus, so a failed head build behind an older ready build still surfaces. Fold the pin's refresh key into the PinnedArtifact object instead of a third useState, track the eviction count with a running counter, avoid a Set round-trip in setThreadMounted, and share the post-publish re-read between the home-seed success and 409-retry paths. Generated-By: PostHog Code Task-Id: 50c0257c-3627-45cd-887c-f87101f06463
1 parent 80de518 commit c4be25c

3 files changed

Lines changed: 16 additions & 18 deletions

File tree

‎packages/core/src/canvas/dashboardsService.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,9 +386,8 @@ export class DashboardsService {
386386
// POST. On the 409 version conflict, re-read the head and retry once
387387
// against the fresh version id rather than failing the channel open.
388388
if (apiErrorStatus(error) !== 409) throw error;
389-
const fresh = await this.get(record.id);
390-
await publish(fresh?.currentVersionId ?? null);
391-
return fresh ?? record;
389+
const conflicted = await this.get(record.id);
390+
await publish(conflicted?.currentVersionId ?? null);
392391
}
393392
const fresh = await this.get(record.id);
394393
return fresh ?? record;

‎packages/ui/src/features/canvas/freeform/FreeformCanvasView.tsx‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
WarningIcon,
1010
} from "@phosphor-icons/react";
1111
import {
12+
currentHeadBuildFailure,
1213
hasActiveCanvasBuild,
1314
latestFinishedCanvasBuild,
1415
publishedCanvasBuild,
@@ -89,6 +90,10 @@ interface PinnedArtifact {
8990
url: string;
9091
/** Epoch ms the pinned URL was minted (the builds fetch that produced it). */
9192
mintedAt: number;
93+
/** The refresh nonce the pin was adopted under, so a remount also re-stamps
94+
* the pin's mint time (otherwise the expiry timer would keep firing on a URL
95+
* that's already been recovered). */
96+
refreshKey: number;
9297
}
9398

9499
// A freeform (React-in-iframe) canvas. The rendered output is, in priority
@@ -258,22 +263,18 @@ export function FreeformCanvasView({
258263
// what guarantees a wedged iframe actually retries: the token endpoint is the
259264
// authority, and a new URL for the same bucket would be byte-identical.
260265
const [artifactRefreshKey, setArtifactRefreshKey] = useState(0);
261-
// Track the refresh key the current pin was adopted under, so a remount also
262-
// re-stamps the pin's mint time (otherwise the expiry timer would keep firing
263-
// on a URL that's already been recovered).
264-
const [pinnedForRefreshKey, setPinnedForRefreshKey] = useState(-1);
265266
if (publishedBuild?.artifactUrl) {
266267
const adoptFresh =
267268
!pinnedArtifact ||
268269
pinnedArtifact.buildId !== publishedBuild.id ||
269-
pinnedForRefreshKey !== artifactRefreshKey;
270+
pinnedArtifact.refreshKey !== artifactRefreshKey;
270271
if (adoptFresh) {
271272
setPinnedArtifact({
272273
buildId: publishedBuild.id,
273274
url: publishedBuild.artifactUrl,
274275
mintedAt: buildsUpdatedAt || Date.now(),
276+
refreshKey: artifactRefreshKey,
275277
});
276-
setPinnedForRefreshKey(artifactRefreshKey);
277278
}
278279
} else if (lifecycle && pinnedArtifact) {
279280
// The lifecycle says there's no published build anymore — drop the pin.
@@ -502,6 +503,7 @@ export function FreeformCanvasView({
502503
!!lifecycle &&
503504
lifecycle.builds.length > 0 &&
504505
(hasActiveCanvasBuild(lifecycle) ||
506+
!!currentHeadBuildFailure(lifecycle) ||
505507
latestFinishedCanvasBuild(lifecycle)?.buildStatus === "failed");
506508
const showToolbar = interactive || hasBuildSignal;
507509

‎packages/ui/src/features/canvas/stores/freeformChatStore.ts‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,17 @@ export const useFreeformChatStore = create<FreeformChatStore>()((set) => {
6565
[threadId]: fn(s.threads[threadId] ?? EMPTY_FREEFORM_THREAD),
6666
};
6767
const mounted = new Set(s.mountedThreadIds);
68+
let evictable = threadOrder.filter((id) => !mounted.has(id)).length;
6869
let index = 0;
69-
while (
70-
threadOrder.length - mountedCount(threadOrder, mounted) > MAX_THREADS &&
71-
index < threadOrder.length
72-
) {
70+
while (evictable > MAX_THREADS && index < threadOrder.length) {
7371
const candidate = threadOrder[index];
7472
if (mounted.has(candidate)) {
7573
index++;
7674
continue;
7775
}
7876
threadOrder.splice(index, 1);
7977
delete threads[candidate];
78+
evictable--;
8079
}
8180
return { threads, threadOrder };
8281
});
@@ -98,17 +97,15 @@ export const useFreeformChatStore = create<FreeformChatStore>()((set) => {
9897
setThreadMounted: (threadId, mounted) => {
9998
set((s) => ({
10099
mountedThreadIds: mounted
101-
? [...new Set([...s.mountedThreadIds, threadId])]
100+
? s.mountedThreadIds.includes(threadId)
101+
? s.mountedThreadIds
102+
: [...s.mountedThreadIds, threadId]
102103
: s.mountedThreadIds.filter((id) => id !== threadId),
103104
}));
104105
},
105106
};
106107
});
107108

108-
function mountedCount(order: string[], mounted: ReadonlySet<string>): number {
109-
return order.reduce((count, id) => (mounted.has(id) ? count + 1 : count), 0);
110-
}
111-
112109
export function useFreeformThread(threadId: string): FreeformThreadState {
113110
return useFreeformChatStore(
114111
(s) => s.threads[threadId] ?? EMPTY_FREEFORM_THREAD,

0 commit comments

Comments
 (0)