In hidden (and later enforced) mode, catalog-listed controls whose only purpose is enabling a violation are not rendered while their saved value is compliant (the four master switches, the Nextcloud backup block); value-listed selects keep only their compliant choices (registration mode, new-page classification, upload policy, SVG policy). Every affected section shows one accessible policy note (i18n de+en) so policy is distinguishable from missing features. A value that was already violating is surfaced exactly like in marked — never silently hidden. The API stays unchanged; enforcement is #246. e2e: hidden half of the marking pack (rows disappear, note visible, already-violating row stays marked, axe WCAG A/AA clean) — verified live locally; CI runs it against a second api (VS_NFD_MODE=hidden, same database) behind its own static server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8
88 lines
3.2 KiB
TypeScript
88 lines
3.2 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import {
|
|
VS_NFD_PROFILE,
|
|
describeCompliance,
|
|
isVsNfdCompliant,
|
|
type VsNfdMode,
|
|
type VsNfdProfileView,
|
|
} from '@dorfteich/shared';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
import { apiGet } from '../lib/api';
|
|
|
|
/**
|
|
* VS-NfD profile marking (issue #244, ADR 0027): in mode `marked`, every
|
|
* catalog-listed control shows an accessible deviation marking. The check
|
|
* runs against the CURRENT control value (saved or not), so the operator
|
|
* is warned at the point of choice. Admin-only surfaces — the endpoint is
|
|
* Site-Admin-guarded, matching every caller.
|
|
*/
|
|
export function useVsNfdMarking(): {
|
|
mode: VsNfdMode;
|
|
view: VsNfdProfileView | undefined;
|
|
/** Translated marking text for a violating value, else undefined. */
|
|
markingFor: (key: string, value: unknown) => string | undefined;
|
|
/** True when the control must not be rendered (#245): mode hidden/
|
|
* enforced and the SAVED value is compliant. */
|
|
hides: (key: string, savedValue: unknown) => boolean;
|
|
} {
|
|
const { t } = useTranslation('settings');
|
|
const query = useQuery({
|
|
queryKey: ['admin', 'vs-nfd-profile'],
|
|
queryFn: () => apiGet<VsNfdProfileView>('/admin/system/vs-nfd-profile'),
|
|
staleTime: 60_000,
|
|
});
|
|
const mode = query.data?.mode ?? 'off';
|
|
return {
|
|
mode,
|
|
view: query.data,
|
|
markingFor(key, value) {
|
|
// Marking applies in `marked`, and in `hidden`/`enforced` to values
|
|
// that are ALREADY violating — an existing violation is never
|
|
// silently hidden (#245).
|
|
if (mode === 'off') return undefined;
|
|
const entry = VS_NFD_PROFILE.find((e) => e.scope === 'instance' && e.key === key);
|
|
if (!entry || isVsNfdCompliant(entry, value)) return undefined;
|
|
return t('admin.vsNfd.marking', { value: describeCompliance(entry.compliance) });
|
|
},
|
|
hides(key, savedValue) {
|
|
// The `hidden` treatment (#245, `enforced` renders the same, #246):
|
|
// a compliant control whose only purpose would be enabling a
|
|
// violation disappears; a violating one stays visible WITH its
|
|
// marking so the deviation is surfaced, never buried.
|
|
if (mode !== 'hidden' && mode !== 'enforced') return false;
|
|
const entry = VS_NFD_PROFILE.find((e) => e.scope === 'instance' && e.key === key);
|
|
return entry !== undefined && isVsNfdCompliant(entry, savedValue);
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The one accessible per-section note that options are hidden by
|
|
* deployment policy (#245) — policy must be distinguishable from missing
|
|
* features for anyone reading the settings page.
|
|
*/
|
|
export function VsNfdHiddenNote(): React.JSX.Element {
|
|
const { t } = useTranslation('settings');
|
|
return (
|
|
<p className="field__hint vs-nfd-hidden-note">
|
|
<span aria-hidden="true">🛈 </span>
|
|
{t('admin.vsNfd.hiddenNote')}
|
|
</p>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The marking element for controls not wrapped in <Field> (checkbox rows):
|
|
* icon + text, colour never alone; wire `id` into the control's
|
|
* aria-describedby so screen readers announce it with the control.
|
|
*/
|
|
export function VsNfdMark({ id, text }: { id?: string; text: string }): React.JSX.Element {
|
|
return (
|
|
<span className="vs-nfd-mark" id={id}>
|
|
<span aria-hidden="true">⚠ </span>
|
|
{text}
|
|
</span>
|
|
);
|
|
}
|