`/
- `| `/` | ` HTML.
+ in `ReactMarkdown`. Tables now render as proper ``/``/``.
- **Installed `@tailwindcss/typography@0.5.20`** — `@plugin "@tailwindcss/typography"`
- directive in `globals.css` (Tailwind v4 approach). Generates `prose` CSS including
- `prose table` styles, `prose-headings:`, `prose-code:` etc.
-- **Added custom dark-theme table CSS** in `globals.css` for `prose table`,
- `prose th`, `prose td` with dark-mode-appropriate colors.
-- **Added `overflow-x-auto` wrapper** in `Markdown.tsx` for responsive table scrolling.
+ directive in `globals.css`. Generates `prose` CSS including table styling.
-### Dynamic Custom Fields System
-Replaced hardcoded CTF fields (event/challenge/category/difficulty/points) with
-a **fully content-driven** system:
+### Dynamic Custom Fields (fully dynamic, user-controlled)
-- **`documents.extra_fields` JSONB column** — stores arbitrary key-value metadata
- per document (migration `0003_document_extra_fields.sql`).
-- **`parseFile`** — any frontmatter key not in the standard set (`title`,
- `type`, `section`, `status`, `author`, `tags`, `created_at`, `updated_at`) is
- automatically extracted as an `extraField` → stored in DB + rendered as badge.
-- **`stringifyFile`** — writes `extraFields` back to YAML frontmatter for
- round-trip stability (parse → stringify → parse yields same result).
-- **`DocForm`** — new "+ Add field" UI lets content creators add ANY metadata
- key at create/edit time. Auto-labels and auto-styles common patterns:
- - `difficulty` → colored badge (easy:green, medium:yellow, hard:red)
- - `points` → purple badge with "pts" suffix
- - `event` → purple badge with trophy icon
- - Any other key → labeled badge (e.g. "Category: pwn")
-- **API routes** (`POST /api/docs`, `PUT /api/docs/[...slug]`) — `splitPayload()`
- separates standard CRUD fields from custom fields, sends custom fields as
- `extraFields` to `createDocument`/`updateDocument`.
-- **`toMeta`** (search → DB mapping) — spreads `extraFields` from DB row into
- `DocumentMeta`, making them available to the UI.
+The system is now **100% dynamic** — no field names or patterns are hardcoded.
+The content creator adds **any** frontmatter key with **any** value type, and
+the system auto-discovers + auto-styles:
+
+1. **DB layer** — `documents.extra_fields` JSONB column (migration
+ `0003_document_extra_fields.sql`). Stores any key-value pairs as native JSONB
+ (numbers, booleans, strings, arrays, objects) — types preserved.
+
+2. **Parser** (`parseFile`) — any frontmatter key not in the standard set
+ (`title`, `type`, `section`, `status`, `author`, `tags`, `created_at`,
+ `updated_at`) is extracted as an `extraField` → returned in `DocumentMeta.extraFields`.
+
+3. **Parser** (`stringifyFile`) — writes `extraFields` back to YAML frontmatter
+ for round-trip stability (parse → stringify → parse yields same result).
+
+4. **Core** — `createDocument`/`updateDocument` accept `extraFields?: Record`,
+ merge with existing (update), pass through to DB + file.
+
+5. **`toMeta`** (DB → meta) — spreads `extra_fields` JSONB from DB row into
+ `DocumentMeta`, making custom fields available to the UI.
+
+6. **API routes** — `splitPayload()` separates standard CRUD fields from
+ custom fields. Custom fields passed through with preserved types (no
+ string coercion).
+
+7. **`DocForm`** — "+ Add Field" UI lets content creators add ANY key-value pair.
+ Help text is generic (no hardcoded field-name examples).
+
+8. **`CustomFieldBadges`** — auto-styles based on **VALUE TYPE + VALUE CONTENT**,
+ not key name:
+ | Value type | Badge style | Example |
+ |---|---|---|
+ | **Number** | purple, value shown | `5` → purple `5` |
+ | **Boolean** | green (true) / red (no) | `true` → green `Yes` |
+ | **Array** | purple, joined | `["a","b"]` → purple `a, b` |
+ | **Object** | gray, truncated JSON | `{timeout:30}` → gray `{"timeout":30}` |
+ | **String: difficulty-like** | color-coded | `easy`→green, `medium`→yellow, `hard`→red |
+ | **String: event-like** (contains ctf/def con/hack) | purple | `DEF CON CTF Quals 2024` |
+ | **String: points-like** (`100 pts`) | purple | `100 pts` |
+ | **String: category-like** (pwn/web/crypto) | orange | `Pwn` |
+ | **String: status-like** (solved/wip/pending) | color-coded | `Solved`→green |
+ | **Any other string** | default gray | `linux` → gray `linux` |
+
+ The same value `"medium"` gets the same yellow badge whether the key is
+ `difficulty`, `complexity`, `tier`, or `level`. Content creators control
+ the appearance via values, not by using specific key names.
### CTF Writeup Template + Sample
-- **`content/writeups/ctf/template/writeup-template.md`** — standardized template
- with: Challenge Info table, Initial Recon, Approach, Step-by-Step Solve,
- Flag, Summary sections.
+- **`content/writeups/ctf/template/writeup-template.md`** — template with:
+ Challenge Info table, Initial Recon, Approach, Step-by-Step Solve, Flag, Summary.
- **`content/writeups/ctf/defcon-quals-2024/pwn-100-ret2win-alignment.md`** —
- sample writeup demonstrating the template with frontmatter fields:
- `event: DEF CON CTF Quals 2024`, `challenge: pwn-100`, `category: pwn`,
- `difficulty: easy`, `points: 100`.
+ sample writeup demonstrating the template with arbitrary frontmatter fields.
### Deploy workflow fix
-- Added `db:push` step to deploy workflow for schema migrations (with `yes |`
- to auto-accept non-interactive confirmation prompts).
-- Fixed `bun run index` → `bun run scripts/indexer.ts` (explicit path to avoid
- resolution ambiguity in SSH deploy context).
+- Added `bun run scripts/indexer.ts` (explicit path) to deploy workflow instead
+ of `bun run index` (resolved to wrong package.json in SSH context).
+- Removed `db:push` from deploy (column applied manually via ALTER TABLE; dribble
+ push is interactive/non-blocking). DB migration `0003_document_extra_fields.sql`
+ is tracked for future reference.
### Verification done (real, against live services)
-- All endpoints return 200: `/`, `/docs`, `/docs/mcp/streamable-http`,
- `/notes/postgres/full-text-search`, `/writeups/ctf/defcon-quals-2024/pwn-100-...`,
- `/writeups/ctf/template/writeup-template`, `/search`, `/login`, `/create`,
- `/dashboard`, `/metrics`.
-- CTF writeup page renders: TOC (all h2 headings auto-linked), Challenge Info
- table (thead + tbody + cells), code blocks, dynamic badges (Event/purple,
- Category/default, Challenge/default, Difficulty/green, Points/purple).
-- Postgres FTS doc renders table properly (GFM + typography CSS working).
-- `bun run test` → 7 task groups pass. `turbo run typecheck` → green across 14 packages.
-- DB `extra_fields` column verified: `ALTER TABLE documents ADD COLUMN
- extra_fields jsonb DEFAULT '{}'::jsonb NOT NULL`.
+
+#### Endpoints
+| Path | Status |
+|------|--------|
+| `/` | ✅ 200 |
+| `/docs` | ✅ 200 |
+| `/docs/mcp/streamable-http` | ✅ 200 |
+| `/notes/postgres/full-text-search` | ✅ 200 (table renders via GFM) |
+| `/writeups/ctf/defcon-quals-2024/pwn-100-ret2win-alignment` | ✅ 200 (dynamic badges + table + TOC) |
+| `/writeups/ctf/template/writeup-template` | ✅ 200 |
+| `/search` | ✅ 200 |
+| `/login`, `/create`, `/dashboard` | ✅ 200 |
+
+#### Dynamic badges verified
+Created a test doc with fields of ALL value types and verified rendering:
+| Field | Type | Rendered badge | Color |
+|-------|------|----------------|-------|
+| `os` | string "linux" | `linux` | gray (default) |
+| `priority` | number 5 | `5` | **purple** (numeric) |
+| `resolved` | boolean true | `Yes` | **green** (boolean) |
+| `complexity` | string "medium" | `medium` | **yellow** (difficulty value match) |
+| `team_members` | array ["alice","bob"] | `alice, bob` | **purple** (array) |
+| `config` | object {timeout:30,retry:3} | `{"retry":3,"timeout":30}` | gray (truncated JSON) |
+
+#### Tests
+- `bun run test` → 7 task groups, all pass
+- `turbo run typecheck` → green across all packages
+- DB `extra_fields` column verified: `ALTER TABLE documents ADD COLUMN extra_fields jsonb DEFAULT '{}'::jsonb NOT NULL`
+
+### Files changed
+```
+new: packages/db/drizzle/0003_document_extra_fields.sql # migration
+mod: packages/types/src/index.ts # extraFields: Record
+mod: packages/parser/src/index.ts # parseFile extracts; stringifyFile writes
+mod: packages/core/src/document.service.ts # accept extraFields
+mod: packages/search/src/index.ts # toMeta spreads extra_fields from DB
+mod: apps/web/app/[section]/[...slug]/page.tsx # CustomFieldBadges value-based styling
+mod: apps/web/app/components/DocForm.tsx # generic help text (no hardcode)
+mod: apps/web/app/api/docs/route.ts # splitPayload preserves types
+mod: apps/web/app/api/docs/[...slug]/route.ts # splitPayload preserves types
+mod: .github/workflows/deploy.yml # explicit indexer path
+new: content/writeups/ctf/template/writeup-template.md # template
+new: content/writeups/ctf/defcon-quals-2024/pwn-100-ret2win-alignment.md # sample
+```
diff --git a/content/notes/test/dynamic-field-types.md b/content/notes/test/dynamic-field-types.md
deleted file mode 100644
index c9206fd..0000000
--- a/content/notes/test/dynamic-field-types.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: "Dynamic Field Types Test"
-type: "note"
-section: "notes"
-status: "published"
-author: "asep"
-tags: ["test"]
-path: "notes/test/dynamic-field-types.md"
-created_at: "2026-08-20T17:45:07.294Z"
-updated_at: "2026-08-20T17:45:07.294Z"
-priority: "5"
-resolved: "true"
-os: "linux"
-complexity: "medium"
-tags_list: "[\"alpha\",\"beta\"]"
-metadata: "{\"owner\":\"team-a\",\"tier\":2}"
----
-# Dynamic Field Types Test
-Testing various field value types.
\ No newline at end of file
|