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
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
122 changes: 122 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,72 @@ 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."
status="Exact native scrollbar width and margin require LastCode desktop or a Chromium-based browser. Firefox uses its larger system scrollbar; styled app scroll areas still follow both sliders."
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.
}

::-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