fix(FN-4228): handle unresolved github tracking detail as loading

Fusion-Task-Id: FN-4228
Fusion-Task-Lineage: 86a2aac1-a45f-4cb0-b004-a95db3676355
This commit is contained in:
Fusion
2026-05-13 01:18:23 -07:00
committed by gsxdsm
parent d682cd02bb
commit 4b671dedb1
2 changed files with 71 additions and 6 deletions

View File

@@ -812,10 +812,21 @@ export function TaskDetailContent({
const canEditGithubTracking = GITHUB_TRACKING_EDITABLE_COLUMNS.has(task.column) && !isSaving;
const githubTrackingEnabled = githubTrackingEnabledDraft ?? (workingTask.githubTracking?.enabled === true);
const githubTrackedIssue = workingTask.githubTracking?.issue;
const githubTrackingDetailPending = detailLoading && typeof task.githubTracking === "undefined";
const showInlineGithubTrackingEnableButton =
canEditGithubTracking && !githubTrackedIssue && (!githubTrackingEnabled || (isSavingGithubTracking && workingTask.githubTracking?.enabled !== true));
canEditGithubTracking
&& !githubTrackedIssue
&& !githubTrackingDetailPending
&& (!githubTrackingEnabled || (isSavingGithubTracking && workingTask.githubTracking?.enabled !== true));
const showGithubTrackingSection = canEditGithubTracking || githubTrackingEnabled || Boolean(githubTrackedIssue);
const githubTrackingStatus = githubTrackedIssue ? "Linked" : githubTrackingEnabled ? "Enabled" : "Disabled";
const githubTrackingStatus = githubTrackingDetailPending
? "Loading"
: githubTrackedIssue
? "Linked"
: githubTrackingEnabled
? "Enabled"
: "Disabled";
const showGithubTrackingSpinner = !githubTrackedIssue && (isSavingGithubTracking || githubTrackingDetailPending);
const effectiveGithubRepoDefault = resolveEffectiveGithubRepoDefault(settings ?? null, globalSettings);
const githubRepoOverrideTrimmed = githubRepoOverrideDraft.trim();
@@ -2411,7 +2422,11 @@ export function TaskDetailContent({
</span>
{!githubTrackedIssue && (
<span className="detail-source-empty">
{githubTrackingEnabled ? "Issue not yet created" : "Tracking is currently disabled"}
{githubTrackingDetailPending
? "Checking tracking status"
: githubTrackingEnabled
? "Issue not yet created"
: "Tracking is currently disabled"}
</span>
)}
</div>
@@ -2425,15 +2440,17 @@ export function TaskDetailContent({
Enable
</button>
)}
{isSavingGithubTracking && !githubTrackedIssue && (
{showGithubTrackingSpinner && (
<span
className="detail-github-tracking-spinner"
role="status"
aria-live="polite"
aria-label="Enabling GitHub tracking"
aria-label={isSavingGithubTracking ? "Enabling GitHub tracking" : "Loading GitHub tracking status"}
>
<Loader2 size={16} className="spin" aria-hidden="true" />
<span className="visually-hidden">Enabling GitHub tracking</span>
<span className="visually-hidden">
{isSavingGithubTracking ? "Enabling GitHub tracking…" : "Loading GitHub tracking status…"}
</span>
</span>
)}
<button

View File

@@ -2301,6 +2301,54 @@ describe("TaskDetailModal", () => {
expect(screen.getByRole("button", { name: "Enable GitHub tracking" })).toHaveTextContent("Enable");
});
it("FN-4228 shows loading state instead of disabled CTA while detail fetch is unresolved", async () => {
const { fetchTaskDetail } = await import("../../api");
const mockFetch = vi.mocked(fetchTaskDetail);
let resolveFetch: ((value: TaskDetail) => void) | undefined;
mockFetch.mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveFetch = resolve;
}),
);
const optimisticTask = makeTask({ id: "FN-001", column: "todo" });
delete (optimisticTask as Partial<Task>).prompt;
delete (optimisticTask as Partial<Task>).githubTracking;
render(
<TaskDetailModal
task={optimisticTask}
onClose={noop}
onOpenDetail={noopOpenDetail}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
expect(screen.getByLabelText("GitHub tracking status")).toHaveTextContent("Loading");
expect(screen.getByText("Checking tracking status")).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Enable GitHub tracking" })).toBeNull();
expect(screen.getByRole("status", { name: "Loading GitHub tracking status" })).toBeInTheDocument();
await act(async () => {
resolveFetch?.({
...(makeTask({ id: "FN-001", column: "todo" }) as TaskDetail),
prompt: "# Spec",
githubTracking: { enabled: false },
});
});
await waitFor(() => {
expect(screen.getByLabelText("GitHub tracking status")).toHaveTextContent("Disabled");
});
expect(screen.getByText("Tracking is currently disabled")).toBeInTheDocument();
expect(screen.queryByRole("status", { name: "Loading GitHub tracking status" })).toBeNull();
expect(screen.getByRole("button", { name: "Enable GitHub tracking" })).toBeInTheDocument();
});
it("enables GitHub tracking via the inline header button without expanding the disclosure", async () => {
const { updateTask } = await import("../../api");
const mockUpdate = vi.mocked(updateTask);