diff --git a/.trellis/tasks/04-03-list-table-v2-tanstack-pretext/prd.md b/.trellis/tasks/04-03-list-table-v2-tanstack-pretext/prd.md
new file mode 100644
index 0000000..0933b4b
--- /dev/null
+++ b/.trellis/tasks/04-03-list-table-v2-tanstack-pretext/prd.md
@@ -0,0 +1,390 @@
+# Rebuild list-table-v2 with TanStack + pretext
+
+## Goal
+
+Replace `el-table-v2` dependency with a custom implementation using:
+- **@chenglou/pretext** for text measurement (column widths, row heights for plain text)
+- **Custom prefix-sum virtualizer** (pretext-table style, lightweight)
+- **TanStack Table** for headless table logic (optional, depends on complexity)
+
+Keep the existing prop interface of `list-table-v2.vue` so consumers don't need changes.
+
+## What I already know
+
+### Existing Component (`list-table-v2.vue`)
+- Vue 3 + TypeScript component
+- Props: `data`, `columns` (with key, dataKey, name, i18n, type, width, minWidth, fixed, align, slot, dict, timestamp, filesize, cellRenderer, headerCellRenderer), `page`, `height`, `minHeight`, `maxHeight`, `example`, `rowKey`, `selectKey`, `treeProps`, `lazy`, `border`, `timestampFormat`, `rowHeight`, `estimatedRowHeight`, `headerHeight`, `debug`
+- Emits: `query`, `selection-change`, `row-click`, `cell-click`
+- Uses mini hidden table for row height probing
+- Uses `el-auto-resizer` + `el-table-v2`
+- Features: pagination, fixed columns, sorting via slot, dict/timestamp/filesize formatting
+
+### pretext-table (React) Implementation Insights
+- **@chenglou/pretext** two-phase API:
+ - `prepare(text, font)` → cached handle (Canvas measureText for segmentation)
+ - `layout(prepared, maxWidth, lineHeight)` → `{ height, lineCount }` (pure arithmetic, no DOM)
+- **Column width**: Binary search with pretext to find minimum single-line width. Samples rows, measures header + cell text widths, respects minWidth/maxWidth
+- **Row height**: `prepare()` per cell text, `layout()` at column width, takes max cell height + padding
+- **Virtual scrolling**: Custom binary search over prefix-sum offsets (NOT TanStack Virtual)
+- Does NOT use TanStack Table or TanStack Virtual
+
+### Available Packages
+- `@tanstack/vue-table` v8.21.3 - headless Vue 3 table
+- `@tanstack/vue-virtual` v3.13.23 - headless Vue 3 virtualization
+- `@chenglou/pretext` v0.0.3 - text measurement
+
+## Virtualization Approach: Custom Prefix-Sum (pretext-table Style)
+
+Chosen over TanStack Virtual because:
+1. Lightweight — binary search + prefix-sum offsets, no library dependency
+2. Works naturally with pretext — all row heights pre-computed via `layout()`, just math
+3. Full control over how pretext integrates
+
+### Algorithm
+1. Pretext computes ALL row heights upfront (pure arithmetic, no DOM)
+2. Build prefix-sum offset array: `offsets[i] = sum(heights[0..i-1])`
+3. Binary search on scroll to find visible range: O(log n)
+4. Render only visible rows + overscan
+
+### cellRenderer Return Types
+
+**Plain VNode (backward compatible):**
+```typescript
+cellRenderer: ({ data }) => {data}
+```
+
+**Size-hinted VNode (new):**
+```typescript
+cellRenderer: ({ data }) => ({
+ vnode: {data},
+ minHeight: 30, // pixel hint for row height estimation
+ minWidth: 100 // pixel hint for column width estimation (no runtime augmentation)
+})
+```
+
+### Runtime Augmentation for Row Heights
+
+For columns with custom `cellRenderer` (where pretext can't measure):
+1. **Initial**: Use `minHeight` from size-hinted return, or sensible default (44px)
+2. **After mount**: Measure actual DOM height per row using ResizeObserver
+3. **Running average per column**: Maintain `Map`
+4. **Self-adjust**: When average shifts >10%, recompute offsets and re-render
+
+### Column Width Strategy (Pre-computed, No Runtime Augmentation)
+
+Flex parameters computed via pretext-measured sampling:
+
+1. **Sample**: For each column, sample values from data rows
+2. **Measure**: For each sampled value, measure "shrink wrap" width via `walkLineRanges` (finds widest line, handles `\n`)
+3. **Compute statistics**: mean (μ), variance (σ²) of measured widths
+4. **Assign flex parameters**:
+ - **flexBasis**: `μ` (average measured width + padding) — can be overridden per column
+ - **flexGrow**: derived from variance (see formula below) — can be overridden per column
+ - **flexShrink**: derived from variance — can be overridden per column
+ - **minWidth**: `max(μ - 2*σ, 50px)` — can be overridden per column
+ - **maxWidth**: `300px` fixed for now
+
+**Variance-based flex formula** (normalized 0-1 scale):
+```typescript
+const varianceScore = Math.min(1, σ / (μ * 0.5)); // 0 = no variance, 1 = high variance
+const flexGrow = 0.1 + varianceScore * 1.9; // 0.1 (stable) to 2.0 (variable)
+const flexShrink = 0.1 + varianceScore * 1.9; // same scaling
+```
+
+**User overrides**: Column definition can specify `flexGrow`, `flexShrink`, `flexBasis`, `minWidth`, `maxWidth` directly, which takes precedence over computed values.
+
+### Dynamic Wrapping Behavior
+
+- CSS `word-break: break-word` + `white-space: normal` on cells
+- When column width reaches `maxWidth` (300px), text wraps to multiple lines
+- When table can't expand (parent has `overflow-x: hidden`), columns compress (flexShrink applies)
+- For custom renderer columns: uses `minWidth` from size-hinted return or default 80px
+
+User can override via column definition props. No runtime width adjustment.
+
+## Assumptions (to validate)
+- pretext performance is acceptable for large datasets (~1M rows)
+- Custom virtualizer approach is sufficient (no TanStack Virtual needed)
+- Runtime height adjustment for custom renderers is acceptable UX
+
+## Open Questions
+
+All resolved:
+- Fixed columns: **YES** (left/right pinning via `fixed: 'left' | 'right'`)
+- flexGrow/flexShrink: **Data-driven via variance**; average affects flexBasis; manual overrides allowed
+- maxWidth: **300px** fixed
+
+## Requirements
+
+- **Backward compatible Props**: Keep existing prop interface (data, columns, page, height, rowKey, border, etc.)
+- **Config-less behavior**: Table works without any width configuration
+- **Content-aware column widths**: Pretext measures sampled data; variance drives flexGrow/flexShrink, mean drives flexBasis
+- **User overrides**: Column definition can override flexGrow, flexShrink, flexBasis, minWidth, maxWidth
+- **Dynamic wrapping**: maxWidth=300px triggers CSS wrapping; parent overflow-x hidden triggers compression
+- **Virtual scrolling**: Custom prefix-sum + binary search virtualizer; all row heights pre-computed via pretext
+- **Runtime height augmentation**: For custom cellRenderer columns, measure DOM after mount, maintain running average
+- **cellRenderer flexibility**: Return plain VNode or `{vnode, minHeight, minWidth}` object
+- **Pagination**: Support existing pagination UI and behavior
+- **Fixed columns**: Support left/right pinning via `fixed: 'left' | 'right'`
+- **Slots**: Support column slots and header slots
+- **Dict/timestamp/filesize formatting**: Keep existing built-in formatters
+
+## Acceptance Criteria (evolving)
+
+- [ ] Existing prop interface maintained (backward compatible)
+- [ ] Table renders correctly without any width config
+- [ ] Column widths adapt to content
+- [ ] Long text wraps instead of overflowing
+- [ ] Virtual scrolling works for large datasets
+- [ ] Row heights estimated via pretext (no DOM measurement for height)
+- [ ] Pagination works
+- [ ] Fixed columns work
+- [ ] Lint/typecheck passes
+
+## Out of Scope (explicit)
+
+- Tree data / expandable rows (not in current impl either)
+- Column resizing by drag (nice to have)
+- Sort/filter (via slot customization, not built-in)
+
+## Technical Notes
+
+### Implementation Structure (planned)
+
+```
+packages/base/data/list-table-v2.vue (main component)
+├── usePretextColumnWidths() # pretext-based column width computation
+├── usePretextRowHeights() # pretext-based row height pre-computation
+├── useVirtualRows() # custom prefix-sum + binary search virtualizer
+└── useRuntimeHeightAugment() # ResizeObserver + running average for custom renderers
+```
+
+### Column Width Computation (`usePretextColumnWidths`)
+
+1. Sample data rows (up to 100 samples evenly distributed)
+2. For each sampled value, measure "shrink wrap" width via pretext:
+ ```typescript
+ let maxW = 0;
+ walkLineRanges(prepared, 10000, line => {
+ if (line.width > maxW) maxW = line.width;
+ });
+ // maxW = minimum width to contain all lines (widest line wins)
+ ```
+ This handles embedded `\n` correctly and avoids binary search.
+3. Compute mean (μ) and variance (σ²) per column
+4. Derive flex parameters:
+ - `flexBasis = μ + padding`
+ - `flexGrow = 0.1 + (σ/μ*0.5) * 1.9` (clamped 0.1-2.0)
+ - `flexShrink = same as flexGrow`
+ - `minWidth = max(μ - 2*σ, 50) + padding`
+ - `maxWidth = 300px`
+5. User overrides from column definition applied after computation
+
+### Row Height Computation (`usePretextRowHeights`)
+
+For plain text columns (no custom renderer):
+1. For each row, for each column:
+ - `prepare(text, font)` → cached
+ - `layout(prepared, columnWidth - padding, lineHeight)` → height
+2. Row height = max cell height + row padding
+
+For custom renderer columns:
+1. Use `minHeight` from size-hinted return
+2. After mount, measure DOM via ResizeObserver
+3. Update running average per column
+4. Recompute offsets when average shifts >10%
+
+### Virtualization (`useVirtualRows`)
+
+1. Build prefix-sum offsets: `offsets[i+1] = offsets[i] + heights[i]`
+2. On scroll: binary search for `startIndex` where `offsets[i+1] >= scrollTop`
+3. Linear scan to find `endIndex` where `offsets[i] < scrollTop + viewportHeight`
+4. Render rows [startIndex-overscan, endIndex+overscan] with `transform: translateY(offsets[startIndex])`
+
+### Packages Needed
+- `@chenglou/pretext` v0.0.4 — text measurement
+
+---
+
+## Implementation Plan (4 PRs)
+
+### PR1: Scaffold Hooks & Types ✅ COMPLETED
+
+**Files created:**
+```
+packages/base/data/list-table-v2/
+├── index.ts # Exports all hooks and types
+├── types.ts # ListTableColumn, ListTableProps, CellRendererResult, etc.
+├── measureText.ts # Cached pretext measurement (prepareWithSegments caching)
+├── usePretextColumnWidths.ts # Flex params from variance/mean
+├── usePretextRowHeights.ts # Pre-computed row heights via pretext
+├── useVirtualRows.ts # Prefix-sum + binary search virtualizer
+└── useRuntimeHeightAugment.ts # ResizeObserver + running average
+```
+
+**Key decisions:**
+- `prepareWithSegments()` for everything (superset type works with `layout()` + `walkLineRanges()`)
+- Single cache for PreparedTextWithSegments, LRU-capped at 10,000 entries
+- Types isolated in `types.ts` for backward compatibility
+
+---
+
+### PR2: Core Virtualizer + Pretext Measurement (PENDING)
+
+**Goal**: Wire hooks into Vue component, implement flex container, basic table shell with virtual scrolling.
+
+**Files to create/modify:**
+- `packages/base/data/list-table-v2/list-table-v2.vue` (rewrite)
+
+**Implementation steps:**
+
+1. **Vue component shell**
+ - Props: same as existing `list-table-v2.vue` (data, columns, page, height, etc.)
+ - Emits: query, selection-change, row-click, cell-click
+ - Use `el-auto-resizer` replacement or manual ResizeObserver for container width
+
+2. **Wire usePretextColumnWidths**
+ - `const { computedConfigs, totalFlexBasis } = usePretextColumnWidths(data, columns, containerWidth)`
+ - Convert computed flex configs to CSS/flexbox styling
+
+3. **Wire usePretextRowHeights**
+ - `const { rowHeights, totalHeight } = usePretextRowHeights(data, columns, columnWidths)`
+ - Returns array of heights for virtualizer
+
+4. **Wire useVirtualRows**
+ - `const { visibleRows, totalHeight, onScroll, scrollToIndex } = useVirtualRows(rowHeights, viewportHeight)`
+ - Container div with `overflow-y: auto`
+ - Inner spacer div with `height: totalHeight`
+ - Render visible rows at `transform: translateY(offsetY)`
+
+5. **Basic cell rendering**
+ - Plain text cells: render formatted value
+ - Use existing formatters (dict, timestamp, filesize)
+
+6. **CSS flex container for columns**
+ - Each column: `flex: ${flexGrow} ${flexShrink} ${flexBasis}px`
+ - `min-width: ${minWidth}px`
+ - `max-width: ${maxWidth}px`
+ - CSS `word-break: break-word` for wrapping
+
+7. **Header row**
+ - Fixed height: 44px (or `headerHeight` prop if provided)
+ - Render header cells with same flex styling
+
+**Acceptance criteria:**
+- [ ] Table renders without any width config
+- [ ] Column widths adapt to content
+- [ ] Scrolling works for 1000+ rows (virtualized)
+- [ ] No layout shift on scroll
+
+---
+
+### PR3: Custom cellRenderer + Runtime Augmentation (PENDING)
+
+**Goal**: Support flexible cellRenderer return types and DOM measurement for custom renderers.
+
+**Files to create/modify:**
+- `packages/base/data/list-table-v2/list-table-v2.vue` (update)
+
+**Implementation steps:**
+
+1. **cellRenderer return type handling**
+ ```typescript
+ function resolveCellContent(cellResult: CellRendererResult, params) {
+ if (!cellResult) return null;
+ // Plain VNode
+ if (cellResult.component) return cellResult; // VNode has 'component'
+ // Size-hinted object: { vnode, minHeight, minWidth }
+ return cellResult.vnode;
+ }
+ ```
+
+2. **Track which columns have custom renderers**
+ - `const customRendererColumns = computed(() => columns.filter(c => c.cellRenderer))`
+
+3. **Wire useRuntimeHeightAugment**
+ - `const { recordHeight, flushSamples, getColumnHeight } = useRuntimeHeightAugment()`
+
+4. **Add ResizeObserver for custom renderer rows**
+ - For each rendered row, observe cells with custom renderers
+ - On resize: call `recordHeight(columnKey, measuredHeight)`
+
+5. **Recompute on significant change**
+ - After `flushSamples()`, check if any column average shifted >10%
+ - If so, update rowHeights and re-trigger virtualizer
+
+6. **Initial height for custom renderers**
+ - Check if cellRenderer returns `{vnode, minHeight}` — use that
+ - Otherwise fall back to 44px default
+
+**Acceptance criteria:**
+- [ ] cellRenderer returning plain VNode works as before
+- [ ] cellRenderer returning `{vnode, minHeight, minWidth}` works
+- [ ] DOM heights measured after mount for custom renderers
+- [ ] Running average updates row heights dynamically
+
+---
+
+### PR4: Fixed Columns, Pagination, Slots, Polish (PENDING)
+
+**Goal**: Full feature parity with existing list-table-v2.vue
+
+**Files to create/modify:**
+- `packages/base/data/list-table-v2/list-table-v2.vue` (update)
+
+**Implementation steps:**
+
+1. **Fixed columns (left/right pinning)**
+ - Split columns into: left-fixed, scrollable, right-fixed
+ - Left-fixed: `position: sticky; left: 0`
+ - Right-fixed: `position: sticky; right: 0`
+ - Main scroll container only contains scrollable columns
+ - Synchronize scroll position between fixed and scrollable headers
+
+2. **Pagination**
+ - Add pagination div below table
+ - Use existing `el-pagination` component
+ - On page/size change: emit `query`
+
+3. **Column slots**
+ - `v-slot:[column.key]` support for custom cell rendering
+ - `v-slot:header-[column.key]` support for custom header rendering
+
+4. **Slots passthrough**
+ - Pass through `slots` from parent component
+
+5. **Built-in formatters**
+ - dict: `formatterByDist()` from existing code
+ - timestamp: `TzDateTime` component
+ - filesize: `formatFileSize()` from existing code
+
+6. **Debug mode**
+ - Show estimated row heights, column flex params
+ - Toggle via `debug` prop
+
+7. **Border styling**
+ - CSS borders on cells/rows when `border` prop is true
+
+8. **Row/column events**
+ - `@row-click`: emit when row div clicked
+ - `@cell-click`: emit when cell clicked
+
+**Acceptance criteria:**
+- [ ] Fixed left/right columns work
+- [ ] Pagination emits query event
+- [ ] Column slots work
+- [ ] dict/timestamp/filesize formatting works
+- [ ] Debug mode shows info
+- [ ] Border styling works
+- [ ] Row/cell click events fire
+
+---
+
+## How to Resume After Context Clear
+
+1. Read `prd.md` for full requirements
+2. Check `task.json` for current PR status (`subtasks` array)
+3. PR1 is complete — hooks are scaffolded in `packages/base/data/list-table-v2/`
+4. Continue with PR2: implement `list-table-v2.vue` component
+5. PR3: add cellRenderer support + runtime augmentation
+6. PR4: fixed columns, pagination, slots, polish
diff --git a/.trellis/tasks/04-03-list-table-v2-tanstack-pretext/task.json b/.trellis/tasks/04-03-list-table-v2-tanstack-pretext/task.json
new file mode 100644
index 0000000..ee1b7bd
--- /dev/null
+++ b/.trellis/tasks/04-03-list-table-v2-tanstack-pretext/task.json
@@ -0,0 +1,82 @@
+{
+ "id": "list-table-v2-tanstack-pretext",
+ "name": "list-table-v2-tanstack-pretext",
+ "title": "Rebuild list-table-v2 with pretext + custom virtualizer",
+ "description": "Replace el-table-v2 with custom virtualized table using @chenglou/pretext for text measurement",
+ "status": "planning",
+ "dev_type": "frontend",
+ "scope": "packages/base/data/list-table-v2/",
+ "priority": "P2",
+ "creator": "hechang27-sprt",
+ "assignee": "hechang27-sprt",
+ "createdAt": "2026-04-03",
+ "completedAt": null,
+ "branch": null,
+ "base_branch": "dev",
+ "worktree_path": null,
+ "current_phase": 1,
+ "next_action": [
+ {
+ "phase": 1,
+ "action": "implement"
+ },
+ {
+ "phase": 2,
+ "action": "check"
+ },
+ {
+ "phase": 3,
+ "action": "finish"
+ },
+ {
+ "phase": 4,
+ "action": "create-pr"
+ }
+ ],
+ "commit": null,
+ "pr_url": null,
+ "subtasks": [
+ {
+ "id": "pr1-scaffold",
+ "title": "PR1: Scaffold hooks and types",
+ "status": "completed",
+ "description": "Install @chenglou/pretext, create hooks structure, basic types"
+ },
+ {
+ "id": "pr2-core-virtualizer",
+ "title": "PR2: Core virtualizer + pretext measurement",
+ "status": "pending",
+ "description": "Wire hooks into Vue component, implement flex container, basic table shell with virtual scrolling"
+ },
+ {
+ "id": "pr3-custom-renderers",
+ "title": "PR3: Custom cellRenderer support + runtime augmentation",
+ "status": "pending",
+ "description": "Implement cellRenderer return types (plain VNode + size-hinted), ResizeObserver for custom renderers"
+ },
+ {
+ "id": "pr4-polish",
+ "title": "PR4: Fixed columns, pagination, slots, polish",
+ "status": "pending",
+ "description": "Fixed column pinning, pagination UI, column slots, dict/timestamp formatting, debug mode"
+ }
+ ],
+ "children": [],
+ "parent": null,
+ "relatedFiles": [
+ "packages/base/data/list-table-v2/",
+ "packages/base/data/list-table-v2.vue"
+ ],
+ "notes": "",
+ "meta": {
+ "pr1_files": [
+ "packages/base/data/list-table-v2/index.ts",
+ "packages/base/data/list-table-v2/types.ts",
+ "packages/base/data/list-table-v2/measureText.ts",
+ "packages/base/data/list-table-v2/usePretextColumnWidths.ts",
+ "packages/base/data/list-table-v2/usePretextRowHeights.ts",
+ "packages/base/data/list-table-v2/useVirtualRows.ts",
+ "packages/base/data/list-table-v2/useRuntimeHeightAugment.ts"
+ ]
+ }
+}
diff --git a/.trellis/tasks/03-31-list-table-v2-resize-perf/task.json b/.trellis/tasks/archive/2026-04/03-31-list-table-v2-resize-perf/task.json
similarity index 93%
rename from .trellis/tasks/03-31-list-table-v2-resize-perf/task.json
rename to .trellis/tasks/archive/2026-04/03-31-list-table-v2-resize-perf/task.json
index 86eb197..0a797ba 100644
--- a/.trellis/tasks/03-31-list-table-v2-resize-perf/task.json
+++ b/.trellis/tasks/archive/2026-04/03-31-list-table-v2-resize-perf/task.json
@@ -3,14 +3,14 @@
"name": "list-table-v2-resize-perf",
"title": "Optimize list-table-v2 resize performance",
"description": "",
- "status": "planning",
+ "status": "completed",
"dev_type": null,
"scope": null,
"priority": "P2",
"creator": "hechang27-sprt",
"assignee": "hechang27-sprt",
"createdAt": "2026-03-31",
- "completedAt": null,
+ "completedAt": "2026-04-03",
"branch": null,
"base_branch": "dev",
"worktree_path": null,