diff --git a/src/components/SettingsFields.tsx b/src/components/SettingsFields.tsx index 1511664..497be5e 100644 --- a/src/components/SettingsFields.tsx +++ b/src/components/SettingsFields.tsx @@ -17,7 +17,11 @@ function readableValue(value: unknown) { const text = String(value) return optionLabels[text] || text } -export function scopedSchema(schema: SettingDesc[], scope: string) { return schema.filter(desc => String(desc.scope) === scope) } +export function scopedSchema(schema: SettingDesc[], scope: string) { + return schema.filter(desc => !desc.scope + ? scope === 'user' || scope === 'project-personal' + : desc.scope === scope || (scope === 'project-personal' && desc.scope === 'user')) +} export function settingInputAttrs(desc: SettingDesc): { type: 'text' | 'password'; autoComplete: string } { return desc.secret || desc.type === 'secret' ? { type: 'password', autoComplete: 'new-password' } : { type: 'text', autoComplete: 'off' } } diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index e7df305..50a6d16 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -25,7 +25,7 @@ export function SettingsModal() { const [notice, setNotice] = useState('') const [busy, setBusy] = useState(false) const schema = settings ? scopedSchema(settings.schema, 'user') : [] - const personalSchema = projectSettings ? (projectSettings.personalSchema || []).filter(desc => ['user', 'project-personal'].includes(String(desc.scope))) : [] + const personalSchema = projectSettings ? scopedSchema(projectSettings.personalSchema || [], 'project-personal') : [] const initial = settings ? scopedDraft(settings.user, schema) : {} const personalInitial = projectSettings ? scopedDraft(projectSettings.personalProject || {}, personalSchema) : {} const dirty = JSON.stringify(draft) !== JSON.stringify(initial) || JSON.stringify(personalDraft) !== JSON.stringify(personalInitial) @@ -41,7 +41,7 @@ export function SettingsModal() { if (projectId) { const out = await api.getSettings(projectId) if (!current()) return false - const personal = (out.personalSchema || []).filter(desc => ['user', 'project-personal'].includes(String(desc.scope))) + const personal = scopedSchema(out.personalSchema || [], 'project-personal') setProjectSettings(out); setPersonalDraft(scopedDraft(out.personalProject || {}, personal)) } return true diff --git a/test/settings-editor.test.tsx b/test/settings-editor.test.tsx index ad68264..28f8350 100644 --- a/test/settings-editor.test.tsx +++ b/test/settings-editor.test.tsx @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { renderToStaticMarkup } from 'react-dom/server' import { SettingInput } from '../src/components/SettingsModal' +import { scopedSchema } from '../src/components/SettingsFields' import { BorrowPanels } from '../src/components/BorrowPanels' import type { SettingDesc } from '../src/contracts' @@ -10,6 +11,20 @@ const field = (type: SettingDesc['type'], options?: string[]): SettingDesc => ({ }) describe('setting editors preserve types and readable values', () => { + it('keeps legacy personal plugin settings visible only in personal scopes', () => { + const schema: SettingDesc[] = [ + { ...field('string'), key: 'plugin.legacy', scope: undefined }, + { ...field('string'), key: 'plugin.user', scope: 'user' }, + { ...field('string'), key: 'plugin.project-personal', scope: 'project-personal' }, + { ...field('string'), key: 'plugin.project', scope: 'project' }, + { ...field('string'), key: 'plugin.system', scope: 'system' }, + ] + const keys = (scope: string) => scopedSchema(schema, scope).map(desc => desc.key) + expect(keys('user')).toEqual(['plugin.legacy', 'plugin.user']) + expect(keys('project-personal')).toEqual(['plugin.legacy', 'plugin.user', 'plugin.project-personal']) + expect(keys('project')).toEqual(['plugin.project']) + expect(keys('system')).toEqual(['plugin.system']) + }) it('uses a number input for numeric values rather than submitting arbitrary strings', () => { expect(renderToStaticMarkup( {}} />)).toContain('type="number"') })