diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx
index 9cbf039a..4b60d7ab 100644
--- a/apps/web/src/components/Sidebar.tsx
+++ b/apps/web/src/components/Sidebar.tsx
@@ -450,6 +450,9 @@ export default function Sidebar() {
const seenThreadOverlays = useUiStateStore((store) => store.seenThreadOverlays);
const threadSeedVisitedAtById = useUiStateStore((store) => store.threadSeedVisitedAtById);
const doneThreadOverlays = useUiStateStore((store) => store.doneThreadOverlays);
+ const threadWrapUpOnPullRequestSettledById = useUiStateStore(
+ (store) => store.threadWrapUpOnPullRequestSettledById,
+ );
const inboxProjectScopeKey = useUiStateStore((store) => store.inboxProjectScopeKey);
const setInboxProjectScope = useUiStateStore((store) => store.setInboxProjectScope);
const inboxEnvironmentScopeId = useUiStateStore((store) => store.inboxEnvironmentScopeId);
@@ -676,10 +679,12 @@ export default function Sidebar() {
const isDone = isThreadDone({ ...thread, lastVisitedAt }, override, {
now: nowIso,
autoDoneAfterDays: INBOX_AUTO_DONE_AFTER_DAYS,
- // A landing the host did not date is taken as now, which files the
- // thread the way it always did.
+ // The thread's own word wins over the app setting. A landing the
+ // host did not date is taken as now, which files the thread the way
+ // it always did.
pullRequestSettledAt:
- wrapUpOnPullRequestSettled && pullRequestSettled
+ (threadWrapUpOnPullRequestSettledById[threadKey] ?? wrapUpOnPullRequestSettled) &&
+ pullRequestSettled
? (pullRequest.settledAt ?? nowIso)
: null,
});
@@ -709,6 +714,7 @@ export default function Sidebar() {
sidebarProjectByKey,
wrapUpOnPullRequestSettled,
threadSeedVisitedAtById,
+ threadWrapUpOnPullRequestSettledById,
],
);
diff --git a/apps/web/src/components/chat/ComposerPullRequestRow.tsx b/apps/web/src/components/chat/ComposerPullRequestRow.tsx
index 2d5a2e1b..9c48daa8 100644
--- a/apps/web/src/components/chat/ComposerPullRequestRow.tsx
+++ b/apps/web/src/components/chat/ComposerPullRequestRow.tsx
@@ -18,11 +18,11 @@ import type {
PullRequestRef,
} from "@threadlines/contracts";
import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { Link } from "@tanstack/react-router";
import { ChevronDownIcon, ExternalLinkIcon, WrenchIcon, XIcon } from "lucide-react";
import { useState } from "react";
import { isElectron } from "../../env";
-import { useSettings, updateSettings } from "../../hooks/useSettings";
import { readLocalApi } from "../../localApi";
import {
pullRequestActionMutationOptions,
@@ -68,6 +68,13 @@ export interface ComposerPullRequest {
/** The thread's own switch: the server watches this pull request while it is on. */
readonly autoFix: boolean;
readonly onAutoFixChange: (next: boolean) => void;
+ /**
+ * Whether this thread files itself under Wrapped once the pull request
+ * merges or closes: the thread's own word, or the app setting until it
+ * gives one.
+ */
+ readonly wrapUpOnSettled: boolean;
+ readonly onWrapUpOnSettledChange: (next: boolean) => void;
}
const CHIP_TONE_CLASS: Readonly<
@@ -253,7 +260,6 @@ function ComposerPullRequestChecksPopover({
const queryClient = useQueryClient();
const detail = pullRequest.detail;
const buckets = composerPullRequestCheckBuckets(detail?.checks ?? []);
- const wrapUpOnSettled = useSettings((settings) => settings.wrapUpThreadsOnPullRequestSettled);
const autoMergeControl = composerAutoMergeControl(detail);
const detailQueryKey = pullRequestQueryKeys.detail(
pullRequest.environmentId,
@@ -363,19 +369,28 @@ function ComposerPullRequestChecksPopover({
onAutoFixChange={pullRequest.onAutoFixChange}
/>
) : null}
-
+ {/* This thread's own choice. The link goes to the app-wide default it
+ stands in for until the box is clicked, and sits outside the label so
+ following it does not also flip the box. */}
+