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
1 change: 1 addition & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const clientSettings: ClientSettings = {
sidebarThreadSortOrder: "created_at",
sidebarThreadPreviewCount: 6,
legacySidebarEnabled: false,
legacySidebarScale: 100,
timestampFormat: "24-hour",
wordWrap: true,
};
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/src/settings/LastCodeSettingsImport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ describe("LastCodeSettingsImport", () => {
const sourceCustom = ProviderInstanceId.make("source_custom");
const lastCodeCustom = ProviderInstanceId.make("lastcode_custom");
const sourceClient = { ...DEFAULT_CLIENT_SETTINGS, fontSizeInterface: 17 };
Reflect.deleteProperty(sourceClient, "legacySidebarScale");
sourceClient.favorites = [
{ provider: codex, model: "gpt-source" },
{ provider: sourceCustom, model: "source-model" },
Expand All @@ -101,6 +102,7 @@ describe("LastCodeSettingsImport", () => {
};
const destinationClient = {
...DEFAULT_CLIENT_SETTINGS,
legacySidebarScale: 75,
favorites: [{ provider: lastCodeCustom, model: "lastcode-model" }],
providerModelPreferences: {
[lastCodeCustom]: { hiddenModels: [], modelOrder: ["lastcode-model"] },
Expand Down Expand Up @@ -209,6 +211,7 @@ describe("LastCodeSettingsImport", () => {
) as unknown,
);
assert.equal(importedClient.fontSizeInterface, 17);
assert.equal(importedClient.legacySidebarScale, 75);
assert.deepEqual(importedClient.favorites, [
{ provider: "lastcode_custom", model: "lastcode-model" },
{ provider: "codex", model: "gpt-source" },
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src/settings/LastCodeSettingsImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,12 @@ function mergeClientSettings(sourceRaw: string, destinationRaw: string | null):
isBuiltInProviderPreference(provider),
),
]);
return `${encodeClientSettingsJson({ ...source, favorites, providerModelPreferences })}\n`;
return `${encodeClientSettingsJson({
...source,
favorites,
providerModelPreferences,
legacySidebarScale: destination.legacySidebarScale,
})}\n`;
}

function mergeServerSettings(sourceRaw: string, destinationRaw: string | null): string {
Expand Down
53 changes: 50 additions & 3 deletions apps/web/src/components/LegacySidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ import { ProjectFavicon } from "./ProjectFavicon";
import { SidebarDraftBlock } from "./Sidebar";
import { useAtomValue } from "@effect/atom-react";
import { autoAnimate } from "@formkit/auto-animate";
import React, { useCallback, useEffect, memo, useMemo, useRef, useState } from "react";
import React, {
useCallback,
useEffect,
memo,
useMemo,
useRef,
useState,
type CSSProperties,
} from "react";
import { useShallow } from "zustand/react/shallow";
import {
DndContext,
Expand Down Expand Up @@ -67,6 +75,7 @@ import { useNavigate, useParams, useRouter } from "@tanstack/react-router";
import {
MAX_SIDEBAR_THREAD_PREVIEW_COUNT,
MIN_SIDEBAR_THREAD_PREVIEW_COUNT,
type LegacySidebarScale,
type SidebarProjectSortOrder,
type SidebarThreadPreviewCount,
type SidebarThreadSortOrder,
Expand Down Expand Up @@ -109,6 +118,7 @@ import { ensureLocalApi, readLocalApi } from "../localApi";
import { type DraftId, useComposerDraftStore } from "../composerDraftStore";
import { useNewThreadHandler } from "../hooks/useHandleNewThread";
import { useDesktopUpdateState } from "../state/desktopUpdate";
import { legacySidebarScaleStyle } from "../legacySidebarScale";

import { useThreadActions } from "../hooks/useThreadActions";
import { projectEnvironment } from "../state/projects";
Expand Down Expand Up @@ -892,6 +902,8 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr
});

interface SidebarProjectThreadListProps {
legacySidebarScale: LegacySidebarScale;
scaleStyle: CSSProperties;
projectKey: string;
projectExpanded: boolean;
hasOverflowingThreads: boolean;
Expand Down Expand Up @@ -948,6 +960,8 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList(
props: SidebarProjectThreadListProps,
) {
const {
legacySidebarScale,
scaleStyle,
projectKey,
projectExpanded,
hasOverflowingThreads,
Expand Down Expand Up @@ -991,6 +1005,8 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList(
<SidebarMenuSub
ref={attachThreadListAutoAnimateRef}
className="mx-0.5 my-0 w-full translate-x-0 gap-0.5 overflow-hidden border-l-0 px-1 py-0 sm:mx-1 sm:px-1.5"
data-legacy-sidebar-scale={legacySidebarScale}
style={scaleStyle}
>
{shouldShowThreadPanel && showEmptyThreadState ? (
<SidebarMenuSubItem className="w-full" data-thread-selection-safe>
Expand Down Expand Up @@ -1075,6 +1091,8 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList(
});

interface SidebarProjectItemProps {
legacySidebarScale: LegacySidebarScale;
scaleStyle: CSSProperties;
project: SidebarProjectSnapshot;
isThreadListExpanded: boolean;
activeRouteThreadKey: string | null;
Expand All @@ -1096,6 +1114,8 @@ interface SidebarProjectItemProps {

const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjectItemProps) {
const {
legacySidebarScale,
scaleStyle,
project,
isThreadListExpanded,
activeRouteThreadKey,
Expand Down Expand Up @@ -2243,7 +2263,11 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec

return (
<>
<div className="group/project-header relative">
<div
className="group/project-header relative"
data-legacy-sidebar-scale={legacySidebarScale}
style={scaleStyle}
>
<SidebarMenuButton
ref={isManualProjectSorting ? dragHandleProps?.setActivatorNodeRef : undefined}
className={`pr-8 group-hover/project-header:bg-sidebar-row-hover group-hover/project-header:text-sidebar-foreground max-sm:pr-14 ${
Expand Down Expand Up @@ -2353,6 +2377,8 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
</div>

<SidebarProjectThreadList
legacySidebarScale={legacySidebarScale}
scaleStyle={scaleStyle}
projectKey={project.projectKey}
projectExpanded={projectExpanded}
hasOverflowingThreads={hasOverflowingThreads}
Expand Down Expand Up @@ -2794,6 +2820,8 @@ interface SidebarProjectsContentProps {
suppressProjectClickForContextMenuRef: React.RefObject<boolean>;
attachProjectListAutoAnimateRef: (node: HTMLElement | null) => void;
projectsLength: number;
legacySidebarScale: LegacySidebarScale;
projectTreeScaleStyle: CSSProperties;
}

// Drafts the user typed into but never sent, rendered above the projects
Expand Down Expand Up @@ -2902,6 +2930,8 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(
suppressProjectClickForContextMenuRef,
attachProjectListAutoAnimateRef,
projectsLength,
legacySidebarScale,
projectTreeScaleStyle,
} = props;

const handleProjectSortOrderChange = useCallback(
Expand Down Expand Up @@ -3028,6 +3058,8 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(
<SortableProjectItem key={project.projectKey} projectId={project.projectKey}>
{(dragHandleProps) => (
<SidebarProjectItem
legacySidebarScale={legacySidebarScale}
scaleStyle={projectTreeScaleStyle}
project={project}
isThreadListExpanded={expandedThreadListsByProject.has(project.projectKey)}
activeRouteThreadKey={
Expand Down Expand Up @@ -3061,6 +3093,8 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(
{sortedProjects.map((project) => (
<SidebarProjectListRow
key={project.projectKey}
legacySidebarScale={legacySidebarScale}
scaleStyle={projectTreeScaleStyle}
project={project}
isThreadListExpanded={expandedThreadListsByProject.has(project.projectKey)}
activeRouteThreadKey={
Expand All @@ -3086,7 +3120,13 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(
)}

{projectsLength === 0 && (
<div className="px-2 pt-4 text-center text-secondary-label text-xs">No projects yet</div>
<div
className="px-2 pt-4 text-center text-secondary-label text-xs"
data-legacy-sidebar-scale={legacySidebarScale}
style={projectTreeScaleStyle}
>
No projects yet
</div>
)}
</SidebarGroup>
</SidebarContent>
Expand All @@ -3104,6 +3144,11 @@ export default function LegacySidebar() {
const sidebarProjectSortOrder = useClientSettings((s) => s.sidebarProjectSortOrder);
const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings);
const sidebarThreadPreviewCount = useClientSettings((s) => s.sidebarThreadPreviewCount);
const legacySidebarScale = useClientSettings<LegacySidebarScale>((s) => s.legacySidebarScale);
const scaleStyle = useMemo(
() => legacySidebarScaleStyle(legacySidebarScale),
[legacySidebarScale],
);
const updateSettings = useUpdateClientSettings();
const handleNewThread = useNewThreadHandler();
const { archiveThread, deleteThread } = useThreadActions();
Expand Down Expand Up @@ -3764,6 +3809,8 @@ export default function LegacySidebar() {
suppressProjectClickForContextMenuRef={suppressProjectClickForContextMenuRef}
attachProjectListAutoAnimateRef={attachProjectListAutoAnimateRef}
projectsLength={projects.length}
legacySidebarScale={legacySidebarScale}
projectTreeScaleStyle={scaleStyle}
/>
<SidebarChromeFooter />
</>
Expand Down
79 changes: 77 additions & 2 deletions apps/web/src/components/settings/LastCodeSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,33 @@ import type {
DesktopLastCodeSettingsState,
LastCodeSettingsImportPreview,
} from "@t3tools/contracts";
import {
DEFAULT_LEGACY_SIDEBAR_SCALE,
LEGACY_SIDEBAR_SCALE_REFERENCE,
MAX_LEGACY_SIDEBAR_SCALE,
MIN_LEGACY_SIDEBAR_SCALE,
} from "@t3tools/contracts/settings";
import { DownloadIcon, MoonStarIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { type CSSProperties, useCallback, useEffect, useState } from "react";

import { isElectron } from "../../env";
import { usePrimarySettings, useUpdatePrimarySettings } from "../../hooks/useSettings";
import { useDesktopUpdateState } from "../../state/desktopUpdate";
import { Button } from "../ui/button";
import { Switch } from "../ui/switch";
import { stackedThreadToast, toastManager } from "../ui/toast";
import { searchableSetting } from "./settingsSearch";
import { SettingsPageContainer, SettingsRow, SettingsSection } from "./settingsLayout";
import {
SettingResetButton,
SettingsPageContainer,
SettingsRow,
SettingsSection,
} from "./settingsLayout";

export function LastCodeSettingsPanel() {
const updateState = useDesktopUpdateState();
const clientSettings = usePrimarySettings();
const updateClientSettings = useUpdatePrimarySettings();
const [settings, setSettings] = useState<DesktopLastCodeSettingsState | null>(null);
const [importPreview, setImportPreview] = useState<LastCodeSettingsImportPreview | null>(null);
const [isSaving, setIsSaving] = useState(false);
Expand Down Expand Up @@ -109,6 +123,13 @@ export function LastCodeSettingsPanel() {
: settings?.showAndInstallLocalNightlies
? "Enabled. LastCode checks your local repository and shows new checkpoints and LastCode revisions in the sidebar."
: "Off by default. No local repositories, builds, or installers are touched while disabled."));
const legacySidebarScaleRatio =
(clientSettings.legacySidebarScale - MIN_LEGACY_SIDEBAR_SCALE) /
(MAX_LEGACY_SIDEBAR_SCALE - MIN_LEGACY_SIDEBAR_SCALE);
const legacySidebarScaleSliderStyle = {
"--settings-slider-progress": `${legacySidebarScaleRatio * 100}%`,
"--settings-slider-fill-offset": `${0.5 - legacySidebarScaleRatio}rem`,
} as CSSProperties;

return (
<SettingsPageContainer>
Expand All @@ -128,6 +149,60 @@ export function LastCodeSettingsPanel() {
/>
}
/>
<SettingsRow
{...searchableSetting("scale-legacy-sidebar")}
description="Scale legacy project and thread rows while leaving the sidebar header, Search field, and Projects heading unchanged. The 75% marker matches the normalized version of the original compact-sidebar patch."
resetAction={
clientSettings.legacySidebarScale !== DEFAULT_LEGACY_SIDEBAR_SCALE ? (
<SettingResetButton
label="legacy sidebar scale"
onClick={() =>
updateClientSettings({ legacySidebarScale: DEFAULT_LEGACY_SIDEBAR_SCALE })
}
/>
) : null
}
control={
<div className="flex w-full items-center gap-3 sm:w-64">
<output
className="min-w-12 rounded-md bg-muted px-2 py-1 text-center font-mono text-xs font-medium tabular-nums text-foreground"
htmlFor="legacy-sidebar-scale"
>
{clientSettings.legacySidebarScale}%
</output>
<div className="relative min-w-0 flex-1 pb-3">
<input
aria-label="Scale legacy sidebar"
className="settings-slider block w-full"
id="legacy-sidebar-scale"
max={MAX_LEGACY_SIDEBAR_SCALE}
min={MIN_LEGACY_SIDEBAR_SCALE}
onChange={(event) => {
const legacySidebarScale = Number(event.currentTarget.value);
if (
Number.isInteger(legacySidebarScale) &&
legacySidebarScale >= MIN_LEGACY_SIDEBAR_SCALE &&
legacySidebarScale <= MAX_LEGACY_SIDEBAR_SCALE
) {
updateClientSettings({ legacySidebarScale });
}
}}
step={1}
style={legacySidebarScaleSliderStyle}
type="range"
value={clientSettings.legacySidebarScale}
/>
<span
aria-hidden="true"
className="pointer-events-none absolute top-5 left-1/2 flex -translate-x-1/2 flex-col items-center text-[9px] leading-none text-muted-foreground"
>
<span className="h-1.5 border-muted-foreground/60 border-l" />
<span className="mt-0.5">{LEGACY_SIDEBAR_SCALE_REFERENCE}%</span>
</span>
</div>
</div>
}
/>
</SettingsSection>
<SettingsSection title="Import from T3 Code" icon={<DownloadIcon className="size-5" />}>
<SettingsRow
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/components/settings/settingsSearch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,11 @@ describe("searchSettings", () => {
targetId: "appearance",
});
});

it("routes legacy sidebar scaling to LastCode settings", () => {
expect(searchSettings("scale legacy sidebar")[0]).toMatchObject({
id: "scale-legacy-sidebar",
to: "/settings/lastcode",
});
});
});
5 changes: 5 additions & 0 deletions apps/web/src/components/settings/settingsSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ export const SETTINGS_SEARCH_ITEMS = [
title: "Show and install local nightlies",
to: "/settings/lastcode",
},
{
id: "scale-legacy-sidebar",
title: "Scale legacy sidebar",
to: "/settings/lastcode",
},
{
id: "import-t3-settings",
title: "Import settings from T3 Code",
Expand Down
23 changes: 23 additions & 0 deletions apps/web/src/legacySidebarScale.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vite-plus/test";

import { legacySidebarScaleStyle } from "./legacySidebarScale";

describe("legacySidebarScaleStyle", () => {
it("leaves the stock sidebar geometry unchanged at 100%", () => {
expect(legacySidebarScaleStyle(100)).toEqual({
zoom: 1,
});
});

it("renders the reference profile at a literal 75%", () => {
const style = legacySidebarScaleStyle(75);

expect(style.zoom).toBe(0.75);
});

it("renders the minimum 50% scale", () => {
expect(legacySidebarScaleStyle(50)).toEqual({
zoom: 0.5,
});
});
});
15 changes: 15 additions & 0 deletions apps/web/src/legacySidebarScale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { CSSProperties } from "react";
import type { LegacySidebarScale } from "@t3tools/contracts/settings";

/**
* Scale the legacy project tree while preserving its layout and scroll bounds.
* CSS zoom participates in layout, so no inverse dimensions are needed.
* Electron's window zoom is applied outside this element and composes with
* this factor.
*/
export function legacySidebarScaleStyle(scale: LegacySidebarScale): CSSProperties {
const ratio = scale / 100;
return {
zoom: ratio,
};
}
Loading