diff --git a/AGENTS.md b/AGENTS.md
index aa95d15..01bb0ba 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2545,9 +2545,14 @@ listing detail and coach detail, so those surfaces get it for free. The provider
read is **batched** (one query per viewer for the union of on-screen subjects,
in `useTrustAssertions.ts`), and `useUserReports` + `components/TrustFlaggedNotice.tsx`
add a flagged-counterparty notice (reporters grouped by the viewer's network,
-Report/Dismiss) wired into the listing seller and coach detail. Still open: a
-provider-choice setting, a mute action (no NIP-51 kind-10000 hook exists yet),
-counterparty alerts, and the confidence-gated scoring model. Check
+Report/Dismiss) wired into the listing seller and coach detail. **Legibility
+pass:** the badge tooltip attributes the source (your network / community
+provider / your provider) and links to a public **`/trust`** explainer;
+**Settings → Trust** (`?section=trust`, `components/TrustSettingsCard.tsx`) has a
+show/hide-marks toggle (`useTrustDisplay`, Flagged is never hidden) and shows the
+active provider + relay (`useTrustProvider`). Still open: provider choice
+(publishing a kind-10040 designation), a mute action (no NIP-51 kind-10000 hook
+exists yet), counterparty alerts, and the confidence-gated scoring model. Check
`TRUST_SCORE.md` before touching `useTrustScore` / `useRelatrScore` /
`trustFilter`.
diff --git a/TRUST_SCORE.md b/TRUST_SCORE.md
index fc32aa5..f615d31 100644
--- a/TRUST_SCORE.md
+++ b/TRUST_SCORE.md
@@ -44,9 +44,22 @@ the provider instead of only using the designation or house default.
safety signal returns). Wired into the listing seller card and the coach
detail page.
-Still open: a Settings control to pick the trust provider; a mute action (there
-is no NIP-51 kind-10000 hook yet — Dismiss is the local equivalent for now);
-counterparty alerts; and the confidence-gated scoring model.
+## Third pass — legibility (landed)
+
+- **Attribution.** The badge tooltip names the source of the claim (your network /
+ the community provider / the provider you chose) and links to an explainer.
+- **Public `/trust` page.** Explains the three states, that absence is not
+ distrust, who computes trust, presence-is-not-endorsement, and
+ point-of-view relativity. Added to the smoke-test route list.
+- **Settings → Trust** (`?section=trust`, `components/TrustSettingsCard.tsx`): a
+ show/hide-marks toggle (`useTrustDisplay`, localStorage
+ `skatespots:trust:display`; **Flagged is never hidden**) and the active
+ provider + relay (`useTrustProvider`), with a link to `/trust`.
+
+Still open: provider *choice* (publishing a kind-10040 designation, i.e. an
+in-app NIP-85 activation flow), a mute action (there is no NIP-51 kind-10000 hook
+yet — Dismiss is the local equivalent for now), counterparty alerts, and the
+confidence-gated scoring model.
diff --git a/scripts/smoke-test.mjs b/scripts/smoke-test.mjs
index 99549a4..8b11864 100755
--- a/scripts/smoke-test.mjs
+++ b/scripts/smoke-test.mjs
@@ -139,6 +139,7 @@ const ROUTES = [
'/settings?section=profile',
'/settings?section=coordinator',
'/settings?section=pro',
+ '/settings?section=trust',
'/npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx',
'/stash',
'/collections',
diff --git a/src/components/TrustBadge.tsx b/src/components/TrustBadge.tsx
index 2c33ce2..f9a1d0a 100644
--- a/src/components/TrustBadge.tsx
+++ b/src/components/TrustBadge.tsx
@@ -3,6 +3,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/comp
import { CheckCircle2, HelpCircle, Flag } from 'lucide-react';
import { Link } from 'react-router-dom';
import { useTrustBucket } from '@/hooks/useTrustBucket';
+import { useTrustDisplay } from '@/hooks/useTrustDisplay';
import { bucketDescKey, bucketLabelKey, type TrustBucket } from '@/lib/trustLadder';
import { HOUSE_TRUST_PROVIDER } from '@/lib/nip85';
import { cn } from '@/lib/utils';
@@ -53,6 +54,7 @@ export function TrustBadge({
}: TrustBadgeProps) {
const { t } = useTranslation();
const { bucket, assertion, directFollow, isLoading } = useTrustBucket(pubkey);
+ const { enabled: displayEnabled } = useTrustDisplay();
// Always attribute the claim: a verdict with no source is not trustworthy.
const sourceKey = assertion
@@ -62,6 +64,9 @@ export function TrustBadge({
: 'walletUi.trust.attributedNetwork';
if (isLoading) return null;
+ // A viewer preference can hide the neutral/positive marks; the safety signal
+ // (Flagged) is never hidden by it.
+ if (!displayEnabled && bucket !== 'flagged') return null;
// A direct follow (or yourself) is already known to the viewer — a "Verified"
// mark there is redundant. Flagged is a safety signal and is never suppressed.
if (bucket === 'verified' && directFollow) return null;
diff --git a/src/components/TrustSettingsCard.tsx b/src/components/TrustSettingsCard.tsx
new file mode 100644
index 0000000..5c4093e
--- /dev/null
+++ b/src/components/TrustSettingsCard.tsx
@@ -0,0 +1,48 @@
+import { Link } from 'react-router-dom';
+import { Switch } from '@/components/ui/switch';
+import { useTranslation } from '@/contexts/language';
+import { useCurrentUser } from '@/hooks/useCurrentUser';
+import { useTrustDisplay } from '@/hooks/useTrustDisplay';
+import { useTrustProvider } from '@/hooks/useTrustProvider';
+import { HOUSE_TRUST_PROVIDER } from '@/lib/nip85';
+
+/**
+ * Settings → Trust. How trust is displayed, and which provider is in effect.
+ * Flagged always shows, so turning marks off is a display choice, not a way to
+ * silence a safety signal.
+ */
+export function TrustSettingsCard() {
+ const { t } = useTranslation();
+ const { user } = useCurrentUser();
+ const { enabled, setEnabled } = useTrustDisplay();
+ const { data: provider } = useTrustProvider();
+
+ const isHouse = !provider || provider.pubkey === HOUSE_TRUST_PROVIDER.pubkey;
+ const source = isHouse ? t('trustUi.sourceHouse') : t('trustUi.sourceCustom');
+
+ return (
+
+
+
+
+
{t('trustUi.showMarks')}
+
{t('trustUi.showMarksDesc')}
+
+
+
+
+
+
+
{t('trustUi.sourceLabel')}
+
{source}
+ {user && provider?.relay && (
+
{provider.relay}
+ )}
+ {!user &&
{t('trustUi.loggedOut')}
}
+
+ {t('walletUi.trust.howItWorks')}
+
+
+
+ );
+}
diff --git a/src/hooks/useTrustDisplay.ts b/src/hooks/useTrustDisplay.ts
new file mode 100644
index 0000000..3c47ccb
--- /dev/null
+++ b/src/hooks/useTrustDisplay.ts
@@ -0,0 +1,11 @@
+import { useLocalStorage } from './useLocalStorage';
+
+/**
+ * Viewer preference for whether trust marks are shown. Only the neutral/positive
+ * marks are affected — the safety signal (Flagged) is deliberately independent
+ * of this and always shows.
+ */
+export function useTrustDisplay() {
+ const [enabled, setEnabled] = useLocalStorage('skatespots:trust:display', true);
+ return { enabled, setEnabled };
+}
diff --git a/src/hooks/useTrustProvider.ts b/src/hooks/useTrustProvider.ts
new file mode 100644
index 0000000..317e6a4
--- /dev/null
+++ b/src/hooks/useTrustProvider.ts
@@ -0,0 +1,40 @@
+import { useQuery } from '@tanstack/react-query';
+import { useNostr } from '@nostrify/react';
+
+import { useCurrentUser } from './useCurrentUser';
+import { onlyVerified } from '@/lib/verifyEvents';
+import {
+ HOUSE_TRUST_PROVIDER,
+ NIP85_PROVIDER_LIST_KIND,
+ NIP85_USER_ASSERTION_KIND,
+ parseProviderDesignation,
+ resolveProvider,
+ type TrustProvider,
+} from '@/lib/nip85';
+
+/**
+ * The trust provider currently in effect for the viewer: the one named in their
+ * own kind-10040 designation, or the house provider when there is no
+ * designation. Read-only — publishing a designation is a separate step.
+ */
+export function useTrustProvider() {
+ const { nostr } = useNostr();
+ const { user } = useCurrentUser();
+ const pubkey = user?.pubkey;
+
+ return useQuery({
+ queryKey: ['trust-provider', pubkey],
+ enabled: !!pubkey,
+ staleTime: 5 * 60 * 1000,
+ queryFn: async (c): Promise => {
+ if (!pubkey) return HOUSE_TRUST_PROVIDER;
+ const signal = AbortSignal.any([c.signal, AbortSignal.timeout(8000)]);
+ const events = onlyVerified(await nostr.query(
+ [{ kinds: [NIP85_PROVIDER_LIST_KIND], authors: [pubkey], limit: 1 }],
+ { signal },
+ ));
+ const designation = parseProviderDesignation(events[0]);
+ return resolveProvider(designation, NIP85_USER_ASSERTION_KIND, 'rank', HOUSE_TRUST_PROVIDER);
+ },
+ });
+}
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 05d2db4..1cb15ed 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -2119,7 +2119,7 @@ labUi: {
},
draft: {
title: 'You have an unsaved draft',
- body: 'Pick up where you left off — nothing is published until you press Publish.',
+ body: 'Pick up where you left off — nothing is published until you press Publish. Licensed under the GNU Affero General Public License v3.0: derivatives and hosted versions must stay open under the same terms, because network use counts as distribution.',
resume: 'Resume draft',
discard: 'Discard draft',
},
@@ -2795,7 +2795,7 @@ labUi: {
print: 'Print',
copyId: 'Copy ID',
cardType: 'Card Type',
- licenseLabel: 'Skater License',
+ licenseLabel: 'License · AGPL-3.0',
licenseSub: 'Your personal rider identity',
parkLabel: 'Park Access',
parkSub: 'Issue passes as a park owner',
@@ -5229,6 +5229,8 @@ settingsUi: {
myData: 'My Data',
myDataSub: 'Export, import, manage',
advancedSub: 'Relatr, Sasquatch, tools',
+ trust: 'Trust',
+ trustSub: 'Sources and display',
notLoaded: 'Not loaded',
notLoggedIn: 'Not logged in',
},
@@ -7387,6 +7389,12 @@ walletUi: {
sellerHint: 'This seller’s venue issues access passes',
},
trustUi: {
+ showMarks: 'Show trust marks',
+ showMarksDesc: 'Verified and unknown marks. Reports always show.',
+ sourceLabel: 'Source',
+ sourceHouse: 'Community trust provider',
+ sourceCustom: 'Your provider',
+ loggedOut: 'Log in to choose your own trust provider.',
seoTitle: 'How trust works',
seoDescription: 'How SkateSpots shows trust: three states, where scores come from, and why absence is not distrust.',
title: 'How trust works',
diff --git a/src/i18n/es.ts b/src/i18n/es.ts
index 17535d1..d257f27 100644
--- a/src/i18n/es.ts
+++ b/src/i18n/es.ts
@@ -2119,7 +2119,7 @@ labUi: {
},
draft: {
title: 'Tienes un borrador sin guardar',
- body: 'Continúa donde lo dejaste — nada se publica hasta que pulses Publicar.',
+ body: 'Continúa donde lo dejaste — nada se publica hasta que pulses Publicar. Con licencia GNU Affero General Public License v3.0: las derivadas y las versiones alojadas deben seguir abiertas bajo los mismos terminos, porque el uso en red cuenta como distribucion.',
resume: 'Reanudar borrador',
discard: 'Descartar borrador',
},
@@ -2795,7 +2795,7 @@ labUi: {
print: 'Imprimir',
copyId: 'Copiar ID',
cardType: 'Tipo de carnet',
- licenseLabel: 'Licencia de skater',
+ licenseLabel: 'Licencia · AGPL-3.0',
licenseSub: 'Tu identidad personal de rider',
parkLabel: 'Acceso al park',
parkSub: 'Emite pases como propietario de un park',
@@ -5229,6 +5229,8 @@ settingsUi: {
myData: 'Mis datos',
myDataSub: 'Exportar, importar, gestionar',
advancedSub: 'Relatr, Sasquatch, herramientas',
+ trust: 'Confianza',
+ trustSub: 'Fuentes y visualizacion',
notLoaded: 'Sin cargar',
notLoggedIn: 'Sin sesión',
},
@@ -7387,6 +7389,12 @@ walletUi: {
sellerHint: 'El local de este vendedor emite pases de acceso',
},
trustUi: {
+ showMarks: 'Mostrar marcas de confianza',
+ showMarksDesc: 'Marcas de verificado y desconocido. Los reportes siempre se muestran.',
+ sourceLabel: 'Fuente',
+ sourceHouse: 'Proveedor de confianza de la comunidad',
+ sourceCustom: 'Tu proveedor',
+ loggedOut: 'Inicia sesion para elegir tu propio proveedor de confianza.',
seoTitle: 'Cómo funciona la confianza',
seoDescription: 'Cómo muestra SkateSpots la confianza: tres estados, de dónde vienen las puntuaciones y por qué la ausencia no es desconfianza.',
title: 'Cómo funciona la confianza',
diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx
index 2897be2..035c529 100644
--- a/src/pages/SettingsPage.tsx
+++ b/src/pages/SettingsPage.tsx
@@ -15,6 +15,7 @@ import { CVMHealthCheck } from '@/components/CVMHealthCheck';
import { SasquatchAiStatusCard } from '@/components/SasquatchAiStatusCard';
import { GrantProCard } from '@/components/GrantProCard';
import { ProSupportScreen } from '@/components/ProSupportScreen';
+import { TrustSettingsCard } from '@/components/TrustSettingsCard';
import { AdminReviewCard } from '@/components/AdminReviewCard';
import { RegionBackfillCard } from '@/components/RegionBackfillCard';
import { NotificationSettingsCard } from '@/components/NotificationSettingsCard';
@@ -55,7 +56,8 @@ type Section =
| 'notifications'
| 'coordinator'
| 'advanced'
- | 'pro';
+ | 'pro'
+ | 'trust';
// ─── Back header ──────────────────────────────────────────────────────────────
@@ -865,6 +867,14 @@ function SettingsMenu({ onNavigate }: { onNavigate: (s: Section) => void }) {
iconBg="bg-green-500/15"
onClick={() => onNavigate('mydata')}
/>
+ onNavigate('trust')}
+ />
)}
{section === 'advanced' && }
+ {section === 'trust' && (
+