Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .agent/context/20260910T-web-landing-redesign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Session Context: Web Landing & Glassmorphism Redesign

## Date/time

- UTC: 2026-09-09T23:51:00Z

## User goal

Replace the bare white login stub with a high-end Behance-grade minimalist dark glassmorphism landing page and console matching the Convergence Stream logo, with full Privy operator authentication, crisp typography, and no cheesy AI icons.

## Assumptions

- Base is `develop` at `1125134f7edcdea4e1e6bc5687300ad67908142c`.
- Strict minimalism: zero AI emojis/icons (no brains, robots, sparkles, or lightbulbs).
- Dark aesthetic matching the avatar: obsidian background (#080b11), glowing cyan luminescence (#00f2fe / #22d3ee), glass cards with subtle borders and backdrop blur.
- All existing Playwright acceptance tests and unit tests must remain 100% green.

## Plan & Execution Status

1. [x] Create design tokens and glassmorphism CSS matching the Concept 3 Convergence Stream avatar (`#06090e`, glowing radial cyan luminescence, frosted glass panels with `backdrop-filter: blur(20px)` and subtle white borders).
2. [x] Build the Landing Hero section with value proposition (`One job. Many retries. One settlement.`) and architectural invariant cards (`01 / ATOMIC PRECISION`, `02 / PRE-EXECUTION POLICY`, `03 / HASHLESS RECOVERY`, `04 / CARDINALITY INVARIANT`). Absolutely NO AI icons or emojis.
3. [x] Integrate the Convergence Stream logo (`/logo.png`) into Top Navigation bar and Technical Footer.
4. [x] Refactor LoginGate into a polished operator experience:
- "Sign in with Privy" button triggering official Privy React SDK modal.
- Operator DID badge with copy action and Sign out button when signed in.
- Preserved reconciliation tree structure so typing machine tokens in advanced drawer never drops focus or unmounts the input.
5. [x] Embed the 4 console tabs (`Create or replay`, `Authoritative status`, `Settlement evidence`, `Recovery evidence`) cleanly inside the Authoritative Execution Engine workspace.
6. [x] Fix automated test isolation: added `.env.test` and updated `test:browser` to run `vite build --mode test` and ignore non-localhost endpoints in `mockApi`, avoiding external Privy network requests in CI.
7. [x] Verify all 64 test files (942 unit/contract tests), 7/7 Playwright browser tests, lint, typecheck, format, and full monorepo build: 100% green.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Thumbs.db
.env
.env.*
!.env.example
!.env.test
.envrc
.direnv/
*.local
Expand Down
1 change: 1 addition & 0 deletions apps/web/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_PRIVY_APP_ID=
4 changes: 3 additions & 1 deletion apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OneShot — Operator Control</title>
<meta name="description" content="OneShot: Exactly-once stablecoin payment settlement engine with pre-execution policy checks and hashless recovery on Arc Testnet." />
<link rel="icon" type="image/png" href="/logo.png" />
<title>OneShot — Arc Testnet Stablecoin Engine</title>
</head>
<body>
<div id="root"></div>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"lint": "eslint src test playwright.config.ts",
"preview": "vite preview",
"test": "vitest run --config vitest.config.ts",
"test:browser": "pnpm run typecheck:browser && pnpm run build && node scripts/run-browser-tests.mjs",
"test:browser": "pnpm run typecheck:browser && tsc -b && vite build --mode test && node scripts/run-browser-tests.mjs",
"typecheck:browser": "tsc -p tsconfig.browser.json --pretty false",
"typecheck": "tsc -b --pretty false"
},
Expand Down
Binary file added apps/web/public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
254 changes: 202 additions & 52 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,64 +88,214 @@ export function App(props: AppProps = {}) {
return (
<ErrorBoundary>
<div className="app-shell">
<header className="app-header">
<nav className="top-nav" aria-label="Site Navigation">
<div className="brand-group">
<img src="/logo.png" alt="OneShot Logo" className="brand-logo" width="36" height="36" />
<div className="brand-text">
<span className="brand-name">ONESHOT</span>
<span className="brand-tag">SETTLEMENT ENGINE</span>
</div>
</div>

<div className="nav-status-group">
<span className="status-badge network-badge">
<span className="status-dot"></span>
Arc Testnet (5042002)
</span>
<span className="status-badge token-badge">Native USDC</span>
</div>

<a href="#console" className="nav-console-link">
Operator Console ↓
</a>
</nav>

<header className="app-header hero-section">
<p className="eyebrow">ONESHOT / ARC TESTNET</p>
<h1>One job. Many retries. One settlement.</h1>
<p>Create a stable payment intent and follow its authoritative state.</p>
<p className="hero-lead">
Deterministic payment lifecycle with pre-execution policy checks, idempotency
enforcement, and hashless recovery on Arc.
</p>
<p className="hero-sublead">
Create a stable payment intent and follow its authoritative state.
</p>
<div className="hero-actions">
<a href="#console" className="btn-hero-cta">
Open Operator Console ↓
</a>
<a
href="https://testnet.arcscan.app"
target="_blank"
rel="noreferrer"
className="btn-hero-secondary"
>
ArcScan Explorer ↗
</a>
</div>
<ReadinessBanner client={apiClient} />
</header>

<LoginGate
session={session}
machineToken={machineToken}
onMachineTokenChange={setMachineToken}
>
<section className="intent-context" aria-label="Selected business intent">
<label htmlFor="selected-intent-input">Active Business Intent ID</label>
<input
id="selected-intent-input"
value={selectedIntentId}
onChange={(event) => setSelectedIntentId(event.target.value)}
placeholder="Create an intent or enter its stable ID"
/>
</section>

<nav className="tabs" aria-label="Application sections" role="tablist">
{TAB_ORDER.map((tab) => (
<button
key={tab}
id={`${tab}-tab`}
ref={(element) => {
tabRefs.current[tab] = element;
}}
type="button"
role="tab"
tabIndex={activeTab === tab ? 0 : -1}
aria-selected={activeTab === tab}
aria-controls={`${tab}-panel`}
onClick={() => setActiveTab(tab)}
onKeyDown={(event) => handleTabKeyDown(event, tab)}
>
{TAB_LABELS[tab]}
</button>
))}
</nav>

<main id={`${activeTab}-panel`} role="tabpanel" aria-labelledby={`${activeTab}-tab`}>
{activeTab === 'create' ? (
<IntentForm client={apiClient} onIntentCreatedOrSelected={selectIntent} />
) : activeTab === 'status' ? (
<IntentStatusView client={apiClient} initialIntentId={selectedIntentId} />
) : activeTab === 'settlement' ? (
<SettlementSurface
businessIntentId={selectedIntentId.trim()}
client={settlementClient}
<section className="invariants-section" aria-label="System Invariants">
<div className="section-header">
<span className="section-eyebrow">ARCHITECTURAL GUARANTEES</span>
<h2>Zero Double-Payment by Mathematical Proof</h2>
</div>

<div className="invariants-grid">
<article className="invariant-card">
<div className="invariant-index">01 / ATOMIC PRECISION</div>
<h3>Exactly-Once Execution</h3>
<p>
Strict idempotency keying guarantees that retries and replays return the verified
authoritative settlement record without duplicate mint or transfer operations.
</p>
</article>

<article className="invariant-card">
<div className="invariant-index">02 / PRE-EXECUTION POLICY</div>
<h3>Pre-Flight Policy Gating</h3>
<p>
Dynamic balance verification, recipient allowlists, and operator volume caps run
prior to mempool submission, denying unauthorized calls before gas is consumed.
</p>
</article>

<article className="invariant-card">
<div className="invariant-index">03 / HASHLESS RECOVERY</div>
<h3>Cryptographic Reconciliation</h3>
<p>
When RPC gateways timeout or transaction hashes are dropped during transit, the
engine queries authoritative ledger receipts and Subgraph indexers to discover truth
safely.
</p>
</article>

<article className="invariant-card">
<div className="invariant-index">04 / CARDINALITY INVARIANT</div>
<h3>Bounded State Machine</h3>
<p>
Strict 1:1 business-intent-to-settlement mapping across all ledger transitions. An
intent marked UNKNOWN strictly freezes all concurrent payouts until proof is
observed.
</p>
</article>
</div>
</section>

<section className="console-container" id="console" aria-label="Operator Console Workspace">
<div className="console-header">
<span className="section-eyebrow">WORKSPACE</span>
<h2>Authoritative Execution Engine</h2>
<p className="console-subtitle">
Inspect real-time ledger states, construct validated payment intents, or audit
cryptographic settlement and recovery evidence.
</p>
</div>

<LoginGate
session={session}
machineToken={machineToken}
onMachineTokenChange={setMachineToken}
>
<section className="intent-context" aria-label="Selected business intent">
<label htmlFor="selected-intent-input">Active Business Intent ID</label>
<input
id="selected-intent-input"
value={selectedIntentId}
onChange={(event) => setSelectedIntentId(event.target.value)}
placeholder="Create an intent or enter its stable ID"
/>
) : (
<RecoverySurface businessIntentId={selectedIntentId.trim()} client={recoveryClient} />
)}
</main>
</LoginGate>
</section>

<nav className="tabs" aria-label="Application sections" role="tablist">
{TAB_ORDER.map((tab) => (
<button
key={tab}
id={`${tab}-tab`}
ref={(element) => {
tabRefs.current[tab] = element;
}}
type="button"
role="tab"
tabIndex={activeTab === tab ? 0 : -1}
aria-selected={activeTab === tab}
aria-controls={`${tab}-panel`}
onClick={() => setActiveTab(tab)}
onKeyDown={(event) => handleTabKeyDown(event, tab)}
>
{TAB_LABELS[tab]}
</button>
))}
</nav>

<main id={`${activeTab}-panel`} role="tabpanel" aria-labelledby={`${activeTab}-tab`}>
{activeTab === 'create' ? (
<IntentForm client={apiClient} onIntentCreatedOrSelected={selectIntent} />
) : activeTab === 'status' ? (
<IntentStatusView client={apiClient} initialIntentId={selectedIntentId} />
) : activeTab === 'settlement' ? (
<SettlementSurface
businessIntentId={selectedIntentId.trim()}
client={settlementClient}
/>
) : (
<RecoverySurface
businessIntentId={selectedIntentId.trim()}
client={recoveryClient}
/>
)}
</main>
</LoginGate>
</section>

<footer className="app-footer">
<div className="footer-top">
<div className="footer-brand">
<div className="brand-group">
<img
src="/logo.png"
alt="OneShot Logo"
className="brand-logo"
width="28"
height="28"
/>
<span className="brand-name">ONESHOT</span>
</div>
<p className="footer-desc">
Stablecoin-native payment lifecycle engine with pre-flight policy gating and
subgraph recovery on Arc Testnet.
</p>
</div>

<div className="footer-meta">
<div className="meta-col">
<span className="meta-label">NETWORK</span>
<span className="meta-value">Arc Testnet (5042002)</span>
</div>
<div className="meta-col">
<span className="meta-label">SETTLEMENT ASSET</span>
<span className="meta-value">Native USDC (ERC-20)</span>
</div>
<div className="meta-col">
<span className="meta-label">TOKEN CONTRACT</span>
<code className="meta-code">0x3600...0000</code>
</div>
</div>
</div>

<div className="footer-bottom">
<span>OneShot Protocol · Built for Circle Arc Challenge</span>
<div className="footer-links">
<a href="https://testnet.arcscan.app" target="_blank" rel="noreferrer">
ArcScan
</a>
<a href="https://github.com/SWOFART/OneShot" target="_blank" rel="noreferrer">
GitHub
</a>
</div>
</div>
</footer>
</div>
</ErrorBoundary>
);
Expand Down
31 changes: 20 additions & 11 deletions apps/web/src/components/LoginGate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,22 @@ export function LoginGate(props: LoginGateProps) {
return (
<section className="login-gate" aria-label="Operator sign-in">
{props.session.status === 'LOADING' ? (
<p>Checking your session…</p>
<p className="loading-text">Checking your session…</p>
) : props.session.status === 'UNCONFIGURED' ? (
<p role="status">
<p role="status" className="gate-unconfigured">
Privy login is not configured for this build. Set <code>VITE_PRIVY_APP_ID</code> to
enable it.
</p>
) : (
<>
<div className="gate-action-box">
<h2>Operator sign-in</h2>
<p>The console reads authoritative payment state. Sign in to continue.</p>
<button type="button" onClick={() => props.session.login()}>
<p className="gate-subtitle">
The console reads authoritative payment state. Sign in to continue.
</p>
<button type="button" className="btn-privy" onClick={() => props.session.login()}>
Sign in with Privy
</button>
</>
</div>
)}
<MachineTokenField value={props.machineToken} onChange={props.onMachineTokenChange} />
</section>
Expand All @@ -73,19 +75,26 @@ export function LoginGate(props: LoginGateProps) {
<section className="operator-identity" aria-label="Operator identity">
{props.session.status === 'SIGNED_IN' && props.session.subject ? (
<>
<span className="operator-badge">OPERATOR</span>
<span>Signed in as</span>
<code>{props.session.subject}</code>
<button type="button" onClick={() => void copySubject(props.session.subject ?? '')}>
<code className="operator-did">{props.session.subject}</code>
<button
type="button"
className="btn-copy"
onClick={() => void copySubject(props.session.subject ?? '')}
>
{copied ? 'Copied' : 'Copy DID'}
</button>
<small>Add this DID to PRIVY_AUTH_ALLOWED_SUBJECTS to grant console access.</small>
<button type="button" onClick={() => props.session.logout()}>
<small className="operator-note">
Add this DID to PRIVY_AUTH_ALLOWED_SUBJECTS to grant console access.
</small>
<button type="button" className="btn-signout" onClick={() => props.session.logout()}>
Sign out
</button>
</>
) : (
<>
<span>Using a machine token</span>
<span className="operator-badge">Using a machine token</span>
<MachineTokenField value={props.machineToken} onChange={props.onMachineTokenChange} />
</>
)}
Expand Down
Loading
Loading