Skip to content
Open
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 @@ -3,7 +3,7 @@ export const ADVANCED_SETTINGS_SECTIONS = [
{ id: "runtime", title: "Runtime", keywords: ["opencode engine", "openwork server", "connection status"] },
{ id: "agent-access", title: "Agent access diagnostics", keywords: ["cloud mcp", "health", "tools", "connections"] },
{ id: "config-sources", title: "OpenCode config sources", keywords: ["runtime db", "injected config", "project config", "global config"] },
{ id: "experimental-engine", title: "Experimental engine", keywords: ["engine v2", "chat engine", "preview"] },
{ id: "experimental-engine", title: "OpenCode engine", keywords: ["engine v2", "experimental engine", "preview"] },
{ id: "workspace-run-mode", title: "Workspace run mode", keywords: ["approvals", "permissions", "keep going", "feature flag"] },
{ id: "developer", title: "Developer", keywords: ["developer mode", "debug", "deep link"] },
];
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface LanguageSectionProps extends Pick<AppearanceViewProps, "busy" | "langu

export function LanguageSection(props: LanguageSectionProps) {
return (
<LayoutSectionItem>
<LayoutSectionItem anchor="language">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.language")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.language.description")}</LayoutSectionItemDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface ThemeSectionProps

export function ThemeSection(props: ThemeSectionProps) {
return (
<LayoutSectionItem className="items-center">
<LayoutSectionItem anchor="theme" className="items-center">
<LayoutSectionItemHeader className="w-full">
<LayoutSectionItemTitle>{t("settings.theme_title")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.appearance_hint")}</LayoutSectionItemDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function WindowSection(props: WindowSectionProps) {
<LayoutSectionDescription>{t("settings.window_appearance_desc")}</LayoutSectionDescription>
</LayoutSectionHeader>

<LayoutSectionItem>
<LayoutSectionItem anchor="hide-titlebar">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.hide_titlebar")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.hide_titlebar_desc")}</LayoutSectionItemDescription>
Expand All @@ -43,7 +43,7 @@ export function WindowSection(props: WindowSectionProps) {
</LayoutSectionItemHeader>
</LayoutSectionItem>

<LayoutSectionItem className="hidden windows:flex linux:flex">
<LayoutSectionItem anchor="menu-bar" className="hidden windows:flex linux:flex">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>Display menu bar</LayoutSectionItemTitle>
<LayoutSectionItemDescription>Show the native desktop menu bar.</LayoutSectionItemDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ export function AdvancedWorkspaceRunModeSection() {
<LayoutSectionTitle>Workspace run mode</LayoutSectionTitle>
<LayoutSectionDescription>Experimental approval controls in the composer.</LayoutSectionDescription>
</LayoutSectionHeader>
<LayoutSectionItem>
<LayoutSectionItem anchor="show-workspace-run-mode">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>Show workspace run mode</LayoutSectionItemTitle>
<LayoutSectionItemDescription>
Expand Down Expand Up @@ -743,7 +743,7 @@ export function AdvancedEngineV2PreviewSection(props: { client: OpenworkServerCl
return (
<LayoutSection id="advanced-experimental-engine">
<LayoutSectionHeader><LayoutSectionTitle>OpenCode engine</LayoutSectionTitle></LayoutSectionHeader>
<LayoutSectionItem>
<LayoutSectionItem anchor="chat-engine">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>Chat engine</LayoutSectionItemTitle>
<LayoutSectionItemHeaderActions>
Expand Down Expand Up @@ -786,7 +786,7 @@ export function AdvancedDeveloperSection(props: AdvancedDeveloperSectionProps) {
<LayoutSectionTitle>{t("settings.developer")}</LayoutSectionTitle>
</LayoutSectionHeader>

<LayoutSectionItem>
<LayoutSectionItem anchor="developer-mode">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.developer_mode_title")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.developer_mode_desc")}</LayoutSectionItemDescription>
Expand Down Expand Up @@ -880,7 +880,7 @@ function ComputerUseRecordingItem() {
.catch((cause) => setError(cause instanceof Error ? cause.message : String(cause)));
};
return (
<LayoutSectionItem>
<LayoutSectionItem anchor="screen-recordings">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>Show computer use in screen recordings</LayoutSectionItemTitle>
<LayoutSectionItemDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function PreferencesView(props: PreferencesViewProps) {
</LayoutSectionHeader>

{/* Show reasoning */}
<LayoutSectionItem>
<LayoutSectionItem anchor="show-model-reasoning">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.show_model_reasoning")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.show_model_reasoning_desc")}</LayoutSectionItemDescription>
Expand All @@ -101,7 +101,7 @@ export function PreferencesView(props: PreferencesViewProps) {
</LayoutSectionItem>

{/* Auto context compaction */}
<LayoutSectionItem>
<LayoutSectionItem anchor="auto-compact">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.auto_compact")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.auto_compact_desc")}</LayoutSectionItemDescription>
Expand All @@ -122,7 +122,7 @@ export function PreferencesView(props: PreferencesViewProps) {
<LayoutSectionTitle>{t("settings.follow_up.title")}</LayoutSectionTitle>
<LayoutSectionDescription>{t("settings.follow_up.section_desc")}</LayoutSectionDescription>
</LayoutSectionHeader>
<LayoutSectionItem>
<LayoutSectionItem anchor="follow-up">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.follow_up.enter")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>
Expand Down Expand Up @@ -158,7 +158,7 @@ export function PreferencesView(props: PreferencesViewProps) {
<LayoutSectionDescription>{t("settings.desktop_notifications.section_desc")}</LayoutSectionDescription>
</LayoutSectionHeader>

<LayoutSectionItem>
<LayoutSectionItem anchor="desktop-notifications">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.desktop_notifications.mode")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.desktop_notifications.mode_desc")}</LayoutSectionItemDescription>
Expand Down Expand Up @@ -199,7 +199,7 @@ export function PreferencesView(props: PreferencesViewProps) {
<LayoutSectionTitle>{t("settings.power_title")}</LayoutSectionTitle>
</LayoutSectionHeader>

<LayoutSectionItem>
<LayoutSectionItem anchor="keep-awake">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.keep_awake")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.keep_awake_desc")}</LayoutSectionItemDescription>
Expand All @@ -225,7 +225,7 @@ export function PreferencesView(props: PreferencesViewProps) {
<LayoutSectionDescription>{t("settings.browser_section_desc")}</LayoutSectionDescription>
</LayoutSectionHeader>

<LayoutSectionItem>
<LayoutSectionItem anchor="open-links-in">
<LayoutSectionItemHeader className="min-h-10 items-center">
<LayoutSectionItemTitle>{t("settings.links.open_in")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.links.open_in_desc")}</LayoutSectionItemDescription>
Expand Down Expand Up @@ -260,7 +260,7 @@ export function PreferencesView(props: PreferencesViewProps) {
<LayoutSectionDescription>{t("settings.privacy_section_desc")}</LayoutSectionDescription>
</LayoutSectionHeader>

<LayoutSectionItem>
<LayoutSectionItem anchor="usage-data">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.analytics_toggle")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.analytics_toggle_desc")}</LayoutSectionItemDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export function UpdatesView(props: UpdatesViewProps) {
<>
<Separator />
{props.alphaChannelSupported && props.releaseChannel ? (
<LayoutSectionItem>
<LayoutSectionItem anchor="release-channel">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>Release channel</LayoutSectionItemTitle>
<LayoutSectionItemDescription>
Expand Down Expand Up @@ -311,7 +311,7 @@ export function UpdatesView(props: UpdatesViewProps) {
</LayoutSectionItem>
) : null}

<LayoutSectionItem>
<LayoutSectionItem anchor="check-automatically">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.background_checks_title")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.background_checks_desc")}</LayoutSectionItemDescription>
Expand All @@ -325,7 +325,7 @@ export function UpdatesView(props: UpdatesViewProps) {
</LayoutSectionItemHeader>
</LayoutSectionItem>

<LayoutSectionItem>
<LayoutSectionItem anchor="download-automatically">
<LayoutSectionItemHeader>
<LayoutSectionItemTitle>{t("settings.auto_update_title")}</LayoutSectionItemTitle>
<LayoutSectionItemDescription>{t("settings.auto_update_desc")}</LayoutSectionItemDescription>
Expand Down
46 changes: 46 additions & 0 deletions apps/app/src/react-app/domains/settings/settings-anchor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect } from "react";

/** How long a setting opened from search stays outlined. */
const FOUND_MS = 1600;
/** Rows that wait on loaded state get about a second to appear. */
const MAX_FRAMES = 60;

const ANCHOR_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;

export function isSettingAnchor(value: string | undefined): value is string {
return value !== undefined && ANCHOR_PATTERN.test(value);
}

/**
* Scrolls the setting named by `/settings/<tab>/<anchor>` into view, moves
* focus to it and outlines it briefly, so someone who searched for one option
* does not have to scan the page for it. `navigationKey` re-runs the reveal
* when the same setting is opened again.
*/
export function useRevealSetting(anchor: string | undefined, navigationKey: string) {
useEffect(() => {
if (!isSettingAnchor(anchor)) return;
let frame = 0;
let frames = 0;
let timer = 0;
let found: HTMLElement | null = null;
const reveal = () => {
found = document.querySelector<HTMLElement>(`[data-setting-anchor="${anchor}"]`);
if (!found) {
frames += 1;
if (frames < MAX_FRAMES) frame = requestAnimationFrame(reveal);
return;
}
found.scrollIntoView({ block: "center" });
found.focus({ preventScroll: true });
found.setAttribute("data-setting-found", "");
timer = window.setTimeout(() => found?.removeAttribute("data-setting-found"), FOUND_MS);
};
frame = requestAnimationFrame(reveal);
return () => {
cancelAnimationFrame(frame);
window.clearTimeout(timer);
found?.removeAttribute("data-setting-found");
};
}, [anchor, navigationKey]);
}
17 changes: 15 additions & 2 deletions apps/app/src/react-app/domains/settings/settings-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,26 @@ export function LayoutSectionDescription({ children, className }: LayoutSectionD
}

interface LayoutSectionItemProps {
/**
* Names this setting so search can open the page at it:
* `/settings/<tab>/<anchor>`. List it in `settings-search-index.ts`.
*/
anchor?: string;
children: React.ReactNode;
className?: string;
}

export function LayoutSectionItem({ children, className }: LayoutSectionItemProps) {
export function LayoutSectionItem({ anchor, children, className }: LayoutSectionItemProps) {
return (
<div className={cn("flex flex-col gap-3", className)}>
<div
data-setting-anchor={anchor}
tabIndex={anchor ? -1 : undefined}
className={cn(
"flex flex-col gap-3",
anchor && "scroll-mt-6 rounded-lg outline-2 outline-offset-8 outline-transparent transition-[outline-color] duration-200 ease-out motion-reduce:transition-none data-setting-found:outline-ring/30",
className,
)}
>
{children}
</div>
);
Expand Down
Loading
Loading