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 (