Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
28d5608
docs(001-fix-diff-gutter): add specification for fixing line numbers …
remarkablemark Mar 4, 2026
d884fc8
docs(001-fix-diff-gutter): clarify gutter visual design
remarkablemark Mar 4, 2026
effbe38
docs(001-fix-diff-gutter): add implementation plan and research artif…
remarkablemark Mar 4, 2026
a1d12c2
docs(001-fix-diff-gutter): remove unnecessary React.FC and useMemo
remarkablemark Mar 4, 2026
b94cac7
docs(001-fix-diff-gutter): remove unnecessary scroll synchronization
remarkablemark Mar 4, 2026
a976989
docs(001-fix-diff-gutter): add implementation tasks
remarkablemark Mar 4, 2026
5287f24
docs(001-fix-diff-gutter): fix analysis findings
remarkablemark Mar 4, 2026
ab7ce7f
feat(001-fix-diff-gutter): implement dual-column line numbers in diff…
remarkablemark Mar 4, 2026
f09032b
fix(001-fix-diff-gutter): increase gutter width to prevent line numbe…
remarkablemark Mar 4, 2026
a06018c
refactor(001-fix-diff-gutter): remove horizontal scroll sync from gutter
remarkablemark Mar 4, 2026
00d00f1
refactor(001-fix-diff-gutter): extract SideBySideGutter component
remarkablemark Mar 4, 2026
65e6cbe
refactor(components): remove `React.FC`
remarkablemark Mar 4, 2026
f9ba3e6
refactor(components): tidy DiffViewer
remarkablemark Mar 4, 2026
99ee555
docs(001-fix-diff-gutter): update spec with implementation details
remarkablemark Mar 4, 2026
b2cd68e
refactor(LineNumberGutter): remove unused styles
remarkablemark Mar 4, 2026
03ab086
fix(DiffViewer): ensure line numbers match content height for wrapped…
remarkablemark Mar 4, 2026
2f639a9
docs(001-fix-diff-gutter): update spec with implementation details
remarkablemark Mar 4, 2026
8171732
refactor: remove unused LineNumberGutter component
remarkablemark Mar 4, 2026
fde1cbc
docs(001-fix-diff-gutter): update spec with dead code elimination
remarkablemark Mar 4, 2026
3e29ec0
refactor(DiffViewer): improve Tailwind styles and classes
remarkablemark Mar 4, 2026
49c4e28
fix: remove SideBySideGutter and use inline grid layout
remarkablemark Mar 4, 2026
d414cf0
docs(001-fix-diff-gutter): update spec for SideBySideGutter removal
remarkablemark Mar 4, 2026
0d0ab95
fix: enable text wrapping in side-by-side diff view
remarkablemark Mar 4, 2026
6d58223
docs(001-fix-diff-gutter): update spec for text wrapping fix
remarkablemark Mar 4, 2026
ac81016
refactor: extract side-by-side view to SideBySideView component
remarkablemark Mar 4, 2026
bd6d6a2
test: move side-by-side tests to SideBySideView.test.tsx
remarkablemark Mar 4, 2026
2bcc3b4
refactor(DiffViewer): remove unused props and tests
remarkablemark Mar 4, 2026
30c54c6
fix(components): fix side-by-side row height alignment
remarkablemark Mar 4, 2026
c911005
docs(specs): update side-by-side spec
remarkablemark Mar 4, 2026
a1bfcec
fix(SideBySideView): improve line number styling
remarkablemark Mar 4, 2026
a3ab3cc
fix(DiffViewer): make line number styling consistent
remarkablemark Mar 4, 2026
def9207
refactor(utils): modularize getDiffLineClasses
remarkablemark Mar 4, 2026
dd35006
docs(AGENTS): update import sort and vitest globals
remarkablemark Mar 4, 2026
fbf4e23
refactor: remove dead code
remarkablemark Mar 4, 2026
7814c1a
test(App): remove duplicate tests
remarkablemark Mar 4, 2026
1c2b917
test: remove low value tests
remarkablemark Mar 4, 2026
05cad38
test(components): remove redundant tests
remarkablemark Mar 4, 2026
d4a7bd9
fix(SideBySideView): pair the removed/added lines on the same row
remarkablemark Mar 4, 2026
55648e0
docs(specs): create 004-side-by-side-alignment
remarkablemark Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ You're an expert engineer for this React app.

```tsx
// ✅ Correct order
import { useState } from 'react';
import userEvent from '@testing-library/user-event';
import App from 'src/components/App';
import brands from './brands';
import type { User } from './types';
import { useState } from 'react';
import { App } from 'src/components/App';

import type { DiffMethod } from './types';
```

### TypeScript Rules
Expand All @@ -69,7 +69,6 @@ import type { User } from './types';
- **Prefer interfaces over types** for object shapes
- **Use proper event types**: `React.MouseEvent`, `React.FormEvent`, etc.
- **Component props**: Define interfaces with clear, descriptive property names
- **Vitest globals** - include `vitest/globals` in tsconfig for global test functions

### Naming Conventions

Expand Down Expand Up @@ -114,7 +113,7 @@ import type { User } from './types';
- **User interactions** - use @testing-library/user-event for simulating user actions
- **Mock external dependencies** - mock API calls, browser APIs, etc.
- **Descriptive test names** - should clearly state what is being tested
- **Vitest globals** - use `vi.fn()`, `vi.mock()`, `vi.clearAllMocks()`
- **Vitest globals** - use `vi.fn()`, `vi.mock()`, `vi.clearAllMocks()`; no need to import test functions
- **Test setup** - global test environment configured in `vite.config.mts` with `globals: true`
- **Coverage exclusions** - Use `/* v8 ignore next -- @preserve */` for a single line that is not testable or `/* v8 ignore start */` and `/* v8 ignore end */` for multiple lines that are not testable

Expand Down
29 changes: 29 additions & 0 deletions QWEN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# diff Development Guidelines

Auto-generated from all feature plans. Last updated: 2026-03-03

## Active Technologies

- TypeScript 5 (strict mode) + React 19, diff library (v8) (001-fix-diff-gutter)

## Project Structure

```text
src/
tests/
```

## Commands

npm test && npm run lint

## Code Style

TypeScript 5 (strict mode): Follow standard conventions

## Recent Changes

- 001-fix-diff-gutter: Restructured DiffViewer to use CSS grid rows with inline line numbers, ensuring line numbers always match content height when text wraps. Removed separate LineNumberGutter component from unified view and unused scroll sync logic.

<!-- MANUAL ADDITIONS START -->
<!-- MANUAL ADDITIONS END -->
34 changes: 34 additions & 0 deletions specs/001-fix-diff-gutter/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Specification Quality Checklist: Fix Line Numbers in Diff Gutter

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-03
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- All items passed validation on 2026-03-03
161 changes: 161 additions & 0 deletions specs/001-fix-diff-gutter/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Data Model: Fix Line Numbers in Diff Gutter

**Date**: 2026-03-03 | **Feature**: 001-fix-diff-gutter

## Core Entities

### DiffLine

A single line of diff output with line number metadata.

**Source**: `src/types/diff.ts`

```typescript
interface DiffLine {
text: string;
type: 'added' | 'removed' | 'unchanged';
originalLineNumber?: number;
modifiedLineNumber?: number;
}
```

**Fields**:

- `text`: The actual line content (without trailing newline)
- `type`: Diff classification determining visual styling
- `'added'`: Line exists only in modified text
- `'removed'`: Line exists only in original text
- `'unchanged'`: Line exists in both texts
- `originalLineNumber`: 1-based index in original text (undefined if type is 'added')
- `modifiedLineNumber`: 1-based index in modified text (undefined if type is 'removed')

**Validation Rules**:

- Line numbers are 1-based (first line is 1, not 0)
- `originalLineNumber` MUST be undefined when `type === 'added'`
- `modifiedLineNumber` MUST be undefined when `type === 'removed'`
- Both line numbers MUST be present when `type === 'unchanged'`
- Line numbers MUST be sequential within each source text

**Usage**:

- Input to `LineNumberGutter` component via `lines` prop
- Rendered in `DiffViewer` component for both unified and side-by-side views

---

### DiffSegment (Input to segmentsToLines)

Flat segment from diff library before line-based transformation.

**Source**: `src/types/diff.ts`

```typescript
interface DiffSegment {
value: string;
type: 'added' | 'removed' | 'unchanged';
}
```

**Relationship to DiffLine**:

- `segmentsToLines()` transforms `DiffSegment[]` → `DiffLine[]`
- Each segment may contain multiple lines (split by `\n`)
- Segment type determines line number assignment logic

---

## Data Flow

```
┌─────────────────┐
│ diff library │
│ (diff method) │
└────────┬────────┘
┌─────────────────┐
│ DiffSegment[] │
│ (flat segments) │
└────────┬────────┘
▼ segmentsToLines()
┌─────────────────┐
│ DiffLine[] │
│ (line-based) │
└────────┬────────┘
┌─────────────────┐
│ DiffViewer │
│ LineNumberGutter│
└─────────────────┘
```

---

## Component Contracts

### LineNumberGutter Props

**Source**: `src/components/LineNumberGutter/LineNumberGutter.types.ts`

```typescript
interface LineNumberGutterProps {
lines: DiffLine[]; // NEW: Line data with metadata
viewMode?: 'unified' | 'side-by-side'; // NEW: View context
scrollTop: number; // Existing: Vertical scroll position
scrollLeft: number; // Existing: Horizontal scroll position
className?: string; // Existing: Additional CSS classes
'aria-label'?: string; // Existing: Accessibility label
}
```

**Changes from current**:

- `lineCount` → replaced by `lines.length`
- `digitCount` → computed internally from `lines` array
- Added `lines` prop for line number data
- Added `viewMode` prop for rendering context

---

### DiffViewer Props

**Source**: `src/components/DiffViewer/DiffViewer.types.ts`

```typescript
interface DiffViewerProps {
result: {
lines: DiffLine[];
hasChanges: boolean;
} | null;
viewMode: 'unified' | 'side-by-side';
diffMethod?: 'characters' | 'words' | 'lines';
enableScrollSync?: boolean;
gutterWidth?: 'auto' | number;
className?: string;
}
```

**No changes required**: Already uses `DiffLine[]` structure.

---

## State Transitions

N/A - This feature involves no state management. Data flows unidirectionally:

1. User inputs text
2. `useDiff` hook computes diff
3. `segmentsToLines` transforms to line-based format
4. Components render read-only diff output

---

## Type Safety Notes

- All interfaces use explicit types (no `any`)
- Optional fields use `?` modifier with undefined handling
- Union types for `type` field ensure exhaustive switch statements
- TypeScript strict mode enforced (no implicit any)
75 changes: 75 additions & 0 deletions specs/001-fix-diff-gutter/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Implementation Plan: Fix Line Numbers in Diff Gutter

**Branch**: `001-fix-diff-gutter` | **Date**: 2026-03-03 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/001-fix-diff-gutter/spec.md`

## Summary

Fix the unified diff view to ensure line numbers always match the height of their corresponding content lines, even when text wraps. The solution restructures the grid layout so each row contains both the line number and content as sibling cells, ensuring they automatically share the same height. Line numbers are rendered inline as the first column of each grid row.

## Technical Context

**Language/Version**: TypeScript 5 (strict mode)
**Primary Dependencies**: React 19, diff library (v8)
**Storage**: N/A (client-side only, no persistence)
**Testing**: Vitest 4 with @testing-library/react and @testing-library/user-event
**Target Platform**: Modern browsers (client-side SPA)
**Project Type**: Web application (static SPA, no backend)
**Performance Goals**: Instant diff rendering, smooth scrolling alignment
**Constraints**: Client-side only, 100% test coverage required, accessibility compliant
**Scale/Scope**: Single feature modification to existing diff viewer component

## Constitution Check

_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._

| Principle | Status | Notes |
| ----------------------------- | ------- | ------------------------------------------------- |
| I. Client-Side Only | ✅ PASS | Feature is UI-only, no backend changes |
| II. Full Test Coverage | ✅ PASS | New tests required for modified component |
| III. Accessibility First | ✅ PASS | Gutter already aria-hidden, no changes needed |
| IV. Type Safety | ✅ PASS | Using existing `DiffLine` type with strict types |
| V. Simplicity and Performance | ✅ PASS | Modifying existing component, no new dependencies |

**Verdict**: All gates pass. Proceed to Phase 0.

## Project Structure

### Documentation (this feature)

```text
specs/001-fix-diff-gutter/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output (N/A - no external interfaces)
└── tasks.md # Phase 2 output
```

### Source Code (repository root)

```text
src/
├── components/
│ ├── LineNumberGutter/
│ │ ├── LineNumberGutter.tsx # Modified for dual-column display
│ │ ├── LineNumberGutter.types.ts # May need type updates
│ │ └── LineNumberGutter.test.tsx # Updated tests
│ └── DiffViewer/
│ ├── DiffViewer.tsx # May need integration updates
│ └── DiffViewer.test.tsx # Updated tests
├── hooks/
│ └── useDiff.ts # Existing hook (no changes expected)
└── utils/
└── segmentsToLines.ts # Existing utility (no changes expected)

tests/
# Integrated with component test files (co-located)
```

**Structure Decision**: Co-located test files with components (existing project convention). No new directories needed.

## Complexity Tracking

No constitution violations. No complexity tracking required.
Loading