Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ const clientSettings: ClientSettings = {
sidebarThreadPreviewCount: 6,
legacySidebarEnabled: false,
legacySidebarScale: 100,
largerScrollbarsEnabled: true,
scrollbarWidth: 10,
scrollbarMargin: 4,
timestampFormat: "24-hour",
wordWrap: true,
};
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/ChatMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1918,7 +1918,7 @@ const MarkdownFileLink = memo(function MarkdownFileLink({
>
{/* The full path: the chip already shows the shortened form, and a link
to the workspace root collapses to a bare label that repeats it. */}
<div className="overflow-x-auto whitespace-nowrap [scrollbar-color:color-mix(in_srgb,var(--contrast-border)_78%,transparent)_transparent] [scrollbar-width:thin] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-[color-mix(in_srgb,var(--contrast-border)_78%,transparent)] [&::-webkit-scrollbar-track]:bg-transparent">
<div className="overflow-x-auto whitespace-nowrap [scrollbar-color:color-mix(in_srgb,var(--contrast-border)_78%,transparent)_transparent] [scrollbar-width:var(--app-native-scrollbar-width)] [&::-webkit-scrollbar]:h-[var(--app-compact-scrollbar-height)] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-[color-mix(in_srgb,var(--contrast-border)_78%,transparent)] [&::-webkit-scrollbar-track]:bg-transparent">
{targetPath}
</div>
</TooltipPopup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ describe("ComposerPendingApprovalPanel", () => {
expect(markup).toContain("max-h-20");
expect(markup).toContain("overflow-auto");
expect(markup).toContain("whitespace-pre");
expect(markup).toContain("[scrollbar-width:thin]");
expect(markup).toContain("[&amp;::-webkit-scrollbar]:h-1.5");
expect(markup).toContain("[scrollbar-width:var(--app-native-scrollbar-width)]");
expect(markup).toContain("[&amp;::-webkit-scrollbar]:h-[var(--app-compact-scrollbar-height)]");
expect(markup).not.toContain("truncate");
expect(markup).not.toContain("line-clamp");
expect(markup).toContain("min-w-0");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const ComposerPendingApprovalPanel = memo(function ComposerPendingApprova
) : null}
<code
aria-label={detailAriaLabel}
className="block max-h-20 min-w-0 flex-1 overflow-auto whitespace-pre font-mono text-[11px] text-foreground/85 [scrollbar-width:thin] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70 [&::-webkit-scrollbar]:h-1.5"
className="block max-h-20 min-w-0 flex-1 overflow-auto whitespace-pre font-mono text-[11px] text-foreground/85 [scrollbar-width:var(--app-native-scrollbar-width)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70 [&::-webkit-scrollbar]:h-[var(--app-compact-scrollbar-height)]"
data-approval-detail="complete"
tabIndex={0}
>
Expand Down
121 changes: 121 additions & 0 deletions apps/web/src/components/settings/LastCodeSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ import {
} from "@t3tools/contracts";
import {
DEFAULT_LEGACY_SIDEBAR_SCALE,
DEFAULT_SCROLLBAR_MARGIN,
DEFAULT_SCROLLBAR_WIDTH,
LEGACY_SIDEBAR_SCALE_REFERENCE,
MAX_LEGACY_SIDEBAR_SCALE,
MAX_SCROLLBAR_MARGIN,
MAX_SCROLLBAR_WIDTH,
MIN_LEGACY_SIDEBAR_SCALE,
MIN_SCROLLBAR_MARGIN,
MIN_SCROLLBAR_WIDTH,
} from "@t3tools/contracts/settings";
import { useAtomValue } from "@effect/atom-react";
import { DownloadIcon, MoonStarIcon, PaletteIcon, ServerIcon } from "lucide-react";
Expand Down Expand Up @@ -46,6 +52,56 @@ const WORKTREE_INDICATOR_PREVIEW_THREAD = {
worktreePath: "/example/worktrees/example",
};

function PixelSlider({
id,
label,
min,
max,
value,
onChange,
}: {
id: string;
label: string;
min: number;
max: number;
value: number;
onChange: (value: number) => void;
}) {
const ratio = (value - min) / (max - min);
const sliderStyle = {
"--settings-slider-progress": `${ratio * 100}%`,
"--settings-slider-fill-offset": `${0.5 - ratio}rem`,
} as CSSProperties;

return (
<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={id}
>
{value}px
</output>
<input
aria-label={label}
className="settings-slider block min-w-0 flex-1"
id={id}
max={max}
min={min}
onChange={(event) => {
const nextValue = Number(event.currentTarget.value);
if (Number.isInteger(nextValue) && nextValue >= min && nextValue <= max) {
onChange(nextValue);
}
}}
step={1}
style={sliderStyle}
type="range"
value={value}
/>
</div>
);
}

export function LastCodeSettingsPanel() {
const updateState = useDesktopUpdateState();
const clientSettings = usePrimarySettings();
Expand Down Expand Up @@ -182,6 +238,71 @@ export function LastCodeSettingsPanel() {
/>
</SettingsSection>
<SettingsSection title="Appearance" icon={<PaletteIcon className="size-5" />}>
<SettingsRow
{...searchableSetting("larger-scrollbars")}
description="Make scrollbar thumbs easier to grab. Margin keeps the thumb clear of pane resize handles."
control={
<Switch
checked={clientSettings.largerScrollbarsEnabled}
onCheckedChange={(checked) =>
updateClientSettings({ largerScrollbarsEnabled: Boolean(checked) })
}
aria-label="Larger scrollbars"
/>
}
/>
{clientSettings.largerScrollbarsEnabled ? (
<>
<SettingsRow
title="Scrollbar width"
description="Set the visible scrollbar thumb width in one-pixel increments."
resetAction={
clientSettings.scrollbarWidth !== DEFAULT_SCROLLBAR_WIDTH ? (
<SettingResetButton
label="scrollbar width"
onClick={() =>
updateClientSettings({ scrollbarWidth: DEFAULT_SCROLLBAR_WIDTH })
}
/>
) : null
}
control={
<PixelSlider
id="scrollbar-width"
label="Scrollbar width"
min={MIN_SCROLLBAR_WIDTH}
max={MAX_SCROLLBAR_WIDTH}
value={clientSettings.scrollbarWidth}
onChange={(scrollbarWidth) => updateClientSettings({ scrollbarWidth })}
/>
}
/>
<SettingsRow
title="Scrollbar margin"
description="Set the clear space between a scrollbar thumb and the pane edge."
resetAction={
clientSettings.scrollbarMargin !== DEFAULT_SCROLLBAR_MARGIN ? (
<SettingResetButton
label="scrollbar margin"
onClick={() =>
updateClientSettings({ scrollbarMargin: DEFAULT_SCROLLBAR_MARGIN })
}
/>
) : null
}
control={
<PixelSlider
id="scrollbar-margin"
label="Scrollbar margin"
min={MIN_SCROLLBAR_MARGIN}
max={MAX_SCROLLBAR_MARGIN}
value={clientSettings.scrollbarMargin}
onChange={(scrollbarMargin) => updateClientSettings({ scrollbarMargin })}
/>
}
/>
</>
) : null}
<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."
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 @@ -233,6 +233,13 @@ describe("searchSettings", () => {
});
});

it("routes scrollbar sizing to LastCode settings", () => {
expect(searchSettings("scrollbar margin")[0]).toMatchObject({
id: "larger-scrollbars",
to: "/settings/lastcode",
});
});

it("routes project icon rounding to LastCode settings", () => {
expect(searchSettings("rounded project icons")[0]).toMatchObject({
id: "rounded-project-icons",
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/components/settings/settingsSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,12 @@ export const SETTINGS_SEARCH_ITEMS = [
title: "Show and install local nightlies",
to: "/settings/lastcode",
},
{
id: "larger-scrollbars",
title: "Larger scrollbars",
to: "/settings/lastcode",
searchTerms: ["width margin resize handle pane drag thumb"],
},
{
id: "scale-legacy-sidebar",
title: "Scale legacy sidebar",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/ui/scroll-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function ScrollBar({
return (
<ScrollAreaPrimitive.Scrollbar
className={cn(
"flex opacity-0 transition-opacity delay-300 data-[orientation=horizontal]:mx-1 data-[orientation=horizontal]:mb-px data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:my-1 data-[orientation=vertical]:mr-px data-[orientation=vertical]:w-1.5 data-[orientation=horizontal]:flex-col data-hovering:opacity-100 data-scrolling:opacity-100 data-hovering:delay-0 data-scrolling:delay-0 data-hovering:duration-100 data-scrolling:duration-100",
"flex opacity-0 transition-opacity delay-300 data-[orientation=horizontal]:mx-1 data-[orientation=horizontal]:mb-[var(--app-scrollbar-margin)] data-[orientation=horizontal]:h-[var(--app-scrollbar-width)] data-[orientation=vertical]:my-1 data-[orientation=vertical]:mr-[var(--app-scrollbar-margin)] data-[orientation=vertical]:w-[var(--app-scrollbar-width)] data-[orientation=horizontal]:flex-col data-hovering:opacity-100 data-scrolling:opacity-100 data-hovering:delay-0 data-scrolling:delay-0 data-hovering:duration-100 data-scrolling:duration-100",
className,
)}
data-slot="scroll-area-scrollbar"
Expand Down
45 changes: 33 additions & 12 deletions apps/web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil

:root {
--app-scrollbar-width: 6px;
--app-scrollbar-margin: 1px;
--app-scrollbar-lane-width: var(--app-scrollbar-width);
--app-native-scrollbar-margin: 0px;
--app-native-scrollbar-width: thin;
--app-compact-scrollbar-height: 6px;
--app-code-scrollbar-height: 7px;
--app-scrollbar-thumb-inset: 1px;
--appearance-contrast-base: 100%;
--appearance-contrast-boost: 0%;
--appearance-contrast-border-boost: 0%;
Expand Down Expand Up @@ -371,7 +378,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
-webkit-mask-size:
100% var(--workspace-titlebar-scroll-fade-height),
100% calc(100% - var(--workspace-titlebar-scroll-fade-height)),
var(--app-scrollbar-width) 100%;
var(--app-scrollbar-lane-width) 100%;
mask-image:
linear-gradient(
to bottom,
Expand All @@ -389,7 +396,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
mask-size:
100% var(--workspace-titlebar-scroll-fade-height),
100% calc(100% - var(--workspace-titlebar-scroll-fade-height)),
var(--app-scrollbar-width) 100%;
var(--app-scrollbar-lane-width) 100%;
}

/* Virtualizers own their native scroll element, so they cannot use ScrollArea's
Expand All @@ -403,11 +410,11 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size:
calc(100% - var(--app-scrollbar-width)) 100%,
var(--app-scrollbar-width) 100%;
calc(100% - var(--app-scrollbar-lane-width)) 100%,
var(--app-scrollbar-lane-width) 100%;
mask-size:
calc(100% - var(--app-scrollbar-width)) 100%,
var(--app-scrollbar-width) 100%;
calc(100% - var(--app-scrollbar-lane-width)) 100%,
var(--app-scrollbar-lane-width) 100%;
}

/* Stage-channel art needs a mask and pseudo-element gradient, so keep the
Expand Down Expand Up @@ -2080,20 +2087,34 @@ code {

/* Scrollbar styling */
::-webkit-scrollbar {
width: var(--app-scrollbar-width);
width: var(--app-scrollbar-lane-width);
}

html[data-larger-scrollbars]::-webkit-scrollbar,
html[data-larger-scrollbars] ::-webkit-scrollbar {
height: var(--app-scrollbar-lane-width);
Comment thread
lastobelus marked this conversation as resolved.
Resolved
}

::-webkit-scrollbar-track {
background: transparent;
}

::-webkit-scrollbar-thumb {
background: var(--app-scrollbar-thumb);
background-color: var(--app-scrollbar-thumb);
background-clip: padding-box;
border-radius: 3px;
}

::-webkit-scrollbar-thumb:vertical {
border-right: var(--app-native-scrollbar-margin) solid transparent;
}

::-webkit-scrollbar-thumb:horizontal {
border-bottom: var(--app-native-scrollbar-margin) solid transparent;
}

::-webkit-scrollbar-thumb:hover {
background: var(--app-scrollbar-thumb-hover);
background-color: var(--app-scrollbar-thumb-hover);
}

/* Chat markdown rendering */
Expand Down Expand Up @@ -2328,7 +2349,7 @@ code {
border-radius: 0.75rem;
background: var(--muted);
padding: 0.8rem 0.9rem;
scrollbar-width: thin;
scrollbar-width: var(--app-native-scrollbar-width);
scrollbar-color: color-mix(in srgb, var(--contrast-border) 78%, transparent) transparent;
}

Expand All @@ -2339,12 +2360,12 @@ code {
}

.chat-markdown pre::-webkit-scrollbar {
height: 7px;
height: var(--app-code-scrollbar-height);
}

.chat-markdown pre::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--contrast-border) 78%, transparent);
background-color: color-mix(in srgb, var(--contrast-border) 78%, transparent);
}

.chat-markdown .chat-markdown-codeblock-header,
Expand Down
26 changes: 26 additions & 0 deletions apps/web/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
import { resolveAndPersistPreferredEditor } from "../editorPreferences";
import { applyAppearanceFontVariables } from "~/appearanceFonts";
import { applyAppearanceContrast } from "~/appearanceContrast";
import { applyScrollbarAppearance } from "~/scrollbarAppearance";
import { useClientSettings } from "../hooks/useSettings";
import { PlanAgentSelectionHeal } from "../planAgentSelectionHeal";
import {
Expand Down Expand Up @@ -142,6 +143,7 @@ function RootRouteView() {
<GlassAppearanceSync />
<FontAppearanceSync />
<ProjectIconAppearanceSync />
<ScrollbarAppearanceSync />
{primaryEnvironmentAuthenticated ? <AuthenticatedTracingBootstrap /> : null}
{primaryEnvironmentAuthenticated ? <DesktopAppActivationCoordinator /> : null}
<RelayClientInstallDialog />
Expand Down Expand Up @@ -204,6 +206,30 @@ function ProjectIconAppearanceSync() {
return null;
}

function ScrollbarAppearanceSync() {
const largerScrollbarsEnabled = useClientSettings((settings) => settings.largerScrollbarsEnabled);
const scrollbarWidth = useClientSettings((settings) => settings.scrollbarWidth);
const scrollbarMargin = useClientSettings((settings) => settings.scrollbarMargin);

useEffect(() => {
const root = document.documentElement;
applyScrollbarAppearance(root, {
enabled: largerScrollbarsEnabled,
width: scrollbarWidth,
margin: scrollbarMargin,
});
return () => {
applyScrollbarAppearance(root, {
enabled: false,
width: scrollbarWidth,
margin: scrollbarMargin,
});
};
}, [largerScrollbarsEnabled, scrollbarMargin, scrollbarWidth]);

return null;
}

function FontAppearanceSync() {
const fontFamilySans = useClientSettings((settings) => settings.fontFamilySans);
const fontFamilyCode = useClientSettings((settings) => settings.fontFamilyCode);
Expand Down
Loading