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/web/src/components/settings/ProjectDefaultsSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { resolveProjectSettings } from "@t3tools/shared/projectSettings";
import { useNavigate } from "@tanstack/react-router";

import { mergeEnvironmentSettings } from "../../hooks/useSettings";
import { isProfileSettingVisible } from "../../otterware/settingsRegistry";
import { getCustomModelOptionsByInstance } from "../../modelSelection";
import {
applyProviderInstanceSettings,
Expand Down Expand Up @@ -213,6 +214,7 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting
settingKeys={["defaultThreadEnvMode"]}
mixed={mixedWorkspace}
id={searchableSetting("new-threads").id}
hidden={!isProfileSettingVisible("new-threads")}
title="Workspace"
description={
isProjectScope
Expand Down Expand Up @@ -347,6 +349,7 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting
settingKeys={["worktreeSubmodules"]}
mixed={mixedSubmodules}
{...searchableSetting("worktree-submodules")}
hidden={!isProfileSettingVisible("worktree-submodules")}
description={
isProjectScope
? "How new worktrees in this project populate git submodules."
Expand Down
31 changes: 27 additions & 4 deletions apps/web/src/components/settings/SettingsPanels.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ProfileWordmark } from "../../otterware/ProfileLogo";
import { isProfileSettingVisible } from "../../otterware/settingsRegistry";
import { SettingsGroup } from "./SettingsGroup";
import { useScopedSettingsWriteAllowed } from "./useScopedSettings";
import { Spinner } from "~/components/ui/spinner";
Expand Down Expand Up @@ -600,7 +601,7 @@
DEFAULT_UNIFIED_SETTINGS.persistComposerContextStrip
? ["Composer context"]
: []),
...getChangedTypographySettingLabels(settings),

Check warning on line 604 in apps/web/src/components/settings/SettingsPanels.tsx

View workflow job for this annotation

GitHub Actions / Lint

react(memo-dependencies)

apps/web/src/components/settings/SettingsPanels.tsx:604:44: Found missing/extra memoization dependencies
...(settings.diffFilesCollapsed !== DEFAULT_UNIFIED_SETTINGS.diffFilesCollapsed
? ["Default diff file state"]
: []),
Expand Down Expand Up @@ -869,7 +870,7 @@
setTheme,
setThemeHalf,
theme,
themeHalves,

Check warning on line 873 in apps/web/src/components/settings/SettingsPanels.tsx

View workflow job for this annotation

GitHub Actions / Lint

react(memo-dependencies)

apps/web/src/components/settings/SettingsPanels.tsx:873:5: Found extra memoization dependencies
updateSettings,
]);

Expand Down Expand Up @@ -1888,12 +1889,12 @@
const [draftSettled, setDraftSettled] = useState(true);
const commitTimerRef = useRef<number | null>(null);
const lastValueRef = useRef(value);
if (lastValueRef.current !== value) {

Check warning on line 1892 in apps/web/src/components/settings/SettingsPanels.tsx

View workflow job for this annotation

GitHub Actions / Lint

react(refs)

apps/web/src/components/settings/SettingsPanels.tsx:1892:7: Cannot access refs during render
// The committed value changed externally (hydration, reset, picker
// selection); adopt it and drop any pending commit of a stale draft.
lastValueRef.current = value;

Check warning on line 1895 in apps/web/src/components/settings/SettingsPanels.tsx

View workflow job for this annotation

GitHub Actions / Lint

react(refs)

apps/web/src/components/settings/SettingsPanels.tsx:1895:17: Cannot access refs during render
if (commitTimerRef.current !== null) {
window.clearTimeout(commitTimerRef.current);

Check warning on line 1897 in apps/web/src/components/settings/SettingsPanels.tsx

View workflow job for this annotation

GitHub Actions / Lint

react(refs)

apps/web/src/components/settings/SettingsPanels.tsx:1897:27: Cannot access refs during render
commitTimerRef.current = null;
}
setDraft(value);
Expand Down Expand Up @@ -2057,7 +2058,7 @@
// on valid input and snaps back to the persisted value on blur.
const [draft, setDraft] = useState(String(value));
useEffect(() => {
setDraft(String(value));

Check warning on line 2061 in apps/web/src/components/settings/SettingsPanels.tsx

View workflow job for this annotation

GitHub Actions / Lint

react(set-state-in-effect)

apps/web/src/components/settings/SettingsPanels.tsx:2061:5: Calling setState synchronously within an effect can trigger cascading renders
}, [value]);

return (
Expand Down Expand Up @@ -2124,7 +2125,13 @@
}, [searchTargetId]);

return (
<section id="legacy-features" ref={targetRef} tabIndex={-1} className="space-y-2.5">
<section
id="legacy-features"
ref={targetRef}
tabIndex={-1}
hidden={!isProfileSettingVisible("legacy-features")}
className="space-y-2.5"
>
<Collapsible open={open} onOpenChange={setOpen}>
<CollapsibleTrigger className="group flex min-h-8 w-full items-center gap-2 px-3 sm:px-4">
<h2 className="text-sm font-normal text-foreground/70 transition-colors group-hover:text-foreground">
Expand Down Expand Up @@ -2277,7 +2284,11 @@
return (
<SettingsPageContainer>
<ProjectDefaultsSettings category="general" />
<SettingsSection id="organization" title="Organization">
<SettingsSection
id="organization"
title="Organization"
hidden={!isProfileSettingVisible("organization")}
>
<SettingsRow
{...searchableSetting("project-grouping")}
description="Combine matching repositories across environments."
Expand Down Expand Up @@ -2618,6 +2629,7 @@
/>
<SettingsRow
{...searchableSetting("hide-whitespace-changes")}
hidden={!isProfileSettingVisible("hide-whitespace-changes")}
description="Set whether the diff panel ignores whitespace-only edits by default."
resetAction={
settings.diffIgnoreWhitespace !== DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace ? (
Expand All @@ -2643,6 +2655,7 @@
/>
<SettingsRow
{...searchableSetting("default-diff-file-state")}
hidden={!isProfileSettingVisible("default-diff-file-state")}
description="Start with files expanded or collapsed when opening diffs or a pull request's Code tab."
resetAction={
settings.diffFilesCollapsed !== DEFAULT_UNIFIED_SETTINGS.diffFilesCollapsed ? (
Expand Down Expand Up @@ -2685,6 +2698,7 @@
/>
<SettingsRow
{...searchableSetting("diff-layout")}
hidden={!isProfileSettingVisible("diff-layout")}
description="Show diffs stacked or side by side. The toggle in the diff toolbar changes this too."
resetAction={
settings.diffLayout !== DEFAULT_UNIFIED_SETTINGS.diffLayout ? (
Expand Down Expand Up @@ -2720,6 +2734,7 @@

<SettingsRow
{...searchableSetting("proactive-panels")}
hidden={!isProfileSettingVisible("proactive-panels")}
description="Open linked pull requests first. Otherwise, open Changes for edits to at least 3 files or 50 lines."
resetAction={
settings.proactivePanelsEnabled !== DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled ? (
Expand Down Expand Up @@ -3075,7 +3090,11 @@
/>
</SettingsSection>

<SettingsSection id="projects-and-threads" title="Projects & threads">
<SettingsSection
id="projects-and-threads"
title="Projects & threads"
hidden={!isProfileSettingVisible("projects-and-threads")}
>
<SettingsRow
serverScoped
settingKeys={["newWorktreesStartFromOrigin"]}
Expand Down Expand Up @@ -3260,7 +3279,11 @@
) : null}
</SettingsSection>

<SettingsSection id="text-generation" title="Text generation">
<SettingsSection
id="text-generation"
title="Text generation"
hidden={!isProfileSettingVisible("text-generation")}
>
<SettingsRow
serverScoped
settingKeys={["textGenerationModelSelection"]}
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/settings/SettingsSidebarNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const T3ConnectSidebarAvatar = lazy(() =>
// Otterware seam: module pages, their icons, grouping and profile visibility
// follow the registry (docs/internals/modules.md).
import {
OTTERWARE_SETTINGS_NAV_LABELS,
OTTERWARE_SETTINGS_SECTION_ICONS,
isProfileSettingsPathVisible,
settingsNavLayout,
Expand Down Expand Up @@ -123,7 +124,7 @@ const UPSTREAM_SETTINGS_ORDER = (Object.keys(SETTINGS_SECTION_LABELS) as Setting

const navItem = (to: SettingsPath): SettingsNavItem => ({
to,
label: SETTINGS_SECTION_LABELS[to],
label: OTTERWARE_SETTINGS_NAV_LABELS[to] ?? SETTINGS_SECTION_LABELS[to],
icon: SETTINGS_SECTION_ICONS[to],
});

Expand Down
8 changes: 7 additions & 1 deletion apps/web/src/components/settings/settingsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
type SettingOverridingProject,
} from "./SettingInheritance";
import { ModuleSettingsMerges } from "../../otterware/ModuleSettingsMerges";
import { showsSettingsScopeSentence } from "../../otterware/settingsRegistry";

const EMPTY_SETTING_KEYS: readonly (keyof ServerSettings)[] = [];

Expand Down Expand Up @@ -551,6 +552,8 @@ export function SettingsPageContainer({
}) {
const navigate = useNavigate();
const hash = useLocation({ select: (location) => location.hash });
const pathname = useLocation({ select: (location) => location.pathname });
const scopeSearch = useOptionalSettingsScope()?.search;
const highlightTarget = useLocation({
select: (location) => location.state.settingsTargetHighlight !== false,
});
Expand All @@ -576,7 +579,10 @@ export function SettingsPageContainer({
data-settings-page-scroll
>
<WorkspacePageContainer width={width} className={cn("gap-8", className)}>
<SettingsScopeSentence />
{/* Otterware seam: module pages name their own scope. */}
{showsSettingsScopeSentence(pathname, scopeSearch ?? {}) ? (
<SettingsScopeSentence />
) : null}
{children}
{/* Otterware seam: module settings pages merged into this host page. */}
<ModuleSettingsMerges />
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/components/settings/settingsSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
OTTERWARE_SETTINGS_LABEL_OVERRIDES,
OTTERWARE_SETTINGS_CATEGORY_SCOPES,
OTTERWARE_SETTINGS_SEARCH_ITEMS,
isProfileSettingVisible,
isProfileSettingsPathVisible,
type OtterwareSettingsPath,
} from "../../otterware/settingsRegistry";
Expand Down Expand Up @@ -1218,6 +1219,7 @@ export function filterAvailableSettingsSearchItems(
return items.filter(
(item) =>
isProfileSettingsPathVisible(item.to) &&
isProfileSettingVisible(item.targetId ?? item.id) &&
(!item.cloudOnly || availability.hasCloudPublicConfig) &&
(!item.environmentOnly || availability.hasEnvironment) &&
(!item.providerSettingsOnly || availability.hasProviderSettingsEnvironment) &&
Expand Down
16 changes: 10 additions & 6 deletions apps/web/src/otterware/ModuleSettingsMerges.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { ProviderConnections } from "./account/ProviderConnections";
/**
* Module settings pages whose placement merges them into the host page being
* shown (ADR 0011): each renders as one section, titled with the page's label,
* below the host page's own rows. `SettingsPageContainer` renders this once
* per page through its seam; module pages themselves add nothing here.
* shown (ADR 0011), below the host page's own sections. A module page draws
* its own titled sections, as on its own page, so nothing wraps it here.
* `SettingsPageContainer` renders this once per page through its seam.
*/
import { useLocation } from "@tanstack/react-router";
import { Suspense } from "react";

import { SettingsSection } from "../components/settings/settingsLayout";
import { SettingsSearchTarget, SettingsSection } from "../components/settings/settingsLayout";
import { mergedModuleSettingsPages } from "./settingsRegistry";

export function ModuleSettingsMerges() {
Expand All @@ -23,11 +23,15 @@ export function ModuleSettingsMerges() {
{mergedModuleSettingsPages(pathname).map((entry) => {
const Page = entry.page.component;
return (
<SettingsSection key={entry.sectionId} id={entry.sectionId} title={entry.page.label}>
<SettingsSearchTarget
key={entry.sectionId}
id={entry.sectionId}
className="flex flex-col gap-8 outline-none"
>
<Suspense fallback={null}>
<Page />
</Suspense>
</SettingsSection>
</SettingsSearchTarget>
);
})}
</>
Expand Down
22 changes: 13 additions & 9 deletions apps/web/src/otterware/calendar/CalendarWebModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ import { toastManager } from "../../components/ui/toast";

const settingsPages = [
{
id: "calendar",
label: "Calendar",
id: "general",
label: "General",
placement: "page",
rows: [
"Week start",
"Time zone",
"Week starts on",
"Show weekends",
"Custom view",
"Working hours",
"Default length",
"Custom-view days",
"Weekends",
"Declined",
"New events go to",
"Show declined events",
"Time zone",
"Reminders",
],
},
Expand All @@ -52,7 +52,7 @@ const settingsPages = [
placement: "connections",
rows: ["Google sign-in", "OAuth client", "Demo accounts"],
},
{ id: "general", label: "Time", placement: "general", rows: ["Time format"] },
{ id: "time", label: "Time", placement: "general", rows: ["Time format"] },
] as const;

const commands: WebModuleCommand[] = [
Expand Down Expand Up @@ -246,8 +246,12 @@ export const CalendarWebModule = createCalendarWebModule({
id:
(
{
"Custom-view days": "calendar-custom-days",
"Week starts on": "calendar-week-start",
"Show weekends": "calendar-weekends",
"Custom view": "calendar-custom-days",
"Default length": "calendar-default-length",
"New events go to": "calendar-default-calendar",
"Show declined events": "calendar-declined",
"Time format": "calendar-hour-format",
} as Record<string, string>
)[title] ?? `calendar-${title.toLowerCase().replaceAll(" ", "-")}`,
Expand Down
Loading
Loading