Skip to content

feat: merge preview → main (MVP launch batch)#10

Merged
phucbm merged 63 commits into
mainfrom
preview
May 24, 2026
Merged

feat: merge preview → main (MVP launch batch)#10
phucbm merged 63 commits into
mainfrom
preview

Conversation

@phucbm
Copy link
Copy Markdown
Contributor

@phucbm phucbm commented May 24, 2026

Summary

  • Chat system with Groq streaming, MCP tools, eval harness
  • Card ranking table with spend selector and animated reorder
  • Compare bar (floating, multi-card, URL sync)
  • Recommendation finder with multi-select intents
  • /mcp and /wallet-chat landing pages
  • Cal Sans fonts, preview banner, noindex for preview env
  • API hardening (apiFetch, Origin header, error surfacing)
  • Evals system with SSE streaming, LLM judge, GitHub push
  • Contact page with topic-picker and mail preview
  • McpVersionBadge async server component
  • Font and config fixes (unicodeRange, turbopack migration)

Test plan

  • Build passes on Vercel
  • Public pages indexed correctly (preview noindex removed on main)
  • Card ranking and compare bar functional
  • Chat/MCP tools not visible (button hidden per CLAUDE.md)

🤖 Generated with Claude Code

phucbm and others added 30 commits May 20, 2026 16:57
Move SPEND_OPTIONS array from card-ranking-table.tsx into lib/spend-options.ts
so it can be reused by the upcoming RecommendationFinder component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Client component with page and compact widget modes. Supports:
- Intent chip picker + spend selector → live rankCards() results
- URL params (?intent=&spend=) sync on full page
- localStorage persistence (ow-rec-prefs)
- "Doanh nghiệp" tab shows coming-soon state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dedicated recommendation page: SSR fetches cards/banks/intents,
renders RecommendationFinder full layout. Includes JSON-LD WebPage
and BreadcrumbList. Auto-added to sitemap via filesystem scanner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fetch getIntents() alongside existing cards/banks, render
RecommendationFinder compact limit={3} widget below the hero section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
useCompareList() hook backed by localStorage (key: compare_list, max 3).
Same useSyncExternalStore pattern as use-recent-compares.ts.
API: compareList, addToCompare, removeFromCompare, toggleCompare,
clearCompare, isInCompare, isFull.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixed bottom bar, z-50, slides up when ≥1 card in compare list.
Shows count, "So sánh ngay" button (enabled at ≥2), and "Xóa" link.
Navigates to /so-sanh/id1-vs-id2[-vs-id3].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Third circle button (IconScale) in hover overlay. Active state when
card is in compare list, disabled when list full and card not in list.
Calls toggleCompare on click with propagation stopped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove useSearchParams(), lastProcessedParam/lastSynced refs, and both
URL-sync effects. Add usePathname-aware navigation: when ≥2 cards
selected, push to /so-sanh/id1-vs-id2 (or replace if already on a
pair page). Prefill uses defaultPair → recent compares → defaults.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/so-sanh?compare=id1,id2 now redirects server-side to /so-sanh/id1-vs-id2.
Preserves old shareable URLs while moving to the canonical pair URL format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- store card name + image_url in compare_list_meta (localStorage)
- addToCompare/toggleCompare accept optional meta; fire sonner toast on add
- CompareBar desktop: 3 slots with thumbnail, name, × remove button
- CompareBar mobile: count-only compact mode
- new CompareButton for card detail page (3 states: add/active/full)
- card-tile passes name+image_url to toggleCompare so bar shows correct data
- mount Toaster in marketing layout above CompareBar
…ankedRow

- Replace single intentSlug with intentSlugs[] for multi-select chips
- Red banner, tab toggle (Cá nhân / Doanh nghiệp), unified layout
- Spend selector uses chevron + Select pattern from card-ranking-table
- URL sync gated on /goi-y-the pathname to prevent homepage redirect
- Export RankedRow from card-ranking-table; use it in right panel
  for tiebreaker indicators and rank badges
- Remove compact prop and CompactResultRow
Add @assistant-ui/react, @assistant-ui/react-ai-sdk, @ai-sdk/groq, ai, zod.
Generate thread.tsx and 11 sibling components via assistant-ui CLI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restricts assistant to Vietnamese card topics only; includes
refusal template for out-of-scope questions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tools: searchCards, getCardDetail, rankCardsForSpend, compareCards, listBanks.
All tools use apiFetch(); rankCardsForSpend delegates to rankCards().
IP-based rate limit: 20 req/min, 429 with Vietnamese error message.
History trimmed to last 12 messages before streamText call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ChatPageClient: useChatRuntime + AssistantChatTransport, localStorage
sync debounced 500ms via onFinish callback.
ChatPage: metadata, ow-chat-page wrapper, renders client component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers: happy path (banks, cashback recommendation, compare, fee inquiry),
out-of-scope (gold price, stock market), vague query, hallucination guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AI SDK v6 sends UIMessage[] (with parts/metadata/id) from the client;
streamText expects ModelMessage[]. Use convertToModelMessages() to bridge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents gotchas: UIMessage vs ModelMessage, tool() API change,
maxSteps removal, streaming method rename, useChatRuntime transport.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move /chat from (marketing) to (chat) route group — bare layout, no header/footer
- Add multi-conversation localStorage store (lib/chat/conversation-store.ts) with legacy migration from ow-chat-history
- Rewrite chat-page-client: shadcn Sidebar + conversation list grouped by date, new/delete per convo, key-based runtime remount on switch
- Fix hydration mismatch: defer localStorage reads to useEffect, init state empty on server
- Add OpenWallet logo + home link to sidebar header
- Remove attachment UI from thread composer (text-only input)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace split-view right panel with floating bottom-right widget.
Desktop: 380×560 popup; mobile: full-screen. ⌘/ shortcut to toggle.
Chat toggle button restyled to match search trigger (border + icon + text).
No more layout content shifting — ChatLayoutWrapper removed.

Card/bank detail pages inject current page into AI system prompt via
ChatContextSetter + buildSystemPrompt, so AI knows what user is viewing.
Context hint badge shown above composer when active.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Logo accepts href prop (string | null); null renders span instead of Link
- Chat panel logo links to /chat
- Maximize button closes panel and navigates to /chat?id=<activeId>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace TOP 1/2/3 text badges with laurel wreath icons
- Tiebreaker reason: shorten fee copy, swap pill badge for bulb icon
- Wrap spend changes in startViewTransition for smooth card reorder
phucbm and others added 28 commits May 22, 2026 10:20
- Rename /so-sanh → /card-battle, /goi-y-the → /card-match
- Add lib/tools.ts as single source of truth for all 4 tool hrefs
- Add 301 redirects for old routes to preserve SEO equity
- Add "Công cụ" hover dropdown in desktop nav, collapsible in mobile nav
- Update footer to derive tools list from lib/tools.ts
- Replace all hardcoded tool hrefs across 12+ files with getTool() imports

Tools: Card Battle, Card Match, Wallet Chat, Wallet MCP
Wire /api/chat to consume tools from MCP server via @ai-sdk/mcp instead
of inline tool definitions. Add scripts/mcp-status.ts for terminal health
monitoring of MCP and API services during dev.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- apiFetch: warn on missing OPENWALLET_API_KEY per request
- apiFetch: specific 401/403 messages instead of generic "API error"
- apiFetch: detect 200+HTML responses (CDN/WAF) and throw with URL hint
- the/[slug] + ngan-hang/[slug]: wrap generateStaticParams in try/catch
- sitemap: wrap bank/card fetches in try/catch, degrade gracefully

Category pages (the/, the-shopee/, etc.) intentionally hard-fail so
build stops with the new clear error message instead of SyntaxError.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
API server ALLOWED_ORIGINS check requires Origin header.
Server-side fetch() sends none → 403. Hardcode openwallet.vn.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap MCP init and streamText in try/catch with console.error
- Return JSON 500 response on fatal error instead of unhandled throw
- Add onError to useChatRuntime: logs to console + shows red banner in UI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…auses stream death

Tool choice conflict: SDK sets tool_choice=none after step 1, model ignores it, Groq
rejects → stream dies with no answer. Removed tools + stopWhen until a compatible
model or workaround is found.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migrate 8 test cases from inline array to evals/cases/*.json
- Add LLM judge (Groq json_object mode) with score + reasoning
- Write JSONL per run, push to openwalletvn/evals via GitHub API
- Add evals/server.ts (port 3006, trigger endpoint)
- Add evals/ui/ — Vite+React UI with RunList, RunDetail, PromptCompare
- Add evals/wrangler.toml for Cloudflare Pages deploy
- Add .github/workflows/eval-run.yml and evals-site.yml
- pnpm evals → single port 3005, /server proxy to backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix CWD bug in server.ts (parent of web/ → web/)
- Add SSE streaming: POST trigger returns runId, GET stream/:runId
  pipes stdout in real-time via EventEmitter fan-out
- Add triggered_by (ui/cli/ci) and system_prompt fields to EvalResult
- UI: two-column layout, dark terminal progress panel, card-based
  run list with tag summary and trigger badge, run detail with full
  system prompt + expandable AI responses + judge/rule disagreement flag
- Rewrite 13 eval cases with realistic Vietnamese queries:
  shopee+supermarket cashback, no annual fee, travel abroad,
  installment 0%, hallucination guards for fabricated rates,
  out-of-scope: gold/stocks/real estate
- Add .claude/docs/evals.md maintenance documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Single case selector: dropdown grouped by tag, runs just one case
- Push to GitHub checkbox: skip polluting results with WIP runs
- Delete run: button per card proxies GitHub Contents API delete
- Re-run failures: button appears when selected run has failing cases
- Live progress: SSE streaming with current case name + N/M counter
- System prompt per run: expandable with GitHub blob link at that SHA
- Trigger source tracking: ui/cli/ci badge on each run card
- Score trend arrow vs previous run, disagreement flag (rule vs judge)
- Copy button on AI response, inline re-run per test case card
- Relative timestamps with absolute on hover, descriptive button labels

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…X features

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…p in footer

- Remove RCGV and Perxel mentions from ve-openwallet and chinh-sach-bao-mat
- Remove "chia sẻ miễn phí" claim (MCP/Chat will be paid features)
- Generalize tool-specific names in legal pages (future-proof)
- Create /lien-he contact page, consolidate all email refs there
- Add Liên hệ link to footer categories
- Mark Wallet App disabled in footer; Wallet Chat/MCP keep landing page links
- Replace WalletAI reference with Wallet Chat in dieu-khoan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(evals): eval system with LLM judge, JSONL push, and React UI
content: remove RCGV/Perxel, add contact page, footer cleanup
…eview

All WIP feature CTAs (MCP, Wallet Chat) now route to /lien-he instead of
direct mailto links. Contact page replaced with interactive ContactForm:
topic picker, mail preview UI, single mailto button as the one exit point.

Also fixes h3 Tailwind typography override in wallet-chat and tightens
layout.md to ban Tailwind size/weight utilities on heading elements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add .text-link to typography.css as single source for prose link style
- Replace inline [&_a]: chains and hardcoded colors across all marketing pages
- Document rule in layout.md: multi-property patterns belong in typography.css
- Rewrite MCP page: why-we-built framing, remove beta key/config sections,
  add inspector.openwallet.vn and wallet-chat references
- Fix incorrect origin story (card directory, not comparison app)
Rewrote changelog.mdx with 12 entries covering Feb 16 to May 24, telling
the full build story for both end users and developer/AI engineer audience.
Added .claude/docs/changelog.md with purpose, rules, format guide and LLM
verification checklist. Updated CLAUDE.md to reference the new doc.

Also includes local font migration for Cal Sans (latin + latin-ext +
vietnamese subsets) to avoid Google Fonts dependency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chat feature not ready for public. Remove ChatToggleButton from
header (desktop + mobile). Block /app from crawlers via robots.txt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
next/font/local does not support unicodeRange per-src. Drop range hints — all three Cal Sans subset files still loaded.
Fetches live version from /health endpoint (ISR 1h) so the
MCP page shows the real version instead of a hardcoded badge.
Self-contained, zero props — reusable anywhere in the app.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwallet Ready Ready Preview, Comment May 24, 2026 6:48am

@phucbm phucbm merged commit c407cf9 into main May 24, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant