From 211e8615fbb37622d1af13eb2026cbb329089c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Tue, 12 May 2026 18:06:14 +0100 Subject: [PATCH] fix: persist container auth, restore tasks filter, refine PR status UI (#883) --- Dockerfile | 7 +- apps/web/app/tasks/page.tsx | 10 +- .../web/components/github/pr-detail-panel.tsx | 29 +++- apps/web/components/github/pr-status-chip.tsx | 8 +- .../components/github/pr-task-icon.test.ts | 30 +++- apps/web/components/github/pr-task-icon.tsx | 15 +- .../components/github/pr-topbar-button.tsx | 6 + apps/web/e2e/pages/session-page.ts | 6 + .../e2e/tests/pr/pr-approve-button.spec.ts | 149 ++++++++++++++++++ apps/web/e2e/tests/pr/pr-status-badge.spec.ts | 55 +++++++ .../e2e/tests/task/task-list-filters.spec.ts | 34 ++++ docker-entrypoint.sh | 4 + docs/docker.md | 39 +++++ docs/k8s.md | 23 +++ 14 files changed, 396 insertions(+), 19 deletions(-) create mode 100644 apps/web/e2e/tests/pr/pr-approve-button.spec.ts diff --git a/Dockerfile b/Dockerfile index 26bc5e1f41..6e7ce6bdec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -77,8 +77,10 @@ RUN apt-get update && \ rm -rf /var/lib/apt/lists/* && \ PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install apprise -# Replace the node base image's default user so we own uid 1000 -RUN userdel -r node && groupadd -r kandev && useradd -r -g kandev -u 1000 -m kandev +# Replace the node base image's default user so we own uid 1000. +# Home is placed under /data so agent CLI auth state (gh, claude, codex, auggie, +# copilot, amp, ...) lives on the PV and survives pod restarts and image upgrades. +RUN userdel -r node && groupadd -r kandev && useradd -r -g kandev -u 1000 -d /data/home -M kandev # Create app directory structure matching what the CLI expects: # /app/apps/backend/bin/kandev @@ -113,6 +115,7 @@ VOLUME ["/data"] ENV KANDEV_NO_BROWSER=1 \ KANDEV_HOME_DIR=/data \ KANDEV_DOCKER_ENABLED=false \ + HOME=/data/home \ NPM_CONFIG_PREFIX=/data/.npm-global \ PATH=/data/.npm-global/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ HOSTNAME=0.0.0.0 \ diff --git a/apps/web/app/tasks/page.tsx b/apps/web/app/tasks/page.tsx index 8667b39206..45c8ce2e9d 100644 --- a/apps/web/app/tasks/page.tsx +++ b/apps/web/app/tasks/page.tsx @@ -8,6 +8,7 @@ import { import { fetchUserSettings } from "@/lib/api"; import { StateHydrator } from "@/components/state-hydrator"; import { mapUserSettingsResponse } from "@/lib/ssr/user-settings"; +import { resolveDesiredWorkflowId } from "@/lib/kanban/resolve-workflow"; import { TasksPageClient } from "./tasks-page-client"; import type { Workflow, @@ -50,8 +51,13 @@ async function fetchWorkspaceData( ); const workflows = workflowsResponse.workflows; - const activeWorkflowId = - workflows.find((w) => w.id === savedWorkflowId)?.id ?? workflows[0]?.id ?? null; + // Preserve "All Workflows" (null/empty saved filter) instead of falling back + // to the first workflow — resolveDesiredWorkflowId handles this consistently + // with the kanban page and the root tasks page. + const activeWorkflowId = resolveDesiredWorkflowId({ + settingsWorkflowId: savedWorkflowId, + workspaceWorkflows: workflows, + }); return { workflows, diff --git a/apps/web/components/github/pr-detail-panel.tsx b/apps/web/components/github/pr-detail-panel.tsx index 875a9e6e14..8bd7ef6b4f 100644 --- a/apps/web/components/github/pr-detail-panel.tsx +++ b/apps/web/components/github/pr-detail-panel.tsx @@ -172,6 +172,25 @@ function DescriptionSection({ body }: { body: string }) { ); } +// GitHub logins are case-insensitive; normalize before comparing. +function shouldHideApproveButton( + taskPR: TaskPR, + feedback: PRFeedback | null, + currentUser: string | null, +): boolean { + const liveState = feedback?.pr.state ?? taskPR.state; + if (liveState !== "open") return true; + const normalizedUser = currentUser?.trim().toLowerCase(); + if (!normalizedUser) return false; + const prAuthor = feedback?.pr.author_login ?? taskPR.author_login; + if (prAuthor?.trim().toLowerCase() === normalizedUser) return true; + return ( + feedback?.reviews?.some( + (r) => r.state === "APPROVED" && r.author?.trim().toLowerCase() === normalizedUser, + ) ?? false + ); +} + function ApproveButton({ taskPR, feedback, @@ -183,14 +202,9 @@ function ApproveButton({ }) { const { toast } = useToast(); const [submitting, setSubmitting] = useState(false); + const currentUser = useAppStore((s) => s.githubStatus.status?.username ?? null); - const liveState = feedback?.pr.state ?? taskPR.state; - if (liveState !== "open") return null; - - const alreadyApproved = feedback?.reviews?.some( - (r) => r.state === "APPROVED" && r.author === feedback.pr.author_login, - ); - if (alreadyApproved) return null; + if (shouldHideApproveButton(taskPR, feedback, currentUser)) return null; const handleApprove = async () => { setSubmitting(true); @@ -211,6 +225,7 @@ function ApproveButton({ return (