Skip to content

Commit ab261a2

Browse files
committed
fix(webapp): serialize preference writes and improve favorite defaults
New favorites now land at the top of the Favorites section, and favoriting a detail page whose title is generic uses the short id from the URL (e.g. "Run: 05hrqq9n", matching the id shown in the runs table). The star tooltip previews that label before you save. Fix sidebar customizations reappearing after Reset + Confirm: every preference writer is a read-modify-write over one JSON column, and a concurrent write (a debounced collapse or width save, a favorite toggle, a dashboard reorder) could land after the clear and write the old customization back from its stale read. All preference writes now re-read the row under a lock inside a transaction, so concurrent writers serialize instead of clobbering each other.
1 parent d4eb604 commit ab261a2

3 files changed

Lines changed: 213 additions & 187 deletions

File tree

apps/webapp/app/components/navigation/FavoritePageButton.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ export function FavoritePageButton({ pageTitle }: { pageTitle?: string }) {
3232
const url = location.pathname + stripFavoriteSearchParam(location.search);
3333
const existing = favorites.find((favorite) => favorite.url === url);
3434
const isFavorited = existing !== undefined;
35-
// A renamed favorite keeps its custom name in the tooltip
36-
const pageName =
37-
existing?.label ?? (pageTitle?.trim() || resolvePageMeta(location.pathname).name);
35+
// The tooltip names the favorite: its custom name once saved, else the label saving would use
36+
// (which includes detail-page ids, e.g. "Run: 05hrqq9n")
37+
const pageName = existing?.label ?? buildFavoriteLabel(location.pathname, pageTitle);
3838

3939
const toggle = () => {
4040
if (existing) {

apps/webapp/app/components/navigation/favoritePages.tsx

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -238,19 +238,41 @@ export function resolvePageMeta(pathname: string): PageMeta {
238238

239239
const MAX_LABEL_LENGTH = 50;
240240

241+
/**
242+
* Short id for a detail page whose last URL segment is a friendly id ("run_cmryyza…05hrqq9n").
243+
* Uses the same 8-character tail the dashboard tables display, so the label matches what the
244+
* user sees elsewhere.
245+
*/
246+
function detailIdFromPath(pathname: string): string | undefined {
247+
const segments = pathname.split("/").filter(Boolean);
248+
const last = segments[segments.length - 1];
249+
if (last && /^(run|batch|session|deployment|schedule|waitpoint)_[a-z0-9]{8,}$/i.test(last)) {
250+
return last.slice(-8);
251+
}
252+
return undefined;
253+
}
254+
241255
/**
242256
* Compose the default side menu label for a favorited page. List pages keep their nav name
243-
* ("Queues"); detail pages get an identifying prefix ("Queue: email-queue"). Users can rename.
257+
* ("Queues"); detail pages get an identifying prefix ("Queue: email-queue", or the short id for
258+
* friendly-id pages: "Run: 05hrqq9n"). Users can rename.
244259
*/
245260
export function buildFavoriteLabel(pathname: string, pageTitle: string | undefined): string {
246261
const meta = resolvePageMeta(pathname);
247262
const title = pageTitle?.trim();
263+
const prefix = meta.singular ?? meta.name;
264+
265+
// Generic titles ("Runs", "Run") identify nothing on a detail page; prefer the short id
266+
const isGenericTitle =
267+
!title ||
268+
title.toLowerCase() === meta.name.toLowerCase() ||
269+
title.toLowerCase() === prefix.toLowerCase();
248270

249-
if (!title || title.toLowerCase() === meta.name.toLowerCase()) {
250-
return meta.name;
271+
if (isGenericTitle) {
272+
const detailId = detailIdFromPath(pathname);
273+
return detailId ? `${prefix}: ${detailId}` : meta.name;
251274
}
252275

253-
const prefix = meta.singular ?? meta.name;
254276
const label = title.toLowerCase().startsWith(prefix.toLowerCase())
255277
? title
256278
: `${prefix}: ${title}`;
@@ -281,9 +303,10 @@ export function useFavorites(): FavoritePage[] {
281303
const icon = fetcher.formData.get("icon");
282304
if (typeof url !== "string" || typeof label !== "string") break;
283305
if (!favorites.some((f) => f.url === url)) {
306+
// Newest favorites go to the top of the section (matches addFavorite server-side)
284307
favorites = [
285-
...favorites,
286308
{ id, url, label, icon: typeof icon === "string" ? icon : undefined },
309+
...favorites,
287310
];
288311
}
289312
break;

0 commit comments

Comments
 (0)