feat: legibility wave — veil conditions, first-run hints, kinder errors
Workstream B of the usability wave (#2 hints, #3 conditions, #4 errors): - GET /api/conditions (new backend/app/routes/conditions.py): composes celestial veil_thinness with the cached NOAA Kp reading; lenient lon parsing (junk degrades to moon-only, never 422); geomagnetic may be null on a cold cache. Route tests stub the cache — no live NOAA calls. - VeilConditions strip in the séance side column: moon glyph + phase, % lit, veil-thinness phrase, Kp line only when data exists. Polls every 10 min; renders nothing while loading; no error state. - ModeHint: per-mode in-fiction one-liner after ~15s idle, suppressed once the mode's sensor runs this session, dismissal persisted in localStorage (qm_hint_<mode>). One mount line per panel. - Error copy upgraded to detect-and-redirect: mic denied points at site settings and the ouija board/wire; WebUSB-unsupported suggests EVP. - i18n en/es parity for every new string; coverage-check template domains extended for the new template-key call sites. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,8 @@ import { EntityCard } from '../components/EntityCard'
|
||||
import { RitualPanel } from '../components/RitualPanel'
|
||||
import { JudgmentPanel } from '../components/JudgmentPanel'
|
||||
import { TelemetryReadout } from '../components/TelemetryReadout'
|
||||
import { VeilConditions } from '../components/VeilConditions'
|
||||
import { ModeHint } from '../components/ModeHint'
|
||||
import { PlanchetteMachine } from '../lib/planchette'
|
||||
import type { PlanchetteSnapshot } from '../lib/planchette'
|
||||
import { EvpListener } from '../lib/evp'
|
||||
@@ -362,6 +364,7 @@ function SeanceSession({ username }: { username: string }) {
|
||||
onToggleOpen={() => setDossierOpen((o) => !o)}
|
||||
/>
|
||||
|
||||
<VeilConditions />
|
||||
<RitualPanel />
|
||||
<JudgmentPanel />
|
||||
|
||||
@@ -477,6 +480,7 @@ function WirePanel() {
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
<ModeHint mode="wire" suppressed={state.passive} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -640,6 +644,7 @@ function EvpPanel() {
|
||||
>
|
||||
{listening ? t('seance.evp.stop') : t('seance.evp.start')}
|
||||
</button>
|
||||
<ModeHint mode="evp" suppressed={listening || pending} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -869,6 +874,7 @@ function RadioPanel() {
|
||||
>
|
||||
{phase === 'sweeping' ? t('seance.radio.stop') : t('seance.radio.start')}
|
||||
</button>
|
||||
<ModeHint mode="radio" suppressed={phase !== 'idle'} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -909,6 +915,7 @@ function OuijaPanel({ snapshot }: { snapshot: PlanchetteSnapshot | null }) {
|
||||
{snapshot && snapshot.spelled.length > 0 && (
|
||||
<p className="ouija-history dim">{snapshot.spelled.slice(-6).join(' · ')}</p>
|
||||
)}
|
||||
<ModeHint mode="ouija" suppressed={word !== null} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1167,6 +1174,7 @@ function EmfPanel() {
|
||||
{t('seance.emf.calibrate', { defaultValue: 'calibrate' })}
|
||||
</button>
|
||||
</div>
|
||||
<ModeHint mode="emf" suppressed={listening || pending} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user