import type { SectionBaseProps } from "./context"; import { SettingsToggleRow } from "../SettingsToggleRow"; import { SettingsHelpTip } from "../SettingsHelpTip"; import { useTranslation } from "react-i18next"; export interface ResearchProjectSectionProps extends SectionBaseProps { researchLimitError: string | null; } /* FNXC:SettingsStyling 2026-07-15-17:35: The section's one plain label+control+help row renders through the shared settings primitives instead of hand-rolled `form-group` + `checkbox-label` markup, so its label, help copy, and padding come from the one settings type scale. `.form-group` itself stays untouched and global — 35 non-settings files style forms with it, so settings migrate off it rather than restyle it underneath the rest of the dashboard. FNXC:SettingsScope 2026-07-15-17:35: `researchSettings` lives in `DEFAULT_PROJECT_SETTINGS`, so the master toggle carries a project badge: research enablement describes one repository's research policy and must not follow the operator to another project. FNXC:SettingsSearch 2026-07-15-17:35: The descriptor key is the dotted path `researchSettings.enabled`, not the bare `researchSettings` blob key, because the toggle owns exactly one leaf of that nested object. The sources and limits below own their own leaves of the same blob, so a bare key would collide the moment either migrates, and search would anchor several controls to one row. FNXC:SettingsStyling 2026-07-15-17:35: Two groups deliberately keep their bespoke markup because they are not plain label+control+help rows: the Enabled Sources grid pairs an always-on locked Web Search row with a checkbox grid (per-source default hints ride "?" tips beside each label), and the limits grid lays four numeric fields plus a shared validation error out side by side (`settings-research-limit-field`). FNXC:SettingsHelp 2026-07-15-21:40: Each limits field is still one control with one help string, so its "Default: N." hangs off the same "?" as the migrated toggle above (`.settings-field-label-row` + `SettingsHelpTip`) rather than printing four paragraphs under a section whose other row hides its help behind an icon. FNXC:SettingsHelp 2026-07-16-12:45: The Enabled Sources copy now rides the same "?" too — operator requirement: no inline description paragraphs in Settings. The always-on Web Search note and each per-source "Default: …" hint hang off a tip beside that option's label (settingKey = the input id); only the "Always on" span stays inline, because it is a status tag, not help. The one thing still inline is the shared limits validation error — a message the operator has to open a tip to find is one they will not see. */ export function ResearchProjectSection({ form, setForm, researchLimitError }: ResearchProjectSectionProps) { const { t } = useTranslation("app"); const limits = form.researchSettings?.limits; const sources = form.researchSettings?.enabledSources; return (<>

{t("settings.researchProject.projectResearchSettings", "Project Research Settings")}

setForm((current) => ({ ...current, researchSettings: { ...(current.researchSettings ?? {}), enabled: v === true, }, }))} />
{/* FNXC:SettingsHelp 2026-07-16-12:45: Inline source hints moved behind the shared "?" affordance beside each option's label — operator requirement: no inline description paragraphs in Settings. The tip is a SIBLING of the checkbox label (a button inside a label breaks click-to-toggle); the "Always on" span stays inline as a status tag. */}
{t("settings.researchProject.webSearchIsAlwaysEnabledConfigureTheSearch", " Web search is always enabled. Configure the search provider under Research Defaults. ")}
{[ ["pageFetch", t("settings.researchProject.pageFetch", "Page Fetch"), "Default: enabled."], ["github", t("settings.researchProject.github", "GitHub"), "Default: disabled."], ["localDocs", t("settings.researchProject.localDocs", "Local Docs"), "Default: enabled."], ["llmSynthesis", t("settings.researchProject.llmSynthesis", "LLM Synthesis"), "Default: enabled."], ].map(([key, label, defaultHint]) => (
{defaultHint}
))}
{t("settings.researchProject.maxConcurrentRunsHint", "Default: 3.")}
setForm((current) => ({ ...current, researchSettings: { ...(current.researchSettings ?? {}), limits: { ...(current.researchSettings?.limits ?? {}), maxConcurrentRuns: event.target.value === "" ? undefined : Number(event.target.value), }, }, }))}/>
{t("settings.researchProject.maxSourcesPerRunHint", "Default: 20.")}
setForm((current) => ({ ...current, researchSettings: { ...(current.researchSettings ?? {}), limits: { ...(current.researchSettings?.limits ?? {}), maxSourcesPerRun: event.target.value === "" ? undefined : Number(event.target.value), }, }, }))}/>
{t("settings.researchProject.maxDurationMsHint", "Default: 300000 (5 minutes).")}
setForm((current) => ({ ...current, researchSettings: { ...(current.researchSettings ?? {}), limits: { ...(current.researchSettings?.limits ?? {}), maxDurationMs: event.target.value === "" ? undefined : Number(event.target.value), }, }, }))}/>
{t("settings.researchProject.requestTimeoutMsHint", "Default: 30000 (30 seconds).")}
setForm((current) => ({ ...current, researchSettings: { ...(current.researchSettings ?? {}), limits: { ...(current.researchSettings?.limits ?? {}), requestTimeoutMs: event.target.value === "" ? undefined : Number(event.target.value), }, }, }))}/>
{/* FNXC:SettingsHelp 2026-07-15-21:40: The validation error stays inline while the fields' help moved behind the "?" — an error the operator has to open a tip to read is an error they will not see. */} {researchLimitError && {researchLimitError}}
); } export default ResearchProjectSection;