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
73 changes: 73 additions & 0 deletions .agent/context/20260907T190600Z-a05-frontend-intent-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Session Context: A05 Frontend Intent and Authoritative Status

## Date/time

- Started: 2026-09-07T19:06:00Z
- Continued: 2026-09-08T12:29:20Z

## User goal and original request

Implement Coder A milestone A05 after Gate P4: "Делай" in response to the identified next packet, A05 Frontend Intent and Authoritative Status.

## Assumptions and non-goals

- Base is current `origin/develop` at `1250dec`.
- A05 consumes frozen OpenAPI v1 and does not add settlement capability.
- The browser receives a demo service token only at runtime and does not persist it.
- B05/C05 composition remains project Gate P5 work.
- No production API deployment, mainnet action, settlement detail UI, or visual polish campaign is included.

## Plan and decisions

- Preserve and finish the existing uncommitted A05 implementation after moving its base from A04 to current `develop`.
- Use generated `@oneshot/contracts` types and a small typed fetch client.
- Keep exact USDC parsing and formatting string/`bigint` based.
- Preserve the same Business Intent ID for replay; generate another ID only through an explicit new-obligation action.
- Stop polling at `UNKNOWN`; expose reconciliation only, never payment retry.
- Replace the Cloudflare placeholder asset with the built A05 SPA.
- Use React/Vite dependencies already present in the workspace; add no design system or state library.

## Files and components

- `apps/web`: application shell, API client, intent form, status view, readiness banner, exact money helpers, tests, and setup documentation.
- Root TypeScript/ESLint/workspace lock configuration includes the new app.
- Root Wrangler assets now point to `apps/web/dist`; the old placeholder is removed.
- Root README lists the operator frontend and its local command.

## Commands and checks

- `pnpm --filter @oneshot/web test`: 27 tests passed.
- `pnpm --filter @oneshot/web lint`: passed.
- `pnpm --filter @oneshot/web typecheck`: passed.
- `pnpm --filter @oneshot/web build`: passed; production source maps remain disabled.
- `pnpm exec wrangler deploy --dry-run`: passed with `apps/web/dist` assets.
- `pnpm format:check`, root `pnpm lint`, root `pnpm typecheck`, generated-contract check, and fixture validation: passed.
- Root `pnpm test`: 576 tests passed across 43 files.
- `TEST_POSTGRES=1 pnpm test:integration`: unavailable locally because no container runtime is running; the first Testcontainers suite reported `Could not find a working container runtime strategy`. Required CI provides the integration environment.
- Local runtime uses Node 22.23.2 and reports the repository's expected Node 24.19.0 engine warning; CI uses `.nvmrc`.

## External documentation findings

- Wrangler 4.127.0 local schema accepts `assets.not_found_handling = "single-page-application"`.
- Added `build.command = "pnpm --filter @oneshot/web build"` to `wrangler.jsonc` so Cloudflare Workers Builds automatically builds the Vite SPA before asset upload.

## Unresolved questions

- None for A05 packet scope. Production API deployment and P5 UI composition remain later work.

## Branch, commit, PR, and review state

- Branch: `milestone/a05-frontend-intent-status`
- Base: `origin/develop` at `1250dec79bc702939fe2a3b0fd00e66bb34128af`
- Initial commit: `d3fbfacdee365d159807256a53011140442867ff`
- PR: [#32](https://github.com/SWOFART/OneShot/pull/32)
- Gate A: IN PROGRESS (free-pi-cli glm-5.3-flash)
- Gate B: pending CI and exact-head review

## Handoff and next steps

- Validate wrangler build integration locally.
- Re-run Gate A, commit fix, push to PR #32.
- Verify CI passes (including Workers Builds).
- Run Gate B and mark PR ready for review.
- Project Gate P5 will compose B05/C05 UI packages after A05 closes.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ lock: OneShot's durable state is.

```text
apps/api HTTP seam
apps/web operator intent and status UI
apps/worker settlement and reconciliation workers
packages/contracts frozen v1 contract pack, OpenAPI, fixtures
packages/domain intent, attempt, and settlement state
Expand All @@ -143,6 +144,7 @@ Requires Node `24.19.0`, pnpm `11.19.0`, and PostgreSQL for integration tests.
pnpm install
pnpm lint && pnpm typecheck && pnpm build
pnpm test
pnpm dev:frontend
```

Integration tests need a database:
Expand Down Expand Up @@ -188,7 +190,7 @@ Under active development. **Testnet only.**
| Settlement adapters and error taxonomy | Implemented, exercised against simulators |
| Recovery evidence and safety core | In progress |
| Subgraph MCP discovery and LLM recovery agent | Planned |
| Operator frontend | Not started |
| Operator frontend | Intent creation, replay/conflict, and authoritative status implemented |

**No live settlement has been executed.** No Privy application, wallet, policy,
or funded testnet account has been provisioned for this build. The adapters are
Expand Down
104 changes: 0 additions & 104 deletions apps/placeholder-frontend/index.html

This file was deleted.

1 change: 1 addition & 0 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_ONESHOT_API_BASE_URL=http://127.0.0.1:3001
11 changes: 11 additions & 0 deletions apps/web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# OneShot web

Minimal operator UI for creating or replaying a Business Intent and reading its authoritative status.

```powershell
pnpm --filter @oneshot/web dev
```

Vite proxies `/v1` and `/health` to the local API. For a separate deployed API, set the public build variable `VITE_ONESHOT_API_BASE_URL`. Enter the demo service token at runtime; the UI keeps it in memory and never persists it.

The client consumes generated `@oneshot/contracts` types from frozen OpenAPI v1. Tests use deterministic fetch responses matching that contract.
12 changes: 12 additions & 0 deletions apps/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OneShot — Intent & Authoritative Status</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@oneshot/web",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc -b && vite build",
"clean": "tsc -b --clean",
"dev": "vite",
"lint": "eslint src test",
"preview": "vite preview",
"test": "vitest run --config vitest.config.ts",
"typecheck": "tsc -b --pretty false"
},
"dependencies": {
"@oneshot/contracts": "workspace:*",
"react": "19.2.8",
"react-dom": "19.2.8"
},
"devDependencies": {
"@testing-library/react": "16.3.3",
"@testing-library/user-event": "14.6.7",
"@types/node": "24.13.3",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.7",
"@vitejs/plugin-react": "6.1.1",
"axe-core": "4.13.0",
"jsdom": "30.0.1",
"typescript": "6.0.3",
"vite": "8.0.0",
"vitest": "5.0.0"
}
}
83 changes: 83 additions & 0 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useMemo, useState } from 'react';

import { OneShotApiClient } from './api/client.js';
import { ErrorBoundary } from './components/ErrorBoundary.js';
import { IntentForm } from './components/IntentForm.js';
import { IntentStatusView } from './components/IntentStatusView.js';
import { ReadinessBanner } from './components/ReadinessBanner.js';
import './styles.css';

type Tab = 'create' | 'status';

export function App() {
const [activeTab, setActiveTab] = useState<Tab>('create');
const [selectedIntentId, setSelectedIntentId] = useState('');
const [authToken, setAuthToken] = useState('');
const client = useMemo(
() =>
new OneShotApiClient({
baseUrl: import.meta.env.VITE_ONESHOT_API_BASE_URL ?? '',
getAuthToken: () => authToken.trim() || null,
}),
[authToken],
);

function showStatus(intentId: string): void {
setSelectedIntentId(intentId);
setActiveTab('status');
}

return (
<ErrorBoundary>
<div className="app-shell">
<header className="app-header">
<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>
<ReadinessBanner client={client} />
</header>

<section className="auth-bar" aria-label="Service authorization">
<label htmlFor="auth-token-input">Demo service token</label>
<input
id="auth-token-input"
type="password"
autoComplete="off"
value={authToken}
onChange={(event) => setAuthToken(event.target.value)}
/>
<small>Memory only. Sent as Bearer authorization.</small>
</section>

<nav className="tabs" aria-label="Application sections" role="tablist">
<button
type="button"
role="tab"
aria-selected={activeTab === 'create'}
aria-controls="create-panel"
onClick={() => setActiveTab('create')}
>
Create or replay
</button>
<button
type="button"
role="tab"
aria-selected={activeTab === 'status'}
aria-controls="status-panel"
onClick={() => setActiveTab('status')}
>
Authoritative status
</button>
</nav>

<main id={activeTab === 'create' ? 'create-panel' : 'status-panel'} role="tabpanel">
{activeTab === 'create' ? (
<IntentForm client={client} onIntentCreatedOrSelected={showStatus} />
) : (
<IntentStatusView client={client} initialIntentId={selectedIntentId} />
)}
</main>
</div>
</ErrorBoundary>
);
}
Loading
Loading