The database is the source of truth; the site is a view over it.
- packages/core/db/schema.sql: vendors, products, categories, the 8-dimension
instrument, assessments, scores, evidence, findings, off-ramp, plus the
derived v_product_score view the game reads.
- packages/core/db/build.mjs: rebuilds bigtech.db, absorbs the legacy 43-product
roster as unassessed, applies seeds, then verifies the instrument's own rules
(weights sum to 100, totals recompute, grades match bands, every score has a
rationale, no tier-4 evidence on a scored dimension, reproducible-build caps
actually enforced).
- packages/core/db/seed/0001-signal.sql: Signal Desktop 8.36.0-alpha.1 on linux,
75.9/100 grade B, weakest dimension local_data_exposure. Evidence points at
the re-runnable audit artifacts in the Servetus vault.
- packages/core/src/data/generated/catalog.{ts,json}: generated export.
Verified: 44 products, 1 assessed, all checks pass.
91 lines
No EOL
2.3 KiB
TypeScript
91 lines
No EOL
2.3 KiB
TypeScript
import { useCallback, useMemo, useState } from 'react';
|
|
import {
|
|
advanceCapture,
|
|
buildSovereigntyReport,
|
|
computeOffRamp,
|
|
createInitialState,
|
|
persistToVault,
|
|
selectOutlaws,
|
|
setPhase,
|
|
transitionAfterShowdown,
|
|
type LoginMethod,
|
|
type PlayerJourneyState,
|
|
} from '@bigtech-sucks/core';
|
|
|
|
export function useGameState() {
|
|
const [state, setState] = useState<PlayerJourneyState>(() => createInitialState());
|
|
|
|
const report = useMemo(
|
|
() => buildSovereigntyReport(state.selectedOutlaws, state.loginMethod),
|
|
[state.selectedOutlaws, state.loginMethod],
|
|
);
|
|
|
|
const offRamp = useMemo(
|
|
() => (state.selectedOutlaws.length ? computeOffRamp(state.selectedOutlaws) : null),
|
|
[state.selectedOutlaws],
|
|
);
|
|
|
|
const vaultExport = useMemo(() => persistToVault(state), [state]);
|
|
|
|
const login = useCallback((method: LoginMethod) => {
|
|
setState((s) => ({ ...s, loginMethod: method }));
|
|
}, []);
|
|
|
|
const goToPhase = useCallback((phase: PlayerJourneyState['phase']) => {
|
|
setState((s) => setPhase(s, phase));
|
|
}, []);
|
|
|
|
const drawLine = useCallback((ids: string[]) => {
|
|
setState((s) => selectOutlaws(s, ids));
|
|
}, []);
|
|
|
|
const startShowdown = useCallback((outlawId: string) => {
|
|
setState((s) => setPhase({ ...s, activeOutlawId: outlawId }, 'showdown'));
|
|
}, []);
|
|
|
|
const showdownStep = useCallback(
|
|
(step: 'round_up' | 'validate' | 'cut') => {
|
|
setState((s) => {
|
|
if (!s.activeOutlawId) return s;
|
|
const advanced = advanceCapture(s, s.activeOutlawId, step);
|
|
if (step === 'cut') {
|
|
return transitionAfterShowdown(advanced);
|
|
}
|
|
return advanced;
|
|
});
|
|
},
|
|
[],
|
|
);
|
|
|
|
const submitWaitlist = useCallback(async (email: string, channel: string) => {
|
|
const payload = {
|
|
email,
|
|
channel,
|
|
settlerSince: state.settlerSince,
|
|
};
|
|
|
|
const res = await fetch('/api/waitlist', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (!res.ok) throw new Error('Waitlist submission failed');
|
|
|
|
setState((s) => ({ ...s, waitlistEmail: email, waitlistChannel: channel }));
|
|
return payload;
|
|
}, [state.settlerSince]);
|
|
|
|
return {
|
|
state,
|
|
report,
|
|
offRamp,
|
|
vaultExport,
|
|
login,
|
|
goToPhase,
|
|
drawLine,
|
|
startShowdown,
|
|
showdownStep,
|
|
submitWaitlist,
|
|
};
|
|
} |