Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/desktop/src/electron/ElectronMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function normalizeContextMenuItems(source: readonly ContextMenuItem[]): ContextM
destructive: sourceItem.destructive === true,
disabled: sourceItem.disabled === true,
...(sourceItem.separatorBefore === true ? { separatorBefore: true } : {}),
...(typeof sourceItem.checked === "boolean" ? { checked: sourceItem.checked } : {}),
};

if (sourceItem.children) {
Expand Down Expand Up @@ -168,6 +169,7 @@ export const make = Effect.gen(function* () {
const itemOption: Electron.MenuItemConstructorOptions = {
label: item.label,
enabled: !item.disabled,
...(typeof item.checked === "boolean" ? { type: "checkbox", checked: item.checked } : {}),
};
if (item.children && item.children.length > 0) {
itemOption.submenu = buildTemplate(item.children, complete);
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/src/components/AppSymbol.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ const ANDROID_ICON_BY_SF_SYMBOL: Partial<Record<SFSymbol, Icon>> = {
checkmark: IconCheck,
"checkmark.circle": IconCircleCheck,
clock: IconClock,
timer: IconClock,
ticket: IconTicket,
cloud: IconCloud,
cube: IconBox,
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/src/features/home/HomeRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export function HomeRouteScreen() {
unsnoozeThread,
pinThread,
unpinThread,
setThreadAutoSettle,
moveThread,
regenerateThreadTitle,
unsettleThread,
Expand Down Expand Up @@ -199,6 +200,7 @@ export function HomeRouteScreen() {
onUnsettleThread={unsettleThread}
onPinThread={pinThread}
onUnpinThread={unpinThread}
onSetThreadAutoSettle={setThreadAutoSettle}
onMoveThread={moveThread}
onRegenerateThreadTitle={regenerateThreadTitle}
onEnvironmentChange={setSelectedEnvironmentId}
Expand Down
23 changes: 23 additions & 0 deletions apps/mobile/src/features/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ interface HomeScreenProps {
readonly onUnsettleThread: (thread: EnvironmentThreadShell) => void;
readonly onPinThread: (thread: EnvironmentThreadShell) => Promise<boolean>;
readonly onUnpinThread: (thread: EnvironmentThreadShell) => Promise<boolean>;
readonly onSetThreadAutoSettle: (
thread: EnvironmentThreadShell,
enabled: boolean,
) => Promise<boolean>;
readonly onMoveThread: (
thread: EnvironmentThreadShell,
direction: ThreadMoveDestination,
Expand Down Expand Up @@ -531,6 +535,12 @@ export function HomeScreen(props: HomeScreenProps) {
},
[props.onUnpinThread],
);
const handleSetThreadAutoSettle = useCallback(
(thread: EnvironmentThreadShell, enabled: boolean) => {
void props.onSetThreadAutoSettle(thread, enabled);
},
[props.onSetThreadAutoSettle],
);
const handleRegenerateThreadTitle = useCallback(
(thread: EnvironmentThreadShell) => {
void props.onRegenerateThreadTitle(thread);
Expand Down Expand Up @@ -606,6 +616,15 @@ export function HomeScreen(props: HomeScreenProps) {
}
return supported;
}, [serverConfigs]);
const autoSettleOptOutEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadAutoSettleOptOut === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const pinReorderEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
Expand Down Expand Up @@ -868,6 +887,7 @@ export function HomeScreen(props: HomeScreenProps) {
onSettleThread={handleSettleThread}
snoozeSupported={snoozeEnvironmentIds.has(thread.environmentId)}
pinningSupported={pinningEnvironmentIds.has(thread.environmentId)}
autoSettleOptOutSupported={autoSettleOptOutEnvironmentIds.has(thread.environmentId)}
reorderSupported={
item.item.pinned
? pinReorderEnvironmentIds.has(thread.environmentId)
Expand All @@ -880,6 +900,7 @@ export function HomeScreen(props: HomeScreenProps) {
onUnsettleThread={handleUnsettleThread}
onPinThread={handlePinThread}
onUnpinThread={handleUnpinThread}
onSetThreadAutoSettle={handleSetThreadAutoSettle}
onMoveThread={handleMoveThread}
onSwipeableClose={handleSwipeableClose}
onSwipeableWillOpen={handleSwipeableWillOpen}
Expand All @@ -902,6 +923,8 @@ export function HomeScreen(props: HomeScreenProps) {
handleSwipeableClose,
handleSwipeableWillOpen,
handleUnsettleThread,
handleSetThreadAutoSettle,
autoSettleOptOutEnvironmentIds,
pinningEnvironmentIds,
machineByEnvironmentId,
pinReorderEnvironmentIds,
Expand Down
46 changes: 46 additions & 0 deletions apps/mobile/src/features/home/useThreadListActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ function environmentSupportsPinReorder(environmentId: EnvironmentThreadShell["en
);
}

function environmentSupportsAutoSettleOptOut(
environmentId: EnvironmentThreadShell["environmentId"],
) {
return (
appAtomRegistry.get(environmentServerConfigsAtom).get(environmentId)?.environment.capabilities
.threadAutoSettleOptOut === true
);
}

function environmentSupportsTitleRegeneration(
environmentId: EnvironmentThreadShell["environmentId"],
) {
Expand Down Expand Up @@ -236,6 +245,11 @@ export function useThreadListActions(): {
readonly unsettleThread: (thread: EnvironmentThreadShell) => Promise<boolean>;
readonly pinThread: (thread: EnvironmentThreadShell) => Promise<boolean>;
readonly unpinThread: (thread: EnvironmentThreadShell) => Promise<boolean>;
/** Sets per-thread automatic settlement on or off. */
readonly setThreadAutoSettle: (
thread: EnvironmentThreadShell,
enabled: boolean,
) => Promise<boolean>;
readonly moveThread: (
thread: EnvironmentThreadShell,
direction: ThreadMoveDestination,
Expand All @@ -247,6 +261,9 @@ export function useThreadListActions(): {
const unsnoozeMutation = useAtomCommand(threadEnvironment.unsnooze, { reportFailure: false });
const pinMutation = useAtomCommand(threadEnvironment.pin, { reportFailure: false });
const unpinMutation = useAtomCommand(threadEnvironment.unpin, { reportFailure: false });
const setAutoSettleMutation = useAtomCommand(threadEnvironment.setAutoSettle, {
reportFailure: false,
});
const updateThreadMetadata = useAtomCommand(threadEnvironment.updateMetadata, {
reportFailure: false,
});
Expand Down Expand Up @@ -433,6 +450,34 @@ export function useThreadListActions(): {
},
[unpinMutation],
);
const setThreadAutoSettle = useCallback(
async (thread: EnvironmentThreadShell, enabled: boolean) => {
if (!environmentSupportsAutoSettleOptOut(thread.environmentId)) {
Alert.alert(
"Could not update auto-settle",
"This environment's server does not support turning auto-settle off per thread yet. Update the server to use it.",
);
return false;
}
selectionHaptic();
const result = await setAutoSettleMutation({
environmentId: thread.environmentId,
input: { threadId: thread.id, enabled },
});
if (result._tag === "Failure") {
const error = Cause.squash(result.cause);
Alert.alert(
"Could not update auto-settle",
error instanceof Error && error.message.trim().length > 0
? error.message
: "The auto-settle setting could not be changed.",
);
return false;
}
return true;
},
[setAutoSettleMutation],
);
const regenerateThreadTitle = useCallback(
async (thread: EnvironmentThreadShell) => {
const key = scopedThreadKey(thread.environmentId, thread.id);
Expand Down Expand Up @@ -652,6 +697,7 @@ export function useThreadListActions(): {
unsettleThread,
pinThread,
unpinThread,
setThreadAutoSettle,
moveThread,
regenerateThreadTitle,
};
Expand Down
14 changes: 14 additions & 0 deletions apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ function ThreadNavigationSidebarPane(
unsettleThread,
pinThread,
unpinThread,
setThreadAutoSettle,
moveThread,
regenerateThreadTitle,
} = useThreadListActions();
Expand Down Expand Up @@ -442,6 +443,15 @@ function ThreadNavigationSidebarPane(
}
return supported;
}, [serverConfigs]);
const autoSettleOptOutEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
if (config.environment.capabilities.threadAutoSettleOptOut === true) {
supported.add(environmentId);
}
}
return supported;
}, [serverConfigs]);
const pinReorderEnvironmentIds = useMemo(() => {
const supported = new Set<EnvironmentId>();
for (const [environmentId, config] of serverConfigs) {
Expand Down Expand Up @@ -936,6 +946,7 @@ function ThreadNavigationSidebarPane(
onSettleThread={settleThread}
snoozeSupported={snoozeEnvironmentIds.has(thread.environmentId)}
pinningSupported={pinningEnvironmentIds.has(thread.environmentId)}
autoSettleOptOutSupported={autoSettleOptOutEnvironmentIds.has(thread.environmentId)}
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
reorderSupported={
item.item.pinned
? pinReorderEnvironmentIds.has(thread.environmentId)
Expand All @@ -948,6 +959,7 @@ function ThreadNavigationSidebarPane(
onUnsettleThread={unsettleThread}
onPinThread={pinThread}
onUnpinThread={unpinThread}
onSetThreadAutoSettle={setThreadAutoSettle}
onMoveThread={moveThread}
onSwipeableClose={handleSwipeableClose}
onSwipeableWillOpen={handleSwipeableWillOpen}
Expand Down Expand Up @@ -1086,6 +1098,8 @@ function ThreadNavigationSidebarPane(
pinReorderEnvironmentIds,
pinThread,
pinningEnvironmentIds,
autoSettleOptOutEnvironmentIds,
setThreadAutoSettle,
projectByKey,
projectTitleByProjectKey,
regenerateThreadTitle,
Expand Down
58 changes: 53 additions & 5 deletions apps/mobile/src/features/threads/thread-list-v2-items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -388,13 +388,16 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
readonly onArchiveThread: (thread: EnvironmentThreadShell) => void;
readonly onPinThread: (thread: EnvironmentThreadShell) => void;
readonly onUnpinThread: (thread: EnvironmentThreadShell) => void;
readonly onSetThreadAutoSettle: (thread: EnvironmentThreadShell, enabled: boolean) => void;
/** False on environments whose server predates thread.settle/unsettle:
swipe + menu fall back to Archive instead of failing on use. */
readonly settlementSupported: boolean;
/** False on servers that predate thread.snooze/unsnooze. */
readonly snoozeSupported: boolean;
/** False on servers that predate thread.pin/unpin. */
readonly pinningSupported: boolean;
/** False on servers that predate thread.auto-settle.set. */
readonly autoSettleOptOutSupported: boolean;
/** False on servers that predate thread title regeneration. */
readonly titleRegenerationSupported: boolean;
/** Server supports reordering this card's section. */
Expand Down Expand Up @@ -430,6 +433,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
onArchiveThread,
onPinThread,
onUnpinThread,
onSetThreadAutoSettle,
onMoveThread,
} = props;
const snoozedRow = props.snoozed === true;
Expand Down Expand Up @@ -481,6 +485,10 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
const handleUnsettle = useCallback(() => onUnsettleThread(thread), [onUnsettleThread, thread]);
const handlePin = useCallback(() => onPinThread(thread), [onPinThread, thread]);
const handleUnpin = useCallback(() => onUnpinThread(thread), [onUnpinThread, thread]);
const handleSetAutoSettle = useCallback(
(enabled: boolean) => onSetThreadAutoSettle(thread, enabled),
[onSetThreadAutoSettle, thread],
);
const handleMoveUp = useCallback(() => onMoveThread?.(thread, "up"), [onMoveThread, thread]);
const handleMoveDown = useCallback(() => onMoveThread?.(thread, "down"), [onMoveThread, thread]);
const handleArchive = useCallback(() => onArchiveThread(thread), [onArchiveThread, thread]);
Expand Down Expand Up @@ -559,6 +567,33 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
variant,
],
);
// A submenu with the current option checked, matching web. This is a
// per-thread setting, not a lifecycle verb.
const autoSettleMenuItems = useMemo<MenuAction[]>(
() =>
props.autoSettleOptOutSupported
? [
{
id: "auto-settle",
title: "Auto-settle behavior",
image: "timer",
subactions: [
{
id: "auto-settle:enabled",
title: "Enabled",
state: thread.autoSettleDisabledAt == null ? "on" : "off",
},
{
id: "auto-settle:disabled",
title: "Disabled",
state: thread.autoSettleDisabledAt == null ? "off" : "on",
},
],
} satisfies MenuAction,
]
: [],
[props.autoSettleOptOutSupported, thread.autoSettleDisabledAt],
);
const titleRegenerationMenuItems = useMemo<MenuAction[]>(
() =>
buildThreadTitleRegenerationMenuItems({
Expand All @@ -578,33 +613,43 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
},
...arrangementMenuItems,
...titleRegenerationMenuItems,
...autoSettleMenuItems,
{ id: "delete", title: "Delete", image: "trash", attributes: { destructive: true } },
],
[arrangementMenuItems, snoozePresetActions, titleRegenerationMenuItems],
[arrangementMenuItems, autoSettleMenuItems, snoozePresetActions, titleRegenerationMenuItems],
);
const cardMenuActions = useMemo<MenuAction[]>(
() => [
CARD_MENU_ACTIONS[0]!,
...arrangementMenuItems,
...titleRegenerationMenuItems,
...autoSettleMenuItems,
...CARD_MENU_ACTIONS.slice(1),
],
[arrangementMenuItems, titleRegenerationMenuItems],
[arrangementMenuItems, autoSettleMenuItems, titleRegenerationMenuItems],
);
// Settled and snoozed rows keep the setting too, matching web where every
// row shares one menu builder.
const slimMenuActions = useMemo<MenuAction[]>(
() => [
SLIM_MENU_ACTIONS[0]!,
...arrangementMenuItems.filter(
(action) => action.id !== "move-up" && action.id !== "move-down",
),
...titleRegenerationMenuItems,
...autoSettleMenuItems,
SLIM_MENU_ACTIONS[1]!,
],
[arrangementMenuItems, titleRegenerationMenuItems],
[arrangementMenuItems, autoSettleMenuItems, titleRegenerationMenuItems],
);
const snoozedMenuActions = useMemo<MenuAction[]>(
() => [SNOOZED_MENU_ACTIONS[0]!, ...titleRegenerationMenuItems, SNOOZED_MENU_ACTIONS[1]!],
[titleRegenerationMenuItems],
() => [
SNOOZED_MENU_ACTIONS[0]!,
...titleRegenerationMenuItems,
...autoSettleMenuItems,
SNOOZED_MENU_ACTIONS[1]!,
],
[autoSettleMenuItems, titleRegenerationMenuItems],
);
const legacyMenuActions = useMemo<MenuAction[]>(
() => [
Expand All @@ -623,6 +668,8 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
if (nativeEvent.event === "unsnooze") handleUnsnooze();
if (nativeEvent.event === "pin") handlePin();
if (nativeEvent.event === "unpin") handleUnpin();
if (nativeEvent.event === "auto-settle:enabled") handleSetAutoSettle(true);
if (nativeEvent.event === "auto-settle:disabled") handleSetAutoSettle(false);
if (nativeEvent.event === "arrange") appAtomRegistry.set(threadArrangementOpenAtom, true);
if (nativeEvent.event === "move-up") handleMoveUp();
if (nativeEvent.event === "move-down") handleMoveDown();
Expand Down Expand Up @@ -655,6 +702,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
handlePin,
handleSettle,
handleSnooze,
handleSetAutoSettle,
handleUnpin,
handleUnsettle,
handleUnsnooze,
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/src/state/use-thread-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function threadDetailToShell(
settledAt: thread.settledAt,
unsettledAt: thread.unsettledAt,
activeOrderKey: thread.activeOrderKey,
autoSettleDisabledAt: thread.autoSettleDisabledAt,
pinnedAt: thread.pinnedAt,
pinOrderKey: thread.pinOrderKey,
snoozedUntil: thread.snoozedUntil ?? null,
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/environment/ServerEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ export const make = Effect.gen(function* () {
threadPinning: true,
threadPinReorder: true,
threadActiveReorder: true,
threadAutoSettleOptOut: true,
threadTitleRegeneration: true,
threadPullRequests: true,
pullRequestStackActions: true,
Expand Down
Loading
Loading