Skip to content

Commit 8d18d94

Browse files
authored
feat: preview → main (v0.2.0)
feat: preview → main (v0.2.0)
2 parents 9de6e84 + f694968 commit 8d18d94

208 files changed

Lines changed: 6173 additions & 5321 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CLAUDE.md

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ When asked to "edit local api repo" → work in `../api`. "edit local mcp repo"
2525
- Storybook: `pnpm storybook`http://localhost:4000
2626

2727
## Core principles
28-
1. **Vercel deployment** SSR/SSG via Vercel. Dynamic routes allowed. Data fetched at build time where possible.
29-
2. **Local-first** wallet data in IndexedDB via Dexie.js. No server accounts.
30-
3. **Vietnamese-first** UI/content targets Vietnamese users. Technical terms stay English.
31-
4. **SEO-first** every public page has JSON-LD, OG metadata, sitemap entries.
28+
1. **Vercel deployment** - SSR/SSG via Vercel. Dynamic routes allowed. Data fetched at build time where possible.
29+
2. **Local-first** - wallet data in IndexedDB via Dexie.js. No server accounts.
30+
3. **Vietnamese-first** - UI/content targets Vietnamese users. Technical terms stay English.
31+
4. **SEO-first** - every public page has JSON-LD, OG metadata, sitemap entries.
3232

3333
## ISR revalidation (required for all marketing pages)
3434

@@ -38,7 +38,7 @@ Every `app/(marketing)/**/page.tsx` that calls any API fetch **must** export:
3838
export const revalidate = 3600;
3939
```
4040

41-
Without this, pages are pure dynamic SSR every request hits the API live. On Vercel, a slow or cold API response causes a function timeout → error page. With `revalidate`, Vercel pre-renders at build time and serves cached HTML, revalidating in the background. Reload-fixes-it is the symptom of a missing `revalidate`.
41+
Without this, pages are pure dynamic SSR - every request hits the API live. On Vercel, a slow or cold API response causes a function timeout → error page. With `revalidate`, Vercel pre-renders at build time and serves cached HTML, revalidating in the background. Reload-fixes-it is the symptom of a missing `revalidate`.
4242

4343
## Breadcrumbs
4444

@@ -65,8 +65,8 @@ Pages currently missing breadcrumbs (add when touching): `dieu-khoan`, `docs`, `
6565
See `@.claude/docs/layout.md` for container conventions and CSS/typography rules.
6666

6767
## API authentication
68-
- Always use `apiFetch()` from `lib/api.ts` auto-injects `X-OpenWallet-Key` header.
69-
- Never use `NEXT_PUBLIC_` prefix for `OPENWALLET_API_KEY` server-only build secret.
68+
- Always use `apiFetch()` from `lib/api.ts` - auto-injects `X-OpenWallet-Key` header.
69+
- Never use `NEXT_PUBLIC_` prefix for `OPENWALLET_API_KEY` - server-only build secret.
7070
- Never use raw `fetch()` for API calls. Pass only path: `apiFetch('/api/v1/cards')`.
7171

7272
## Blog content rules
@@ -76,44 +76,44 @@ See `@.claude/docs/layout.md` for container conventions and CSS/typography rules
7676
- **Images**: `/public/images/posts/<slug>/<filename>.webp`
7777

7878
## Writing rules
79-
- **No em dashes ():** Never use em dashes in any page content or metadata. Replace with a comma, colon, parentheses, or restructure the sentence. Em dashes are a visible AI writing signal.
79+
- **No em dashes (-):** Never use em dashes in any page content or metadata. Replace with a comma, colon, parentheses, or restructure the sentence. Em dashes are a visible AI writing signal.
8080
- **Tone for public pages:** Professional and honest, not corporate or casual. Use "chúng tôi" consistently. Avoid overly informal phrasing.
8181

8282
## Feature gate status
8383

84-
### Wallet app (`/app/*`) FROZEN
84+
### Wallet app (`/app/*`) - FROZEN
8585
- Routes return 404 unless `WALLET_ENABLED=true` env var is set.
8686
- Gate is in `app/app/layout.tsx` (server component) → calls `notFound()`.
8787
- Shell code lives in `app/app/app-shell.tsx` (client component, untouched).
88-
- `WalletNavButton` component exists but is not used anywhere do not add it to nav.
88+
- `WalletNavButton` component exists but is not used anywhere - do not add it to nav.
8989
- Do not delete wallet code. May revive later. ROI currently too low vs competitors.
9090
- `public/robots.txt` has `Disallow: /app`. Keep this.
9191

9292
### Chat (`/chat`, `openwallet-chat`)
93-
- **Chat button hidden** `ChatToggleButton` removed from `components/layout/header.tsx` (both desktop + mobile). Do not re-add unless explicitly requested.
93+
- **Chat button hidden** - `ChatToggleButton` removed from `components/layout/header.tsx` (both desktop + mobile). Do not re-add unless explicitly requested.
9494
- Not ready for public release. Needs evals + system prompt iteration first.
95-
- See `evals/` folder for eval harness.
95+
- **Full chat docs (arch, logging, Langfuse, evals, dev plan):** `.claude/docs/chat.md`
9696

9797
### `so-sanh-404-redirect`
9898
- Active. Handles legacy `/card-battle/X-vs-Y` URLs → redirects to `/card-battle?compare=X,Y`.
9999
- Lives in `components/layout/so-sanh-404-redirect.tsx`, used in `app/not-found.tsx`. Do not remove.
100100

101101
## Card ranking
102102

103-
**Architecture SSOT: `../api/.claude/docs/card-recommendation-architecture.md`** authoritative doc for intent model, intent groups, ranking rules, and data quality requirements. Read this before touching any ranking or recommendation code.
103+
**Architecture SSOT: `../api/.claude/docs/card-recommendation-architecture.md`** - authoritative doc for intent model, intent groups, ranking rules, and data quality requirements. Read this before touching any ranking or recommendation code.
104104

105105
**Ranking logic lives in the API repo.** The web repo only consumes results:
106-
- `lib/cashback-calc.ts` cashback estimation (mirrors API logic for display)
107-
- `lib/card-ranker.ts` sort order only (cashback desc → annual_fee asc → network_popularity asc)
108-
- `components/marketing/card-ranking-table.tsx` UI with spend selector
109-
- `components/marketing/recommendation-finder.tsx` macro→micro→atomic intent selector + `POST /api/ranking` proxy
106+
- `lib/cashback-calc.ts` - cashback estimation (mirrors API logic for display)
107+
- `lib/card-ranker.ts` - sort order only (cashback desc → annual_fee asc → network_popularity asc)
108+
- `components/marketing/card-ranking-table.tsx` - UI with spend selector
109+
- `components/marketing/recommendation-finder.tsx` - macro→micro→atomic intent selector + `POST /api/ranking` proxy
110110

111111
## Meta-rules
112112
1. Before any task: check `.claude/commands/` for a relevant command file.
113113
2. After creating a command: add it to the commands table below.
114114
3. After non-trivial task: check if `.claude/docs/learnings/` needs a new note.
115-
4. **Question-only mode:** If the message starts with `question:` or `answer me:`, only answer do NOT edit any files or run any commands.
116-
5. **Changelog:** After card data or feature changes, add entry to `content/changelog.mdx`. Use `/add-changelog`. Card/feature scope only no UI or page structure changes. See `.claude/docs/changelog.md` for purpose, rules, tone, and format guide.
115+
4. **Question-only mode:** If the message starts with `question:` or `answer me:`, only answer - do NOT edit any files or run any commands.
116+
5. **Changelog:** After card data or feature changes, add entry to `content/changelog.mdx`. Use `/add-changelog`. Card/feature scope only - no UI or page structure changes. See `.claude/docs/changelog.md` for purpose, rules, tone, and format guide.
117117

118118
## Custom commands
119119

@@ -128,8 +128,10 @@ See `@.claude/docs/layout.md` for container conventions and CSS/typography rules
128128
| `/add-ow-ui` | `commands/add-ow-ui.md` | Move component to `ow-ui/`, rename to `Ow*`, create story, report usages |
129129
| `/commit-all` | `commands/commit-all.md` | Stage + commit all changes in logical groups with conventional commit messages |
130130
| `/sync-api-types` | `commands/sync-api-types.md` | Regenerate types from API schema, diff changes, scan codebase, suggest updates |
131+
| `/edit-system-prompt` | `commands/edit-system-prompt.md` | Edit Owie's system prompt, enforce invariants, push to Langfuse |
131132

132133
@.claude/docs/architecture.md
133134
@.claude/docs/layout.md
134135
@.claude/docs/DESIGN.md
135136
@.claude/docs/design-system.md
137+
@.claude/docs/chat.md

.claude/commands/add-changelog.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Only `title` and `date` are required.
2121

2222
## Content
2323

24-
Write the body in Markdown below the frontmatter. Keep it concise 1–3 short paragraphs.
24+
Write the body in Markdown below the frontmatter. Keep it concise - 1–3 short paragraphs.
2525

2626
- Describe what changed and why
2727
- Link to relevant pages if applicable

.claude/commands/add-ow-ui.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ If user passes a name (e.g. `chip`), search `components/` for the file. Multiple
2424

2525
- Move file to `components/ow-ui/`
2626
- Rename file to `ow-<name>.tsx` if not already (e.g. `chip.tsx``ow-chip.tsx`)
27-
- Rename the exported function to `Ow<Name>` (PascalCase with `Ow` prefix) e.g. `Chip``OwChip`
27+
- Rename the exported function to `Ow<Name>` (PascalCase with `Ow` prefix) - e.g. `Chip``OwChip`
2828
- Add `ow-<name>` CSS class to the wrapper element's `className` if not already present (per component conventions)
2929
- Update all imports across the codebase to the new path + new export name
3030

@@ -42,8 +42,8 @@ Before writing the component, apply **design-system.md §6**:
4242
- Interactive styles: `[&:is(button,a)]:cursor-pointer [&:is(button,a)]:hover:bg-primary/10`
4343

4444
**B) Component owns its internal content** (logo + text, icon + label, avatar + name) → use `href` prop:
45-
- `href?: string` renders `<Link href>` when provided, `<span>` otherwise
46-
- Do NOT use `asChild` Slot requires single child and will throw when component renders multiple internal elements
45+
- `href?: string` - renders `<Link href>` when provided, `<span>` otherwise
46+
- Do NOT use `asChild` - Slot requires single child and will throw when component renders multiple internal elements
4747

4848
### 4. Props discipline
4949

@@ -57,7 +57,7 @@ Before writing the component, apply **design-system.md §6**:
5757

5858
Create `components/ow-ui/ow-<name>.stories.tsx` following the structure and rules in `commands/create-story.md`.
5959

60-
Title: `'OW UI/OwName'` use `'Card UI/OwName'` for card-related components.
60+
Title: `'OW UI/OwName'` - use `'Card UI/OwName'` for card-related components.
6161

6262
### 6. Report usages
6363

.claude/commands/commit-all.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ List every modified, deleted, and untracked file. Do NOT stage anything yet.
1616

1717
### 2. Group files logically
1818

19-
Analyze the files and group them by **semantic intent** what changed together for the same reason. Each group becomes one commit.
19+
Analyze the files and group them by **semantic intent** - what changed together for the same reason. Each group becomes one commit.
2020

2121
Grouping heuristics (in priority order):
2222
- Same feature/component → one commit
@@ -38,9 +38,9 @@ Order commits so each builds on the previous. General order:
3838

3939
For each group, in order:
4040

41-
1. `git add <specific files>` never `git add .` or `git add -A`
41+
1. `git add <specific files>` - never `git add .` or `git add -A`
4242
2. Craft commit message:
43-
- Subject: `<type>(<scope>): <what changed>` ≤50 chars, imperative, lowercase after colon
43+
- Subject: `<type>(<scope>): <what changed>` - ≤50 chars, imperative, lowercase after colon
4444
- Body: only when the **why** is non-obvious. One short line max.
4545
- Types: `feat` `fix` `refactor` `style` `chore` `docs` `test` `perf` `build` `ci`
4646
- Scope: filename stem, component name, or area (e.g. `ow-logo`, `storybook`, `header`)
@@ -56,7 +56,7 @@ After all commits, output a table:
5656

5757
## Rules
5858

59-
- Never use `git add .` or `git add -A` add files explicitly by name
59+
- Never use `git add .` or `git add -A` - add files explicitly by name
6060
- Never skip `--no-verify` unless user explicitly asks
6161
- Never commit files that look like secrets (`.env`, credentials)
6262
- If a file's intent is ambiguous, ask before grouping

.claude/commands/create-story.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ Create a `.stories.tsx` file for a component. The user will pass a component pat
44

55
## Steps
66

7-
1. **Find the component** if user passes just a name (e.g. `card-image`), search `components/` for the file. If multiple matches, ask which one.
7+
1. **Find the component** - if user passes just a name (e.g. `card-image`), search `components/` for the file. If multiple matches, ask which one.
88

9-
2. **Read the component** understand its props interface, variants, required vs optional props.
9+
2. **Read the component** - understand its props interface, variants, required vs optional props.
1010

11-
3. **Generate the story file** create `<same-dir>/<filename>.stories.tsx` following the structure below.
11+
3. **Generate the story file** - create `<same-dir>/<filename>.stories.tsx` following the structure below.
1212

1313
## Story structure
1414

@@ -72,7 +72,7 @@ export const VariantB: Story = {
7272

7373
| Situation | Use |
7474
|-----------|-----|
75-
| Props are boolean / enum / string | `args` Controls work |
75+
| Props are boolean / enum / string | `args` - Controls work |
7676
| Props are objects, ReactNode, or asChild pattern | `render: () =>` |
7777
| Overview story (always) | `render: () =>` |
7878
| Component has no props | `render: () => <ComponentName />` |
@@ -97,13 +97,13 @@ Map folder → Storybook group:
9797

9898
## Story rules
9999

100-
- `Overview` is always the **first export** autodocs renders it as the Primary hero at top
101-
- `includePrimary={false}` is set globally in `.storybook/preview.tsx` no double render
100+
- `Overview` is always the **first export** - autodocs renders it as the Primary hero at top
101+
- `includePrimary={false}` is set globally in `.storybook/preview.tsx` - no double render
102102
- Use realistic Vietnamese data in args (card names, bank names, Vietnamese text)
103103
- Cover key visual states: default, empty/null props, active/inactive, loading (if shimmer exists)
104104
- If component requires API data or complex objects, import type from the relevant `lib/api-types.ts` or mock a minimal object
105-
- Never mock Next.js router `@storybook/nextjs-vite` handles it automatically
106-
- Never mock `apiFetch` if component fetches data, note it in a comment and use a static prop version if available
105+
- Never mock Next.js router - `@storybook/nextjs-vite` handles it automatically
106+
- Never mock `apiFetch` - if component fetches data, note it in a comment and use a static prop version if available
107107

108108
## After creating
109109

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Edit Owie's system prompt
2+
3+
Edit `lib/chat/system-prompt.ts` then push to Langfuse as new production version.
4+
5+
## Invariants - NEVER change without explicit user instruction
6+
7+
These rules are load-bearing. Breaking them silently breaks product behavior or trust:
8+
9+
1. **Owie persona** - name is "Owie", role is OpenWallet.vn card advisor. Warm + professional tone.
10+
2. **CT address** - always address user as "CT". Keep explanation when asked.
11+
3. **Vietnamese default** - respond in Vietnamese unless user writes in another language.
12+
4. **Tool-before-answer** - NEVER fabricate cashback rates, fees, or interest rates. Always call a tool first.
13+
5. **Scope refusal template** - use exact Vietnamese refusal template for off-topic questions. Do not soften or remove.
14+
6. **Card links** - only link cards via `/the/<slug>` using tool-retrieved slugs. Never invent slugs.
15+
7. **No `#` headings** - response format rule. Only `##` and below.
16+
8. **Tool routing logic (Cat A/B/C)** - the tool call sequences are precise. Do not collapse or reorder steps.
17+
18+
## Steps
19+
20+
### 1. Understand the change request
21+
- What category: persona, scope, tool rules, response format, language?
22+
- Does it conflict with any invariant above? If yes, ask for explicit confirmation before proceeding.
23+
24+
### 2. Edit the prompt
25+
File: `lib/chat/system-prompt.ts``SYSTEM_PROMPT` const.
26+
27+
Prompt writing rules:
28+
- **Persona first** - name/role/tone at top
29+
- **Positive over negative** - "do X" beats "don't do Y" where possible
30+
- **Concrete over vague** - add examples for new rules, not just descriptions
31+
- **No contradictions** - new rules must not conflict with existing ones; later rule wins but inconsistently
32+
- **Tool rules last** - LLMs read top-heavy; scope/persona matters most at top
33+
34+
### 3. Sync the fallback
35+
`buildSystemPrompt()` in same file uses `SYSTEM_PROMPT` as base - no separate edit needed. But if `pageContext` injection logic changes, update the `if (pageContext.type === ...)` blocks too.
36+
37+
### 4. Check evals coverage
38+
- Does the change affect any of the 13 eval test cases in `evals/`?
39+
- If new behavior added: note it needs a new eval case (don't write it now unless asked).
40+
41+
### 5. Push to Langfuse
42+
**STOP - do NOT run the push command automatically. Show the diff and ask for confirmation first.**
43+
44+
After editing, show the user what changed, then ask: "Push to Langfuse as version N?"
45+
46+
Only run after explicit user approval:
47+
```bash
48+
pnpm push:prompt
49+
```
50+
- Pushes `SYSTEM_PROMPT` const to Langfuse as new `production` version
51+
- Live within 60s (cache TTL in `fetchSystemPrompt()`)
52+
- Confirm output shows `✓ Pushed version N`
53+
- **Note:** `STATIC_LISTS` (persona/merchant lists injected by `buildSystemPrompt()`) are NOT pushed - they are always injected at runtime from local constants. Langfuse only stores the base prompt text.
54+
55+
### 6. Verify
56+
- Check Langfuse UI or wait 60s and test via `/chat`
57+
- Hardcoded fallback in `lib/chat/system-prompt.ts` is the source of truth for the push script - they stay in sync automatically
58+
59+
## What NOT to change via this command
60+
- `buildSystemPrompt()` function signature or `pageContext` types - that's a code change, not a prompt change
61+
- `push-prompt.ts` script - separate concern
62+
- Eval test cases - use eval workflow instead

.claude/commands/generate-images.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ Write prompts in **English** for Gemini image generation.
4242
- Avoid tables inside images (use only when data density requires it, non-cover only)
4343

4444
### Bank brand colors
45-
- **Vietcombank** `#006633` (dark green)
46-
- **BIDV** `#003087` (dark blue) + red accent
47-
- **VietinBank** `#c8102e` (red) + navy
48-
- **Techcombank** `#e30613` (bright red)
49-
- **MB Bank** `#004f9f` (teal/navy)
50-
- **VPBank** `#00a650` (green) + orange (issues Amex)
51-
- **Shinhan Bank** `#003b8e` (blue, also issues Amex)
52-
- **ACB** `#0066b2` (blue)
53-
- **TPBank** `#6b21a8` (purple)
45+
- **Vietcombank** - `#006633` (dark green)
46+
- **BIDV** - `#003087` (dark blue) + red accent
47+
- **VietinBank** - `#c8102e` (red) + navy
48+
- **Techcombank** - `#e30613` (bright red)
49+
- **MB Bank** - `#004f9f` (teal/navy)
50+
- **VPBank** - `#00a650` (green) + orange (issues Amex)
51+
- **Shinhan Bank** - `#003b8e` (blue, also issues Amex)
52+
- **ACB** - `#0066b2` (blue)
53+
- **TPBank** - `#6b21a8` (purple)
5454

5555
### General prompt rules
5656
- Specify visual style: "flat illustration", "minimalist", "isometric"

0 commit comments

Comments
 (0)