# Frontend Bug Patterns / 前端Bug模式

Reference file for bug-fixer skill. Contains documented frontend bug patterns extracted from V12 dashboard development experience.

Last updated: 2026-06-24

---

## Pattern 1: CSS Selector Mismatch Bug

**Symptom**:
Style switching doesn't work despite correct JavaScript logic. CSS classes are toggled but visual changes don't appear.

**Root Cause**:
CSS uses `.kpi-main-card.{style}` selector but HTML elements use `.kpi-card.{style}` class. The selector name doesn't match the actual class names in the template.

**Diagnostic Steps**:
1. `grep` for ALL CSS selectors in the stylesheet (e.g., `grep -o '\.[a-zA-Z-]*' styles.css`)
2. `grep` for actual HTML class names in template files (e.g., `grep -o 'class="[^"]*"' *.html`)
3. Compare both lists to identify mismatches
4. Document all discovered mismatches with file/line references

**Fix**:
Align CSS selectors with actual HTML class names:
```css
/* Before (incorrect) */
.kpi-main-card.fold .icon { ... }

/* After (correct) */
.kpi-card.fold .icon { ... }
```

---

## Pattern 2: Event Handler Not Bound Bug

**Symptom**:
Click events have no response on dynamically-styled elements after class/style changes occur.

**Root Cause**:
Event handlers are bound conditionally during initialization (e.g., `if (styleClass === 'drawer') bindDrawerHandler()`) but the element's style/class changes later via dynamic switching, leaving the handler unbound for the new state.

**Diagnostic Steps**:
1. Search for event handler binding code (e.g., `addEventListener`, `onclick`)
2. Check if handlers are bound conditionally during initialization
3. Trace when style/class changes occur after initialization (e.g., `classList.toggle()`)
4. Verify if handler logic depends on specific class states
5. Use `debugger` to verify handler execution on changed elements

**Fix**:
Always bind handlers unconditionally, add runtime guard checks:
```javascript
// Before (conditional binding)
if (card.classList.contains('drawer')) {
  card.addEventListener('click', handleDrawerClick);
}

// After (unconditional + guard)
card.addEventListener('click', function(e) {
  if (!card.classList.contains('kpi-card-drawer')) return;
  handleDrawerClick(e);
});
```

---

## Pattern 3: Multiple Click Accumulation Bug

**Symptom**:
Breadcrumb path resets instead of accumulating on multi-chart click operations. Each click replaces the path instead of appending new segments.

**Root Cause**:
Event handler replaces the path array (`drillPath = [...]`) instead of using immutable pattern (`.slice()` + `.push()`). This discards previous path segments on each click.

**Diagnostic Steps**:
1. Set breakpoint in the breadcrumb click handler
2. Trace how `drillPath` is modified on each click
3. Look for `drillPath = [...]` assignment vs `drillPath.push(...)`
4. Verify array immutability patterns are followed

**Fix**:
Use immutable array pattern to accumulate path:
```javascript
// Before (replaces path)
drillPath = ['root', newSegment];

// After (appends segment)
var currentPath = appState.breadcrumb.drillPath.slice();
currentPath.push(newSegment);
appState.breadcrumb.drillPath = currentPath;
```

---

## Pattern 4: Chart Overlap Bug

**Symptom**:
ECharts instances render on top of each other, creating visual overlapping artifacts. Charts appear merged or stacked incorrectly.

**Root Cause**:
`.echarts-chart` containers lack `min-height` property and/or `position: relative`. Without proper sizing and positioning, chart canvases overlay each other in the same viewport space.

**Diagnostic Steps**:
1. Inspect `.echarts-chart` container CSS in DevTools
2. Check for missing `min-height` or `height` property
3. Verify `position` property (should be `relative` or `absolute`)
4. Check `z-index` values for proper layer ordering
5. Measure actual container dimensions vs expected chart size

**Fix**:
Add proper container sizing and positioning:
```css
.echarts-chart {
  width: 100%;
  min-height: 340px;
  height: 360px;
  position: relative;
  z-index: 1;
}
```

---

## Pattern 5: Drawer Invisible Bug

**Symptom**:
Drawer panel doesn't appear despite `openDrawer()` function being called. No visual feedback occurs.

**Root Cause**:
`.drawer { display: none; }` is hard-set in base CSS, and `.drawer.open { display: flex; }` override is not triggered due to:
- Class not being added to DOM element
- CSS specificity issue (base selector comes after `.open` selector)
- `openDrawer()` function not being called or erroring before execution

**Diagnostic Steps**:
1. Check if `openDrawer()` is called (add `console.log('openDrawer called')`)
2. Verify `overlay.classList.add('open')` executes successfully
3. Inspect DOM element after `openDrawer()` call - does it have `.open` class?
4. Check CSS order in stylesheet - `.open` selector must come after base selector
5. Check CSS specificity - ensure `.drawer.open` has higher specificity than `.drawer`

**Fix**:
Verify function execution and CSS order:
```javascript
function openDrawer() {
  console.log('openDrawer called');  // Debug log
  var overlay = document.getElementById('drawer-overlay');
  var panel = document.getElementById('drawer-panel');
  overlay.classList.add('open');  // Verify this executes
  panel.classList.add('open');
}
```

```css
/* CSS order matters - .open must come AFTER base */
.drawer {
  display: none;
}

.drawer.open {
  display: flex;
}
```

---

## Pattern 6: Data Visualization Silent Failure

**Symptom**:
Ring chart renders visually but click events do nothing. No error appears in console. User expects drill-down behavior but nothing happens.

**Root Cause**:
Chart data is collapsed into invisible or near-invisible slices (e.g., `value: 0` for filtered items). Small slices (angle < 2°) become unclickable in ECharts, making interactions fail silently.

**Diagnostic Steps**:
1. Check ECharts pie series data array for zero or very small values
2. Calculate slice angles: `angle = (value / total) * 360`
3. Verify click handler is bound to chart instance
4. Check if slices are too small to interact (< 2° typically unclickable)
5. Test with `minAngle` configuration to force minimum slice size
6. Use ECharts `getZr().on('click', ...)` to debug click events at canvas level

**Fix**:
Set minimum slice angle and/or handle zero values:
```javascript
option = {
  series: [{
    type: 'pie',
    minAngle: 10,  // Minimum angle in degrees for smallest slice
    data: [
      { value: 23, name: '储备' },
      { value: 0, name: '初筛' },  // Zero values: handle gracefully
      { value: 15, name: '立项' }
    ]
  }]
};

// Alternative: Filter out zero values before rendering
var filteredData = rawData.filter(function(item) {
  return item.value > 0;
});
```

---

## Quick Reference Table

| Pattern | Key Symptom | Common Fix |
|---------|-------------|------------|
| CSS Selector Mismatch | Styles not applying | Align selector names with HTML classes |
| Event Handler Not Bound | Clicks unresponsive after style change | Bind unconditionally + runtime guards |
| Multiple Click Accumulation | Path resets instead of appending | Use `.slice()` + `.push()` pattern |
| Chart Overlap | Charts stack on each other | Add `min-height`, `position: relative` |
| Drawer Invisible | Drawer doesn't appear | Verify class add + CSS order |
| Data Viz Silent Failure | Chart clicks do nothing | Set `minAngle` or filter zero values |