import { useTranslation } from "react-i18next"; import { TrackingRepoSelect, type TrackingRepoOption } from "../../TrackingRepoSelect"; import { SettingsToggleRow } from "../SettingsToggleRow"; import { SettingsHelpTip } from "../SettingsHelpTip"; import { SettingsSelectRow } from "../SettingsSelectRow"; import { SettingsTextRow } from "../SettingsTextRow"; import type { SectionBaseProps } from "./context"; export interface SourceControlSectionProps extends SectionBaseProps { projectTrackingRepoOptions: TrackingRepoOption[]; projectTrackingRepoLoading: boolean; projectTrackingRepoError: string | null; } /* FNXC:SourceControl 2026-07-15-20:30: Project source-control settings were split across two sections that neither named nor contained the topic: GitHub Tracking + the GitLab URL disclosure lived in "General · Project", while GitHub/GitLab authentication lived in "Merge". An operator wiring up GitLab had to visit both, and `gitlabEnabled` was writable from BOTH — two enable toggles for one key, so the last section saved won. This section is the single project-scoped home for GitHub/GitLab, which is what removes that duplicate rather than merely hiding it. The GitLab URL block and the GitLab auth block are now ONE disclosure with ONE `gitlabEnabled` toggle in its summary. Both blocks were already governed by that same key (FN-7453), so merging them costs no behavior — the two toggles were always writing the same setting. FNXC:SettingsScope 2026-07-15-20:30: Rows on `gitlabEnabled`, `gitlabInstanceUrl`, `gitlabApiBaseUrl`, `gitlabAuthTokenType`, `gitlabAuthToken`, and `githubTrackingDefaultRepo` carry NO scope badge: every one of them is declared in BOTH `DEFAULT_GLOBAL_SETTINGS` and `DEFAULT_PROJECT_SETTINGS`, so a "project" badge would assert a scope the schema does not support. The section name ("Source Control · Project") carries the scope instead. The GitHub-only keys ARE project-only in the schema, so they keep their badge. FNXC:SettingsStyling 2026-07-15-20:30: Plain label+control+help rows render through the shared settings primitives. `githubAuthToken`/`gitlabAuthToken` use the primitive's `type: "password"` (which defaults `autocomplete="off"`) — previously these rows had to stay hand-rolled to avoid SettingsTextRow's hardcoded `type="text"` rendering a stored token in plain sight. Rows that stay bespoke: the tracking-mode select (its help is TWO blocks, the second conditional on unrelated model settings — a descriptor `help` is one string), the tracking-repo select (custom widget), and the disclosure chrome itself. FNXC:SettingsHelp 2026-07-15-22:40: Staying off the primitive does NOT mean falling back to inline help. Both bespoke rows above render their copy through the same `SettingsHelpTip` as every migrated row, so the section shows one help idiom rather than a "?" on some rows and a paragraph on others. The tip hangs off the label line, so a custom control (or conditional, multi-part copy) is no obstacle — that is what its `ReactNode` children are for. */ export function SourceControlSection({ form, setForm, projectTrackingRepoOptions, projectTrackingRepoLoading, projectTrackingRepoError, }: SourceControlSectionProps) { const { t } = useTranslation("app"); return (<>

{t("settings.general.gitHubTracking", "GitHub Tracking")}

{/* FNXC:SettingsHelp 2026-07-15-22:40: Both help strings ride in ONE tip rather than two inline ``s. They are the same row's help — what the mode does, and how the issue title is derived — and the descriptor-based rows beside this one already carry a "?", so leaving these inline made the section render two idioms side by side. Two strings in one bubble (not two triggers) because an operator asking "what does this control do?" wants both answers at once; the second is a caveat on the first, not a separate topic. This is what `SettingsHelpTip`'s `ReactNode` children buy: the trailing fragment stays CONDITIONAL on the summarization settings, which a single-string descriptor `help` could not express — the reason this row is still hand-rolled. */}
{t("settings.general.controlsWhetherNewlyCreatedTasksHaveGitHubIssue", " Controls whether newly created tasks have GitHub issue tracking enabled by default. Individual tasks can still override this from the task detail modal. ")} {/* FNXC:SettingsGeneral 2026-06-22-03:20: Tracking-issue helper copy. The FN-6771 JSX→t() extraction left a raw HTML entity ("'") in this default string. As a t() argument the string is a plain JS value (not JSX-decoded), so the entity rendered verbatim as the literal "'" instead of an apostrophe. Use a real apostrophe so the copy reads correctly in both modal and embedded presentations. */} {t("settings.general.trackingIssuesUseThisTaskAposSTitle", " Tracking issues use this task's title. If a task has no title yet, Fusion can summarize its description using the title summarization model in Project Models. ")} {!form.autoSummarizeTitles && !form.useAiMergeCommitSummary && !form.githubTrackingEnabledByDefault ? t("settings.general.enableSummarizationInProjectModelsToConfigureThatModel", " Enable summarization in Project Models to configure that model.") : ""}
{/* FNXC:GithubImportTracking 2026-07-01-00:00: This checkbox is project-scoped and import-specific: operators can link imported GitHub issues to GitHub tracking without turning tracking on for every new task. */} setForm((f) => ({ ...f, githubLinkImportedIssuesToTracking: v === true }))} /> {/* FNXC:SettingsHelp 2026-07-15-22:40: The row keeps its bespoke `TrackingRepoSelect` widget, but its help still reads like every neighbour's — the affordance belongs to the label line, not to the control, so a custom widget is no reason to fall back to an inline paragraph. */}
{t("settings.general.defaultRepoUsedWhenCreatingGitHubIssuesFor", "Default repo used when creating GitHub issues for tracked tasks. Falls back to the global default if blank.")}
setForm((f) => ({ ...f, githubTrackingDefaultRepo: nextValue || undefined }))}/>
setForm((f) => ({ ...f, githubTrackingDedupEnabled: v === true }))} />

{t("settings.merge.gitHubAuthentication", "GitHub Authentication")}

{/* FNXC:SettingsStyling 2026-07-15-17:35: No `help` — this row carried no help copy before the migration, and inventing one would be new operator-facing text rather than a restyle. */} setForm((f) => ({ ...f, githubAuthMode: v as "gh-cli" | "token" }))} /> {(form.githubAuthMode ?? "gh-cli") === "token" && ( setForm((f) => ({ ...f, githubAuthToken: v || undefined }))} /> )}

{t("settings.general.gitLabConfiguration", "GitLab Configuration")}

{/* FNXC:GitLabEnablement 2026-07-02-00:00: FN-7453 keeps saved GitLab URL settings separate from the active integration switch. The disclosure is collapsed by default to reduce Settings noise; the summary toggle remains reachable without expanding advanced self-managed URL fields. FNXC:SourceControl 2026-07-15-20:30: This is the ONLY `gitlabEnabled` control in the project scope. The Merge section rendered a second one (id `mergeGitlabEnabled`) governing the same key from a different screen; both are now this one toggle. */}
{t("settings.general.gitLabConfiguration", "GitLab Configuration")} {/* FNXC:SettingsHelp 2026-07-16-12:45: Inline disclosure hint moved behind the shared "?" affordance beside the summary title — operator requirement: no inline description paragraphs in Settings. The copy stays conditional on `gitlabEnabled` (the tip's ReactNode children carry it verbatim). The wrapping span stops propagation, same as the summary's checkbox label, so opening the tip never toggles the disclosure. */} event.stopPropagation()}> {form.gitlabEnabled === false ? t("settings.general.gitLabDisabledHint", "GitLab API imports, comments, close/reopen, and refresh operations are disabled. Saved URLs and tokens remain stored for re-enable.") : t("settings.general.gitLabEnabledHint", "Configure GitLab.com or self-managed GitLab URLs. Blank values inherit global fallbacks and then GitLab.com. No default — unset (unset behaves as enabled until explicitly disabled).")}
setForm((f) => ({ ...f, gitlabInstanceUrl: v || undefined }))} /> /api/v4. Override only when a self-managed GitLab API is served from a different absolute http:// or https:// URL."), type: "url", placeholder: "https://gitlab.com/api/v4", disabled: form.gitlabEnabled === false, }} value={form.gitlabApiBaseUrl ?? ""} onChange={(v) => setForm((f) => ({ ...f, gitlabApiBaseUrl: v || undefined }))} /> {/* FNXC:GitLabEnablement 2026-07-02-00:00: FN-7453 makes project GitLab auth controls collapsible and governed by the same project-scoped enable switch as URL settings. Disabling GitLab preserves saved tokens but blocks outbound API side effects before auth validation. FNXC:SourceControl 2026-07-15-20:30: The auth block keeps BOTH its own heading and its own enable/disable hint after the merge: the URL hint above describes what disabling does to imports/refresh, while this one describes the PRIVATE-TOKEN auth contract and the token's global fallback. Neither string is a paraphrase of the other, so collapsing them into one would delete operator-facing copy rather than deduplicate it. FNXC:SettingsHelp 2026-07-16-12:45: That hint now rides the shared "?" beside the auth heading instead of an inline paragraph — operator requirement: no inline description paragraphs in Settings. The copy stays conditional on `gitlabEnabled` inside the tip. */}
{t("settings.merge.gitLabAuthentication", "GitLab Authentication")}
{form.gitlabEnabled === false ? t("settings.merge.gitLabDisabledHint", "GitLab comments, close/reopen, import fetches, and refresh operations are disabled. Saved tokens remain stored for re-enable.") : t("settings.merge.gitLabAuthDetails", "Fusion uses GitLab REST API token authentication with the PRIVATE-TOKEN header. Leave the token blank to clear the project override and fall back to a configured global GitLab token or GITLAB_TOKEN where available. No default — unset (unset behaves as enabled until explicitly disabled).")}
setForm((f) => ({ ...f, gitlabAuthTokenType: v as "personal" | "project" | "group" }))} /> setForm((f) => ({ ...f, gitlabAuthToken: v || undefined }))} />
); } export default SourceControlSection;