Skip to content
Merged
178 changes: 77 additions & 101 deletions apps/web/src/App.tsx

Large diffs are not rendered by default.

35 changes: 23 additions & 12 deletions apps/web/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ import type {
RuntimeStatus,
ValuationResult,
} from "./types";
import weekendDivergence from "../fixtures/weekend_divergence.json";
import consoleWeekendDivergenceV2 from "../fixtures/console_weekend_divergence_v2.json";

export const BASE = import.meta.env.DEV ? "" : (import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000");
export const DEFAULT_ASSET = "NVDAx";
export const PRIMARY_ASSET_OPTIONS = ["NVDAx", "SPYx", "QQQx", "AAPLx"] as const;
export type PrimaryAsset = (typeof PRIMARY_ASSET_OPTIONS)[number];
const LEGACY_REFERENCE_PROFILE = "legacy_xperp_vs_p1ac";
const EVIDENCE_SEMANTICS_V2 = "p1a_xstock_band_with_xperp_review_corroboration_v2";

const FIXTURE = weekendDivergence as unknown as ValuationResult[];
const CONSOLE_BACKUP = consoleWeekendDivergenceV2 as unknown as ValuationResult[];
export const DEMO_REPLAY_QUERY_KEY = ["demo-replay", DEFAULT_ASSET, "weekend_divergence", EVIDENCE_SEMANTICS_V2] as const;

export class ApiError extends Error {
constructor(public status: number, public detail: string) {
Expand Down Expand Up @@ -101,7 +101,7 @@ function normalizeAssetInfo(value: unknown): AssetInfo {
historical_data_available: item.historical_data_available ?? false,
historical_panel_file_available: item.historical_panel_file_available ?? false,
canonical_panel_verified: item.canonical_panel_verified ?? false,
historical_replay_ready: item.historical_replay_ready ?? item.historical_data_available ?? false,
historical_replay_ready: item.historical_replay_ready ?? false,
historical_replay_mode: item.historical_replay_mode ?? null,
historical_panel_error_code: item.historical_panel_error_code ?? null,
challenger_detector_status: item.challenger_detector_status ?? "DETECTOR_STATUS_UNAVAILABLE",
Expand All @@ -114,8 +114,8 @@ function normalizeAssetInfo(value: unknown): AssetInfo {
latest_observation_timestamp: item.latest_observation_timestamp ?? null,
latest_observation_age_seconds: item.latest_observation_age_seconds ?? null,
latest_observation_freshness: item.latest_observation_freshness ?? "unavailable",
// Only the legacy NVDAx deployment is known to expose these routes without
// readiness metadata. Missing metadata never enables another asset.
// Preserve the pre-registry NVDAx migration seam only. Explicit metadata
// enables any bound asset; missing metadata fails closed for SPYx/AAPLx.
onchain_binding_configured: item.onchain_binding_configured ?? legacyNvda,
readiness_error_codes: Array.isArray(item.readiness_error_codes)
? item.readiness_error_codes
Expand Down Expand Up @@ -247,15 +247,15 @@ export async function fetchOnchainEnforcement(asset = DEFAULT_ASSET): Promise<On
return assertScopedResponse(data, asset, "enforcement");
}

export type ReplaySource = "backend-scenario" | "offline-fixture";
export type ReplaySource = "backend-scenario" | "console-backup";
export interface ReplayResponse {
results: ValuationResult[];
source: ReplaySource;
}

/**
* Demo replay sequence. Tries the live backend first; on any failure falls back
* to the bundled fixture and reports which source was used.
* Demo replay sequence. Tries the production classifier first; on failure uses
* a separate Console-only snapshot of that same v2 scenario response.
*/
export async function fetchDemoReplay(
asset = DEFAULT_ASSET,
Expand All @@ -268,9 +268,20 @@ export async function fetchDemoReplay(
const results = await getJSON<ValuationResult[]>(
`/api/replay/${asset}?source=scenario&scenario=${encodeURIComponent(scenario)}`,
);
if (!Array.isArray(results) || results.length === 0) throw new Error("empty replay");
if (!isV2Demo(results)) throw new Error("backend scenario does not satisfy the v2 Demo contract");
return { results: assertAssetRows(results, asset), source: "backend-scenario" };
} catch {
return { results: assertAssetRows(FIXTURE, asset), source: "offline-fixture" };
return { results: assertAssetRows(CONSOLE_BACKUP, asset), source: "console-backup" };
}
}

function isV2Demo(results: unknown): results is ValuationResult[] {
return Array.isArray(results)
&& results.length === 6
&& results.every((row) => row && typeof row === "object"
&& (row as ValuationResult).asset === DEFAULT_ASSET
&& (row as ValuationResult).evidence_semantics === EVIDENCE_SEMANTICS_V2
&& (row as ValuationResult).validation_target === "xstock_observed_price"
&& (row as ValuationResult).xperp_role === "second_market_challenger"
&& (row as ValuationResult).xperp_index_price != null);
}
4 changes: 3 additions & 1 deletion apps/web/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface ChallengerDetectorEvidence {
score: number | null;
review_threshold: number;
challenge_threshold: number;
research_band: "support" | "watch" | "review" | "unavailable";
research_band: string;
promotion_status: string;
}

Expand Down Expand Up @@ -183,6 +183,8 @@ export interface OnchainControlPlane extends OnchainEvaluation {
policy: OnchainPolicy;
attestation: OnchainAttestation | null;
registry_fresh: boolean;
publication_compatible?: boolean;
publication_compatibility_status?: string | null;
}

export interface OnchainEnforcement extends OnchainEvaluation {
Expand Down
11 changes: 5 additions & 6 deletions apps/web/src/components/AssetPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ type AssetPickerOption = {
const ASSET_DISPLAY_NAMES: Record<string, string> = {
NVDAx: "NVIDIA Tokenized Equity",
SPYx: "S&P 500 Tokenized ETF",
QQQx: "Nasdaq-100 Tokenized ETF",
AAPLx: "Apple Tokenized Equity",
};

export function assetDisplayName(asset: string): string {
return ASSET_DISPLAY_NAMES[asset] ?? "Tokenized equity";
return ASSET_DISPLAY_NAMES[asset] ?? "Tokenized Equity";
}

export function assetAvailabilityLabel(operationalReady: boolean | undefined): string {
if (operationalReady === undefined) return "Checking";
return operationalReady ? "Available" : "Coming soon";
return operationalReady ? "Available" : "Coming Soon";
}

export function AssetPicker({ assets, selectedAsset, onChange }: {
Expand Down Expand Up @@ -110,16 +109,16 @@ export function AssetPicker({ assets, selectedAsset, onChange }: {
type="search"
role="combobox"
value={query}
aria-label="Search assets"
aria-label="Search Assets"
aria-autocomplete="list"
aria-controls="asset-picker-listbox"
aria-expanded="true"
aria-activedescendant={filtered[highlighted] ? `asset-option-${filtered[highlighted].asset}` : undefined}
placeholder="Search assets…"
placeholder="Search Assets…"
onChange={(event) => setQuery(event.target.value)}
onKeyDown={handleSearchKeyDown}
/>
<div id="asset-picker-listbox" className="asset-picker__list" role="listbox" aria-label="Available assets">
<div id="asset-picker-listbox" className="asset-picker__list" role="listbox" aria-label="Available Assets">
{filtered.map((option, index) => <button
type="button"
role="option"
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/DocsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const guides: readonly Guide[] = [
{ eyebrow: "Follow publication", title: "Separate observation from delivery.", body: "The publisher writes an authorized attestation to ValidationRegistry. Curator policy is stored separately, and RiskGuard evaluates both before the consumer acts.", tip: "The browser is read-only: it never signs or initiates a transaction.", visual: "contracts" },
{ eyebrow: "Enforce safely", title: "Fail closed on stale or mismatched state.", body: "Check chain ID, contract addresses, asset identity, sequence, valid-until, policy maximum age, and current attestation before accepting a decision.", tip: "Do not pair cached operational evidence with a newer onchain policy state.", visual: "guard" },
],
checks: [["Operational validation", "NVDAx, SPYx, and AAPLx have live schedulers, asset-specific runtimes, and canonical historical replay."], ["Current network", "ValidationRegistry and RiskGuard are deployed on X Layer testnet for NVDAx only."], ["Current status", "Research prototype—not audited production lending infrastructure; QQQx remains offline research-only."]],
checks: [["Operational validation", "NVDAx, SPYx, and AAPLx have live schedulers, asset-specific runtimes, and canonical historical replay."], ["Current network", "A shared ValidationRegistry and RiskGuard are deployed on X Layer testnet with asset-specific bindings for NVDAx, SPYx, and AAPLx."], ["Current status", "Research prototype—not audited production lending infrastructure; QQQx remains offline research-only."]],
},
{
id: "researchers", label: documentationProfiles.researchers.label, eyebrow: "Model and evaluation review", title: "Audit the challenger without overstating the evidence.",
Expand Down Expand Up @@ -149,7 +149,7 @@ export function DocsPage() {
<section className="docs-section role-checks"><div className="docs-section__lead docs-section__lead--compact"><p className="marketing-kicker">Working reference</p><h2>{guide.id === "everyone" ? "Three states, with no hidden policy instruction." : "What to verify before you move on."}</h2></div><div className="role-checks__grid">{guide.checks.map(([label, copy]) => <article key={label}><span>{label}</span><p>{copy}</p></article>)}</div><a className="role-guide__cta" href={guide.href} target={guide.href.startsWith("http") ? "_blank" : undefined} rel={guide.href.startsWith("http") ? "noreferrer" : undefined}>{guide.cta} <span aria-hidden="true">↗</span></a></section>
</div>
<section id="glossary" className="docs-section docs-section--glossary"><div className="docs-section__lead docs-section__lead--compact"><p className="marketing-kicker">Shared vocabulary</p><h2>Use the product’s terms precisely.</h2><p>These definitions stay constant across every profile. Short examples show how each term appears in the current product.</p></div><dl className="glossary-grid">{glossary.map(({ term, definition, example }) => <div key={term}><dt>{term}</dt><dd>{definition}<small className="glossary-example"><span>EXAMPLE</span>{example}</small></dd></div>)}</dl><div className="role-glossary"><div className="role-glossary__heading"><p className="marketing-kicker">Also useful for</p><h3>{guide.label}</h3><p>These supporting concepts change with your selected profile, so the glossary stays focused on the decisions and questions you are most likely to encounter.</p></div><dl className="role-glossary__grid">{supplementaryTerms.map(({ term, definition, example }) => <div key={term}><dt>{term}</dt><dd>{definition}<small>{example}</small></dd></div>)}</dl></div></section>
<section className="docs-section docs-section--scope"><div><p className="marketing-kicker">Current product scope</p><h2>A focused vertical slice.</h2><dl><div><dt>Operational validation</dt><dd>NVDAx / NVDA · SPYx / SPY · AAPLx / AAPL</dd></div><div><dt>Production asset selector</dt><dd>NVDAx, SPYx, AAPLx; each has a live scheduler, asset-specific runtime, and canonical historical panel.</dd></div><div><dt>Research-only asset</dt><dd>QQQx retains offline quant and historical research but is not exposed through production HTTP.</dd></div><div><dt>Validation semantics</dt><dd>Observed xStock target · P1a-C model-based challenger · exact-time X-Perp second-market evidence.</dd></div><div><dt>Onchain binding</dt><dd>NVDAx only · X Layer testnet</dd></div></dl></div><div><p className="marketing-kicker">Trust and limitations</p><h2>Research evidence, not a production guarantee.</h2><ul><li>No exact “true” fair value is observable while the strongest market is closed.</li><li>Historical diagnostics and retrospective tri-source results are not production guarantees.</li><li>The testnet contracts are not audited production lending infrastructure.</li><li>Valtide does not choose a universal LTV, trade, liquidate, or custody assets; consuming applications enforce their own policies.</li></ul></div></section>
<section className="docs-section docs-section--scope"><div><p className="marketing-kicker">Current product scope</p><h2>A focused vertical slice.</h2><dl><div><dt>Operational validation</dt><dd>NVDAx / NVDA · SPYx / SPY · AAPLx / AAPL</dd></div><div><dt>Production asset selector</dt><dd>NVDAx, SPYx, AAPLx; each has a live scheduler, asset-specific runtime, and canonical historical panel.</dd></div><div><dt>Research-only asset</dt><dd>QQQx retains offline quant and historical research but is not exposed through production HTTP.</dd></div><div><dt>Validation semantics</dt><dd>Observed xStock target · P1a-C model-based challenger · exact-time X-Perp second-market evidence.</dd></div><div><dt>Onchain binding</dt><dd>NVDAx · SPYx · AAPLx · X Layer testnet</dd></div></dl></div><div><p className="marketing-kicker">Trust and limitations</p><h2>Research evidence, not a production guarantee.</h2><ul><li>No exact “true” fair value is observable while the strongest market is closed.</li><li>Historical diagnostics and retrospective tri-source results are not production guarantees.</li><li>The testnet contracts are not audited production lending infrastructure.</li><li>Valtide does not choose a universal LTV, trade, liquidate, or custody assets; consuming applications enforce their own policies.</li></ul></div></section>
</main>
<MarketingFooter />
</div>;
Expand Down
Loading
Loading