# Quality Guidelines > Code quality standards for frontend development. --- ## Overview (To be filled by the team) --- ## Forbidden Patterns (To be filled by the team) --- ## Required Patterns (To be filled by the team) --- ## Testing Requirements (To be filled by the team) --- ## Development Workflow ### Package Changes Testing When making changes to `packages/` directory and testing with `examples/`: 1. **Vite dev server auto-reloads**: The dev server (`bun run dev`) automatically reloads changes in `packages/` when you access the example pages 2. **Verify changes took effect**: Modify `DEV_MODE_TS` in `packages/manage/router/index.vue` - this string is displayed at `.el-menu .menu-footer` (bottom of sidebar menu). Changing it forces the app to refresh. 3. **Build before finishing**: Run `bun run build:lib` to ensure the library builds correctly with your changes ```typescript // In packages/manage/router/index.vue - change this string to force refresh const DEV_MODE_TS = "2026-03-26T03:50:00.000Z"; // Update timestamp to force reload ``` ### Build Commands This project uses **bun** as package manager: | Command | Purpose | When to Use | |---------|---------|-------------| | `bun run dev` | Start Vite dev server | Testing example project | | `bun run build` | Build examples project | **DO NOT USE** - examples don't need build | | `bun run build:lib` | Build library for external repos | When done with changes, to verify build | | `bun run lint` | Lint code | Before commit | **Common mistake**: Running `bun run build` which builds the examples project unnecessarily. The examples project is for development only and Vite handles hot reload automatically. ### Quick Test Cycle ```bash # 1. Make changes to packages/ # 2. Dev server auto-reloads when accessing example pages # 3. Modify DEV_MODE_TS in packages/manage/router/index.vue if changes don't appear # 4. Run build:lib when done to verify bun run build:lib ``` ### Browser Automation with agent-browser When using `agent-browser` for testing: **DO**: Use CSS selectors with `wait` instead of fixed timeouts ```bash # Good - waits for element to appear agent-browser wait ".el-table-v2__row" && agent-browser snapshot # Bad - arbitrary timeout, may be too short or waste time agent-browser wait 3000 ``` **DO**: Check console for errors after page interaction ```bash agent-browser click "@e1" && agent-browser console ``` **DO**: Navigate with hash routes properly ```bash # Hash-based SPA routes need explicit navigation agent-browser navigate http://localhost:5173/#/table-v2 ``` **Common wait patterns**: - `agent-browser wait --load networkidle` - for page navigation - `agent-browser wait ".my-selector"` - for element appearance - `agent-browser wait --url "**/pattern"` - for URL changes --- ## Common Mistakes / Gotchas ### el-table-v2 probe row timing When using a probe row to dynamically measure row height for el-table-v2's virtual scrolling: **Problem**: Using a single `requestAnimationFrame` causes inconsistent row heights (first 3 rows taller than rest). **Cause**: el-table-v2's virtual scroller calculates positions based on `estimatedRowHeight`, but a single frame isn't enough for it to fully settle. **Solution**: Use TWO `requestAnimationFrame` calls before measuring: ```typescript await nextTick(); await new Promise(resolve => requestAnimationFrame(resolve)); // Frame 1 await new Promise(resolve => requestAnimationFrame(resolve)); // Frame 2 - el-table-v2 now settled const height = probeRowRef.value?.offsetHeight; ``` **Verification**: All rows should have identical heights and consistent spacing. ### el-table-v2 resize with probe row (clear-then-set pattern) When handling window/container resize with a probe row for dynamic row height: **Problem**: Clearing `estimatedRowHeight` to trigger re-measure creates a visible flash (1-2 frames) where el-table-v2 uses default height. **Root Cause**: The gap between clearing the old value and setting the new value is visible to el-table-v2. **Solution**: Measure first (capturing new values), then use `queueMicrotask` to clear and set in the SAME microtask: ```typescript // 1. Measure first while old values still set const headerHeight = headerEl?.offsetHeight; const rowHeight = firstRow?.offsetHeight; const newHeader = headerHeight && headerHeight > 0 ? headerHeight : estimatedHeaderHeight.value; const newRow = rowHeight && rowHeight > 0 ? rowHeight : estimatedRowHeight.value; // 2. Clear then set in same microtask (before next paint) estimatedRowHeight.value = undefined; estimatedHeaderHeight.value = undefined; queueMicrotask(() => { estimatedRowHeight.value = newRow; estimatedHeaderHeight.value = newHeader; }); ``` **Why queueMicrotask works**: It defers the set to the end of the current microtask queue, but BEFORE the next paint. So the sequence is: - Microtask 1: `undefined` is set (triggers el-table-v2 re-measure) - Microtask 2 (queued): New value is set - Paint: Only one paint happens with the correct value **Without queueMicrotask**: The clear and set happen in separate Vue reactivity updates, causing TWO paints (one with undefined, one with new value). --- ### Vue 3 Template: VNode Rendering **Problem**: Using `{renderFunction()}` in Vue template renders the function's string representation, not the returned VNode. **Why**: In Vue 3 templates, `{xxx}` is treated as literal text interpolation, not JSX expression. **Correct Pattern**: Use functional components that delegate to render functions: ```typescript // Define functional component that delegates to render function const MiniTableCell = (params: CellRendererParams) => renderCellContent(params); const MiniTableHeader = (params: HeaderCellRendererParams) => renderHeaderCellContent(params); // Use in template as component (NOT as {MiniTableCell(...)}) ``` **Why this works**: Vue functional components receive VNode params and return VNodes, which Vue renders correctly. Calling `renderFunction()` directly in template gives `{[object Promise]}` or stringified function output. --- ## Code Review Checklist (To be filled by the team)