mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-08 03:10:26 +00:00
feat(web): hide Updated/Changes by default, reorder Elapsed after Updated
Updated and Changes are now hidden by default in both the main runs list and the Children sub-tab — they're still toggleable via the column picker. The column order shifts so Elapsed lives between Updated and Changes (i.e. after Created/Updated), keeping the time-related columns grouped on the right. Defaults are applied in two places: fresh sessions (no stored prefs) and existing v1 stored prefs that have no `hide` field. Users who explicitly cleared all hides keep that choice; stored `hide: ""` serializes round-trip as `?hide=` (empty value) so the URL distinguishes "show every column" from "use defaults". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1724cbadcd
commit
febd5e2d2e
5 changed files with 60 additions and 22 deletions
|
|
@ -1,6 +1,35 @@
|
|||
import type { ListRunsDirectionEnum, ListRunsSortEnum } from "@qltysh/fabro-api-client";
|
||||
|
||||
import { parseHiddenColumns, serializeHiddenColumns } from "./toggleable-column";
|
||||
import type { ToggleableColumn } from "./toggleable-column";
|
||||
|
||||
// Columns hidden by default in both the main runs list and the Children
|
||||
// sub-tab. Users can still reveal them via the column picker.
|
||||
export const DEFAULT_HIDDEN_RUN_LIST_COLUMNS: readonly ToggleableColumn[] = [
|
||||
"updated",
|
||||
"changes",
|
||||
];
|
||||
|
||||
function defaultHideString(): string {
|
||||
return serializeHiddenColumns(new Set(DEFAULT_HIDDEN_RUN_LIST_COLUMNS)) ?? "";
|
||||
}
|
||||
|
||||
// Read the `hide` URL param honouring the convention that an absent param
|
||||
// means "use defaults" while an explicit empty value means "show every
|
||||
// column". Both forms persist round-trips through the column picker.
|
||||
export function hiddenColumnsFromSearchParams(
|
||||
searchParams: URLSearchParams,
|
||||
): Set<ToggleableColumn> {
|
||||
const raw = searchParams.get("hide");
|
||||
if (raw == null) return new Set(DEFAULT_HIDDEN_RUN_LIST_COLUMNS);
|
||||
return parseHiddenColumns(raw);
|
||||
}
|
||||
|
||||
function readHideField(searchParams: URLSearchParams): string {
|
||||
const raw = searchParams.get("hide");
|
||||
if (raw == null) return defaultHideString();
|
||||
return serializeHiddenColumns(parseHiddenColumns(raw)) ?? "";
|
||||
}
|
||||
|
||||
export type ViewMode = "columns" | "list";
|
||||
|
||||
|
|
@ -111,7 +140,7 @@ export function defaultRunsWorkspacePreferences(): RunsWorkspacePreferences {
|
|||
sort: "created_at",
|
||||
direction: "desc",
|
||||
size: DEFAULT_LIST_PAGE_SIZE,
|
||||
hide: "",
|
||||
hide: defaultHideString(),
|
||||
page: 1,
|
||||
};
|
||||
}
|
||||
|
|
@ -144,7 +173,6 @@ function normalizeStoredRunsWorkspacePreferences(value: unknown): RunsWorkspaceP
|
|||
return defaultRunsWorkspacePreferences();
|
||||
}
|
||||
|
||||
const hiddenColumns = parseHiddenColumns(stringValue(record.hide));
|
||||
const size = record.size;
|
||||
|
||||
return {
|
||||
|
|
@ -158,11 +186,20 @@ function normalizeStoredRunsWorkspacePreferences(value: unknown): RunsWorkspaceP
|
|||
sort: parseSort(stringValue(record.sort)),
|
||||
direction: parseDirection(stringValue(record.direction)),
|
||||
size: parsePageSize(typeof size === "number" || typeof size === "string" ? String(size) : null),
|
||||
hide: serializeHiddenColumns(hiddenColumns) ?? "",
|
||||
hide: normalizeStoredHide(stringValue(record.hide)),
|
||||
page: 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Stored records from before the default-hidden-columns change have no `hide`
|
||||
// field. Treat the absence as "use the new defaults" so existing users pick up
|
||||
// the new default automatically. An explicit empty string means the user
|
||||
// chose to show every column, so preserve it.
|
||||
function normalizeStoredHide(stored: string | null): string {
|
||||
if (stored == null) return defaultHideString();
|
||||
return serializeHiddenColumns(parseHiddenColumns(stored)) ?? "";
|
||||
}
|
||||
|
||||
export function runsWorkspacePreferencesFromSearchParams(
|
||||
searchParams: URLSearchParams,
|
||||
): RunsWorkspacePreferences {
|
||||
|
|
@ -177,7 +214,7 @@ export function runsWorkspacePreferencesFromSearchParams(
|
|||
sort: parseSort(searchParams.get("sort")),
|
||||
direction: parseDirection(searchParams.get("direction")),
|
||||
size: parsePageSize(searchParams.get("size")),
|
||||
hide: serializeHiddenColumns(parseHiddenColumns(searchParams.get("hide"))) ?? "",
|
||||
hide: readHideField(searchParams),
|
||||
page: parsePage(searchParams.get("page")),
|
||||
};
|
||||
}
|
||||
|
|
@ -195,7 +232,7 @@ export function runsWorkspacePreferencesToSearchParams(
|
|||
if (preferences.sort !== "created_at") params.set("sort", preferences.sort);
|
||||
if (preferences.direction === "asc") params.set("direction", "asc");
|
||||
if (preferences.size !== DEFAULT_LIST_PAGE_SIZE) params.set("size", String(preferences.size));
|
||||
if (preferences.hide !== "") params.set("hide", preferences.hide);
|
||||
if (preferences.hide !== defaultHideString()) params.set("hide", preferences.hide);
|
||||
if (preferences.page > 1) params.set("page", String(preferences.page));
|
||||
return params;
|
||||
}
|
||||
|
|
@ -281,7 +318,7 @@ export function defaultChildRunsListPreferences(): ChildRunsListPreferences {
|
|||
sort: "created_at",
|
||||
direction: "desc",
|
||||
size: DEFAULT_LIST_PAGE_SIZE,
|
||||
hide: "",
|
||||
hide: defaultHideString(),
|
||||
page: 1,
|
||||
};
|
||||
}
|
||||
|
|
@ -292,7 +329,6 @@ function normalizeStoredChildRunsListPreferences(value: unknown): ChildRunsListP
|
|||
return defaultChildRunsListPreferences();
|
||||
}
|
||||
|
||||
const hiddenColumns = parseHiddenColumns(stringValue(record.hide));
|
||||
const size = record.size;
|
||||
|
||||
return {
|
||||
|
|
@ -303,7 +339,7 @@ function normalizeStoredChildRunsListPreferences(value: unknown): ChildRunsListP
|
|||
sort: parseSort(stringValue(record.sort)),
|
||||
direction: parseDirection(stringValue(record.direction)),
|
||||
size: parsePageSize(typeof size === "number" || typeof size === "string" ? String(size) : null),
|
||||
hide: serializeHiddenColumns(hiddenColumns) ?? "",
|
||||
hide: normalizeStoredHide(stringValue(record.hide)),
|
||||
page: 1,
|
||||
};
|
||||
}
|
||||
|
|
@ -319,7 +355,7 @@ export function childRunsListPreferencesFromSearchParams(
|
|||
sort: parseSort(searchParams.get("sort")),
|
||||
direction: parseDirection(searchParams.get("direction")),
|
||||
size: parsePageSize(searchParams.get("size")),
|
||||
hide: serializeHiddenColumns(parseHiddenColumns(searchParams.get("hide"))) ?? "",
|
||||
hide: readHideField(searchParams),
|
||||
page: parsePage(searchParams.get("page")),
|
||||
};
|
||||
}
|
||||
|
|
@ -334,7 +370,7 @@ export function childRunsListPreferencesToSearchParams(
|
|||
if (preferences.sort !== "created_at") params.set("sort", preferences.sort);
|
||||
if (preferences.direction === "asc") params.set("direction", "asc");
|
||||
if (preferences.size !== DEFAULT_LIST_PAGE_SIZE) params.set("size", String(preferences.size));
|
||||
if (preferences.hide !== "") params.set("hide", preferences.hide);
|
||||
if (preferences.hide !== defaultHideString()) params.set("hide", preferences.hide);
|
||||
if (preferences.page > 1) params.set("page", String(preferences.page));
|
||||
return params;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,11 +112,6 @@ export function RunTableRow({
|
|||
<span className={`font-mono text-xs ${statusDisplay.text}`}>{run.statusLabel}</span>
|
||||
</span>
|
||||
</td>
|
||||
{show("elapsed") && (
|
||||
<td className="whitespace-nowrap px-3 py-2.5 font-mono text-xs text-fg-muted">
|
||||
{run.elapsed}
|
||||
</td>
|
||||
)}
|
||||
{show("repo") && (
|
||||
<td className="whitespace-nowrap px-3 py-2.5 font-mono text-xs font-medium text-teal-500">
|
||||
{run.repo}
|
||||
|
|
@ -164,6 +159,11 @@ export function RunTableRow({
|
|||
{run.lastEventAt != null ? formatRelativeTime(run.lastEventAt) : ""}
|
||||
</td>
|
||||
)}
|
||||
{show("elapsed") && (
|
||||
<td className="whitespace-nowrap px-3 py-2.5 text-right font-mono text-xs text-fg-muted">
|
||||
{run.elapsed}
|
||||
</td>
|
||||
)}
|
||||
{show("changes") && (
|
||||
<td className="whitespace-nowrap px-3 py-2.5 text-right font-mono text-xs tabular-nums">
|
||||
{run.additions != null && <span className="text-mint">+{run.additions.toLocaleString()}</span>}
|
||||
|
|
|
|||
|
|
@ -134,9 +134,6 @@ export function RunsListView({
|
|||
/>
|
||||
</th>
|
||||
<SortHeader label="Status" sortKey="status" activeSort={sort} direction={direction} onClick={onSortClick} />
|
||||
{show("elapsed") && (
|
||||
<SortHeader label="Elapsed" sortKey="elapsed" activeSort={sort} direction={direction} onClick={onSortClick} />
|
||||
)}
|
||||
{show("repo") && (
|
||||
<SortHeader label="Repo" sortKey="repo" activeSort={sort} direction={direction} onClick={onSortClick} />
|
||||
)}
|
||||
|
|
@ -150,6 +147,9 @@ export function RunsListView({
|
|||
{show("updated") && (
|
||||
<SortHeader label="Updated" sortKey="updated_at" activeSort={sort} direction={direction} align="right" onClick={onSortClick} />
|
||||
)}
|
||||
{show("elapsed") && (
|
||||
<SortHeader label="Elapsed" sortKey="elapsed" activeSort={sort} direction={direction} align="right" onClick={onSortClick} />
|
||||
)}
|
||||
{show("changes") && (
|
||||
<SortHeader label="Changes" sortKey="changes" activeSort={sort} direction={direction} align="right" onClick={onSortClick} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
childRunsListPreferencesToSearchParams,
|
||||
createdCutoffMsFor,
|
||||
createdFilterOptions,
|
||||
hiddenColumnsFromSearchParams,
|
||||
parseCreatedFilter,
|
||||
parseDirection,
|
||||
parsePage,
|
||||
|
|
@ -21,7 +22,7 @@ import {
|
|||
} from "../components/runs-list/preferences";
|
||||
import type { ChildRunsListPreferences, CreatedFilter } from "../components/runs-list/preferences";
|
||||
import { RunsListView } from "../components/runs-list/runs-list-view";
|
||||
import { parseHiddenColumns, serializeHiddenColumns } from "../components/runs-list/toggleable-column";
|
||||
import { serializeHiddenColumns } from "../components/runs-list/toggleable-column";
|
||||
import type { ToggleableColumn } from "../components/runs-list/toggleable-column";
|
||||
import { SECONDARY_BUTTON_CLASS } from "../components/ui";
|
||||
import { ApiError } from "../lib/api-client";
|
||||
|
|
@ -48,7 +49,7 @@ export default function RunChildren() {
|
|||
const page = parsePage(searchParams.get("page"));
|
||||
const pageSize = parsePageSize(searchParams.get("size"));
|
||||
const hiddenColumns = useMemo(
|
||||
() => parseHiddenColumns(searchParams.get("hide")),
|
||||
() => hiddenColumnsFromSearchParams(searchParams),
|
||||
[searchParams],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { FilterButton } from "../components/runs-list/filter-button";
|
|||
import {
|
||||
createdCutoffMsFor,
|
||||
createdFilterOptions,
|
||||
hiddenColumnsFromSearchParams,
|
||||
parseCreatedFilter,
|
||||
parseDirection,
|
||||
parsePage,
|
||||
|
|
@ -51,7 +52,7 @@ import type {
|
|||
ViewMode,
|
||||
} from "../components/runs-list/preferences";
|
||||
import { RunsListView } from "../components/runs-list/runs-list-view";
|
||||
import { parseHiddenColumns, serializeHiddenColumns } from "../components/runs-list/toggleable-column";
|
||||
import { serializeHiddenColumns } from "../components/runs-list/toggleable-column";
|
||||
import type { ToggleableColumn } from "../components/runs-list/toggleable-column";
|
||||
import { mutateRunListCaches } from "../lib/board-cache";
|
||||
import { shouldRefreshBoardForEvent, useBoardEvents } from "../lib/board-events";
|
||||
|
|
@ -746,7 +747,7 @@ export default function Runs() {
|
|||
const page = parsePage(searchParams.get("page"));
|
||||
const pageSize = parsePageSize(searchParams.get("size"));
|
||||
const hiddenColumns = useMemo(
|
||||
() => parseHiddenColumns(searchParams.get("hide")),
|
||||
() => hiddenColumnsFromSearchParams(searchParams),
|
||||
[searchParams],
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue