From e5987421107e46237f74a58a1e2fa8bad0d7e773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Almeida?= <57680069+zeval@users.noreply.github.com> Date: Sat, 12 Sep 2026 10:06:05 +0200 Subject: [PATCH] feat(github): improve mobile dashboard parity (#3614) --- apps/web/app/github/github-page-client.tsx | 157 +++++-------- .../github/my-github/issue-list.tsx | 173 +++++---------- .../github/my-github/list-toolbar.tsx | 4 + .../my-github/mobile-results-pagination.tsx | 115 ++++++++++ .../github/my-github/mobile-views-picker.tsx | 80 +++++++ .../my-github/pr-row-task-indicator.test.tsx | 5 +- .../github/my-github/presets-scope-bar.tsx | 5 +- .../github/my-github/presets-sidebar.tsx | 103 ++++----- .../my-github/results-pagination.test.tsx | 77 +++++++ .../github/my-github/results-pagination.tsx | 23 +- .../my-github/save-preset-dialog.test.tsx | 64 ++++++ .../github/my-github/save-preset-dialog.tsx | 57 ++++- .../saved-query-load-status.test.tsx | 20 ++ .../my-github/saved-query-load-status.tsx | 47 ++++ .../use-saved-preset-actions.test.ts | 26 ++- .../my-github/use-saved-preset-actions.ts | 11 +- .../use-saved-presets-recovery.test.ts | 137 ++++++++++++ .../github/my-github/use-saved-presets.ts | 132 +++++++---- .../integrations/change-request-list.tsx | 8 +- .../integration-list-toolbar.test.tsx | 37 +++- .../integrations/integration-list-toolbar.tsx | 58 +++-- .../integration-start-task-menu.tsx | 2 +- .../presets-scope-bar-base.test.tsx | 44 +++- .../integrations/presets-scope-bar-base.tsx | 72 ++++-- .../integrations/task-row-indicator.tsx | 15 +- .../components/kanban/mobile-menu-sheet.tsx | 5 +- .../navigation/app-nav-sections.tsx | 32 ++- .../navigation/app-nav-sheet.test.tsx | 17 +- .../components/navigation/app-nav-sheet.tsx | 1 + .../navigation/use-task-view-navigation.tsx | 91 ++++++++ .../session-task-switcher-sheet-hooks.ts | 33 ++- .../mobile/session-task-switcher-sheet.tsx | 24 +- apps/web/e2e/pages/mobile-github-page.ts | 4 +- .../github/github-scope-bar-focus.spec.ts | 43 ++++ .../github/mobile-github-results.spec.ts | 178 +++++++++++++++ .../github/mobile-github-sidebar.spec.ts | 98 ++++++++- .../mobile-github-view-recovery.spec.ts | 101 +++++++++ .../mobile-issue-list-task-indicator.spec.ts | 6 + .../github/mobile-task-view-access.spec.ts | 96 ++++++++ .../workflow-step-move-overrides-helpers.ts | 8 + apps/web/src/locales/en/github.json | 9 + apps/web/src/locales/en/integrations.json | 5 +- apps/web/src/locales/en/sidebar.json | 1 + apps/web/src/locales/pseudo/github.json | 9 + apps/web/src/locales/pseudo/integrations.json | 5 +- apps/web/src/locales/pseudo/sidebar.json | 1 + apps/web/src/locales/pt-pt/github.json | 9 + apps/web/src/locales/pt-pt/integrations.json | 5 +- apps/web/src/locales/pt-pt/sidebar.json | 1 + apps/web/src/locales/zh-cn/github.json | 9 + apps/web/src/locales/zh-cn/integrations.json | 5 +- apps/web/src/locales/zh-cn/sidebar.json | 1 + apps/web/src/locales/zh-hk/github.json | 9 + apps/web/src/locales/zh-hk/integrations.json | 5 +- apps/web/src/locales/zh-hk/sidebar.json | 1 + apps/web/src/locales/zh-tw/github.json | 9 + apps/web/src/locales/zh-tw/integrations.json | 5 +- apps/web/src/locales/zh-tw/sidebar.json | 1 + .../integration-followups.md | 49 +++++ docs/plans/github-mobile-parity/plan.md | 207 ++++++++++++++++++ .../task-01-query-recovery.md | 78 +++++++ .../task-02-view-picker.md | 81 +++++++ .../task-03-task-views.md | 77 +++++++ .../task-04-result-actions.md | 75 +++++++ .../task-05-pagination.md | 76 +++++++ .../task-06-toolbar-page-picker.md | 84 +++++++ docs/public/integrations.md | 8 + docs/specs/integrations/README.md | 2 + .../requirements/github-dashboard-mobile.md | 38 ++++ .../system-design/github-dashboard-mobile.md | 47 ++++ docs/specs/ui/README.md | 3 + .../requirements/mobile-task-view-access.md | 31 +++ .../system-design/mobile-task-view-access.md | 23 ++ 73 files changed, 2751 insertions(+), 427 deletions(-) create mode 100644 apps/web/components/github/my-github/mobile-results-pagination.tsx create mode 100644 apps/web/components/github/my-github/mobile-views-picker.tsx create mode 100644 apps/web/components/github/my-github/results-pagination.test.tsx create mode 100644 apps/web/components/github/my-github/save-preset-dialog.test.tsx create mode 100644 apps/web/components/github/my-github/saved-query-load-status.test.tsx create mode 100644 apps/web/components/github/my-github/saved-query-load-status.tsx create mode 100644 apps/web/components/github/my-github/use-saved-presets-recovery.test.ts create mode 100644 apps/web/components/navigation/use-task-view-navigation.tsx create mode 100644 apps/web/e2e/tests/github/github-scope-bar-focus.spec.ts create mode 100644 apps/web/e2e/tests/github/mobile-github-results.spec.ts create mode 100644 apps/web/e2e/tests/github/mobile-github-view-recovery.spec.ts create mode 100644 apps/web/e2e/tests/github/mobile-task-view-access.spec.ts create mode 100644 docs/plans/github-mobile-parity/integration-followups.md create mode 100644 docs/plans/github-mobile-parity/plan.md create mode 100644 docs/plans/github-mobile-parity/task-01-query-recovery.md create mode 100644 docs/plans/github-mobile-parity/task-02-view-picker.md create mode 100644 docs/plans/github-mobile-parity/task-03-task-views.md create mode 100644 docs/plans/github-mobile-parity/task-04-result-actions.md create mode 100644 docs/plans/github-mobile-parity/task-05-pagination.md create mode 100644 docs/plans/github-mobile-parity/task-06-toolbar-page-picker.md create mode 100644 docs/specs/integrations/requirements/github-dashboard-mobile.md create mode 100644 docs/specs/integrations/system-design/github-dashboard-mobile.md create mode 100644 docs/specs/ui/requirements/mobile-task-view-access.md create mode 100644 docs/specs/ui/system-design/mobile-task-view-access.md diff --git a/apps/web/app/github/github-page-client.tsx b/apps/web/app/github/github-page-client.tsx index fdc4d5b078..59832d5683 100644 --- a/apps/web/app/github/github-page-client.tsx +++ b/apps/web/app/github/github-page-client.tsx @@ -2,10 +2,8 @@ import Link from "@/components/routing/app-link"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { IconBrandGithub, IconMenu2 } from "@tabler/icons-react"; +import { IconBrandGithub } from "@tabler/icons-react"; import { Alert, AlertDescription } from "@kandev/ui/alert"; -import { Button } from "@kandev/ui/button"; -import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@kandev/ui/sheet"; import { PageShell } from "@/components/page-shell"; import { useGitHubStatus } from "@/hooks/domains/github/use-github-status"; import { usePRKeyToTasks } from "@/hooks/domains/github/use-pr-key-to-tasks"; @@ -14,10 +12,10 @@ import type { Repository, Workflow, WorkflowStep } from "@/lib/types/http"; import type { GitHubIssue, GitHubPR, TaskIssueLink, TaskPR } from "@/lib/types/github"; import { PRList } from "@/components/github/my-github/pr-list"; import { IssueList } from "@/components/github/my-github/issue-list"; -import { - PresetsSidebar, - type SidebarSelection, -} from "@/components/github/my-github/presets-sidebar"; +import type { SidebarSelection } from "@/components/github/my-github/presets-sidebar"; +import { MobileViewsPicker } from "@/components/github/my-github/mobile-views-picker"; +import { SavedQueryLoadStatus } from "@/components/github/my-github/saved-query-load-status"; +import { useResponsiveBreakpoint } from "@/hooks/use-responsive-breakpoint"; import { PresetsScopeBar } from "@/components/github/my-github/presets-scope-bar"; import { PR_PRESETS, @@ -61,23 +59,6 @@ type GitHubPageClientProps = { repositories: Repository[]; }; -function MobileFiltersButton({ show, onClick }: { show: boolean; onClick: () => void }) { - const { t } = useTranslation(); - if (!show) return null; - return ( - - ); -} - function NotAuthenticatedNotice({ workspaceId, personalRequired, @@ -253,6 +234,18 @@ function useSearchInteractionControls( }; } +function useSaveQueryDialog(canSaveCurrent: boolean) { + const [saveDialogOpen, setSaveDialogOpen] = useState(false); + const saveDialogFocusRef = useRef(null); + const onOpenSaveDialog = () => { + if (!canSaveCurrent) return; + saveDialogFocusRef.current = + document.activeElement instanceof HTMLElement ? document.activeElement : null; + setSaveDialogOpen(true); + }; + return { saveDialogOpen, setSaveDialogOpen, saveDialogFocusRef, onOpenSaveDialog }; +} + function useGitHubPageState(workspaceId: string | null, enabled: boolean) { const { t } = useTranslation(); const { pr: resolvedPrPresets, issue: resolvedIssuePresets } = @@ -265,7 +258,6 @@ function useGitHubPageState(workspaceId: string | null, enabled: boolean) { commit: commitCustomQuery, } = useCommittedQuery(resolvedPrPresets[0]?.filter ?? ""); const [repoFilter, setRepoFilterRaw] = useState(""); - const [saveDialogOpen, setSaveDialogOpen] = useState(false); const savedPresetStore = useSavedPresets(workspaceId); const { autoResetSearch, markSearchInteracted, setCustomQuery, setRepoFilter } = useSearchInteractionControls(setCustomQueryRaw, setRepoFilterRaw); @@ -305,10 +297,7 @@ function useGitHubPageState(workspaceId: string | null, enabled: boolean) { workspaceId, }); const repos = useRepoOptions(workspaceId, selection, committedQuery, search.items, repoFilter); - const title = useMemo( - () => resolveTitle(selection, savedPresets, resolvedPrPresets, resolvedIssuePresets, t), - [selection, savedPresets, resolvedPrPresets, resolvedIssuePresets, t], - ); + const title = resolveTitle(selection, savedPresets, resolvedPrPresets, resolvedIssuePresets, t); const onSelect = useSidebarSelectionHandler({ currentKind: selection.kind, @@ -321,10 +310,13 @@ function useGitHubPageState(workspaceId: string | null, enabled: boolean) { markSearchInteracted, }); - const canSaveCurrent = customQuery.trim().length > 0 || repoFilter.length > 0; + const canSaveCurrent = + !savedPresetStore.loading && + !savedPresetStore.error && + (customQuery.trim().length > 0 || repoFilter.length > 0); // i18n-exempt: persisted as the saved query's name, so it must not depend on the creating locale. const suggestedLabel = customQuery.trim() || (repoFilter ? `In ${repoFilter}` : "Saved query"); - const onOpenSaveDialog = () => canSaveCurrent && setSaveDialogOpen(true); + const saveDialog = useSaveQueryDialog(canSaveCurrent); return { selection, @@ -335,15 +327,18 @@ function useGitHubPageState(workspaceId: string | null, enabled: boolean) { repoFilter, setRepoFilter, savedPresets, + savedQueryLoad: { + loading: savedPresetStore.loading, + error: savedPresetStore.error, + retry: savedPresetStore.retry, + }, search, repoOptions: repos, title, onSelect, canSaveCurrent, suggestedLabel, - saveDialogOpen, - setSaveDialogOpen, - onOpenSaveDialog, + ...saveDialog, onConfirmSave, onDeleteSaved, onToggleSavedDefault, @@ -367,6 +362,7 @@ function AuthenticatedLayout({ onStartTask: (payload: LaunchPayload) => void; }) { const { selection, search, repoOptions, title } = state; + const { isMobile } = useResponsiveBreakpoint(); const prKeyToTasks = usePRKeyToTasks(workspaceId ?? null); const issueKeyToTasks = useIssueKeyToTasks(workspaceId ?? null); useAllWorkflowSnapshots(workspaceId ?? null); @@ -378,6 +374,11 @@ function AuthenticatedLayout({ selected={selection} onSelect={state.onSelect} savedPresets={state.savedPresets} + savedStatus={ + state.savedQueryLoad.loading || state.savedQueryLoad.error ? ( + + ) : undefined + } onDeleteSaved={state.onDeleteSaved} onToggleSavedDefault={state.onToggleSavedDefault} defaultMutationPendingId={state.defaultMutationPendingId} @@ -388,6 +389,25 @@ function AuthenticatedLayout({ /> + ) : undefined + } count={search.total} loading={search.loading} lastFetchedAt={search.lastFetchedAt} @@ -401,7 +421,7 @@ function AuthenticatedLayout({ onRefresh={search.refresh} /> {!workspaceId && } -
+
void; - state: GitHubPageState; - onSelect: (s: Parameters[0]) => void; - onSaveCurrent: () => void; -}) { - const { t } = useTranslation(); - return ( - - - - {t("github:filters")} - - - - - ); -} - export function GitHubPageClient({ workspaceId, workflows, @@ -476,7 +455,6 @@ export function GitHubPageClient({ const { status, loaded } = useGitHubStatus(workspaceId ?? null); const authed = hasGitHubUserIdentity(status); const [launchPayload, setLaunchPayload] = useState(null); - const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const state = useGitHubPageState(workspaceId ?? null, authed); const { presets: storedPresets } = useGitHubActionPresets(workspaceId ?? null); const prPresets = useMemo(() => resolvePRPresets(storedPresets), [storedPresets]); @@ -488,19 +466,6 @@ export function GitHubPageClient({ const onStartTask = useCallback((payload: LaunchPayload) => setLaunchPayload(payload), []); const onCloseLaunch = useCallback(() => setLaunchPayload(null), []); - // Close the mobile sheet after any sidebar selection. KindToggle clicks also - // route through onSelect — closing on every selection is acceptable UX since - // the user always wants to see the list after picking a kind or preset. - // No useCallback: `state` is a fresh object every render, so memoizing - // these handlers would be deceptive — they'd still be new refs each pass. - const onMobileSidebarSelect = (s: Parameters[0]) => { - state.onSelect(s); - setMobileSidebarOpen(false); - }; - const onMobileSaveCurrent = () => { - setMobileSidebarOpen(false); - state.onOpenSaveDialog(); - }; return ( } scroll="none" - actions={ - setMobileSidebarOpen(true)} /> - } >
{!loaded && ( @@ -536,13 +498,6 @@ export function GitHubPageClient({ /> )}
- ; }; -function StartTaskMenu({ - issue, - presets, - onStartTask, -}: { - issue: GitHubIssue; - presets: TaskPreset[]; - onStartTask: IssueListProps["onStartTask"]; -}) { - const { t } = useTranslation(); - const launch = (preset: TaskPreset) => onStartTask({ kind: "issue", issue, preset }); - return ( - - - - - - {presets.map((p) => { - const ItemIcon = p.icon; - return ( - launch(p)} - > - -
- {p.label} - {p.hint} -
-
- ); - })} -
-
- ); -} - function IssueLabels({ labels }: { labels: string[] }) { if (!labels?.length) return null; return ( <> - {labels.slice(0, 4).map((l) => ( - - {l} + {labels.slice(0, 4).map((label) => ( + + {label} ))} {labels.length > 4 && ( @@ -103,78 +57,65 @@ function IssueRow({ ? "text-emerald-600 dark:text-emerald-400" : "text-purple-600 dark:text-purple-400"; return ( -
- -
- - {issue.title} - -
- + } + title={issue.title} + href={issue.html_url} + metadata={ + <> + {issue.repo_owner}/{issue.repo_name}#{issue.number} · - + {t("github:byAuthorOpenedAgo", { author: issue.author_login, time: formatRelativeTime(issue.created_at), })} - ({ - id: task.task_id, - taskId: task.task_id, - fallbackTitle: task.task_title, - }))} - testIdPrefix="issue-row-task-indicator" - /> -
-
-
- -
-
+ + } + taskIndicator={ + ({ + id: task.task_id, + taskId: task.task_id, + fallbackTitle: task.task_title, + }))} + testIdPrefix="issue-row-task-indicator" + /> + } + action={ + onStartTask({ kind: "issue", issue, preset })} + triggerTestId="issue-start-task-trigger" + itemTestId="issue-start-task-preset" + /> + } + testId="issue-row" + dataAttributes={{ "data-issue-number": issue.number }} + /> ); } -function IssueListBody({ +export function IssueList({ + items, loading, error, - items, presets, onStartTask, issueKeyToTasks, }: IssueListProps) { const { t } = useTranslation(); - if (loading) { - return ( -
- -
- ); - } - if (error) { - return
{error}
; - } - if (items.length === 0) { - return ( -
- {t("github:noIssuesMatchThisFilter")} -
- ); - } return ( -
+ {items.map((issue) => { const key = `${issue.repo_owner}/${issue.repo_name}#${issue.number}`; return ( @@ -187,14 +128,6 @@ function IssueListBody({ /> ); })} -
- ); -} - -export function IssueList(props: IssueListProps) { - return ( -
- -
+ ); } diff --git a/apps/web/components/github/my-github/list-toolbar.tsx b/apps/web/components/github/my-github/list-toolbar.tsx index 565d2ec848..467a0199ec 100644 --- a/apps/web/components/github/my-github/list-toolbar.tsx +++ b/apps/web/components/github/my-github/list-toolbar.tsx @@ -1,11 +1,13 @@ "use client"; import { useTranslation } from "react-i18next"; +import type { ReactNode } from "react"; import { IntegrationListToolbar } from "@/components/integrations/integration-list-toolbar"; import { RepoFilterCombobox } from "./repo-filter-combobox"; type ListToolbarProps = { title: string; + titleControl?: ReactNode; count: number; loading: boolean; lastFetchedAt: Date | null; @@ -21,6 +23,7 @@ type ListToolbarProps = { export function ListToolbar({ title, + titleControl, count, loading, lastFetchedAt, @@ -37,6 +40,7 @@ export function ListToolbar({ return ( void; +}; + +function PagePicker({ page, totalPages, onPageChange }: PagePickerProps) { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const selectedPage = useRef(null); + const currentPageId = useId(); + return ( + + + + + { + if (!selectedPage.current) return; + event.preventDefault(); + selectedPage.current?.focus({ preventScroll: true }); + selectedPage.current?.scrollIntoView({ block: "center" }); + }} + > + + {t("github:choosePage")} + + + + +
+ {Array.from({ length: totalPages }, (_, index) => index + 1).map((value) => ( + + ))} +
+
+
+ ); +} + +export function MobileResultsPagination({ range, ...props }: PagePickerProps & { range: string }) { + const { t } = useTranslation(); + const { page, totalPages, onPageChange } = props; + return ( + + ); +} diff --git a/apps/web/components/github/my-github/mobile-views-picker.tsx b/apps/web/components/github/my-github/mobile-views-picker.tsx new file mode 100644 index 0000000000..f5062614d8 --- /dev/null +++ b/apps/web/components/github/my-github/mobile-views-picker.tsx @@ -0,0 +1,80 @@ +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { IconChevronDown } from "@tabler/icons-react"; +import { Button } from "@kandev/ui/button"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@kandev/ui/drawer"; +import { PresetsSidebar, type PresetsSidebarProps } from "./presets-sidebar"; + +export function MobileViewsPicker({ + title, + onSelect, + onSaveCurrent, + ...props +}: PresetsSidebarProps & { title: string }) { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const saveAfterClose = useRef(false); + const saveFrame = useRef(0); + useEffect( + () => () => { + saveAfterClose.current = false; + cancelAnimationFrame(saveFrame.current); + }, + [], + ); + return ( + + + + + { + if (!saveAfterClose.current) return; + saveAfterClose.current = false; + saveFrame.current = requestAnimationFrame(onSaveCurrent); + }} + > + + {t("github:views")} + + + + + { + onSelect(selection); + if (selection.source !== "kind-switch") setOpen(false); + }} + onSaveCurrent={() => { + saveAfterClose.current = true; + setOpen(false); + }} + /> + + + ); +} diff --git a/apps/web/components/github/my-github/pr-row-task-indicator.test.tsx b/apps/web/components/github/my-github/pr-row-task-indicator.test.tsx index 28e1969f95..339248b947 100644 --- a/apps/web/components/github/my-github/pr-row-task-indicator.test.tsx +++ b/apps/web/components/github/my-github/pr-row-task-indicator.test.tsx @@ -82,11 +82,12 @@ describe("PRRowTaskIndicator", () => { expect(container.textContent).toContain("2"); }); - it("keeps long task titles in the DOM while visually truncating them", () => { + it("keeps long task titles readable with desktop-only truncation", () => { const longTitle = "This is an extremely long pull request title that should be truncated"; renderWithStore(); const btn = screen.getByRole("button"); expect(btn.textContent).toContain(longTitle); - expect(screen.getByText(longTitle).classList.contains("truncate")).toBe(true); + expect(screen.getByText(longTitle).classList.contains("md:truncate")).toBe(true); + expect(screen.getByText(longTitle).classList.contains("wrap-anywhere")).toBe(true); }); }); diff --git a/apps/web/components/github/my-github/presets-scope-bar.tsx b/apps/web/components/github/my-github/presets-scope-bar.tsx index 7e8c48f5c8..0207ca5d54 100644 --- a/apps/web/components/github/my-github/presets-scope-bar.tsx +++ b/apps/web/components/github/my-github/presets-scope-bar.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useRef } from "react"; +import { useCallback, useRef, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { IntegrationScopeBar } from "@/components/integrations/presets-scope-bar-base"; import { PR_PRESETS, ISSUE_PRESETS, type PresetOption } from "./search-bar"; @@ -12,6 +12,7 @@ type PresetsScopeBarProps = { selected: SidebarSelection; onSelect: (request: SidebarSelectionRequest) => void; savedPresets: SavedPreset[]; + savedStatus?: ReactNode; onDeleteSaved: (id: string) => void; canSaveCurrent: boolean; onSaveCurrent: () => void; @@ -30,7 +31,7 @@ const KINDS = [ /** * Horizontal scope bar for the /github dashboard (desktop). Thin wrapper over * the shared {@link IntegrationScopeBar}; mobile keeps the vertical - * PresetsSidebar in a sheet. + * PresetsSidebar in the Views drawer. */ export function PresetsScopeBar({ prPresets = PR_PRESETS, diff --git a/apps/web/components/github/my-github/presets-sidebar.tsx b/apps/web/components/github/my-github/presets-sidebar.tsx index 908da4d38d..9a0b5f140a 100644 --- a/apps/web/components/github/my-github/presets-sidebar.tsx +++ b/apps/web/components/github/my-github/presets-sidebar.tsx @@ -3,6 +3,8 @@ import { IconX, IconDeviceFloppy, IconBookmark } from "@tabler/icons-react"; import type { Icon } from "@tabler/icons-react"; import { useCallback } from "react"; +import { Button } from "@kandev/ui/button"; +import { SavedQueryLoadStatus, type SavedQueryLoadState } from "./saved-query-load-status"; import { cn } from "@/lib/utils"; import { PR_PRESETS, ISSUE_PRESETS, type PresetOption, type PresetGroup } from "./search-bar"; import type { SavedPreset } from "./saved-preset-model"; @@ -24,7 +26,7 @@ export type SidebarSelectionRequest = | SidebarSelection | { kind: SidebarSelection["kind"]; source: "kind-switch" }; -type PresetsSidebarProps = { +export type PresetsSidebarProps = { selected: SidebarSelection; onSelect: (request: SidebarSelectionRequest) => void; savedPresets: SavedPreset[]; @@ -35,6 +37,7 @@ type PresetsSidebarProps = { defaultMutationPendingId: string | null; prPresets?: PresetOption[]; issuePresets?: PresetOption[]; + savedQueryLoad?: SavedQueryLoadState; }; function KindToggle({ @@ -46,11 +49,12 @@ function KindToggle({ }) { const { t } = useTranslation(); return ( -
+
{(["pr", "issue"] as const).map((value) => ( {trailing}
@@ -146,20 +150,18 @@ function SavedSection({ onSelect, onDelete, kind, - canSaveCurrent, - onSaveCurrent, onToggleSavedDefault, defaultMutationPendingId, + savedQueryLoad, }: { saved: SavedPreset[]; selected: SidebarSelection; onSelect: (s: SidebarSelection) => void; onDelete: (id: string) => void; kind: "pr" | "issue"; - canSaveCurrent: boolean; - onSaveCurrent: () => void; onToggleSavedDefault: (preset: SavedPreset) => void; defaultMutationPendingId: string | null; + savedQueryLoad?: SavedQueryLoadState; }) { const { t } = useTranslation(); const deletion = useSavedTaskViewDeleteConfirmation(saved); @@ -167,7 +169,8 @@ function SavedSection({ return ( <> - {saved.length === 0 && ( + {savedQueryLoad && } + {saved.length === 0 && !savedQueryLoad?.loading && !savedQueryLoad?.error && (
{t("github:noSavedQueriesYet")}
@@ -185,21 +188,6 @@ function SavedSection({ onToggleDefault={() => void onToggleSavedDefault(preset)} /> ))} - ); } @@ -297,7 +285,9 @@ export function PresetsSidebar({ defaultMutationPendingId, prPresets = PR_PRESETS, issuePresets = ISSUE_PRESETS, + savedQueryLoad, }: PresetsSidebarProps) { + const { t } = useTranslation(); const presets = selected.kind === "pr" ? prPresets : issuePresets; const saved = savedPresets.filter((p) => p.kind === selected.kind); const onKindChange = useCallback( @@ -308,33 +298,48 @@ export function PresetsSidebar({ [onSelect, selected.kind], ); return ( -