feat(git-sync): per-space 'Enable Git sync' toggle (Phase C, §7.1)

UI opt-in for git-sync, mirroring the existing sharing/comments settings pattern
(no new endpoint, no new mechanism; orchestrator read query untouched):
- UpdateSpaceDto.gitSyncEnabled?: boolean.
- SpaceRepo.updateGitSyncSettings: jsonb-merge into settings.gitSync.<key>
  (COALESCE || jsonb_build_object — never clobbers sibling sharing/comments);
  stored as a real jsonb boolean so the orchestrator's
  settings->'gitSync'->>'enabled' = 'true' matches.
- SpaceService.updateSpace handles the flag (audit diff) via the existing
  CASL-guarded space update path (Manage/Settings).
- client: Switch in edit-space-form (optimistic mutate + revert-on-error,
  readOnly-aware) + space types + 2 i18n keys.
- space.service.spec extended (calls updateGitSyncSettings; no-op when undefined).
tsc clean (server+client); jest src/core/space 4 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude code agent 227
2026-06-21 15:42:52 +03:00
parent efb96ae498
commit 0a364dfcac
7 changed files with 161 additions and 2 deletions

View File

@@ -1,5 +1,14 @@
import { Group, Box, Button, TextInput, Stack, Textarea } from "@mantine/core";
import React from "react";
import {
Group,
Box,
Button,
TextInput,
Stack,
Textarea,
Divider,
Switch,
} from "@mantine/core";
import React, { useState } from "react";
import { useForm } from "@mantine/form";
import { zod4Resolver } from "mantine-form-zod-resolver";
import { z } from "zod/v4";
@@ -29,6 +38,23 @@ export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) {
const { t } = useTranslation();
const updateSpaceMutation = useUpdateSpaceMutation();
const [gitSyncEnabled, setGitSyncEnabled] = useState<boolean>(
space?.settings?.gitSync?.enabled ?? false,
);
const handleGitSyncToggle = async (value: boolean) => {
const previous = gitSyncEnabled;
setGitSyncEnabled(value); // optimistic update
try {
await updateSpaceMutation.mutateAsync({
spaceId: space.id,
gitSyncEnabled: value,
});
} catch (err) {
setGitSyncEnabled(previous); // revert on failure
}
};
const form = useForm<FormValues>({
validate: zod4Resolver(formSchema),
initialValues: {
@@ -104,6 +130,18 @@ export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) {
</Group>
)}
</form>
<Divider my="lg" />
<Switch
label={t("Enable Git sync")}
description={t("Sync this space's pages to a Git repository.")}
checked={gitSyncEnabled}
disabled={readOnly || updateSpaceMutation.isPending}
onChange={(event) =>
handleGitSyncToggle(event.currentTarget.checked)
}
/>
</Box>
</>
);

View File

@@ -13,9 +13,14 @@ export interface ISpaceCommentsSettings {
allowViewerComments?: boolean;
}
export interface ISpaceGitSyncSettings {
enabled?: boolean;
}
export interface ISpaceSettings {
sharing?: ISpaceSharingSettings;
comments?: ISpaceCommentsSettings;
gitSync?: ISpaceGitSyncSettings;
}
export interface ISpace {
@@ -35,6 +40,7 @@ export interface ISpace {
// for updates
disablePublicSharing?: boolean;
allowViewerComments?: boolean;
gitSyncEnabled?: boolean;
}
interface IMembership {