diff --git a/.sisyphus/notepads/dm-dashboard-ai/learnings.md b/.sisyphus/notepads/dm-dashboard-ai/learnings.md
index fe6b0f3..69fbc0a 100644
--- a/.sisyphus/notepads/dm-dashboard-ai/learnings.md
+++ b/.sisyphus/notepads/dm-dashboard-ai/learnings.md
@@ -172,8 +172,8 @@ Task: Generate and apply Drizzle migration for updated DB schema (Task 2 complet
};
});
```
- - The mock DB then implements a simple `evaluateCondition()` recursive evaluator that checks `type === "eq"` against item properties (converting snake_case column names to camelCase).
- - This allows the mock to correctly filter by `userId` and `id`, making cross-user access tests reliable.
+ - The mock DB then implements a simple `evaluateCondition()` recursive evaluator that checks `type === "eq"` against item properties (converting snake_case column names to camelCase).
+ - This allows the mock to correctly filter by `userId` and `id`, making cross-user access tests reliable.
6. **Test coverage**
- `tests/notes-api.test.ts`: 19 tests covering auth 401s, CORS OPTIONS, GET list, POST create, POST validation errors, PUT update, PUT 404 (missing + cross-user), DELETE remove, DELETE 404 (missing + cross-user), missing/invalid id params.
@@ -382,3 +382,332 @@ Task: Generate and apply Drizzle migration for updated DB schema (Task 2 complet
- `npx tsc --noEmit`: 0 errors in project code (1 pre-existing parse error in `tests/history-api.test.ts`)
- `npm run build`: fails due to pre-existing auth env validation at build-time (`JWT_SECRET` missing) — unrelated to this change
+---
+
+# NPC Result Card Component — Planning Findings
+
+## Date: 2026-05-15
+
+### Existing Patterns Analyzed
+
+**DmCard.astro** (`src/components/dm/DmCard.astro`):
+- Base classes: `rounded-xl bg-[var(--bg-card)] border border-[var(--border-gold-strong)] shadow-lg shadow-black/20 hover:border-[var(--border-gold)] transition-all duration-[var(--transition-base)]`
+- Padding variants: `none` (""), `sm` (p-4), `md` (p-5), `lg` (p-6)
+- Props: `class?: string`, `padding?: "none" | "sm" | "md" | "lg"`, `dataTestid?: string`
+- Use `padding="lg"` for the NPC card to match the spacious feel of DM cards.
+
+**DmButton.astro** (`src/components/dm/DmButton.astro`):
+- Variants: `primary` (accent bg, white text), `secondary` (card bg + border), `ghost`
+- Sizes: `sm` (px-4 py-1.5 text-sm rounded-lg), `md` (px-6 py-2.5 text-base rounded-xl), `lg`
+- For action buttons, use `variant="secondary" size="sm"` for "Copy JSON" and `variant="primary" size="sm"` for "Regenerate".
+
+**DM Theme CSS (`src/styles/dm-theme.css`)**:
+- Backgrounds: `--bg-primary: #16120e`, `--bg-card: #221e18`, `--bg-secondary: #1e1912`
+- Text: `--text-primary: #f4f4f5`, `--text-secondary: #a1a1aa`, `--text-muted: #71717a`, `--text-cream: #e8dcc8`
+- Borders: `--border-color: #3a3428`, `--border-gold: rgba(200,168,75,0.15)`, `--border-gold-strong: rgba(180,150,80,0.1)`
+- Gold accents: `--gold: #c8a84b`, `--gold-light: #d4b76a`, `--gold-dark: #a88a3a`
+
+**⚠️ CRITICAL: Theme Discrepancy — Purple vs Orange**
+- `src/styles/dm-theme.css` sets `--accent: #534AB7` (purple), but AGENTS.md claims DM overrides to `#E87722` (orange).
+- The task mandates: "Orange theme (#E87722), NO purple".
+- **Recommendation**: Update `dm-theme.css` line 3 from `--accent: #534AB7` to `--accent: #E87722` and update `--accent-light`, `--accent-dark`, `--accent-hover` accordingly. This fixes the theme for ALL DM components at once (DmButton primary, focus rings, etc.). Then the NPC card can safely use `var(--accent)`.
+- If modifying the theme CSS is out of scope, hardcode `#E87722` in the NPC card for all accent usage (stat values, story border, tags, buttons).
+
+### NPCResult Schema (`src/lib/ai/types.ts`)
+
+Available fields:
+- `name: string`, `race: string`, `role: string`, `level: number`
+- `hp: number`, `ac: number`, `cr: string`, `speed: string`
+- `appearance: string`, `trait: string`, `motivation: string`, `secret: string`, `history: string`
+
+**Missing fields for tags**: There is **no `tags`, `skills`, or `abilities` array** in `NPCResult`. The requirement asks for "Tags: skills, abilities (pill chips)".
+- **Decision**: Derive pseudo-tags from `race` and `role` (display both as chips), OR omit the tags section entirely since the data model doesn't support it. Recommending the pseudo-tag approach for visual completeness.
+
+### Sanitization Strategy
+
+**Astro auto-escapes server-rendered expressions** — `{npc.name}`, `{npc.appearance}`, etc. are safe by default. No manual escaping needed in the `.astro` template.
+
+**No `innerHTML` usage**: The card can be fully static markup. Interactivity (copy JSON, regenerate) uses `addEventListener` on existing DOM nodes. Copy JSON does `navigator.clipboard.writeText(JSON.stringify(npc))`. Regenerate calls the passed callback.
+
+**No shared `escapeHtml` utility** exists in `src/lib/client/`. It is duplicated in 3 files. For this component, since it's pure Astro template with no dynamic HTML injection, `escapeHtml` is unnecessary. But if future iterations add client-side NPC rendering, consolidate `escapeHtml` into `src/lib/client/escape-html.ts`.
+
+### i18n Gaps
+
+`src/i18n/dm-translations.ts` has **no NPC-specific labels**. Must add:
+```ts
+// NPC labels
+npcName: "Имя",
+npcRace: "Раса",
+npcRole: "Класс",
+npcLevel: "Уровень",
+npcHp: "ХП",
+npcAc: "КЗ",
+npcCr: "ОП",
+npcSpeed: "Скорость",
+npcAppearance: "Внешность",
+npcTrait: "Черта",
+npcMotivation: "Мотивация",
+npcSecret: "Тайна",
+npcHistory: "История",
+npcCopyJson: "Копировать JSON",
+npcCopied: "Скопировано",
+npcRegenerate: "Перегенерировать",
+```
+
+### Clipboard Utility
+
+`src/lib/client/clipboard.ts` exports `CopyFeedback` class for copy → checkmark icon swap animation. Can be used for the "Copy JSON" button if implementing a visual feedback state.
+
+### Recommended Component Structure
+
+```astro
+---
+import DmCard from "./DmCard.astro";
+import DmButton from "./DmButton.astro";
+import { dmTranslations as T } from "@/i18n/dm-translations";
+import type { NPCResult } from "@/lib/ai/types";
+
+export interface Props {
+ npc: NPCResult;
+ onRegenerate?: () => void;
+}
+
+const { npc, onRegenerate } = Astro.props;
+---
+
+
+
+