Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -2199,7 +2199,7 @@ describe("ProviderRuntimeIngestion", () => {
});

it("starts a new streaming assistant message segment after approval", async () => {
const harness = await createHarness({ serverSettings: { enableAssistantStreaming: true } });
const harness = await createHarness({ serverSettings: { enableLegacyTokenStreaming: true } });
const startedAt = "2026-03-28T07:00:00.000Z";
const pausedAt = "2026-03-28T07:00:01.000Z";
const resumedAt = "2026-03-28T07:00:02.000Z";
Expand Down Expand Up @@ -2306,7 +2306,7 @@ describe("ProviderRuntimeIngestion", () => {
});

it("streams assistant deltas when thread.turn.start requests streaming mode", async () => {
const harness = await createHarness({ serverSettings: { enableAssistantStreaming: true } });
const harness = await createHarness({ serverSettings: { enableLegacyTokenStreaming: true } });
const now = "2026-01-01T00:00:00.000Z";

await Effect.runPromise(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1655,7 +1655,7 @@ const make = Effect.gen(function* () {

const assistantDeliveryMode: AssistantDeliveryMode = yield* Effect.map(
serverSettingsService.getSettings,
(settings) => (settings.enableAssistantStreaming ? "streaming" : "buffered"),
(settings) => (settings.enableLegacyTokenStreaming ? "streaming" : "buffered"),
);
if (assistantDeliveryMode === "buffered") {
const spillChunk = yield* appendBufferedAssistantText(assistantMessageId, assistantDelta);
Expand Down Expand Up @@ -1691,7 +1691,7 @@ const make = Effect.gen(function* () {
const detailedThread = yield* getLoadedThreadDetail();
const assistantDeliveryMode: AssistantDeliveryMode = yield* Effect.map(
serverSettingsService.getSettings,
(settings) => (settings.enableAssistantStreaming ? "streaming" : "buffered"),
(settings) => (settings.enableLegacyTokenStreaming ? "streaming" : "buffered"),
);
const flushedMessageIds =
assistantDeliveryMode === "buffered"
Expand Down
12 changes: 0 additions & 12 deletions apps/web/src/components/settings/BetaSettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export function BetaSettingsPanel() {
const sidebarAutoSettleAfterDays = useClientSettings(
(settings) => settings.sidebarAutoSettleAfterDays,
);
const planModeEnabled = useClientSettings((settings) => settings.planModeEnabled);
const updateSettings = useUpdateClientSettings();

return (
Expand Down Expand Up @@ -115,17 +114,6 @@ export function BetaSettingsPanel() {
) : null}
</>
) : null}
<SettingsRow
{...searchableSetting("restore-plan-mode")}
description="Legacy feature. Brings back the Build/Plan toggle in the composer along with the /plan and /default commands and the Shift+Tab shortcut. While off, every thread runs in build mode."
control={
<Switch
checked={planModeEnabled}
onCheckedChange={(checked) => updateSettings({ planModeEnabled: Boolean(checked) })}
aria-label="Restore plan mode (legacy)"
/>
}
/>
</SettingsSection>
</SettingsPageContainer>
);
Expand Down
131 changes: 99 additions & 32 deletions apps/web/src/components/settings/SettingsPanels.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArchiveIcon, ArchiveX, LoaderIcon, SettingsIcon } from "lucide-react";
import { ArchiveIcon, ArchiveX, ChevronRightIcon, LoaderIcon, SettingsIcon } from "lucide-react";
import { Link } from "@tanstack/react-router";
import type { CSSProperties, ReactNode } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
Expand Down Expand Up @@ -79,6 +79,7 @@ import { useProjects } from "../../state/entities";
import { useArchivedThreadSnapshots } from "../../lib/archivedThreadsState";
import { formatRelativeTimeLabel } from "../../timestampFormat";
import { Button } from "../ui/button";
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible";
import {
Dialog,
DialogDescription,
Expand Down Expand Up @@ -476,8 +477,9 @@ export function useSettingsRestore(onRestored?: () => void) {
...(settings.diffIgnoreWhitespace !== DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace
? ["Diff whitespace changes"]
: []),
...(settings.enableAssistantStreaming !== DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming
? ["Assistant output"]
...(settings.enableLegacyTokenStreaming !==
DEFAULT_UNIFIED_SETTINGS.enableLegacyTokenStreaming
? ["Stream token by token"]
: []),
...(settings.enableProviderUpdateChecks !==
DEFAULT_UNIFIED_SETTINGS.enableProviderUpdateChecks
Expand Down Expand Up @@ -521,7 +523,7 @@ export function useSettingsRestore(onRestored?: () => void) {
settings.fontSizePrompt,
settings.fontSizeTerminal,
settings.glassOpacity,
settings.enableAssistantStreaming,
settings.enableLegacyTokenStreaming,
settings.enableProviderUpdateChecks,
settings.sidebarProjectGroupingMode,
settings.sidebarThreadPreviewCount,
Expand Down Expand Up @@ -601,7 +603,7 @@ export function useSettingsRestore(onRestored?: () => void) {
glassOpacity: DEFAULT_UNIFIED_SETTINGS.glassOpacity,
sidebarThreadPreviewCount: DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount,
sidebarProjectGroupingMode: DEFAULT_UNIFIED_SETTINGS.sidebarProjectGroupingMode,
enableAssistantStreaming: DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming,
enableLegacyTokenStreaming: DEFAULT_UNIFIED_SETTINGS.enableLegacyTokenStreaming,
enableProviderUpdateChecks: DEFAULT_UNIFIED_SETTINGS.enableProviderUpdateChecks,
backgroundActivity: DEFAULT_UNIFIED_SETTINGS.backgroundActivity,
backgroundActivityProfile: DEFAULT_UNIFIED_SETTINGS.backgroundActivityProfile,
Expand Down Expand Up @@ -1504,6 +1506,96 @@ function FontFamilySettingsRow({
);
}

// Both legacy rows sit behind the fold, so a settings-search jump has to
// expand the section before its target can mount and scroll.
const LEGACY_FEATURE_TARGET_IDS: ReadonlySet<string> = new Set([
"legacy-plan-mode",
"legacy-token-streaming",
]);

/**
* Retired features kept only for users who still depend on them. Collapsed by
* default so they stay out of the everyday settings path; a settings-search
* jump to one of the rows unfolds the section.
*/
function LegacyFeaturesSection() {
const settings = usePrimarySettings();
const updateSettings = useUpdatePrimarySettings();
const [open, setOpen] = useState(false);
const searchTargetId = useSettingsSearchTargetId();
// Unfold once per search jump; tracking the handled id lets the user fold
// the section back up without the still-set target immediately reopening it.
const lastExpandedTargetRef = useRef<string | null>(null);
useEffect(() => {
if (searchTargetId === null) {
// A handled jump clears the target; forgetting it here lets a later
// jump to the same row expand the section again.
lastExpandedTargetRef.current = null;
return;
}
if (!LEGACY_FEATURE_TARGET_IDS.has(searchTargetId)) return;
if (lastExpandedTargetRef.current === searchTargetId) return;
lastExpandedTargetRef.current = searchTargetId;
setOpen(true);
}, [searchTargetId]);
Comment thread
macroscopeapp[bot] marked this conversation as resolved.

return (
<section className="space-y-3">
<Collapsible open={open} onOpenChange={setOpen}>
<CollapsibleTrigger className="group flex min-h-8 w-full items-center gap-2 px-3 sm:px-4">
<h2 className="text-lg font-semibold tracking-[-0.025em] text-muted-foreground transition-colors group-hover:text-foreground">
Legacy features
</h2>
<ChevronRightIcon className="size-4 text-muted-foreground transition-transform duration-200 group-data-panel-open:rotate-90" />
</CollapsibleTrigger>
<CollapsiblePanel>
<div className="relative space-y-1 overflow-visible pt-3 text-foreground">
<SettingsRow
{...searchableSetting("legacy-plan-mode")}
description="Brings back the Build/Plan toggle in the composer along with the /plan and /default commands and the Shift+Tab shortcut. While off, every thread runs in build mode."
control={
<Switch
checked={settings.planModeEnabled}
onCheckedChange={(checked) =>
updateSettings({ planModeEnabled: Boolean(checked) })
}
aria-label="Plan mode (legacy)"
/>
}
/>
<SettingsRow
{...searchableSetting("legacy-token-streaming")}
description="Paints assistant output token by token instead of in complete chunks. Not recommended: it is significantly slower, and long responses become harder to follow. Kept only for compatibility with the old behavior."
control={
<Switch
checked={settings.enableLegacyTokenStreaming}
onCheckedChange={(checked) => {
if (!checked) {
updateSettings({ enableLegacyTokenStreaming: false });
return;
}
void (async () => {
const api = readLocalApi();
const confirmed = await (api ?? ensureLocalApi()).dialogs.confirm(
[
"Turn on token-by-token output?",
"It is significantly slower than the default buffered output and hurts the reading experience. This switch exists only for backwards compatibility.",
].join("\n"),
);
if (confirmed) updateSettings({ enableLegacyTokenStreaming: true });
})();
}}
aria-label="Stream token by token (legacy)"
/>
}
/>
</div>
</CollapsiblePanel>
</Collapsible>
</section>
);
}

export function GeneralSettingsPanel() {
const settings = usePrimarySettings();
const updateSettings = useUpdatePrimarySettings();
Expand Down Expand Up @@ -1664,33 +1756,6 @@ export function GeneralSettingsPanel() {
}
/>

<SettingsRow
{...searchableSetting("assistant-output")}
description="Show token-by-token output while a response is in progress."
resetAction={
settings.enableAssistantStreaming !==
DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming ? (
<SettingResetButton
label="assistant output"
onClick={() =>
updateSettings({
enableAssistantStreaming: DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming,
})
}
/>
) : null
}
control={
<Switch
checked={settings.enableAssistantStreaming}
onCheckedChange={(checked) =>
updateSettings({ enableAssistantStreaming: Boolean(checked) })
}
aria-label="Stream assistant messages"
/>
}
/>

<SettingsRow
{...searchableSetting("provider-update-checks")}
description="Check installed provider CLIs for newer available versions."
Expand Down Expand Up @@ -2050,6 +2115,8 @@ export function GeneralSettingsPanel() {
}
/>
</SettingsSection>

<LegacyFeaturesSection />
</SettingsPageContainer>
);
}
Expand Down
20 changes: 10 additions & 10 deletions apps/web/src/components/settings/settingsSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,6 @@ export const SETTINGS_SEARCH_ITEMS = [
title: "Hide whitespace changes",
to: "/settings/general",
},
{
id: "assistant-output",
title: "Assistant output",
to: "/settings/general",
},
{
id: "provider-update-checks",
title: "Provider update checks",
Expand Down Expand Up @@ -156,6 +151,16 @@ export const SETTINGS_SEARCH_ITEMS = [
title: "Diagnostics",
to: "/settings/general",
},
{
id: "legacy-plan-mode",
title: "Plan mode (legacy)",
to: "/settings/general",
},
{
id: "legacy-token-streaming",
title: "Stream token by token (legacy)",
to: "/settings/general",
},
{
id: "keybindings",
title: "Keybindings",
Expand Down Expand Up @@ -187,11 +192,6 @@ export const SETTINGS_SEARCH_ITEMS = [
to: "/settings/beta",
targetId: "sidebar-v2",
},
{
id: "restore-plan-mode",
title: "Restore plan mode (legacy)",
to: "/settings/beta",
},
{
id: "archive",
title: "Archived threads",
Expand Down
9 changes: 7 additions & 2 deletions packages/contracts/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,12 @@ export const BackgroundActivitySettings = Schema.Struct({
export type BackgroundActivitySettings = typeof BackgroundActivitySettings.Type;

export const ServerSettings = Schema.Struct({
enableAssistantStreaming: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
// Legacy token-by-token assistant output. Deliberately a fresh key (was
// `enableAssistantStreaming`): decoding drops the old key, so everyone,
// including prior opt-ins, resets to the buffered default.
enableLegacyTokenStreaming: Schema.Boolean.pipe(
Schema.withDecodingDefault(Effect.succeed(false)),
),
enableProviderUpdateChecks: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
backgroundActivity: BackgroundActivitySettings,
// Legacy flat fields retained for old settings files and old clients. New
Expand Down Expand Up @@ -701,7 +706,7 @@ const OpenCodeSettingsPatch = Schema.Struct({

export const ServerSettingsPatch = Schema.Struct({
// Server settings
enableAssistantStreaming: Schema.optionalKey(Schema.Boolean),
enableLegacyTokenStreaming: Schema.optionalKey(Schema.Boolean),
enableProviderUpdateChecks: Schema.optionalKey(Schema.Boolean),
backgroundActivity: Schema.optionalKey(
Schema.Struct({
Expand Down
Loading