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
21 changes: 13 additions & 8 deletions apps/web/src/app/(app)/settings/_components/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
AutomationCodeReviewConfigProvider,
CodeReviewModelDataProvider,
DefaultCodeReviewConfigProvider,
InitialParametersProvider,
PlatformConfigProvider,
ScopedCodeReviewConfigProvider,
type CodeReviewModelData,
Expand Down Expand Up @@ -89,6 +90,7 @@ type SettingsLayoutProps = React.PropsWithChildren<{
initialConfigValue: FormattedGlobalCodeReviewConfig;
initialDefaultConfig: InitialDefaultConfig;
initialPlatformConfig: InitialPlatformConfig;
initialParameters: Partial<Record<string, { uuid: string; configKey: string; configValue: string } | null>>;
initialModelData: CodeReviewModelData;
}>;

Expand All @@ -98,6 +100,7 @@ export const SettingsLayout = ({
initialConfigValue,
initialDefaultConfig,
initialPlatformConfig,
initialParameters,
initialModelData,
}: SettingsLayoutProps) => {
const { teamId } = useSelectedTeamId();
Expand Down Expand Up @@ -145,14 +148,16 @@ export const SettingsLayout = ({

return (
<CodeReviewModelDataProvider value={initialModelData}>
<SettingsLayoutShell
teamId={effectiveTeamId}
configValue={liveShellQuery?.configValue ?? initialConfigValue}
defaultConfig={defaultConfig ?? initialDefaultConfig}
platformConfig={platformConfig ?? initialPlatformConfig}
isMCPAvailable={isMCPAvailable}>
{children}
</SettingsLayoutShell>
<InitialParametersProvider value={{ initialTeamId, parameters: initialParameters }}>
<SettingsLayoutShell
teamId={effectiveTeamId}
configValue={liveShellQuery?.configValue ?? initialConfigValue}
defaultConfig={defaultConfig ?? initialDefaultConfig}
platformConfig={platformConfig ?? initialPlatformConfig}
isMCPAvailable={isMCPAvailable}>
{children}
</SettingsLayoutShell>
</InitialParametersProvider>
</CodeReviewModelDataProvider>
);
};
Expand Down
42 changes: 42 additions & 0 deletions apps/web/src/app/(app)/settings/_components/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,45 @@ export const CodeReviewModelDataProvider = (
);

export { resolveCodeReviewConfigForScope };

/**
* Generic bridge for SSR-seeded parameter values.
* Keyed by ParametersConfigKey, so any new parameter only adds a key — no new provider.
* Carries `initialTeamId` so consumers can guard: only apply the seed when
* the active team matches the one the seed was fetched for (same pattern as
* the shell's `effectiveTeamId === initialTeamId` check).
*/
type InitialParameterValue = {
uuid: string;
configKey: string;
configValue: string;
} | null;

type InitialParametersBridge = {
initialTeamId: string;
parameters: Partial<Record<string, InitialParameterValue>>;
};

const InitialParametersContext = createContext<InitialParametersBridge>({
initialTeamId: "",
parameters: {},
});

export const useInitialTeamId = (): string => {
return useContext(InitialParametersContext).initialTeamId;
};

export const useInitialParameter = (key: string): InitialParameterValue => {
const { parameters } = useContext(InitialParametersContext);
return parameters[key] ?? null;
};

export const InitialParametersProvider = (
props: React.PropsWithChildren & {
value: InitialParametersBridge;
},
) => (
<InitialParametersContext.Provider value={props.value}>
{props.children}
</InitialParametersContext.Provider>
);
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
import {
useCodeReviewConfig,
useDefaultCodeReviewConfig,
useInitialParameter,
useInitialTeamId,
} from "../../_components/context";
import { useCodeReviewRouteParams } from "../../_hooks";
import { normalizePromptFormValues } from "./custom-prompts/_utils/custom-prompts-state";
Expand All @@ -31,6 +33,8 @@ export default function Layout(props: React.PropsWithChildren) {
const { teamId } = useSelectedTeamId();
const config = useCodeReviewConfig();
const defaultCodeReviewConfig = useDefaultCodeReviewConfig();
const bridgeInitialTeamId = useInitialTeamId();
const initialLanguage = useInitialParameter(ParametersConfigKey.LANGUAGE_CONFIG);
const { directoryId } = useCodeReviewRouteParams();
const parameters = useOptionalParameterQuery<LanguageValue>(
ParametersConfigKey.LANGUAGE_CONFIG,
Expand All @@ -49,7 +53,7 @@ export default function Layout(props: React.PropsWithChildren) {
repositoryId,
directoryId,
);
const language = parameters.data?.configValue ?? LanguageValue.ENGLISH;
const language = parameters.data?.configValue ?? (teamId === bridgeInitialTeamId ? (initialLanguage?.configValue as LanguageValue) : undefined) ?? LanguageValue.ENGLISH;
const initialFormValues = useMemo(
() =>
normalizePromptFormValues(
Expand Down Expand Up @@ -87,28 +91,6 @@ export default function Layout(props: React.PropsWithChildren) {
dirtyFields,
} = form.formState;

// TEMP DEBUG (save-in-setup): logs why the Save button stays disabled.
useEffect(() => {
// eslint-disable-next-line no-console
console.log("[save-debug] formState", {
hydrationKey,
canEdit,
formDisabled: !canEdit,
isDirty: formIsDirty,
isValid: formIsValid,
isSubmitting: formIsSubmitting,
dirtyFieldKeys: Object.keys(dirtyFields ?? {}),
saveDisabled: !formIsDirty || !formIsValid,
});
}, [
canEdit,
dirtyFields,
formIsDirty,
formIsValid,
formIsSubmitting,
hydrationKey,
]);

useEffect(() => {
if (
!shouldHydrateCodeReviewForm(
Expand All @@ -119,14 +101,6 @@ export default function Layout(props: React.PropsWithChildren) {
return;
}

// TEMP DEBUG (save-in-setup): a reset here right after an edit would
// explain a dirty flag that never sticks.
// eslint-disable-next-line no-console
console.log("[save-debug] form.reset()", {
from: hydratedStateKeyRef.current,
to: hydrationKey,
});

form.reset(initialFormValues);
hydratedStateKeyRef.current = hydrationKey;
}, [form, hydrationKey, initialFormValues]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@ export const useCodeReviewSettingsMutation = (params: {
),
refetchType: "all",
});
void queryClient.invalidateQueries({
queryKey: generateQueryKey(PARAMETERS_PATHS.GET_BY_KEY, {
params: {
key: ParametersConfigKey.LANGUAGE_CONFIG,
teamId,
},
}),
refetchType: "all",
});
};

const saveSettings = async (
Expand Down
14 changes: 14 additions & 0 deletions apps/web/src/app/(app)/settings/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {
getDefaultCodeReviewParameterNoCache,
getFormattedCodeReviewParameterNoCache,
getPlatformConfigParameterNoCache,
getTeamParametersNoCache,
} from "@services/parameters/fetch";
import { ParametersConfigKey } from "@services/parameters/types";
import { PageBoundary } from "src/core/components/page-boundary";
import { Skeleton } from "src/core/components/ui/skeleton";

Expand Down Expand Up @@ -61,11 +63,20 @@ export default async function Layout({ children }: React.PropsWithChildren) {
initialShellConfig,
initialDefaultConfig,
initialPlatformConfig,
initialLanguageConfig,
initialByokModels,
] = await Promise.all([
getFormattedCodeReviewParameterNoCache(initialTeamId),
getDefaultCodeReviewParameterNoCache(),
getPlatformConfigParameterNoCache(initialTeamId),
getTeamParametersNoCache<{
uuid: string;
configKey: string;
configValue: string;
}>({
key: ParametersConfigKey.LANGUAGE_CONFIG,
teamId: initialTeamId,
}).catch(() => null),
// Drives the BYOK model selector's catalog. Empty on error / no BYOK.
byokProvider
? getLLMProviderModels(byokProvider).catch(() => [])
Expand All @@ -90,6 +101,9 @@ export default async function Layout({ children }: React.PropsWithChildren) {
initialConfigValue={initialShellConfig.configValue}
initialDefaultConfig={initialDefaultConfig}
initialPlatformConfig={initialPlatformConfig}
initialParameters={{
[ParametersConfigKey.LANGUAGE_CONFIG]: initialLanguageConfig,
}}
initialModelData={{
llmConfigStatus: initialLLMConfigStatus,
byokModels: initialByokModels,
Expand Down
Loading