Oracle3
Oracle3: 예측시장 거래 엔진·MCP 서버
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A responsive banking web client for the Kifiya Web Developer Challenge, built with Next.js 16, React 19 and TypeScript against the challenge banking API. Customers sign in, see their accounts and history, open accounts, send transfers and pay bills, on a phone or a desktop, in light or dark.
Live:, deployed on Vercel from main against the real API. Sign up there, or sign in with a demo user from the API's documentation. Every pull request also gets its own preview deployment.README 전문 보기 ↗A responsive banking web client for the Kifiya Web Developer Challenge, built with Next.js 16, React 19 and TypeScript against the challenge banking API. Customers sign in, see their accounts and history, open accounts, send transfers and pay bills, on a phone or a desktop, in light or dark.
Live: https://banking-app-challenge-beta.vercel.app, deployed on Vercel from main against the real API. Sign up there, or sign in with a demo user from the API's documentation. Every pull request also gets its own preview deployment.
The Kifiya name and logo belong to Kifiya and are used here only because the challenge's design uses them. The site asks search engines not to index it, and its footer says it is a reference client for the challenge.
Requires Node.js 24 (see .nvmrc) and pnpm 12. Without pnpm, corepack enable installs the version pinned in package.json.
pnpm install
cp .env.example .env.local
pnpm dev
Open http://localhost:3000 and register, or sign in with an account you already have on the challenge API.
Set NEXT_PUBLIC_API_MOCKING=on in .env.local and restart pnpm dev. Every API call is then answered by an in-browser mock (MSW) with sample data, and nothing is sent to the shared API. Sign in with the demo users documented by the API: demo.jane (two accounts with history), demo.john (a transfer recipient) or demo.empty (no transactions), all with the password Password123!. The mock keeps its data in localStorage, so a reload keeps you signed in and keeps your transfers; clear the site's data to start again from the seed.
Access tokens last 10 minutes. Set NEXT_PUBLIC_DEV_TOOLS=on and restart to get a Session button in the bottom-right corner. It shows when both tokens expire and how often this tab has refreshed. Expire access token now followed by 3 calls at once shows three 401s, a single refresh, and three retries that succeed. Expire refresh token too shows the session ending and the login page's "session expired" banner. It works with the mock and with the real API, because it only replaces the token this tab holds.
All three are read in the browser, so they are built into the JavaScript bundle: never put a secret in them. Restart pnpm dev, or rebuild, after changing one. They are checked when the app starts (src/shared/config/env.ts); a missing or malformed value stops it with a message naming the variable.
| Variable | Required | Default | What it does |
|---|---|---|---|
NEXT_PUBLIC_API_BASE_URL | yes | — | Banking API origin without a trailing slash, e.g. https://challenge-api.qena.dev |
NEXT_PUBLIC_API_MOCKING | no | off | on answers every API call from the in-browser mock, with sample data |
NEXT_PUBLIC_DEV_TOOLS | no | off | on shows the session inspector described above |
Every screen in the brief and the design, each with loading, empty and error states:
Small additions beyond the brief, all in the existing design language:
kifiya-<account>-<date>.csv.The full write-up is docs/architecture.md; each larger choice has a decision record in docs/decisions/.
src/proxy.ts) only redirects signed-out visitors early; the client is the real check.src/app holds routes only. src/features/<domain> (auth, accounts, transactions, transfers, bills, profile) holds the API calls, queries and components for each domain. src/shared holds the HTTP client, UI components, layout, money and date helpers, and theme. Imports go one way, app → features → shared, and ESLint enforces it.src/shared/api/http-client.ts) adds the token, refreshes it, and turns every failure into an ApiError (with the API's error code) or a NetworkError (offline, timeout, unreachable). Error codes map to plain messages; the server's own message text is never shown. Types are generated from the API's OpenAPI document./transfer/receipt/<id> (ADR-0008).docs/design/design-tokens.json generates the theme CSS, so colours, type and spacing come from the spec in one place ().The access token lasts 10 minutes and the refresh token 24 hours. Refresh happens when a request gets a 401, as the API documentation describes (ADR-0005):
request ──401──┐
request ──401──┼─► refresh() ─── one shared refresh per tab, however many requests failed
request ──401──┘ │
▼
Web Lock "kifiya-refresh" ─── one refresh at a time across all tabs
│
did another tab already rotate the refresh token?
├─ yes → use its new tokens
└─ no → POST /api/auth/refresh-token
├─ 200 → keep the new access token in memory and the new
│ refresh token in storage, tell the other tabs,
│ retry each failed request once
└─ 401 → end the session in every tab, clear the cached
data, go to /login?reason=expired
BroadcastChannel, so signing out in one tab signs out all of them.| Token | Where kept | Why |
|---|---|---|
| Access (10 min) | Memory only | Never written anywhere a script could find it after reload |
| Refresh (24 h) | localStorage | Must survive a reload and be shared by tabs |
The trade-off (ADR-0003): the API returns tokens in the response body and has no cookie support, so a browser-only client has to keep the refresh token somewhere a script can read. A successful XSS attack could steal it. The app lowers that risk:
eval, and no HTML from the API rendered as HTML.nosniff, a strict referrer policy and noindex.?next= return path after sign-in only accepts paths inside the app.In production this would change: a backend-for-frontend (Next.js route handlers) would sit between the browser and the API. It would keep both tokens in HttpOnly; Secure; SameSite=Strict cookies, refresh on the server, and protect mutations against CSRF. The browser would then never see a token, and an XSS attack could no longer carry one away. It isn't built here because the brief asks for a client of the given API, with the refresh flow handled in that client.
Each has its own document with every detail: testing.md, accessibility.md, security.md, performance.md and fidelity.md. What every screen does, down to each message, is in features.md.
zod/mini cut about 320 KB of script from every page.Where the brief, design and API disagree or leave gaps, the decision is recorded in docs/spec-notes.md. The ones a user would notice:
Intl, so other languages would mainly need the text translated.I built this with Claude Code as a pair programmer. I set the process: an issue for every task, a branch and pull request per issue, CI before every merge, and decisions written down as they were made. I reviewed the work as it landed and checked the main flows myself in Chrome. Claude Code wrote most of the code, tests and documentation under that process, and ran the browser checks against a local mock so that automated runs never sent credentials to the shared API.
One thing that changed after review: my own test with a duplicated tab showed that logging out in one tab left the other stuck on its loading screen. The fix gave tabs a BroadcastChannel to share new tokens, logout and expiry, and serialised refreshes across tabs with Web Locks, so two tabs can never both spend the same refresh token (ADR-0005). An end-to-end test now logs out in one tab and checks the other.
| Command | What it does |
|---|---|
pnpm dev | Start the development server |
pnpm build / pnpm start | Production build / serve it |
pnpm test | Unit and component tests (Vitest); test:watch, test:coverage |
pnpm e2e | Playwright tests on a mock-mode build; once: pnpm exec playwright install |
pnpm typecheck | Generate Next.js route types, then tsc --noEmit |
pnpm lint / pnpm lint:fix | ESLint, zero warnings allowed |
pnpm format / pnpm format:check | Prettier |
pnpm fidelity | Screenshot every spec frame into docs/fidelity/ |
pnpm lighthouse | Lighthouse on every page against a local mock API (see docs/performance.md) |
pnpm api:snapshot | Download the API's current OpenAPI document to docs/api/openapi.json |
pnpm api:types / pnpm api:types:check | Generate API types from the snapshot / check they're current |
pnpm tokens / pnpm tokens:check | Generate the theme CSS from docs/design/design-tokens.json / check it's current |
docs/ has every detail: the features screen by screen, architecture, decision records, security, testing, accessibility, performance, design fidelity, API notes, spec notes, requirements traceability, the backlog and the workflow. Work is tracked as issues and milestones on the project board. Releases are listed in CHANGELOG.md.