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
4 changes: 1 addition & 3 deletions apps/web/src/components/AppSidebarLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,7 @@ function SidebarControl() {
const { toggleSidebar, isMobile, mobileSheetMounted } = useSidebar();
const isSidebarVisible = useSidebarVisibility();
const environmentIdentificationMode = useEnvironmentIdentificationMode();
const stageBackdropVariant = useSidebarStageBackdropVariant(
environmentIdentificationMode === "artwork",
);
const stageBackdropVariant = useSidebarStageBackdropVariant(environmentIdentificationMode);
const shortcutLabel = shortcutLabelForCommand(keybindings, "sidebar.toggle", {
context: { usagePageOpen },
});
Expand Down
13 changes: 9 additions & 4 deletions apps/web/src/components/SidebarStageBackdrop.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useAtomValue } from "@effect/atom-react";
import type { EnvironmentIdentificationMode } from "@t3tools/contracts";
import { useId } from "react";

import { APP_STAGE_LABEL } from "../branding";
Expand All @@ -12,9 +13,11 @@ const STAGE_BACKDROP_VIEW_BOX = "0 0 8192 96";

export function resolveSidebarStageBackdropVariant(
stageLabel: string,
enabled = true,
mode: EnvironmentIdentificationMode = "artwork",
): SidebarStageBackdropVariant | null {
if (!enabled) return null;
if (mode === "nightly-artwork") return "nightly";
if (mode === "dev-artwork") return "dev";
if (mode !== "artwork") return null;
const normalized = stageLabel.trim().toLowerCase();
if (normalized === "nightly") return "nightly";
if (normalized === "dev") return "dev";
Expand All @@ -40,8 +43,10 @@ export function useEnvironmentStageLabel(): string {
});
}

export function useSidebarStageBackdropVariant(enabled = true): SidebarStageBackdropVariant | null {
return resolveSidebarStageBackdropVariant(useEnvironmentStageLabel(), enabled);
export function useSidebarStageBackdropVariant(
mode: EnvironmentIdentificationMode,
): SidebarStageBackdropVariant | null {
return resolveSidebarStageBackdropVariant(useEnvironmentStageLabel(), mode);
}

export function SidebarStageBackdrop({ variant }: { variant: SidebarStageBackdropVariant }) {
Expand Down
4 changes: 1 addition & 3 deletions apps/web/src/components/chat/ComposerPrimaryActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({
}) === "queue";
const alternateAction = alternateComposerDispatchAction(followUpBehavior);
const isSendDisabled = !canOperateThread || sendDisabledReason !== null;
const stageBackdropVariant = useSidebarStageBackdropVariant(
environmentIdentificationMode === "artwork",
);
const stageBackdropVariant = useSidebarStageBackdropVariant(environmentIdentificationMode);

const renderStopGenerationButton = (insidePendingAction: boolean) => (
<Tooltip key="interrupt">
Expand Down
132 changes: 84 additions & 48 deletions apps/web/src/components/settings/AppearanceSettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
MIN_PANEL_ANIMATION_DURATION_MS,
} from "@t3tools/contracts/settings";
import {
resolveEnvironmentIdentificationPillLabel,
resolveSidebarStageBackdropVariant,
type SidebarStageBackdropVariant,
useEnvironmentStageLabel,
} from "../SidebarStageBackdrop";
import { useCustomThemes } from "../../hooks/useCustomThemes";
Expand All @@ -35,10 +36,40 @@ import { TypographySection } from "./TypographySettings";

const ENVIRONMENT_IDENTIFICATION_LABELS: Record<EnvironmentIdentificationMode, string> = {
artwork: "Artwork",
"nightly-artwork": "Nightly artwork",
"dev-artwork": "Dev artwork",
pill: "Version pill",
none: "None",
};

// The default "artwork" follows the running channel, so it is labeled as that channel's art,
// or as "None" on Latest. The other channel's art is offered as a fixed choice.
function environmentIdentificationOptions(
stageVariant: SidebarStageBackdropVariant | null,
): ReadonlyArray<readonly [EnvironmentIdentificationMode, string]> {
if (stageVariant === "dev") {
return [
["artwork", "Dev artwork"],
["nightly-artwork", "Nightly artwork"],
["pill", "Version pill"],
["none", "None"],
];
}
if (stageVariant === "nightly") {
return [
["artwork", "Nightly artwork"],
["dev-artwork", "Dev artwork"],
["pill", "Version pill"],
["none", "None"],
];
}
return [
["artwork", "None"],
["nightly-artwork", "Nightly artwork"],
["dev-artwork", "Dev artwork"],
];
}

const CHAT_WIDTH_LABELS: Record<ChatWidth, string> = {
comfortable: "Comfortable",
wide: "Wide",
Expand All @@ -61,8 +92,13 @@ export function AppearanceSettingsPanel() {
const settings = useScopedSettings();
const updateSettings = useUpdateScopedSettings();
const environmentStageLabel = useEnvironmentStageLabel();
const showEnvironmentIdentification =
resolveEnvironmentIdentificationPillLabel(environmentStageLabel) !== null;
const environmentStageVariant = resolveSidebarStageBackdropVariant(environmentStageLabel);
const environmentIdentificationOptionList =
environmentIdentificationOptions(environmentStageVariant);
const environmentIdentificationLabel =
environmentIdentificationOptionList.find(
([value]) => value === settings.environmentIdentificationMode,
)?.[1] ?? ENVIRONMENT_IDENTIFICATION_LABELS[settings.environmentIdentificationMode];
const glassOpacityRatio =
(settings.glassOpacity - MIN_GLASS_OPACITY) / (MAX_GLASS_OPACITY - MIN_GLASS_OPACITY);
const glassOpacitySliderStyle = {
Expand Down Expand Up @@ -316,52 +352,52 @@ export function AppearanceSettingsPanel() {
) : null
}
/>
{showEnvironmentIdentification ? (
<SettingsRow
{...searchableSetting("environment-identification")}
description="Choose how Dev and Nightly environments are identified."
resetAction={
settings.environmentIdentificationMode !==
DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE ? (
<SettingResetButton
label="environment identification"
onClick={() =>
updateSettings({
environmentIdentificationMode: DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE,
})
}
/>
) : null
}
control={
<Select
value={settings.environmentIdentificationMode}
onValueChange={(value) => {
if (value === "artwork" || value === "pill" || value === "none") {
updateSettings({ environmentIdentificationMode: value });
}
}}
<SettingsRow
{...searchableSetting("environment-identification")}
description={
environmentStageVariant
? "Choose how Dev and Nightly environments are identified."
: "Show Dev or Nightly artwork in the sidebar header."
}
resetAction={
settings.environmentIdentificationMode !== DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE ? (
<SettingResetButton
label="environment identification"
onClick={() =>
updateSettings({
environmentIdentificationMode: DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE,
})
}
/>
) : null
}
control={
<Select
value={settings.environmentIdentificationMode}
onValueChange={(value) => {
const option = environmentIdentificationOptionList.find(
([optionValue]) => optionValue === value,
);
if (option) updateSettings({ environmentIdentificationMode: option[0] });
}}
>
<SelectTrigger
size="sm"
className="w-full sm:w-40"
aria-label="Environment identification"
>
<SelectTrigger
size="sm"
className="w-full sm:w-40"
aria-label="Environment identification"
>
<SelectValue>
{ENVIRONMENT_IDENTIFICATION_LABELS[settings.environmentIdentificationMode]}
</SelectValue>
</SelectTrigger>
<SelectPopup align="end" alignItemWithTrigger={false}>
{Object.entries(ENVIRONMENT_IDENTIFICATION_LABELS).map(([value, label]) => (
<SelectItem hideIndicator key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
}
/>
) : null}
<SelectValue>{environmentIdentificationLabel}</SelectValue>
</SelectTrigger>
<SelectPopup align="end" alignItemWithTrigger={false}>
{environmentIdentificationOptionList.map(([value, label]) => (
<SelectItem hideIndicator key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
}
/>
<SettingsRow
{...searchableSetting("composer-context")}
description="Keep branch and worktree controls below the composer after a thread starts."
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/settings/settingsSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ export const SETTINGS_SEARCH_ITEMS = [
id: "environment-identification",
title: "Environment identification",
to: "/settings/appearance",
searchTerms: ["dev nightly artwork pill label hide none"],
searchTerms: ["dev nightly latest channel artwork pill label hide none"],
targetId: "appearance-interface",
},
{
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/sidebar/SidebarChrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({
const environmentIdentificationMode = useEnvironmentIdentificationMode();
const backdropVariant = resolveSidebarStageBackdropVariant(
stageLabel,
environmentIdentificationMode === "artwork",
environmentIdentificationMode,
);
const pillLabel =
environmentIdentificationMode === "pill"
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/hooks/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,8 @@ export function resolveEnvironmentIdentificationMode(input: {
paletteThemeAllowsArtwork?: boolean;
}): EnvironmentIdentificationMode {
if (!input.settingsHydrated) return "none";
return input.paletteThemeActive && !input.paletteThemeAllowsArtwork && input.mode === "artwork"
const isArtwork = input.mode !== "pill" && input.mode !== "none";
return input.paletteThemeActive && !input.paletteThemeAllowsArtwork && isArtwork
? "pill"
: input.mode;
}
Expand Down
10 changes: 9 additions & 1 deletion packages/contracts/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,15 @@ export const TerminalFontSize = Schema.Int.check(
export type TerminalFontSize = typeof TerminalFontSize.Type;
const DEFAULT_TERMINAL_FONT_SIZE: TerminalFontSize = 12;

export const EnvironmentIdentificationMode = Schema.Literals(["artwork", "pill", "none"]);
// "artwork" follows the running channel and shows nothing on Latest. The fixed artwork
// modes let any channel opt into a specific channel's art.
export const EnvironmentIdentificationMode = Schema.Literals([
"artwork",
"nightly-artwork",
"dev-artwork",
"pill",
"none",
]);
export type EnvironmentIdentificationMode = typeof EnvironmentIdentificationMode.Type;
export const DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE: EnvironmentIdentificationMode = "artwork";

Expand Down
Loading