diff --git a/AGENTS.md b/AGENTS.md index 5740294..9de2516 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 @@ -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 @@ -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 diff --git a/QWEN.md b/QWEN.md new file mode 100644 index 0000000..6a03c9d --- /dev/null +++ b/QWEN.md @@ -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. + + + diff --git a/specs/001-fix-diff-gutter/checklists/requirements.md b/specs/001-fix-diff-gutter/checklists/requirements.md new file mode 100644 index 0000000..ce4abe1 --- /dev/null +++ b/specs/001-fix-diff-gutter/checklists/requirements.md @@ -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 diff --git a/specs/001-fix-diff-gutter/data-model.md b/specs/001-fix-diff-gutter/data-model.md new file mode 100644 index 0000000..1553383 --- /dev/null +++ b/specs/001-fix-diff-gutter/data-model.md @@ -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) diff --git a/specs/001-fix-diff-gutter/plan.md b/specs/001-fix-diff-gutter/plan.md new file mode 100644 index 0000000..2f9ce77 --- /dev/null +++ b/specs/001-fix-diff-gutter/plan.md @@ -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. diff --git a/specs/001-fix-diff-gutter/quickstart.md b/specs/001-fix-diff-gutter/quickstart.md new file mode 100644 index 0000000..f2961b0 --- /dev/null +++ b/specs/001-fix-diff-gutter/quickstart.md @@ -0,0 +1,114 @@ +# Quickstart: Fix Line Numbers in Diff Gutter + +**Date**: 2026-03-03 | **Feature**: 001-fix-diff-gutter + +## Overview + +This feature fixes the unified diff view gutter to display actual source line numbers instead of sequential indices. The gutter shows two columns (original | modified) with GitHub-style visual treatment. + +## Development Setup + +```bash +# Ensure you're on the feature branch +git checkout 001-fix-diff-gutter + +# Install dependencies (if not already done) +npm install + +# Start development server +npm start +``` + +## Files to Modify + +1. **src/components/LineNumberGutter/LineNumberGutter.types.ts** + - Update props interface to accept `lines: DiffLine[]` + +2. **src/components/LineNumberGutter/LineNumberGutter.tsx** + - Implement dual-column rendering + - Add GitHub-style visual treatment + +3. **src/components/LineNumberGutter/LineNumberGutter.test.tsx** + - Add tests for dual-column display + - Test all acceptance scenarios from spec + +4. **src/components/DiffViewer/DiffViewer.tsx** + - Update `LineNumberGutter` usage to pass `lines` prop + +5. **src/components/DiffViewer/DiffViewer.test.tsx** + - Add integration tests for line number accuracy + +## Implementation Order + +1. Update types (`LineNumberGutter.types.ts`) +2. Implement component logic (`LineNumberGutter.tsx`) +3. Write unit tests (`LineNumberGutter.test.tsx`) +4. Integrate with `DiffViewer` +5. Write integration tests +6. Run quality gates + +## Quality Gates + +Run all before committing: + +```bash +# Lint (zero errors) +npm run lint + +# Type check (zero errors) +npm run lint:tsc + +# Tests (100% coverage required) +npm run test:ci + +# Build (clean production build) +npm run build +``` + +## Testing Scenarios + +Test these cases manually in the browser: + +1. **Lines removed from middle**: Verify modified line numbers are lower than original +2. **Lines added at beginning**: Verify unchanged lines show correct offset +3. **Removed line**: Shows original number, blank modified column +4. **Added line**: Shows blank original column, modified number +5. **Unchanged line**: Shows both numbers side-by-side +6. **Scroll alignment**: Line numbers stay aligned during scroll + +## Accessibility Check + +- Gutter remains `aria-hidden="true"` (decorative) +- Content area has `aria-live="polite"` +- Keyboard navigation works (tab through interactive elements) +- Screen reader announces diff content correctly + +## Visual Reference + +Compare against GitHub's diff view: + +- Two narrow columns in gutter +- Subtle vertical divider between columns +- Muted gray for empty/missing numbers +- Consistent alignment with content rows + +## Definition of Done + +- [ ] All functional requirements implemented (FR-001 through FR-010) +- [ ] All acceptance scenarios passing +- [ ] 100% test coverage maintained +- [ ] Lint passes with zero errors +- [ ] Type check passes with zero errors +- [ ] Build succeeds +- [ ] Manual testing completed for all scenarios +- [ ] No visual regressions +- [ ] Accessibility verified + +## Next Steps + +After implementation: + +1. Run `/speckit.tasks` to generate task breakdown +2. Commit changes with conventional commit message +3. Create pull request +4. Request review diff --git a/specs/001-fix-diff-gutter/research.md b/specs/001-fix-diff-gutter/research.md new file mode 100644 index 0000000..4a88a4f --- /dev/null +++ b/specs/001-fix-diff-gutter/research.md @@ -0,0 +1,130 @@ +# Research: Fix Line Numbers in Diff Gutter + +**Date**: 2026-03-03 | **Feature**: 001-fix-diff-gutter + +## Technical Decisions + +### Decision 1: Dual-Column Gutter Rendering Approach + +**What was chosen**: Modify `LineNumberGutter` component to accept `lines: DiffLine[]` array and render two columns per row using CSS grid. + +**Why chosen**: + +- Direct access to `originalLineNumber` and `modifiedLineNumber` from `DiffLine` metadata +- Ensures perfect alignment between gutter and content rows +- Leverages existing `segmentsToLines` utility that already computes correct line numbers +- Minimal changes to `DiffViewer` component structure + +**Alternatives considered**: + +- Separate gutter components for original/modified: Rejected due to synchronization complexity +- CSS-only dual-column approach: Rejected because line numbers come from data, not CSS counters +- Inline line numbers in content area: Rejected because spec requires separate gutter for visual consistency + +--- + +### Decision 2: GitHub-Style Visual Treatment + +**What was chosen**: Two narrow columns with subtle vertical divider, muted gray for missing numbers. + +**Why chosen**: + +- Follows established GitHub/GitLab convention users are familiar with +- Clear visual separation prevents reading errors +- Muted colors for empty cells reduce visual noise +- Accessible without relying solely on color differentiation + +**Alternatives considered**: + +- Single space separator: Rejected for insufficient visual separation +- Distinct background strips: Rejected as too visually heavy for this utility app +- Different text colors per column: Rejected; color alone shouldn't convey meaning + +--- + +### Decision 3: No Scroll Synchronization Needed + +**What was chosen**: Remove scroll synchronization logic. Gutter and content share the same scroll container naturally via CSS grid layout. + +**Why chosen**: + +- No `max-height` constraint exists on the diff container +- Content expands to fit all lines (no vertical scrolling) +- Only horizontal scrolling for long lines (handled by grid layout) +- Eliminates `useRef`, `useState`, and `useEffect` complexity +- Aligns with Constitution Principle V (Simplicity) + +**Alternatives considered**: + +- Keep existing scroll sync pattern: Rejected as unnecessary complexity +- Add `max-height` with scrollable container: Rejected; changes UX, not required by spec + +--- + +### Decision 4: Type Safety for Dual-Column Props + +**What was chosen**: Extend `LineNumberGutterProps` interface with `lines: DiffLine[]` and `viewMode?: 'unified' | 'side-by-side'`. + +**Why chosen**: + +- Maintains TypeScript strict mode compliance +- Enables compile-time validation of line number data +- Clear component contract for future maintainers + +**Alternatives considered**: + +- Separate props for original/modified line arrays: Rejected; loses correlation between pairs +- Generic props with union types: Rejected; over-engineering for this use case + +--- + +## Best Practices Applied + +### React Component Design + +- Functional component with implicit TypeScript typing (no `React.FC`) +- Props interface in separate `.types.ts` file +- Co-located test file with full coverage +- Avoid `useMemo` unless profiling shows performance issues (YAGNI) +- Direct CSS grid layout (no DOM refs needed) + +### Accessibility + +- Gutter remains `aria-hidden` (decorative) +- Content area retains `aria-live="polite"` for diff updates +- Keyboard navigation unaffected (gutter is not interactive) + +### Testing Strategy + +- Unit tests for `LineNumberGutter` rendering +- Integration tests in `DiffViewer.test.tsx` +- Test scenarios cover all acceptance criteria from spec +- Maintain 100% coverage across statements, branches, functions, lines + +### CSS/Tailwind Approach + +- Utility classes only (no custom CSS) +- CSS grid for dual-column layout +- Consistent spacing with existing design tokens +- Responsive considerations (side-by-side view on desktop only) + +--- + +## Implementation Risks & Mitigations + +| Risk | Likelihood | Impact | Mitigation | +| ------------------------------- | ---------- | ------ | ----------------------------------------------------------------------- | +| CSS grid misalignment | Medium | High | Test with varying line lengths; verify gutter-content row heights match | +| Line number computation errors | Low | High | Rely on existing `segmentsToLines` tests; add regression tests | +| Visual regression | Medium | Medium | Manual testing; compare against GitHub diff view | +| Digit width causes layout shift | Low | Medium | Test with 1-4 digit line numbers; verify stable gutter width | +| Accessibility regression | Low | High | Verify aria attributes unchanged; screen reader testing | + +--- + +## References + +- Existing `segmentsToLines.ts` utility (already computes correct line numbers) +- Existing `LineNumberGutter.tsx` component (base implementation) +- GitHub diff view pattern (visual reference) +- Constitution v1.0.0 (technology constraints) diff --git a/specs/001-fix-diff-gutter/spec.md b/specs/001-fix-diff-gutter/spec.md new file mode 100644 index 0000000..08ad8d5 --- /dev/null +++ b/specs/001-fix-diff-gutter/spec.md @@ -0,0 +1,163 @@ +# Feature Specification: Fix Line Numbers in Diff Gutter + +**Feature Branch**: `001-fix-diff-gutter` +**Created**: 2026-03-03 +**Status**: Implemented +**Input**: User description: "Fix line numbers in diff gutter" + +## Clarifications + +### Session 2026-03-03 + +- Q: How should the two line number columns be visually separated and styled in the unified view gutter? → A: Small gap with subtle vertical divider line, muted color for empty/missing numbers (GitHub-style) +- Q: Should long lines wrap or scroll horizontally? → A: Long lines should scroll horizontally with `whitespace-nowrap` to preserve line alignment +- Q: What HTML structure should be used for perfect line alignment? → A: CSS grid layout with `grid-cols-2` for dual-column line numbers, `overflow-x-auto` container for horizontal scrolling +- Q: How can we ensure line numbers take the same height as content when text wraps? → A: Use CSS grid rows where each row contains both line number and content as sibling cells in the same grid row, ensuring they automatically share the same height + +## User Scenarios & Testing _(mandatory)_ + +### User Story 1 - Correct Line Numbers in Unified Diff View (Priority: P1) + +As a user reviewing differences between two texts, I want the line number gutter in unified view to display the actual source line numbers from the original and modified texts so I can accurately reference specific lines when discussing changes with others. + +Currently, the gutter shows sequential numbers (1, 2, 3...) that don't correspond to the actual line numbers in the source texts. The user needs to see the real line numbers from both the original and modified texts side-by-side in the gutter. + +**Why this priority**: This is the core issue - line numbers that don't match the source texts are misleading and defeat the purpose of having line numbers for reference and communication. + +**Independent Test**: Paste two multi-line texts where lines have been removed or added. Verify that the gutter displays the correct original and modified line numbers that match the actual positions in the source texts, not sequential indices. + +**Acceptance Scenarios**: + +1. **Given** two texts where the modified text has 3 lines removed from the middle, **When** viewing the unified diff, **Then** lines after the removal show the correct modified line numbers (which will be lower than original line numbers due to the removal) +2. **Given** two texts where the modified text has 5 lines added at the beginning, **When** viewing the unified diff, **Then** unchanged lines show correct original line numbers (1, 2, 3...) and correct modified line numbers (6, 7, 8...) +3. **Given** a removed line at original position 10, **When** viewing the unified diff, **Then** the gutter shows "10" in the original column and blank in the modified column +4. **Given** an added line at modified position 15, **When** viewing the unified diff, **Then** the gutter shows blank in the original column and "15" in the modified column +5. **Given** an unchanged line at original position 5 and modified position 7, **When** viewing the unified diff, **Then** the gutter shows "5" and "7" side-by-side + +--- + +### User Story 2 - Correct Line Numbers in Side-by-Side Diff View (Priority: P2) + +As a user comparing texts in side-by-side view, I want each column to display the correct source line numbers so I can track corresponding lines between versions. + +The side-by-side view already displays line numbers, but they should be verified to show the correct source line numbers consistently with the unified view fix. + +**Why this priority**: Side-by-side view is a secondary view mode. The unified view is the default and most commonly used, so it takes priority. + +**Independent Test**: Switch to side-by-side view with texts that have additions and removals. Verify each column shows correct line numbers matching the source texts. + +**Acceptance Scenarios**: + +1. **Given** two texts with differences in side-by-side view, **When** viewing the diff, **Then** the original column shows correct original line numbers and the modified column shows correct modified line numbers +2. **Given** a removed line in side-by-side view, **When** viewing the diff, **Then** the original column shows the line with its correct line number and the modified column shows a blank placeholder row with no line number +3. **Given** an added line in side-by-side view, **When** viewing the diff, **Then** the modified column shows the line with its correct line number and the original column shows a blank placeholder row with no line number + +--- + +### Edge Cases + +- What happens when line numbers have different digit counts (e.g., original has 9 lines, modified has 105 lines)? The gutter width should accommodate the maximum digit count without clipping, and alignment should remain consistent. +- What happens with very large line numbers (1000+)? The gutter should display the full number without truncation. +- What happens when the diff method is "characters" or "words" but the text contains newlines? Line numbers should still be computed correctly based on newline positions within segments. +- What happens when one text is empty? The gutter should show line numbers only for the non-empty text side. +- What happens with consecutive added/removed lines? Each line should show its correct sequential line number on the appropriate side. +- What happens when a line is very long (exceeds viewport width)? The line should scroll horizontally without wrapping, and the line number should stay aligned with the line. + +## Requirements _(mandatory)_ + +### Functional Requirements + +- **FR-001**: The unified diff view MUST display line numbers in a dual-column gutter with GitHub-style visual treatment: + - Left column: original line numbers + - Right column: modified line numbers + - Small gap between columns (`gap-1` or `gap-2`) + - Muted color for empty/missing numbers +- **FR-002**: Each line number displayed MUST correspond to the actual line position in the source text (original or modified), not the sequential index in the diff output +- **FR-003**: Removed lines MUST show the correct original line number and blank for modified +- **FR-004**: Added lines MUST show blank for original and the correct modified line number +- **FR-005**: Unchanged lines MUST show both the correct original line number and correct modified line number +- **FR-006**: The diff MUST use the `originalLineNumber` and `modifiedLineNumber` metadata from the `DiffLine` type to display accurate line numbers +- **FR-007**: The gutter width MUST dynamically adjust to accommodate the maximum digit count of line numbers without clipping +- **FR-008**: Line numbers MUST remain correctly aligned with their corresponding diff content lines during scrolling +- **FR-009**: The side-by-side view MUST display correct original line numbers in the original column and correct modified line numbers in the modified column +- **FR-010**: Placeholder rows for missing lines (added lines in original column, removed lines in modified column) MUST NOT display any line number +- **FR-011**: Long lines MUST NOT wrap; they MUST scroll horizontally using `whitespace-nowrap` CSS class +- **FR-012**: The gutter MUST use CSS grid layout (`grid-cols-2`) for dual-column line number display + +### Key Entities + +- **DiffLine**: A single line of diff output containing text content, diff type (added/removed/unchanged), `originalLineNumber` (the actual line position in the original text, or undefined if added), and `modifiedLineNumber` (the actual line position in the modified text, or undefined if removed) + +## Assumptions + +- The `segmentsToLines` utility already computes correct `originalLineNumber` and `modifiedLineNumber` metadata for each `DiffLine` +- The `DiffViewer` component has access to the complete `result.lines` array with line number metadata +- CSS grid layout provides reliable row alignment with proper `whitespace-nowrap` on content + +## Implementation Notes + +### Component Structure + +- **DiffViewer**: Main component rendering both unified and side-by-side views. Unified view uses grid rows with inline line numbers; side-by-side view uses flex rows with text wrapping +- **SideBySideGutter**: ~~Removed~~ - Was replaced by inline line numbers in side-by-side view (refactored in commit 49c4e28) +- **LineNumberGutter**: ~~Removed~~ - Was replaced by inline line numbers in unified view (refactored in commit 8171732) + +### HTML Structure + +```html + +