<!--
SPDX-FileCopyrightText: 2026 Jared Callaham <jared.callaham@gmail.com>

SPDX-License-Identifier: GPL-3.0-or-later
-->

# Implementation Plan: Sum Block Quadrant Configuration

**Branch**: `011-sum-quadrant-config` | **Date**: 2026-01-14 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/011-sum-quadrant-config/spec.md`

## Summary

Enable interactive configuration of Sum block port signs through direct quadrant clicking, replacing text-based parameter editing with visual manipulation. Users click quadrants (top, left, bottom) to cycle through "+", "-", and "|" (no port) states. The feature uses single-click interaction (not double-click) to achieve <100ms response time, with ellipse-aware geometric detection that scales correctly with resized blocks.

**Key Technical Decision**: Research revealed that double-click requires 300ms debounce (violating performance requirements), so implementation uses single-click with drag-distance detection to prevent accidental configuration changes during block repositioning.

## Technical Context

**Language/Version**: TypeScript 5.9 (frontend), Python 3.11+ (backend)
**Primary Dependencies**: React 19.2.3, React Flow 11.11.4, anywidget (Jupyter widget framework), KaTeX 0.16.27, Tailwind CSS v4
**Storage**: JSON diagram files (existing persistence via Pydantic schemas)
**Testing**: Vitest 2.1.8 + jsdom (frontend), pytest (backend)
**Target Platform**: Jupyter Notebook/Lab (browser-based widget)
**Project Type**: Web application (Jupyter widget with Python backend + TypeScript frontend)
**Performance Goals**: <100ms click-to-update latency, 60fps for hover interactions, <1ms quadrant detection
**Constraints**: Must work with resized Sum blocks (40x40 to any dimension), no drag conflicts, single-click only (no 300ms delay)
**Scale/Scope**: Affects only Sum block component (~1 file frontend, verify backend), ~3 new utility files, ~70 new tests

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

### Initial Check (Pre-Research)

| Principle | Status | Notes |
|-----------|--------|-------|
| **I. Simplicity Over Features** | ✅ PASS | Removes properties panel complexity, replaces text editing with direct manipulation |
| **II. Python Ecosystem First** | ✅ PASS | UI enhancement, doesn't affect Python API or data ownership |
| **III. Test-Driven Development** | ⚠️ REQUIRES VERIFICATION | Must verify tests written FIRST before implementation (see quickstart.md:177-184) |
| **IV. Clean Separation of Concerns** | ✅ PASS | Business logic (sign cycling) in utility functions, UI in component layer |
| **V. User Experience Standards** | ✅ PASS | <100ms performance requirement explicit in FR-012, matches industry standards |

**Gate Decision**: CONDITIONAL PASS - proceed to research, but TDD compliance MUST be verified during implementation.

### Post-Design Check

| Principle | Status | Notes |
|-----------|--------|-------|
| **I. Simplicity Over Features** | ✅ PASS | Single-click simpler than double-click, reuses existing hover patterns |
| **II. Python Ecosystem First** | ✅ PASS | Leverages existing Pydantic schemas, no new Python dependencies |
| **III. Test-Driven Development** | ✅ PASS | Test files identified: `ellipseQuadrantDetection.test.ts`, `ellipseQuadrantPaths.test.ts`, `SumBlock.test.tsx` - MUST write before implementation |
| **IV. Clean Separation of Concerns** | ✅ PASS | Geometry utilities separate from React components, parameter updates via existing action pattern |
| **V. User Experience Standards** | ✅ PASS | Single-click achieves <50ms (better than 100ms target), proven hover pattern from `OrthogonalEditableEdge.tsx` |

**Final Gate Decision**: PASS - all principles satisfied, TDD workflow documented in tasks.md

## Project Structure

### Documentation (this feature)

```text
specs/011-sum-quadrant-config/
├── spec.md              # Feature specification (WHAT + WHY)
├── plan.md              # This file (HOW)
├── research.md          # Technical decisions and alternatives
├── quickstart.md        # User guide and test scenarios
├── checklists/          # Quality validation
│   └── requirements.md  # Spec quality checklist
└── tasks.md             # Actionable task list (generated by /speckit.tasks)
```

### Source Code (repository root)

```text
# Frontend (TypeScript/React)
js/
├── src/
│   ├── blocks/
│   │   └── SumBlock.tsx              # MODIFY: Add hover state, click handlers, quadrant overlays
│   ├── utils/
│   │   ├── ellipseQuadrantDetection.ts    # NEW: Geometric quadrant detection algorithm
│   │   └── ellipseQuadrantPaths.ts        # NEW: SVG path generation for quadrant overlays
│   └── test/
│       ├── ellipseQuadrantDetection.test.ts    # NEW: Unit tests for detection
│       ├── ellipseQuadrantPaths.test.ts        # NEW: Unit tests for SVG paths
│       └── SumBlock.test.tsx                   # MODIFY: Add quadrant interaction tests
├── vite.config.ts                    # VERIFY: Test configuration (already exists)
└── package.json                      # VERIFY: Vitest dependency (already exists)

# Backend (Python)
src/
└── lynx/
    ├── blocks/
    │   └── sum.py                    # VERIFY: Port regeneration on signs change (existing)
    ├── diagram.py                    # VERIFY: update_block_parameter handles signs array (existing)
    └── widget.py                     # VERIFY: Widget action handling (existing)
```

**Structure Decision**:
- Frontend follows existing `js/src/` structure with new utilities in `/utils/` (matches `portMarkerGeometry.ts` pattern)
- Tests colocated in `/js/src/test/` (existing pattern)
- Backend changes minimal - verify existing sign cycling logic works correctly
- No new Python files needed - leverages existing Pydantic parameter update flow

## Complexity Tracking

> **Fill ONLY if Constitution Check has violations that must be justified**

*No violations - all constitution principles satisfied.*

## Architecture

### Component Hierarchy

```
SumBlock.tsx (modified)
├── useState: hoveredQuadrant           # Track which quadrant user is hovering
├── useState: mouseDownPos              # Track drag vs click distinction
├── useCallback: handleMouseDown        # Record initial mouse position
├── useCallback: handleClick            # Detect quadrant, cycle sign, send to Python
├── SVG rendering:
│   ├── <ellipse>                      # Existing circle/oval shape
│   ├── {[0,1,2].map => Quadrant overlays}  # NEW: Transparent interactive regions
│   │   ├── <path> clickable           # Transparent overlay for mouse events
│   │   └── <path> highlight           # Visible only when hovered
│   ├── <text>                         # Existing "+" and "-" symbols
│   └── <line>                         # Existing X lines for Sum symbol
└── Parameter update via sendAction     # Existing pattern from useBlockLabel
```

### Data Flow

```
User clicks quadrant
  ↓
handleClick detects mouse position relative to ellipse center
  ↓
ellipseQuadrantDetection.ts calculates which quadrant (0=top, 1=left, 2=bottom, null=outside/right)
  ↓
Current sign retrieved from data.parameters["signs"][quadrantIndex]
  ↓
Sign cycled: "+" → "-" → "|" → "+"
  ↓
sendAction("updateBlockParameter", {blockId, parameterName: "signs", value: newSignsArray})
  ↓
Python widget.py receives action
  ↓
diagram.py::update_block_parameter updates signs parameter
  ↓
sum.py port regeneration triggered (if sign changed to/from "|")
  ↓
Widget state synced back to frontend
  ↓
React re-renders SumBlock with new sign
```

### Key Algorithms

**1. Ellipse Quadrant Detection** (from research.md:22-81)

```typescript
function detectEllipseQuadrant(
  clickX: number,      // Relative to ellipse center
  clickY: number,      // Relative to ellipse center
  radiusX: number,     // Ellipse horizontal radius
  radiusY: number      // Ellipse vertical radius
): number | null {
  // Step 1: Verify click is inside ellipse boundary
  const normalized = (clickX ** 2) / (radiusX ** 2) + (clickY ** 2) / (radiusY ** 2);
  if (normalized > 1) return null; // Outside ellipse

  // Step 2: Normalize to circle space (handles deformed ellipses)
  const normX = clickX / radiusX;
  const normY = clickY / radiusY;

  // Step 3: Calculate polar angle
  const angleDeg = (Math.atan2(normY, normX) * 180) / Math.PI;

  // Step 4: Map angle to quadrant (0-3, with right=3 non-clickable)
  if (angleDeg >= -90 && angleDeg < -30) return 0;      // Top
  else if (angleDeg >= 150 || angleDeg < -150) return 1; // Left
  else if (angleDeg >= 30 && angleDeg < 150) return 2;   // Bottom
  else return 3; // Right (output port - not configurable)
}
```

**Performance**: O(1), ~0.5ms per click (simple arithmetic)

**2. Sign Cycling State Machine** (from research.md:502-519)

```typescript
function cycleSign(currentSign: string): string {
  switch (currentSign) {
    case "+": return "-";
    case "-": return "|";
    case "|": return "+";
    default: return "+"; // Fallback for invalid state
  }
}
```

**3. Drag vs Click Detection** (from research.md:132-154)

```typescript
const handleMouseDown = (e: React.MouseEvent) => {
  mouseDownPos.current = { x: e.clientX, y: e.clientY };
};

const handleClick = (e: React.MouseEvent) => {
  // Calculate distance moved since mouseDown
  const delta = Math.hypot(
    e.clientX - mouseDownPos.current.x,
    e.clientY - mouseDownPos.current.y
  );

  if (delta > 5) {
    return; // Was a drag, not a click - ignore
  }

  // Proceed with quadrant detection and sign cycling
  // ...
};
```

**Threshold**: 5px movement distinguishes drag from click

## Testing Strategy

### Test-Driven Development Workflow

**CRITICAL**: Per Constitution Principle III, tests MUST be written FIRST and FAIL before implementation.

**Test Execution Order** (Red-Green-Refactor):

1. **Phase 1**: Write `ellipseQuadrantDetection.test.ts` → Run (expect failures) → Implement `ellipseQuadrantDetection.ts` → Run (expect pass)
2. **Phase 2**: Write `ellipseQuadrantPaths.test.ts` → Run (expect failures) → Implement `ellipseQuadrantPaths.ts` → Run (expect pass)
3. **Phase 3**: Write `SumBlock.test.tsx` (quadrant interaction tests) → Run (expect failures) → Modify `SumBlock.tsx` → Run (expect pass)

### Test Coverage Requirements

**Unit Tests** (Vitest):
- `ellipseQuadrantDetection.test.ts`:
  - Inside vs outside ellipse boundary (10 test cases)
  - Quadrant mapping for all 4 quadrants (12 test cases)
  - Deformed ellipses (wide, tall, extreme ratios) (8 test cases)
  - Boundary edge cases (clicks on quadrant dividing lines) (6 test cases)
  - Center clicks (< 20% radius) (4 test cases)
  - **Total**: ~40 tests

- `ellipseQuadrantPaths.test.ts`:
  - SVG path generation for each quadrant (3 test cases)
  - Path correctness with different ellipse dimensions (6 test cases)
  - Arc calculations (start/end angles) (6 test cases)
  - **Total**: ~15 tests

- `SumBlock.test.tsx` (additions):
  - Click cycling through all sign states (9 test cases)
  - Hover state changes (6 test cases)
  - Drag vs click distinction (4 test cases)
  - Right quadrant non-clickable (2 test cases)
  - Connection cleanup when sign → "|" (3 test cases)
  - **Total**: ~24 new tests

**Integration Tests** (existing test infrastructure):
- Verify Python backend receives correct signs array
- Verify port regeneration triggered correctly
- Verify connection cleanup on port removal

**Performance Tests**:
- Quadrant detection completes in <1ms (benchmark test)
- Click-to-update latency <100ms (integration test with mocked Python)
- Hover response <16ms (60fps target)

### Test Infrastructure

**Existing** (from CLAUDE.md):
- Vitest 2.1.8 configured with jsdom environment (`js/vite.config.ts`)
- React Testing Library 16.1.0 for component tests
- 162 frontend tests currently passing (as of feature 007)
- `js/src/test/setup.ts` for React 19 compatibility

**No new test infrastructure needed** - leverage existing Vitest setup.

## Dependencies

### Frontend

**Existing Dependencies** (no additions required):
- React 19.2.3 (useState, useCallback)
- React Flow 11.11.4 (node rendering, drag handling)
- Tailwind CSS v4 (color variables for hover highlight)

**Rationale**: Feature uses only standard React hooks and SVG primitives. No new libraries needed.

### Backend

**Existing Dependencies** (no additions required):
- Python 3.11+
- Pydantic (parameter validation)
- anywidget (widget communication)

**Rationale**: Sign parameter already exists, backend logic already handles array updates.

## Migration & Compatibility

### Backward Compatibility

**Existing Diagrams**:
- JSON schema unchanged - `signs` parameter already exists as `[top, left, bottom]` array
- Existing diagrams load without modification
- Users who manually edited signs strings will see same behavior, just with new UI

**Breaking Changes**: NONE
- Properties panel removed ONLY for Sum blocks (other blocks unaffected)
- Python API unchanged (users can still set `sum_block.parameters["signs"] = ["+", "-", "|"]`)

### Rollout Strategy

**Phase 1**: Deploy frontend changes only
- Single-click interaction enabled
- Properties panel hidden for Sum blocks
- Backend already supports required operations

**No migration script needed** - feature is purely additive to existing functionality.

## Performance Analysis

### Latency Budget

| Operation | Budget | Expected | Validation |
|-----------|--------|----------|------------|
| Quadrant detection | <10ms | <1ms | Benchmark test with 1000 iterations |
| Sign cycling logic | <5ms | <0.1ms | Unit test timing |
| React re-render | <50ms | <20ms | React DevTools Profiler |
| Python round-trip | <100ms | <30ms | Integration test (mocked backend) |
| **Total click-to-update** | **<100ms** | **<50ms** | **User-facing metric (FR-012)** |

### Rendering Performance

| Metric | Target | Expected | Validation |
|--------|--------|----------|------------|
| Hover highlight response | <16ms (60fps) | <5ms | Chrome DevTools Performance tab |
| SVG path rendering (per block) | N/A | <1ms (GPU accelerated) | Browser built-in optimization |
| 20 Sum blocks with hover | 60fps | 60fps | Performance test in example notebook |

**Risk Mitigation**: If hover state causes re-render performance issues (unlikely), use CSS `:hover` instead of React state.

## Implementation Phases

### Phase 0: Research ✅ COMPLETE

**Artifacts**:
- ✅ `research.md` - Technical decisions documented
- ✅ `quickstart.md` - User scenarios and test cases defined

**Key Decisions**:
- Single-click (not double-click) for <100ms performance
- Transparent SVG path overlays for hover detection
- Keyboard navigation deferred to future iteration

### Phase 1: Design ✅ COMPLETE

**Artifacts**:
- ✅ `plan.md` - This file (implementation strategy)
- ❌ `data-model.md` - NOT NEEDED (no new entities, existing `signs` parameter)
- ❌ `contracts/` - NOT NEEDED (no new API endpoints, existing `updateBlockParameter` action)
- ✅ `quickstart.md` - User guide with test scenarios

**Rationale**: Feature reuses existing backend infrastructure. No new data models or API contracts required.

### Phase 2: Agent Context Update

**Action**: Run `.specify/scripts/bash/update-agent-context.sh claude`

**Expected Changes**:
- Update CLAUDE.md with new utilities: `ellipseQuadrantDetection.ts`, `ellipseQuadrantPaths.ts`
- Document SumBlock.tsx quadrant interaction pattern
- Add to "Key Components" section (similar to Port Markers, Block Resizing)

## Risk Assessment

### Technical Risks

| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| Click precision on small Sum blocks (40x40) | Medium | Low | Wide quadrant arcs (120°), increase min size to 56x56 if needed |
| Accidental clicks during drag | Medium | Medium | 5px movement threshold, undo support |
| Hover performance with many blocks | Low | Low | Proven pattern from `OrthogonalEditableEdge.tsx`, CSS fallback |
| React Flow drag conflicts | Low | High | `stopPropagation()` on click events, tested in research phase |

### Process Risks

| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| TDD not followed (tests written after code) | Medium | High (constitution violation) | Enforce test-first in task order, gate implementation on failing tests |
| Performance regression not caught | Low | Medium | Benchmark tests in CI, performance profiling in PR review |

## Success Criteria

**Implementation Complete When**:
1. ✅ All tests written FIRST and fail (Red phase)
2. ✅ Implementation makes tests pass (Green phase)
3. ✅ ~79 new tests passing (40 detection + 15 paths + 24 interaction)
4. ✅ Performance benchmarks pass (<100ms click-to-update)
5. ✅ Manual testing with quickstart.md scenarios
6. ✅ Properties panel removed for Sum blocks (verify no UI access)
7. ✅ Existing diagrams load and work correctly (regression test)

**User-Facing Validation** (from spec.md Success Criteria):
- SC-001: Configure all three ports via clicks (no properties panel)
- SC-002: 100% click accuracy inside oval, 0% outside oval
- SC-003: Identical behavior to text-based editing (port regeneration, connections)
- SC-004: Works with resized blocks on first attempt
- SC-005: Visual updates <100ms

## Next Steps

1. ✅ Phase 0 & 1 complete (research, design artifacts generated)
2. **→ Run agent context update** (see command below)
3. **→ Generate tasks.md** via `/speckit.tasks` command
4. **→ Begin implementation** following TDD workflow in tasks.md

### Agent Context Update Command

```bash
.specify/scripts/bash/update-agent-context.sh claude
```

**Expected Output**: CLAUDE.md updated with new "Sum Block Quadrant Configuration" section under "Key Components"

---

**Status**: Ready for Task Generation ✅
**Next Command**: `/speckit.tasks`
**Branch**: `011-sum-quadrant-config`
**Estimated Effort**: 2-3 implementation sessions (utilities + component + tests)
