diff --git a/web/features/new-rag/__tests__/knowledge-settings-form.spec.tsx b/web/features/new-rag/__tests__/knowledge-settings-form.spec.tsx index 2c2a4bb4cfc..732f3d26214 100644 --- a/web/features/new-rag/__tests__/knowledge-settings-form.spec.tsx +++ b/web/features/new-rag/__tests__/knowledge-settings-form.spec.tsx @@ -362,11 +362,10 @@ describe('KnowledgeSettingsForm', () => { const nameInput = screen.getByRole('textbox', { name: 'datasetSettings.form.name' }) await user.clear(nameInput) await user.type(nameInput, 'Updated camera specs') - await user.click( - screen.getByRole('button', { - name: 'dataset.newKnowledge.settings.saveChanges', - }), - ) + const saveButton = screen.getByRole('button', { + name: 'dataset.newKnowledge.settings.saveChanges', + }) + await user.click(saveButton) await waitFor(() => { expect(serviceMock.patchSpace).toHaveBeenCalledWith( @@ -380,7 +379,7 @@ describe('KnowledgeSettingsForm', () => { ) }) expect(serviceMock.patchSettings).not.toHaveBeenCalled() - expect(toastMock.success).not.toHaveBeenCalled() + expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess') }) it('finishes the basic info draft before refreshing saved server data', async () => { @@ -433,6 +432,12 @@ describe('KnowledgeSettingsForm', () => { ) await waitFor(() => expect(serviceMock.patchSpace).toHaveBeenCalledOnce()) + const saveButton = screen.getByRole('button', { + name: 'dataset.newKnowledge.settings.saveChanges', + }) + expect(saveButton).toHaveAttribute('aria-disabled', 'true') + expect(saveButton).toHaveTextContent('dataset.newKnowledge.settings.saveChanges') + expect(screen.queryByText('common.operation.saving')).not.toBeInTheDocument() const apiAccessSwitch = screen.getByRole('switch', { name: 'dataset.newKnowledge.apiAgentAccess', }) @@ -624,7 +629,74 @@ describe('KnowledgeSettingsForm', () => { name: 'dataset.newKnowledge.settings.saveChanges', }), ).toBeDisabled() - expect(toastMock.success).not.toHaveBeenCalled() + expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess') + }) + + it('keeps access channels submittable before the first model profile activates', async () => { + const user = userEvent.setup() + renderForm({ + settings: { + ...settings, + active_profile_available: false, + active_profile_revisions: {}, + capabilities: { + deep: false, + index: false, + ingest: true, + query: false, + research: false, + source_sync: true, + }, + }, + }) + + const apiAccessSwitch = screen.getByRole('switch', { + name: 'dataset.newKnowledge.apiAgentAccess', + }) + const workflowAccessSwitch = screen.getByRole('switch', { + name: 'dataset.newKnowledge.workflowAccess', + }) + expect(apiAccessSwitch).not.toHaveAttribute('aria-disabled', 'true') + expect(workflowAccessSwitch).not.toHaveAttribute('aria-disabled', 'true') + expect(apiAccessSwitch).toHaveAccessibleDescription( + 'dataset.newKnowledge.settings.apiAccessDescription', + ) + + await user.click(apiAccessSwitch) + + await waitFor(() => { + expect(serviceMock.patchExternalAccess).toHaveBeenCalledWith( + { + body: { + agent_enabled: false, + mcp_enabled: true, + service_api_enabled: false, + workflow_enabled: true, + }, + params: { control_space_id: 'space-1' }, + }, + expect.anything(), + ) + }) + await waitFor(() => expect(apiAccessSwitch).toHaveAttribute('aria-checked', 'false')) + expect(apiAccessSwitch).not.toHaveAttribute('aria-disabled', 'true') + expect(workflowAccessSwitch).not.toHaveAttribute('aria-disabled', 'true') + + await user.click(apiAccessSwitch) + + await waitFor(() => expect(serviceMock.patchExternalAccess).toHaveBeenCalledTimes(2)) + expect(serviceMock.patchExternalAccess).toHaveBeenLastCalledWith( + { + body: { + agent_enabled: true, + mcp_enabled: true, + service_api_enabled: true, + workflow_enabled: true, + }, + params: { control_space_id: 'space-1' }, + }, + expect.anything(), + ) }) it('keeps unrelated form controls interactive while external access is saving', async () => { @@ -732,7 +804,7 @@ describe('KnowledgeSettingsForm', () => { await waitFor(() => expect(workflowAccessSwitch).toHaveAttribute('aria-checked', 'true')) }) - it('restores the Workflow access switch after failure and retries the intended value', async () => { + it('keeps the Workflow access edit and shows an error toast after failure', async () => { const user = userEvent.setup() serviceMock.patchExternalAccess.mockRejectedValueOnce(new Error('network error')) renderForm({ @@ -747,27 +819,14 @@ describe('KnowledgeSettingsForm', () => { }) await user.click(workflowAccessSwitch) - expect(await screen.findByText('dataset.newKnowledge.settings.saveFailed')).toBeInTheDocument() - expect(workflowAccessSwitch).toHaveAttribute('aria-checked', 'false') - await user.click(screen.getByRole('button', { name: 'common.operation.retry' })) - - await waitFor(() => expect(serviceMock.patchExternalAccess).toHaveBeenCalledTimes(2)) - expect(serviceMock.patchExternalAccess).toHaveBeenLastCalledWith( - { - body: { - agent_enabled: true, - mcp_enabled: true, - service_api_enabled: true, - workflow_enabled: true, - }, - params: { control_space_id: 'space-1' }, - }, - expect.anything(), + await waitFor(() => + expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.settings.saveFailed'), ) - await waitFor(() => expect(workflowAccessSwitch).toHaveAttribute('aria-checked', 'true')) + expect(workflowAccessSwitch).toHaveAttribute('aria-checked', 'true') + expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument() }) - it('restores the API access switch after failure and retries the intended value', async () => { + it('keeps the API access edit and shows an error toast after failure', async () => { const user = userEvent.setup() serviceMock.patchExternalAccess.mockRejectedValueOnce(new Error('network error')) renderForm({ @@ -784,24 +843,11 @@ describe('KnowledgeSettingsForm', () => { expect(apiAccessSwitch).toHaveAttribute('aria-checked', 'false') await user.click(apiAccessSwitch) - expect(await screen.findByText('dataset.newKnowledge.settings.saveFailed')).toBeInTheDocument() - expect(apiAccessSwitch).toHaveAttribute('aria-checked', 'false') - await user.click(screen.getByRole('button', { name: 'common.operation.retry' })) - - await waitFor(() => expect(serviceMock.patchExternalAccess).toHaveBeenCalledTimes(2)) - expect(serviceMock.patchExternalAccess).toHaveBeenLastCalledWith( - { - body: { - agent_enabled: true, - mcp_enabled: true, - service_api_enabled: true, - workflow_enabled: true, - }, - params: { control_space_id: 'space-1' }, - }, - expect.anything(), + await waitFor(() => + expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.settings.saveFailed'), ) - await waitFor(() => expect(apiAccessSwitch).toHaveAttribute('aria-checked', 'true')) + expect(apiAccessSwitch).toHaveAttribute('aria-checked', 'true') + expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument() }) it('requires the exact knowledge name before deletion', async () => { @@ -837,7 +883,7 @@ describe('KnowledgeSettingsForm', () => { expect(routerMock.replace).toHaveBeenCalledWith('/datasets?view=new') }) - it('keeps edits and offers retry after saving fails', async () => { + it('keeps edits and shows a standard error toast after saving fails', async () => { const user = userEvent.setup() serviceMock.patchSpace.mockRejectedValueOnce(new Error('network error')) renderForm() @@ -845,20 +891,24 @@ describe('KnowledgeSettingsForm', () => { const nameInput = screen.getByRole('textbox', { name: 'datasetSettings.form.name' }) await user.clear(nameInput) await user.type(nameInput, 'Camera specs draft') - await user.click( - screen.getByRole('button', { - name: 'dataset.newKnowledge.settings.saveChanges', - }), + const saveButton = screen.getByRole('button', { + name: 'dataset.newKnowledge.settings.saveChanges', + }) + await user.click(saveButton) + + await waitFor(() => + expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.settings.saveFailed'), ) - - expect(await screen.findByText('dataset.newKnowledge.settings.saveFailed')).toBeInTheDocument() expect(nameInput).toHaveValue('Camera specs draft') + expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument() + expect(saveButton).toBeEnabled() - await user.click(screen.getByRole('button', { name: 'common.operation.retry' })) + await user.click(saveButton) await waitFor(() => expect(serviceMock.patchSpace).toHaveBeenCalledTimes(2)) + expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess') }) - it('retries an immediate retrieval settings update without submitting basic info', async () => { + it('shows an error toast for a failed retrieval settings update without submitting basic info', async () => { const user = userEvent.setup() serviceMock.patchSettings.mockRejectedValueOnce(new Error('settings unavailable')) renderForm() @@ -869,10 +919,10 @@ describe('KnowledgeSettingsForm', () => { }), ) - expect(await screen.findByText('dataset.newKnowledge.settings.saveFailed')).toBeInTheDocument() - await user.click(screen.getByRole('button', { name: 'common.operation.retry' })) - - await waitFor(() => expect(serviceMock.patchSettings).toHaveBeenCalledTimes(2)) + await waitFor(() => + expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.settings.saveFailed'), + ) + expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument() expect(serviceMock.patchSpace).not.toHaveBeenCalled() }) @@ -1109,25 +1159,11 @@ describe('KnowledgeSettingsForm', () => { await user.type(thresholdInput, '0.72') await user.tab() - expect(await screen.findByText('dataset.newKnowledge.permissionRestricted')).toBeInTheDocument() - expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument() - expect(thresholdInput).toHaveValue('0.72') - - await user.click(screen.getByRole('button', { name: 'common.operation.retry' })) - - await waitFor(() => expect(serviceMock.patchSettings).toHaveBeenCalledTimes(2)) - expect(serviceMock.patchSettings).toHaveBeenLastCalledWith( - { - body: { - expectedRevision: 5, - retrieval: expect.objectContaining({ - scoreThreshold: expect.objectContaining({ value: 0.72 }), - }), - }, - params: { control_space_id: 'space-1' }, - }, - expect.anything(), + await waitFor(() => + expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.permissionRestricted'), ) + expect(screen.queryByText('dataset.newKnowledge.permissionRestricted')).not.toBeInTheDocument() + expect(thresholdInput).toHaveValue('0.72') }) it('requires a rerank model for a legacy knowledge base and saves it as enabled', async () => { @@ -1416,7 +1452,7 @@ describe('KnowledgeSettingsForm', () => { name: 'dataset.newKnowledge.settings.systemReasoningModelLabel', }), ) - expect(await screen.findByRole('status')).toHaveTextContent('common.operation.saving') + expect(screen.queryByText('common.operation.saving')).not.toBeInTheDocument() fireEvent.change( screen.getByRole('textbox', { @@ -1450,6 +1486,9 @@ describe('KnowledgeSettingsForm', () => { }, expect.anything(), ) + await waitFor(() => + expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess'), + ) } finally { vi.useRealTimers() } @@ -1558,10 +1597,10 @@ describe('KnowledgeSettingsForm', () => { ) expect( screen.getByRole('switch', { name: 'dataset.newKnowledge.apiAgentAccess' }), - ).toHaveAttribute('aria-disabled', 'true') + ).not.toHaveAttribute('aria-disabled', 'true') expect( screen.getByRole('switch', { name: 'dataset.newKnowledge.workflowAccess' }), - ).toHaveAttribute('aria-disabled', 'true') + ).not.toHaveAttribute('aria-disabled', 'true') await user.click( screen.getByRole('button', { @@ -1817,7 +1856,7 @@ describe('KnowledgeSettingsForm', () => { }), ) - expect(await screen.findByRole('status')).toHaveTextContent('common.operation.saving') + expect(screen.queryByText('common.operation.saving')).not.toBeInTheDocument() expect(toastMock.success).not.toHaveBeenCalled() resolveMigration({ @@ -1831,11 +1870,11 @@ describe('KnowledgeSettingsForm', () => { updated_at: '2026-07-28T00:01:00Z', }) - await waitFor(() => expect(screen.queryByRole('status')).not.toBeInTheDocument()) - expect(toastMock.success).not.toHaveBeenCalled() + await waitFor(() => expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess')) + expect(screen.queryByText('common.operation.saving')).not.toBeInTheDocument() }) - it('offers retry when a durable profile migration fails', async () => { + it('shows an error toast when a durable profile migration fails', async () => { const user = userEvent.setup() serviceMock.patchSettings.mockResolvedValueOnce({ migration: { @@ -1869,11 +1908,11 @@ describe('KnowledgeSettingsForm', () => { }), ) - expect(await screen.findByText('dataset.newKnowledge.settings.saveFailed')).toBeInTheDocument() + await waitFor(() => + expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.settings.saveFailed'), + ) + expect(screen.queryByText('dataset.newKnowledge.settings.saveFailed')).not.toBeInTheDocument() expect(toastMock.success).not.toHaveBeenCalled() - await user.click(screen.getByRole('button', { name: 'common.operation.retry' })) - - await waitFor(() => expect(serviceMock.patchSettings).toHaveBeenCalledTimes(2)) }) it('fully locks the page for a view-only user', () => { diff --git a/web/features/new-rag/components/knowledge-space-card.tsx b/web/features/new-rag/components/knowledge-space-card.tsx index 3d259dd2f84..b76abb97398 100644 --- a/web/features/new-rag/components/knowledge-space-card.tsx +++ b/web/features/new-rag/components/knowledge-space-card.tsx @@ -69,7 +69,7 @@ export function KnowledgeSpaceCard({ -

+

{summary?.description || t(($) => $['newKnowledge.noDescription'])}

diff --git a/web/features/new-rag/document-chunk-tree.tsx b/web/features/new-rag/document-chunk-tree.tsx index 111e16c868c..8282de55983 100644 --- a/web/features/new-rag/document-chunk-tree.tsx +++ b/web/features/new-rag/document-chunk-tree.tsx @@ -11,6 +11,8 @@ import { chunkTreeLabel, visibleDocumentChunkNodes } from './document-detail-mod const VIRTUALIZATION_THRESHOLD = 80 const TREE_ROW_SIZE = 30 +const TREE_ROW_INLINE_PADDING = 8 +const TREE_DEPTH_INDENT = 16 function AutomaticChunkPageLoader({ fetchNextPage, @@ -204,7 +206,10 @@ export function DocumentChunkTreePanel({ 'bg-state-base-hover ring-1 ring-state-accent-solid ring-inset', )} role="treeitem" - style={style} + style={{ + ...style, + paddingInlineStart: TREE_ROW_INLINE_PADDING + depth * TREE_DEPTH_INDENT, + }} tabIndex={-1} onClick={() => { setFocusedNodeId(node.id) diff --git a/web/features/new-rag/document-detail-page.tsx b/web/features/new-rag/document-detail-page.tsx index 62fd56ae915..aa7a31cc179 100644 --- a/web/features/new-rag/document-detail-page.tsx +++ b/web/features/new-rag/document-detail-page.tsx @@ -323,7 +323,7 @@ export function DocumentDetailPage({ const document = documentQuery.data return ( -
+
{ const types = Array.from(event.dataTransfer.types ?? []) diff --git a/web/features/new-rag/knowledge-settings-form.tsx b/web/features/new-rag/knowledge-settings-form.tsx index 9631d0881fb..e31650de97c 100644 --- a/web/features/new-rag/knowledge-settings-form.tsx +++ b/web/features/new-rag/knowledge-settings-form.tsx @@ -71,11 +71,6 @@ const RERANK_MODEL_LABEL_ID = 'knowledge-rerank-model-label' const RERANK_MODEL_ERROR_ID = 'knowledge-rerank-model-error' type BasicSaveSlice = 'members' | 'space' -type SaveErrorSlice = 'basic' | 'externalAccess' | 'settings' -type SaveError = { - kind: 'generic' | 'permission' - slice: SaveErrorSlice -} type ExternalAccessDraft = { apiEnabled: boolean @@ -298,7 +293,6 @@ export function KnowledgeSettingsForm({ }), ) const [nameTouched, setNameTouched] = useState(false) - const [saveError, setSaveError] = useState() const [isBasicRefreshing, setIsBasicRefreshing] = useState(false) const [pendingMigrationId, setPendingMigrationId] = useState() const [pendingEmbeddingModel, setPendingEmbeddingModel] = useState() @@ -366,7 +360,6 @@ export function KnowledgeSettingsForm({ const canManageAccess = space.permission_keys.includes('knowledge_space_access_config') const canDelete = space.permission_keys.includes('knowledge_space_delete') const initialModelSetup = !settings.active_profile_available - const modelSetupReady = settings.active_profile_available const readinessFieldLabel = (field: KnowledgeFsSettingsResponse['issues'][number]['field']) => { if (field === 'embedding') return tSettings(($) => $['form.embeddingModel']) if (field === 'reasoning') return tCommon(($) => $['modelProvider.systemReasoningModel.key']) @@ -451,15 +444,23 @@ export function KnowledgeSettingsForm({ settingsMutation.isPending || Boolean(pendingMigrationId) const basicFieldsDisabled = !canEdit || isBasicSaving - const externalAccessDisabled = !canEdit || !canManageAccess || !modelSetupReady + const externalAccessDisabled = !canEdit || !canManageAccess const retrievalFieldsDisabled = !canEdit || (!initialModelSetup && embeddingDirty) const saveDisabled = - !basicDirty || - nameInvalid || - descriptionInvalid || - membersInvalid || - isBasicSaving || - serverConflict + !basicDirty || nameInvalid || descriptionInvalid || membersInvalid || serverConflict + const showSaveSuccess = useCallback( + () => toast.success(tCommon(($) => $['api.actionSuccess'])), + [tCommon], + ) + const showSaveError = useCallback( + (error?: unknown) => + toast.error( + error instanceof Response && error.status === 403 + ? t(($) => $['newKnowledge.permissionRestricted']) + : t(($) => $['newKnowledge.settings.saveFailed']), + ), + [t], + ) const startDraft = () => onDraftStart?.() const beginSettingsDraft = (patch: Partial) => { if (!basicDirtyRef.current) settingsDraftOwnsFormLockRef.current = true @@ -477,7 +478,6 @@ export function KnowledgeSettingsForm({ setVisibility(space.visibility) setSelectedMemberIds(initialSelectedMemberIds) setNameTouched(false) - if (saveError?.slice === 'basic') setSaveError(undefined) onDraftFinish?.() } @@ -524,12 +524,14 @@ export function KnowledgeSettingsForm({ setRetrievalBaseline(savedRetrievalFingerprint) } void invalidateSettingsQueries().then(() => { + const hasQueuedSettingsDraft = Boolean(queuedSettingsDraftRef.current) migratingSettingsDraftRef.current = undefined - if (!queuedSettingsDraftRef.current) pendingSettingsDraftRef.current = undefined - resumeSettingsQueueAfterMigrationRef.current = Boolean(queuedSettingsDraftRef.current) + if (!hasQueuedSettingsDraft) pendingSettingsDraftRef.current = undefined + resumeSettingsQueueAfterMigrationRef.current = hasQueuedSettingsDraft setPendingMigrationId(undefined) + if (!hasQueuedSettingsDraft) showSaveSuccess() if ( - !queuedSettingsDraftRef.current && + !hasQueuedSettingsDraft && settingsDraftOwnsFormLockRef.current && !basicDirtyRef.current ) { @@ -550,8 +552,7 @@ export function KnowledgeSettingsForm({ resumeSettingsQueueAfterMigrationRef.current = false // oxlint-disable-next-line eslint-react/set-state-in-effect -- A terminal remote migration retires the local polling guard. setPendingMigrationId(undefined) - // oxlint-disable-next-line eslint-react/set-state-in-effect -- The authoritative failed migration transitions the save UI to its retry state. - setSaveError({ kind: 'generic', slice: 'settings' }) + showSaveError() } }, [ invalidateSettingsQueries, @@ -559,12 +560,12 @@ export function KnowledgeSettingsForm({ migrationQuery.isError, onDraftFinish, pendingMigrationId, + showSaveError, + showSaveSuccess, ]) const performBasicSave = async () => { - if (saveDisabled || !canEdit) return - - setSaveError(undefined) + if (saveDisabled || isBasicSaving || !canEdit) return try { const saveSlice = async ( @@ -612,14 +613,15 @@ export function KnowledgeSettingsForm({ ) } completedBasicSaveFingerprintsRef.current = {} + showSaveSuccess() setIsBasicRefreshing(true) onDraftFinish?.() void invalidateSettingsQueries().then( () => setIsBasicRefreshing(false), () => setIsBasicRefreshing(false), ) - } catch { - setSaveError({ kind: 'generic', slice: 'basic' }) + } catch (error) { + showSaveError(error) } } @@ -635,7 +637,6 @@ export function KnowledgeSettingsForm({ let nextDraft: ExternalAccessDraft | undefined = draft while (nextDraft) { queuedExternalAccessDraftRef.current = undefined - setSaveError(undefined) try { await externalAccessMutation.mutateAsync({ body: { @@ -648,12 +649,10 @@ export function KnowledgeSettingsForm({ }) externalAccessBaselineRef.current = nextDraft nextDraft = queuedExternalAccessDraftRef.current - } catch { + } catch (error) { queuedExternalAccessDraftRef.current = undefined externalAccessSaveInFlightRef.current = false - setApiEnabled(externalAccessBaselineRef.current.apiEnabled) - setWorkflowEnabled(externalAccessBaselineRef.current.workflowEnabled) - setSaveError({ kind: 'generic', slice: 'externalAccess' }) + showSaveError(error) return } } @@ -661,6 +660,7 @@ export function KnowledgeSettingsForm({ externalAccessSaveInFlightRef.current = false setApiEnabled(externalAccessBaselineRef.current.apiEnabled) setWorkflowEnabled(externalAccessBaselineRef.current.workflowEnabled) + showSaveSuccess() await invalidateSettingsQueries() } @@ -705,7 +705,6 @@ export function KnowledgeSettingsForm({ } } - setSaveError(undefined) try { const result = await settingsMutation.mutateAsync({ body, @@ -729,10 +728,7 @@ export function KnowledgeSettingsForm({ await invalidateSettingsQueries() return 'saved' as const } catch (error) { - setSaveError({ - kind: error instanceof Response && error.status === 403 ? 'permission' : 'generic', - slice: 'settings', - }) + showSaveError(error) return 'failed' as const } } @@ -754,10 +750,12 @@ export function KnowledgeSettingsForm({ settingsSaveInFlightRef.current = true let nextDraft: SettingsDraft | undefined = draft let saveResult: Awaited> = 'skipped' + let didSave = false try { while (nextDraft) { queuedSettingsDraftRef.current = undefined saveResult = await saveSettingsDraft(nextDraft) + if (saveResult === 'saved') didSave = true if (saveResult === 'failed' || saveResult === 'migration') break nextDraft = queuedSettingsDraftRef.current } @@ -781,6 +779,7 @@ export function KnowledgeSettingsForm({ if (!latestDraftIsSaved) return pendingSettingsDraftRef.current = undefined + if (didSave) showSaveSuccess() if (settingsDraftOwnsFormLockRef.current && !basicDirtyRef.current) { settingsDraftOwnsFormLockRef.current = false onDraftFinish?.() @@ -815,18 +814,6 @@ export function KnowledgeSettingsForm({ void performBasicSave() } - const retrySave = () => { - if (saveError?.slice === 'externalAccess') { - void performExternalAccessSave(pendingExternalAccessRef.current) - return - } - if (saveError?.slice === 'settings') { - void performSettingsSave(pendingSettingsDraftRef.current ?? liveSettingsDraftRef.current) - return - } - requestSave() - } - const armHistoryGuard = useCallback(() => { globalThis.history.pushState(globalThis.history.state, '', globalThis.location.href) historyGuardArmedRef.current = true @@ -993,23 +980,6 @@ export function KnowledgeSettingsForm({ /> )} - {saveError && ( -
- - - {saveError.kind === 'permission' - ? t(($) => $['newKnowledge.permissionRestricted']) - : t(($) => $['newKnowledge.settings.saveFailed'])} - - -
- )} - {serverConflict && (
)} - {pendingMigrationId && ( -
- - {tCommon(($) => $['operation.saving'])} -
- )} -

{t(($) => $['newKnowledge.settings.basicInfo'])} @@ -1151,9 +1111,7 @@ export function KnowledgeSettingsForm({ {tCommon(($) => $['operation.cancel'])}

)} @@ -1360,7 +1318,6 @@ export function KnowledgeSettingsForm({
+