The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocode-fork\frontend\src\components\SpecEditor.tsx has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
    12→  created_at: string
    13→  change_summary: string | null
    14→}
    15→
    16→export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
    17→  const [spec, setSpec] = useState<SpecData | null>(null)
    18→  const [loading, setLoading] = useState(true)
    19→  const [error, setError] = useState<string | null>(null)
    20→  const [content, setContent] = useState('')
    21→  const [saving, setSaving] = useState(false)
    22→  const [hasChanges, setHasChanges] = useState(false)
    23→  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
    24→  const [showDiff, setShowDiff] = useState(false)
    25→
    26→  useEffect(() => {
    27→    fetchSpec()
    28→  }, [projectName])

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
  const [showDiff, setShowDiff] = useState(false)

import { useState, useEffect } from 'react'

interface SpecEditorProps {
  projectName: string
  onBack: () => void
}

interface SpecData {
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)

  useEffect(() => {
    fetchSpec()
  }, [projectName])

  const fetchSpec = async () => {
    try {
      setLoading(true)
      const res = await fetch(`/api/projects/${projectName}/spec`)
      if (!res.ok) throw new Error('Failed to fetch spec')
      const data: SpecData = await res.json()
      setSpec(data)
      setContent(data.content)
      setError(null)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setLoading(false)
    }
  }

  const handleContentChange = (newContent: string) => {
    setContent(newContent)
    setHasChanges(newContent !== spec?.content)
  }

  const handleSave = async () => {
    try {
      setSaving(true)
      setError(null)

      const res = await fetch(`/api/projects/${projectName}/spec`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          content,
          change_summary: 'Manual edit from Spec Editor'
        }),
      })

      if (!res.ok) {
        const data = await res.json()
        throw new Error(data.detail || 'Failed to save spec')
      }

      const updatedSpec: SpecData = await res.json()
      setSpec(updatedSpec)
      setHasChanges(false)
      setShowSaveSuccess(true)
      setTimeout(() => setShowSaveSuccess(false), 3000)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setSaving(false)
    }
  }

  const handleReset = () => {
    if (spec) {
      setContent(spec.content)
      setHasChanges(false)
    }
  }

  // Simple syntax highlighting for YAML/markdown-like content
  const getHighlightedContent = (text: string) => {
    // This is a simplified approach - for full syntax highlighting,
    // we would use a library like Prism.js or CodeMirror
    return text
  }

  if (loading) {
    return (

          Loading spec...

    )
  }

  return (

      {/* Header */}

          <button
            onClick={onBack}
            className="flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
            </svg>
            Back
          </button>

Spec Editor

          {spec && (

              Version {spec.version}

          )}
          {hasChanges && (

              Unsaved changes

          )}
          {showSaveSuccess && (

              Saved successfully

          )}

          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>
          <button
            onClick={handleSave}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {saving ? 'Saving...' : 'Save'}
          </button>

      {/* Error */}
      {error && (

          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {error}

      )}

      {/* Editor */}

          <textarea
            value={content}
            onChange={(e) => handleContentChange(e.target.value)}
            className="w-full h-[500px] p-4 font-mono text-sm bg-surface-elevated border border-border rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
            placeholder="# Project Specification

Enter your project specification here...

## Features
- Feature 1
- Feature 2

## Requirements
- Requirement 1
- Requirement 2"
            spellCheck={false}
          />

            {content.split('\n').length} lines | {content.length} characters

        {/* Metadata */}
        {spec && (

Spec Info

                Version:
                {spec.version}

                Last Updated:
                {new Date(spec.created_at).toLocaleString()}

                Last Change:
                {spec.change_summary || 'N/A'}

        )}

  )
}

export default SpecEditor

+  const [showDiff, setShowDiff] = useState(false)


Now let me add a diff computation function and update the header to include a "Show Diff" button:


Edit

  // Simple syntax highlighting for YAML/markdown-like content
  const getHighlightedContent = (text: string) => {
    // This is a simplified approach - for full syntax highlighting,
    // we would use a library like Prism.js or CodeMirror
    return text
  }

  // Simple line-by-line diff computation
  interface DiffLine {
    type: 'unchanged' | 'added' | 'removed'
    content: string
    lineNumber?: number
  }

  const computeDiff = (): { oldLines: DiffLine[], newLines: DiffLine[] } => {
    const originalLines = (spec?.content || '').split('\n')
    const currentLines = content.split('\n')

    const oldLines: DiffLine[] = []
    const newLines: DiffLine[] = []

    const maxLen = Math.max(originalLines.length, currentLines.length)

    for (let i = 0; i < maxLen; i++) {
      const oldLine = originalLines[i]
      const newLine = currentLines[i]

      if (oldLine === undefined) {
        // Line was added
        oldLines.push({ type: 'unchanged', content: '' })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else if (newLine === undefined) {
        // Line was removed
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: '' })
      } else if (oldLine !== newLine) {
        // Line was modified
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else {
        // Line unchanged
        oldLines.push({ type: 'unchanged', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: newLine, lineNumber: i + 1 })
      }
    }

    return { oldLines, newLines }
  }


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocode-fork\frontend\src\components\SpecEditor.tsx has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
    85→      setHasChanges(false)
    86→    }
    87→  }
    88→
    89→  // Simple line-by-line diff computation
    90→  interface DiffLine {
    91→    type: 'unchanged' | 'added' | 'removed'
    92→    content: string
    93→    lineNumber?: number
    94→  }
    95→
    96→  const computeDiff = (): { oldLines: DiffLine[], newLines: DiffLine[] } => {
    97→    const originalLines = (spec?.content || '').split('\n')
    98→    const currentLines = content.split('\n')
    99→
   100→    const oldLines: DiffLine[] = []
   101→    const newLines: DiffLine[] = []
   102→
   103→    const maxLen = Math.max(originalLines.length, currentLines.length)
   104→
   105→    for (let i = 0; i < maxLen; i++) {
   106→      const oldLine = originalLines[i]
   107→      const newLine = currentLines[i]
   108→
   109→      if (oldLine === undefined) {
   110→        // Line was added
   111→        oldLines.push({ type: 'unchanged', content: '' })
   112→        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
   113→      } else if (newLine === undefined) {
   114→        // Line was removed
   115→        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
   116→        newLines.push({ type: 'unchanged', content: '' })
   117→      } else if (oldLine !== newLine) {
   118→        // Line was modified
   119→        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
   120→        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
   121→      } else {
   122→        // Line unchanged
   123→        oldLines.push({ type: 'unchanged', content: oldLine, lineNumber: i + 1 })
   124→        newLines.push({ type: 'unchanged', content: newLine, lineNumber: i + 1 })
   125→      }
   126→    }
   127→
   128→    return { oldLines, newLines }
   129→  }
   130→
   131→  if (loading) {
   132→    return (
   133→      <div className="bg-surface rounded-lg border border-border p-6">

  // Simple line-by-line diff computation
  interface DiffLine {
    type: 'unchanged' | 'added' | 'removed'
    content: string
    lineNumber?: number
  }

  const computeDiff = (): { oldLines: DiffLine[], newLines: DiffLine[] } => {
    const originalLines = (spec?.content || '').split('\n')
    const currentLines = content.split('\n')

    const oldLines: DiffLine[] = []
    const newLines: DiffLine[] = []

    const maxLen = Math.max(originalLines.length, currentLines.length)

    for (let i = 0; i < maxLen; i++) {
      const oldLine = originalLines[i]
      const newLine = currentLines[i]

      if (oldLine === undefined) {
        // Line was added
        oldLines.push({ type: 'unchanged', content: '' })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else if (newLine === undefined) {
        // Line was removed
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: '' })
      } else if (oldLine !== newLine) {
        // Line was modified
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else {
        // Line unchanged
        oldLines.push({ type: 'unchanged', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: newLine, lineNumber: i + 1 })
      }
    }

    return { oldLines, newLines }
  }

import { useState, useEffect } from 'react'

interface SpecEditorProps {
  projectName: string
  onBack: () => void
}

interface SpecData {
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
  const [showDiff, setShowDiff] = useState(false)

  useEffect(() => {
    fetchSpec()
  }, [projectName])

  const fetchSpec = async () => {
    try {
      setLoading(true)
      const res = await fetch(`/api/projects/${projectName}/spec`)
      if (!res.ok) throw new Error('Failed to fetch spec')
      const data: SpecData = await res.json()
      setSpec(data)
      setContent(data.content)
      setError(null)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setLoading(false)
    }
  }

  const handleContentChange = (newContent: string) => {
    setContent(newContent)
    setHasChanges(newContent !== spec?.content)
  }

  const handleSave = async () => {
    try {
      setSaving(true)
      setError(null)

      const res = await fetch(`/api/projects/${projectName}/spec`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          content,
          change_summary: 'Manual edit from Spec Editor'
        }),
      })

      if (!res.ok) {
        const data = await res.json()
        throw new Error(data.detail || 'Failed to save spec')
      }

      const updatedSpec: SpecData = await res.json()
      setSpec(updatedSpec)
      setHasChanges(false)
      setShowSaveSuccess(true)
      setTimeout(() => setShowSaveSuccess(false), 3000)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setSaving(false)
    }
  }

  const handleReset = () => {
    if (spec) {
      setContent(spec.content)
      setHasChanges(false)
    }
  }

  // Simple syntax highlighting for YAML/markdown-like content
  const getHighlightedContent = (text: string) => {
    // This is a simplified approach - for full syntax highlighting,
    // we would use a library like Prism.js or CodeMirror
    return text
  }

  if (loading) {
    return (

          Loading spec...

    )
  }

  return (

      {/* Header */}

          <button
            onClick={onBack}
            className="flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
            </svg>
            Back
          </button>

Spec Editor

          {spec && (

              Version {spec.version}

          )}
          {hasChanges && (

              Unsaved changes

          )}
          {showSaveSuccess && (

              Saved successfully

          )}

          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>
          <button
            onClick={handleSave}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {saving ? 'Saving...' : 'Save'}
          </button>

      {/* Error */}
      {error && (

          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {error}

      )}

      {/* Editor */}

          <textarea
            value={content}
            onChange={(e) => handleContentChange(e.target.value)}
            className="w-full h-[500px] p-4 font-mono text-sm bg-surface-elevated border border-border rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
            placeholder="# Project Specification

Enter your project specification here...

## Features
- Feature 1
- Feature 2

## Requirements
- Requirement 1
- Requirement 2"
            spellCheck={false}
          />

            {content.split('\n').length} lines | {content.length} characters

        {/* Metadata */}
        {spec && (

Spec Info

                Version:
                {spec.version}

                Last Updated:
                {new Date(spec.created_at).toLocaleString()}

                Last Change:
                {spec.change_summary || 'N/A'}

        )}

  )
}

export default SpecEditor

-  // Simple syntax highlighting for YAML/markdown-like content

-  const getHighlightedContent = (text: string) => {

-    // This is a simplified approach - for full syntax highlighting,

-    // we would use a library like Prism.js or CodeMirror

+  // Simple line-by-line diff computation

+    type: 'unchanged' | 'added' | 'removed'

+  const computeDiff = (): { oldLines: DiffLine[], newLines: DiffLine[] } => {

+    const originalLines = (spec?.content || '').split('\n')

+    const currentLines = content.split('\n')

+    const maxLen = Math.max(originalLines.length, currentLines.length)

+        oldLines.push({ type: 'unchanged', content: '' })

+        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })

+      } else if (newLine === undefined) {

+        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })

+        newLines.push({ type: 'unchanged', content: '' })

+      } else if (oldLine !== newLine) {

+        oldLines.push({ type: 'unchanged', content: oldLine, lineNumber: i + 1 })

+        newLines.push({ type: 'unchanged', content: newLine, lineNumber: i + 1 })


Now let me add a "Show Diff" button to the header and the diff view component:


          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>
          <button
            onClick={handleSave}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {saving ? 'Saving...' : 'Save'}
          </button>

          {hasChanges && (
            <button
              onClick={() => setShowDiff(!showDiff)}
              className={`px-3 py-1.5 text-sm border rounded transition-colors ${
                showDiff
                  ? 'border-accent text-accent bg-accent/10'
                  : 'border-border hover:bg-surface-elevated'
              }`}
            >
              {showDiff ? 'Hide Diff' : 'Show Diff'}
            </button>
          )}
          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>
          <button
            onClick={handleSave}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {saving ? 'Saving...' : 'Save'}
          </button>


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocode-fork\frontend\src\components\SpecEditor.tsx has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
   169→              Saved successfully
   170→
   171→          )}
   172→

   173→

   174→          {hasChanges && (
   175→            <button
   176→              onClick={() => setShowDiff(!showDiff)}
   177→              className={`px-3 py-1.5 text-sm border rounded transition-colors ${
   178→                showDiff
   179→                  ? 'border-accent text-accent bg-accent/10'
   180→                  : 'border-border hover:bg-surface-elevated'
   181→              }`}
   182→            >
   183→              {showDiff ? 'Hide Diff' : 'Show Diff'}
   184→            </button>
   185→          )}
   186→          <button
   187→            onClick={handleReset}
   188→            disabled={!hasChanges || saving}
   189→            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
   190→          >
   191→            Reset
   192→          </button>
   193→          <button
   194→            onClick={handleSave}
   195→            disabled={!hasChanges || saving}
   196→            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
   197→          >
   198→            {saving ? 'Saving...' : 'Save'}
   199→          </button>
   200→

   201→

   202→
   203→      {/* Error */}
   204→      {error && (
   205→

          {hasChanges && (
            <button
              onClick={() => setShowDiff(!showDiff)}
              className={`px-3 py-1.5 text-sm border rounded transition-colors ${
                showDiff
                  ? 'border-accent text-accent bg-accent/10'
                  : 'border-border hover:bg-surface-elevated'
              }`}
            >
              {showDiff ? 'Hide Diff' : 'Show Diff'}
            </button>
          )}
          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>
          <button
            onClick={handleSave}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {saving ? 'Saving...' : 'Save'}
          </button>

import { useState, useEffect } from 'react'

interface SpecEditorProps {
  projectName: string
  onBack: () => void
}

interface SpecData {
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
  const [showDiff, setShowDiff] = useState(false)

  useEffect(() => {
    fetchSpec()
  }, [projectName])

  const fetchSpec = async () => {
    try {
      setLoading(true)
      const res = await fetch(`/api/projects/${projectName}/spec`)
      if (!res.ok) throw new Error('Failed to fetch spec')
      const data: SpecData = await res.json()
      setSpec(data)
      setContent(data.content)
      setError(null)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setLoading(false)
    }
  }

  const handleContentChange = (newContent: string) => {
    setContent(newContent)
    setHasChanges(newContent !== spec?.content)
  }

  const handleSave = async () => {
    try {
      setSaving(true)
      setError(null)

      const res = await fetch(`/api/projects/${projectName}/spec`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          content,
          change_summary: 'Manual edit from Spec Editor'
        }),
      })

      if (!res.ok) {
        const data = await res.json()
        throw new Error(data.detail || 'Failed to save spec')
      }

      const updatedSpec: SpecData = await res.json()
      setSpec(updatedSpec)
      setHasChanges(false)
      setShowSaveSuccess(true)
      setTimeout(() => setShowSaveSuccess(false), 3000)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setSaving(false)
    }
  }

  const handleReset = () => {
    if (spec) {
      setContent(spec.content)
      setHasChanges(false)
    }
  }

  // Simple line-by-line diff computation
  interface DiffLine {
    type: 'unchanged' | 'added' | 'removed'
    content: string
    lineNumber?: number
  }

  const computeDiff = (): { oldLines: DiffLine[], newLines: DiffLine[] } => {
    const originalLines = (spec?.content || '').split('\n')
    const currentLines = content.split('\n')

    const oldLines: DiffLine[] = []
    const newLines: DiffLine[] = []

    const maxLen = Math.max(originalLines.length, currentLines.length)

    for (let i = 0; i < maxLen; i++) {
      const oldLine = originalLines[i]
      const newLine = currentLines[i]

      if (oldLine === undefined) {
        // Line was added
        oldLines.push({ type: 'unchanged', content: '' })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else if (newLine === undefined) {
        // Line was removed
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: '' })
      } else if (oldLine !== newLine) {
        // Line was modified
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else {
        // Line unchanged
        oldLines.push({ type: 'unchanged', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: newLine, lineNumber: i + 1 })
      }
    }

    return { oldLines, newLines }
  }

  if (loading) {
    return (

          Loading spec...

    )
  }

  return (

      {/* Header */}

          <button
            onClick={onBack}
            className="flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
            </svg>
            Back
          </button>

Spec Editor

          {spec && (

              Version {spec.version}

          )}
          {hasChanges && (

              Unsaved changes

          )}
          {showSaveSuccess && (

              Saved successfully

          )}

          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>
          <button
            onClick={handleSave}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {saving ? 'Saving...' : 'Save'}
          </button>

      {/* Error */}
      {error && (

          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {error}

      )}

      {/* Editor */}

          <textarea
            value={content}
            onChange={(e) => handleContentChange(e.target.value)}
            className="w-full h-[500px] p-4 font-mono text-sm bg-surface-elevated border border-border rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
            placeholder="# Project Specification

Enter your project specification here...

## Features
- Feature 1
- Feature 2

## Requirements
- Requirement 1
- Requirement 2"
            spellCheck={false}
          />

            {content.split('\n').length} lines | {content.length} characters

        {/* Metadata */}
        {spec && (

Spec Info

                Version:
                {spec.version}

                Last Updated:
                {new Date(spec.created_at).toLocaleString()}

                Last Change:
                {spec.change_summary || 'N/A'}

        )}

  )
}

export default SpecEditor

<div className="flex items-center gap-2">

+              onClick={() => setShowDiff(!showDiff)}

+              className={`px-3 py-1.5 text-sm border rounded transition-colors ${

+                  ? 'border-accent text-accent bg-accent/10'

+                  : 'border-border hover:bg-surface-elevated'

+              {showDiff ? 'Hide Diff' : 'Show Diff'}


Now let me add the diff view panel right after the error section:


{/* Error */}
      {error && (

          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {error}

      )}

      {/* Editor */}

{/* Error */}
      {error && (

          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {error}

      )}

      {/* Diff View */}
      {showDiff && hasChanges && (

Changes Preview

                  Removed

                  Added

              {/* Original (left side) */}

Original (v{spec?.version})

                  {computeDiff().oldLines.map((line, idx) => (

                        {line.lineNumber || ''}

                      {line.content || '\u00A0'}

                  ))}

              {/* Modified (right side) */}

Modified

                  {computeDiff().newLines.map((line, idx) => (

                        {line.lineNumber || ''}

                      {line.content || '\u00A0'}

                  ))}

      )}

      {/* Editor */}


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocode-fork\frontend\src\components\SpecEditor.tsx has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
   199→          </button>
   200→

   201→

   202→
   203→      {/* Error */}
   204→      {error && (
   205→

   206→          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
   207→            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
   208→          </svg>
   209→          {error}
   210→

   211→      )}
   212→
   213→      {/* Diff View */}
   214→      {showDiff && hasChanges && (
   215→

   216→

   217→

   218→
Changes Preview

   219→

   220→
   221→
   222→                  Removed
   223→
   224→
   225→
   226→                  Added
   227→
   228→

   229→

   230→

   231→              {/* Original (left side) */}
   232→

   233→
Original (v{spec?.version})

   234→

   235→                  {computeDiff().oldLines.map((line, idx) => (
   236→

   244→
   245→                        {line.lineNumber || ''}
   246→
   247→                      {line.content || '\u00A0'}
   248→

   249→                  ))}
   250→

   251→

   252→              {/* Modified (right side) */}
   253→

   254→
Modified

   255→

   256→                  {computeDiff().newLines.map((line, idx) => (
   257→

   265→
   266→                        {line.lineNumber || ''}
   267→
   268→                      {line.content || '\u00A0'}
   269→

   270→                  ))}
   271→

   272→

   273→

   274→

   275→

   276→      )}
   277→
   278→      {/* Editor */}
   279→

   280→

   281→          <textarea
   282→            value={content}

{/* Error */}
      {error && (

          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {error}

      )}

      {/* Diff View */}
      {showDiff && hasChanges && (

Changes Preview

                  Removed

                  Added

              {/* Original (left side) */}

Original (v{spec?.version})

                  {computeDiff().oldLines.map((line, idx) => (

                        {line.lineNumber || ''}

                      {line.content || '\u00A0'}

                  ))}

              {/* Modified (right side) */}

Modified

                  {computeDiff().newLines.map((line, idx) => (

                        {line.lineNumber || ''}

                      {line.content || '\u00A0'}

                  ))}

      )}

      {/* Editor */}

import { useState, useEffect } from 'react'

interface SpecEditorProps {
  projectName: string
  onBack: () => void
}

interface SpecData {
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
  const [showDiff, setShowDiff] = useState(false)

  useEffect(() => {
    fetchSpec()
  }, [projectName])

  const fetchSpec = async () => {
    try {
      setLoading(true)
      const res = await fetch(`/api/projects/${projectName}/spec`)
      if (!res.ok) throw new Error('Failed to fetch spec')
      const data: SpecData = await res.json()
      setSpec(data)
      setContent(data.content)
      setError(null)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setLoading(false)
    }
  }

  const handleContentChange = (newContent: string) => {
    setContent(newContent)
    setHasChanges(newContent !== spec?.content)
  }

  const handleSave = async () => {
    try {
      setSaving(true)
      setError(null)

      const res = await fetch(`/api/projects/${projectName}/spec`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          content,
          change_summary: 'Manual edit from Spec Editor'
        }),
      })

      if (!res.ok) {
        const data = await res.json()
        throw new Error(data.detail || 'Failed to save spec')
      }

      const updatedSpec: SpecData = await res.json()
      setSpec(updatedSpec)
      setHasChanges(false)
      setShowSaveSuccess(true)
      setTimeout(() => setShowSaveSuccess(false), 3000)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setSaving(false)
    }
  }

  const handleReset = () => {
    if (spec) {
      setContent(spec.content)
      setHasChanges(false)
    }
  }

  // Simple line-by-line diff computation
  interface DiffLine {
    type: 'unchanged' | 'added' | 'removed'
    content: string
    lineNumber?: number
  }

  const computeDiff = (): { oldLines: DiffLine[], newLines: DiffLine[] } => {
    const originalLines = (spec?.content || '').split('\n')
    const currentLines = content.split('\n')

    const oldLines: DiffLine[] = []
    const newLines: DiffLine[] = []

    const maxLen = Math.max(originalLines.length, currentLines.length)

    for (let i = 0; i < maxLen; i++) {
      const oldLine = originalLines[i]
      const newLine = currentLines[i]

      if (oldLine === undefined) {
        // Line was added
        oldLines.push({ type: 'unchanged', content: '' })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else if (newLine === undefined) {
        // Line was removed
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: '' })
      } else if (oldLine !== newLine) {
        // Line was modified
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else {
        // Line unchanged
        oldLines.push({ type: 'unchanged', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: newLine, lineNumber: i + 1 })
      }
    }

    return { oldLines, newLines }
  }

  if (loading) {
    return (

          Loading spec...

    )
  }

  return (

      {/* Header */}

          <button
            onClick={onBack}
            className="flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
            </svg>
            Back
          </button>

Spec Editor

          {spec && (

              Version {spec.version}

          )}
          {hasChanges && (

              Unsaved changes

          )}
          {showSaveSuccess && (

              Saved successfully

          )}

          {hasChanges && (
            <button
              onClick={() => setShowDiff(!showDiff)}
              className={`px-3 py-1.5 text-sm border rounded transition-colors ${
                showDiff
                  ? 'border-accent text-accent bg-accent/10'
                  : 'border-border hover:bg-surface-elevated'
              }`}
            >
              {showDiff ? 'Hide Diff' : 'Show Diff'}
            </button>
          )}
          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>
          <button
            onClick={handleSave}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {saving ? 'Saving...' : 'Save'}
          </button>

      {/* Error */}
      {error && (

          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {error}

      )}

      {/* Editor */}

          <textarea
            value={content}
            onChange={(e) => handleContentChange(e.target.value)}
            className="w-full h-[500px] p-4 font-mono text-sm bg-surface-elevated border border-border rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
            placeholder="# Project Specification

Enter your project specification here...

## Features
- Feature 1
- Feature 2

## Requirements
- Requirement 1
- Requirement 2"
            spellCheck={false}
          />

            {content.split('\n').length} lines | {content.length} characters

        {/* Metadata */}
        {spec && (

Spec Info

                Version:
                {spec.version}

                Last Updated:
                {new Date(spec.created_at).toLocaleString()}

                Last Change:
                {spec.change_summary || 'N/A'}

        )}

  )
}

export default SpecEditor

+          <div className="bg-surface-elevated rounded-lg border border-border overflow-hidden">

+            <div className="flex items-center justify-between px-4 py-2 border-b border-border bg-surface">

+              <div className="flex items-center gap-4 text-xs">

+                <span className="flex items-center gap-1">

+            <div className="grid grid-cols-2 divide-x divide-border max-h-[300px] overflow-auto">

+              {/* Original (left side) */}

+                <div className="font-mono text-xs space-y-0.5">

+                  {computeDiff().oldLines.map((line, idx) => (

+                      className={`px-2 py-0.5 rounded ${

+                        line.type === 'removed'

+                          ? 'bg-error/20 text-error'

+                          : 'text-text-secondary'

+                      <span className="inline-block w-6 text-right mr-2 opacity-50">

+                        {line.lineNumber || ''}

+                      {line.content || '\u00A0'}

+              {/* Modified (right side) */}

+                  {computeDiff().newLines.map((line, idx) => (

+                        line.type === 'added'

+                          ? 'bg-success/20 text-success'


Now let me test the diff view. First, let me take a snapshot:


### Page state
- Page URL: http://localhost:5173/
- Page Title: Autocoder+
- Page Snapshot:
```yaml
- generic [ref=e3]:
  - banner [ref=e4]:
    - heading "Autocoder+" [level=1] [ref=e5]
    - generic [ref=e6]: v1.0.0
  - main [ref=e7]:
    - generic [ref=e8]:
      - generic [ref=e9]:
        - heading "Welcome to Autocoder+" [level=2] [ref=e10]
        - paragraph [ref=e11]: Enhanced autonomous coding agent with interactive permission management, dual-agent execution, and infrastructure discovery.
        - generic [ref=e12]:
          - heading "Backend Status" [level=3] [ref=e13]
          - generic [ref=e14]:
            - generic [ref=e17]: Connected
            - generic [ref=e18]:
              - generic [ref=e19]: Service:autocoder-plus
              - generic [ref=e20]: Version:1.0.0
              - generic [ref=e21]: Status:healthy
              - generic [ref=e22]: Last check:2:19:24 AM
        - generic [ref=e23]:
          - generic [ref=e24]:
            - generic [ref=e25]: "0"
            - generic [ref=e26]: Features
            - generic [ref=e27]: pending
          - generic [ref=e28]:
            - generic [ref=e29]: "0"
            - generic [ref=e30]: Passing
            - generic [ref=e31]: tests
          - generic [ref=e32]:
            - generic [ref=e33]: "0"
            - generic [ref=e34]: In Progress
            - generic [ref=e35]: features
          - generic [ref=e36]:
            - generic [ref=e37]: "0"
            - generic [ref=e38]: Stalled
            - generic [ref=e39]: blocked
      - generic [ref=e445]:
        - generic [ref=e446]:
          - button "Back to Projects" [ref=e447] [cursor=pointer]:
            - img [ref=e448]
            - text: Back to Projects
          - generic [ref=e450]:
            - button "Kanban Board" [ref=e451] [cursor=pointer]
            - button "Spec Editor" [active] [ref=e452] [cursor=pointer]
        - generic [ref=e514]:
          - generic [ref=e515]:
            - generic [ref=e516]:
              - button "Back" [ref=e517] [cursor=pointer]:
                - img [ref=e518]
                - text: Back
              - heading "Spec Editor" [level=2] [ref=e520]
              - generic [ref=e521]: Version 6
            - generic [ref=e522]:
              - button "Reset" [disabled] [ref=e523]
              - button "Save" [disabled] [ref=e524]
          - generic [ref=e525]:
            - generic [ref=e526]:
              - 'textbox "# Project Specification Enter your project specification here... ## Features - Feature 1 - Feature 2 ## Requirements - Requirement 1 - Requirement 2" [ref=e527]':
                - /placeholder: "# Project Specification\n\nEnter your project specification here...\n\n## Features\n- Feature 1\n- Feature 2\n\n## Requirements\n- Requirement 1\n- Requirement 2"
                - text: "# Updated Project Spec This is the updated project specification. ## Features - Feature 1: User authentication - Feature 2: Dashboard display ## Requirements - Must support dark mode"
              - generic [ref=e528]: 10 lines | 185 characters
            - generic [ref=e529]:
              - heading "Spec Info" [level=4] [ref=e530]
              - generic [ref=e531]:
                - generic [ref=e532]: Version:6
                - generic [ref=e533]: Last Updated:1/9/2026, 2:19:14 AM
                - generic [ref=e534]: Last Change:Manual edit from Spec Editor
  - contentinfo [ref=e441]:
    - generic [ref=e444]: Ready
```


The page is showing the Spec Editor. I need to make some changes to enable the "Show Diff" button. Let me modify the content:


### Ran Playwright code
await page.getByRole('textbox', { name: '# Project Specification Enter' }).click();

### Page state
- Page URL: http://localhost:5173/
- Page Title: Autocoder+
- Page Snapshot:
```yaml
- <changed> button "Spec Editor" [ref=e452] [cursor=pointer]
- <changed> 'textbox "# Project Specification Enter your project specification here... ## Features - Feature 1 - Feature 2 ## Requirements - Requirement 1 - Requirement 2" [active] [ref=e527]':
  - /placeholder: "# Project Specification\n\nEnter your project specification here...\n\n## Features\n- Feature 1\n- Feature 2\n\n## Requirements\n- Requirement 1\n- Requirement 2"
  - text: "# Updated Project Spec This is the updated project specification. ## Features - Feature 1: User authentication - Feature 2: Dashboard display ## Requirements - Must support dark mode"
```


# Updated Project Spec

This is the MODIFIED project specification.

## Features
- Feature 1: User authentication
- Feature 2: Dashboard display
- Feature 3: NEW FEATURE ADDED

## Requirements
- Must support dark mode
- NEW REQUIREMENT


### Ran Playwright code
await page.getByRole('textbox', { name: '# Project Specification Enter' }).fill('# Updated Project Spec\n\nThis is the MODIFIED project specification.\n\n## Features\n- Feature 1: User authentication\n- Feature 2: Dashboard display\n- Feature 3: NEW FEATURE ADDED\n\n## Requirements\n- Must support dark mode\n- NEW REQUIREMENT');


### Page state
- Page URL: http://localhost:5173/
- Page Title: Autocoder+
- Page Snapshot:
```yaml
- generic [ref=e3]:
  - banner [ref=e4]:
    - heading "Autocoder+" [level=1] [ref=e5]
    - generic [ref=e6]: v1.0.0
  - main [ref=e7]:
    - generic [ref=e8]:
      - generic [ref=e9]:
        - heading "Welcome to Autocoder+" [level=2] [ref=e10]
        - paragraph [ref=e11]: Enhanced autonomous coding agent with interactive permission management, dual-agent execution, and infrastructure discovery.
        - generic [ref=e12]:
          - heading "Backend Status" [level=3] [ref=e13]
          - generic [ref=e14]:
            - generic [ref=e17]: Connected
            - generic [ref=e18]:
              - generic [ref=e19]: Service:autocoder-plus
              - generic [ref=e20]: Version:1.0.0
              - generic [ref=e21]: Status:healthy
              - generic [ref=e22]: Last check:2:19:24 AM
        - generic [ref=e23]:
          - generic [ref=e24]:
            - generic [ref=e25]: "0"
            - generic [ref=e26]: Features
            - generic [ref=e27]: pending
          - generic [ref=e28]:
            - generic [ref=e29]: "0"
            - generic [ref=e30]: Passing
            - generic [ref=e31]: tests
          - generic [ref=e32]:
            - generic [ref=e33]: "0"
            - generic [ref=e34]: In Progress
            - generic [ref=e35]: features
          - generic [ref=e36]:
            - generic [ref=e37]: "0"
            - generic [ref=e38]: Stalled
            - generic [ref=e39]: blocked
      - generic [ref=e445]:
        - generic [ref=e446]:
          - button "Back to Projects" [ref=e447] [cursor=pointer]:
            - img [ref=e448]
            - text: Back to Projects
          - generic [ref=e450]:
            - button "Kanban Board" [ref=e451] [cursor=pointer]
            - button "Spec Editor" [ref=e452] [cursor=pointer]
        - generic [ref=e514]:
          - generic [ref=e515]:
            - generic [ref=e516]:
              - button "Back" [ref=e517] [cursor=pointer]:
                - img [ref=e518]
                - text: Back
              - heading "Spec Editor" [level=2] [ref=e520]
              - generic [ref=e521]: Version 6
              - generic [ref=e535]: Unsaved changes
            - generic [ref=e522]:
              - button "Show Diff" [ref=e536] [cursor=pointer]
              - button "Reset" [ref=e523] [cursor=pointer]
              - button "Save" [ref=e524] [cursor=pointer]
          - generic [ref=e525]:
            - generic [ref=e526]:
              - 'textbox "# Project Specification Enter your project specification here... ## Features - Feature 1 - Feature 2 ## Requirements - Requirement 1 - Requirement 2" [active] [ref=e527]':
                - /placeholder: "# Project Specification\n\nEnter your project specification here...\n\n## Features\n- Feature 1\n- Feature 2\n\n## Requirements\n- Requirement 1\n- Requirement 2"
                - text: "# Updated Project Spec This is the MODIFIED project specification. ## Features - Feature 1: User authentication - Feature 2: Dashboard display - Feature 3: NEW FEATURE ADDED ## Requirements - Must support dark mode - NEW REQUIREMENT"
              - generic [ref=e528]: 12 lines | 235 characters
            - generic [ref=e529]:
              - heading "Spec Info" [level=4] [ref=e530]
              - generic [ref=e531]:
                - generic [ref=e532]: Version:6
                - generic [ref=e533]: Last Updated:1/9/2026, 2:19:14 AM
                - generic [ref=e534]: Last Change:Manual edit from Spec Editor
  - contentinfo [ref=e441]:
    - generic [ref=e444]: Ready
```


I can see the "Show Diff" button appeared! Let me click it to show the diff view:


### Ran Playwright code
await page.getByRole('button', { name: 'Show Diff' }).click();

### Page state
- Page URL: http://localhost:5173/
- Page Title: Autocoder+
- Page Snapshot:
```yaml
- <changed> generic [ref=e514]:
  - generic [ref=e515]:
    - ref=e516 [unchanged]
    - generic [ref=e522]:
      - button "Hide Diff" [active] [ref=e537] [cursor=pointer]
      - ref=e523 [unchanged]
      - ref=e524 [unchanged]
  - generic [ref=e539]:
    - generic [ref=e540]:
      - heading "Changes Preview" [level=4] [ref=e541]
      - generic [ref=e542]:
        - generic [ref=e545]: Removed
        - generic [ref=e548]: Added
    - generic [ref=e549]:
      - generic [ref=e550]:
        - generic [ref=e551]: Original (v6)
        - generic [ref=e552]:
          - generic [ref=e553]:
            - generic [ref=e554]: "1"
            - text: "# Updated Project Spec"
          - generic [ref=e556]: "2"
          - generic [ref=e557]:
            - generic [ref=e558]: "3"
            - text: This is the updated project specification.
          - generic [ref=e560]: "4"
          - generic [ref=e561]:
            - generic [ref=e562]: "5"
            - text: "## Features"
          - generic [ref=e563]:
            - generic [ref=e564]: "6"
            - text: "- Feature 1: User authentication"
          - generic [ref=e565]:
            - generic [ref=e566]: "7"
            - text: "- Feature 2: Dashboard display"
          - generic [ref=e568]: "8"
          - generic [ref=e569]:
            - generic [ref=e570]: "9"
            - text: "## Requirements"
          - generic [ref=e571]:
            - generic [ref=e572]: "10"
            - text: "- Must support dark mode"
      - generic [ref=e575]:
        - generic [ref=e576]: Modified
        - generic [ref=e577]:
          - generic [ref=e578]:
            - generic [ref=e579]: "1"
            - text: "# Updated Project Spec"
          - generic [ref=e581]: "2"
          - generic [ref=e582]:
            - generic [ref=e583]: "3"
            - text: This is the MODIFIED project specification.
          - generic [ref=e585]: "4"
          - generic [ref=e586]:
            - generic [ref=e587]: "5"
            - text: "## Features"
          - generic [ref=e588]:
            - generic [ref=e589]: "6"
            - text: "- Feature 1: User authentication"
          - generic [ref=e590]:
            - generic [ref=e591]: "7"
            - text: "- Feature 2: Dashboard display"
          - generic [ref=e592]:
            - generic [ref=e593]: "8"
            - text: "- Feature 3: NEW FEATURE ADDED"
          - generic [ref=e595]: "9"
          - generic [ref=e596]:
            - generic [ref=e597]: "10"
            - text: "## Requirements"
          - generic [ref=e598]:
            - generic [ref=e599]: "11"
            - text: "- Must support dark mode"
          - generic [ref=e600]:
            - generic [ref=e601]: "12"
            - text: "- NEW REQUIREMENT"
  - generic [ref=e525]:
    - generic [ref=e526]:
      - 'textbox "# Project Specification Enter your project specification here... ## Features - Feature 1 - Feature 2 ## Requirements - Requirement 1 - Requirement 2" [ref=e527]':
        - /placeholder: "# Project Specification\n\nEnter your project specification here...\n\n## Features\n- Feature 1\n- Feature 2\n\n## Requirements\n- Requirement 1\n- Requirement 2"
        - text: "# Updated Project Spec This is the MODIFIED project specification. ## Features - Feature 1: User authentication - Feature 2: Dashboard display - Feature 3: NEW FEATURE ADDED ## Requirements - Must support dark mode - NEW REQUIREMENT"
      - ref=e528 [unchanged]
    - ref=e529 [unchanged]
```


The diff view is working. I can see:
1. ✅ "Hide Diff" button appeared (toggle)
2. ✅ "Changes Preview" heading with legend showing "Removed" and "Added"
3. ✅ Side-by-side comparison:
   - Left: "Original (v6)" showing the old content
   - Right: "Modified" showing the new content
4. ✅ Line numbers are shown
5. ✅ The changes are visible - "MODIFIED" vs "updated", "NEW FEATURE ADDED", and "NEW REQUIREMENT" are highlighted

Let me take a screenshot to verify the highlighting colors are correct:


### Ran Playwright code
// Screenshot viewport and save it as C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocode-fork\.playwright-mcp\diff-view-screenshot.png
await page.screenshot({
scale: 'css',
  type: 'png'
});

### Files
- [Screenshot of viewport](C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocode-fork\.playwright-mcp\diff-view-screenshot.png)


The diff view is working correctly with proper highlighting:
1. ✅ Side-by-side comparison shown (Original v6 on left, Modified on right)
2. ✅ Removed lines highlighted in red/pink (left side shows removed lines)
3. ✅ Added lines highlighted in green (right side shows new lines)
4. ✅ Line numbers displayed
5. ✅ "Hide Diff" button to toggle off
6. ✅ Legend shows "Removed" and "Added" color indicators

The diff view is working perfectly! Let me mark Feature #51 as passing:


Spec diff view shows changes

Verify side-by-side diff when editing spec

Step 1: Navigate to Spec Editor with existing spec

Step 4: Verify side-by-side comparison shown

Step 5: Verify additions highlighted in green

Step 6: Verify deletions highlighted in red


Feature #51 is passing. Let me get the next feature:


Spec version history displays all versions

Verify spec history shows all previous versions

Step 1: Create project and update spec 3 times

Step 4: Verify each shows version number and timestamp

Step 5: Verify change summaries displayed


Excellent progress! 50/106 features passing (47.2% - almost halfway!). Feature #52: "Spec version history displays all versions" - I need to add a version history view to the Spec Editor.


Let me update the SpecEditor to add version history functionality. I'll add a tab or button to show version history:


     1→import { useState, useEffect } from 'react'
     2→
     3→interface SpecEditorProps {
     4→  projectName: string
     5→  onBack: () => void
     6→}
     7→
     8→interface SpecData {
     9→  project_id: number
    10→  version: number
    11→  content: string
    12→  created_at: string
    13→  change_summary: string | null
    14→}
    15→
    16→export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
    17→  const [spec, setSpec] = useState<SpecData | null>(null)
    18→  const [loading, setLoading] = useState(true)
    19→  const [error, setError] = useState<string | null>(null)
    20→  const [content, setContent] = useState('')
    21→  const [saving, setSaving] = useState(false)
    22→  const [hasChanges, setHasChanges] = useState(false)
    23→  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
    24→  const [showDiff, setShowDiff] = useState(false)
    25→
    26→  useEffect(() => {
    27→    fetchSpec()
    28→  }, [projectName])
    29→
    30→  const fetchSpec = async () => {
    31→    try {
    32→      setLoading(true)
    33→      const res = await fetch(`/api/projects/${projectName}/spec`)
    34→      if (!res.ok) throw new Error('Failed to fetch spec')
    35→      const data: SpecData = await res.json()
    36→      setSpec(data)
    37→      setContent(data.content)
    38→      setError(null)
    39→    } catch (err) {
    40→      setError(err instanceof Error ? err.message : 'Unknown error')
    41→    } finally {
    42→      setLoading(false)
    43→    }
    44→  }
    45→
    46→  const handleContentChange = (newContent: string) => {
    47→    setContent(newContent)
    48→    setHasChanges(newContent !== spec?.content)
    49→  }
    50→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import { useState, useEffect } from 'react'

interface SpecEditorProps {
  projectName: string
  onBack: () => void
}

interface SpecData {
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
  const [showDiff, setShowDiff] = useState(false)

  useEffect(() => {
    fetchSpec()
  }, [projectName])

  const fetchSpec = async () => {
    try {
      setLoading(true)
      const res = await fetch(`/api/projects/${projectName}/spec`)
      if (!res.ok) throw new Error('Failed to fetch spec')
      const data: SpecData = await res.json()
      setSpec(data)
      setContent(data.content)
      setError(null)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setLoading(false)
    }
  }

  const handleContentChange = (newContent: string) => {
    setContent(newContent)
    setHasChanges(newContent !== spec?.content)
  }


Let me add the history state and fetch function:


Edit

interface SpecData {
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
  const [showDiff, setShowDiff] = useState(false)

interface SpecData {
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

interface SpecHistoryEntry {
  id: number
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
  const [showDiff, setShowDiff] = useState(false)
  const [showHistory, setShowHistory] = useState(false)
  const [history, setHistory] = useState<SpecHistoryEntry[]>([])
  const [loadingHistory, setLoadingHistory] = useState(false)


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocode-fork\frontend\src\components\SpecEditor.tsx has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
     4→  projectName: string
     5→  onBack: () => void
     6→}
     7→
     8→interface SpecData {
     9→  project_id: number
    10→  version: number
    11→  content: string
    12→  created_at: string
    13→  change_summary: string | null
    14→}
    15→
    16→interface SpecHistoryEntry {
    17→  id: number
    18→  project_id: number
    19→  version: number
    20→  content: string
    21→  created_at: string
    22→  change_summary: string | null
    23→}
    24→
    25→export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
    26→  const [spec, setSpec] = useState<SpecData | null>(null)
    27→  const [loading, setLoading] = useState(true)
    28→  const [error, setError] = useState<string | null>(null)
    29→  const [content, setContent] = useState('')
    30→  const [saving, setSaving] = useState(false)
    31→  const [hasChanges, setHasChanges] = useState(false)
    32→  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
    33→  const [showDiff, setShowDiff] = useState(false)
    34→  const [showHistory, setShowHistory] = useState(false)
    35→  const [history, setHistory] = useState<SpecHistoryEntry[]>([])
    36→  const [loadingHistory, setLoadingHistory] = useState(false)
    37→
    38→  useEffect(() => {
    39→    fetchSpec()
    40→  }, [projectName])

interface SpecData {
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

interface SpecHistoryEntry {
  id: number
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
  const [showDiff, setShowDiff] = useState(false)
  const [showHistory, setShowHistory] = useState(false)
  const [history, setHistory] = useState<SpecHistoryEntry[]>([])
  const [loadingHistory, setLoadingHistory] = useState(false)

import { useState, useEffect } from 'react'

interface SpecEditorProps {
  projectName: string
  onBack: () => void
}

interface SpecData {
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
  const [showDiff, setShowDiff] = useState(false)

  useEffect(() => {
    fetchSpec()
  }, [projectName])

  const fetchSpec = async () => {
    try {
      setLoading(true)
      const res = await fetch(`/api/projects/${projectName}/spec`)
      if (!res.ok) throw new Error('Failed to fetch spec')
      const data: SpecData = await res.json()
      setSpec(data)
      setContent(data.content)
      setError(null)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setLoading(false)
    }
  }

  const handleContentChange = (newContent: string) => {
    setContent(newContent)
    setHasChanges(newContent !== spec?.content)
  }

  const handleSave = async () => {
    try {
      setSaving(true)
      setError(null)

      const res = await fetch(`/api/projects/${projectName}/spec`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          content,
          change_summary: 'Manual edit from Spec Editor'
        }),
      })

      if (!res.ok) {
        const data = await res.json()
        throw new Error(data.detail || 'Failed to save spec')
      }

      const updatedSpec: SpecData = await res.json()
      setSpec(updatedSpec)
      setHasChanges(false)
      setShowSaveSuccess(true)
      setTimeout(() => setShowSaveSuccess(false), 3000)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setSaving(false)
    }
  }

  const handleReset = () => {
    if (spec) {
      setContent(spec.content)
      setHasChanges(false)
    }
  }

  // Simple line-by-line diff computation
  interface DiffLine {
    type: 'unchanged' | 'added' | 'removed'
    content: string
    lineNumber?: number
  }

  const computeDiff = (): { oldLines: DiffLine[], newLines: DiffLine[] } => {
    const originalLines = (spec?.content || '').split('\n')
    const currentLines = content.split('\n')

    const oldLines: DiffLine[] = []
    const newLines: DiffLine[] = []

    const maxLen = Math.max(originalLines.length, currentLines.length)

    for (let i = 0; i < maxLen; i++) {
      const oldLine = originalLines[i]
      const newLine = currentLines[i]

      if (oldLine === undefined) {
        // Line was added
        oldLines.push({ type: 'unchanged', content: '' })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else if (newLine === undefined) {
        // Line was removed
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: '' })
      } else if (oldLine !== newLine) {
        // Line was modified
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else {
        // Line unchanged
        oldLines.push({ type: 'unchanged', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: newLine, lineNumber: i + 1 })
      }
    }

    return { oldLines, newLines }
  }

  if (loading) {
    return (

          Loading spec...

    )
  }

  return (

      {/* Header */}

          <button
            onClick={onBack}
            className="flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
            </svg>
            Back
          </button>

Spec Editor

          {spec && (

              Version {spec.version}

          )}
          {hasChanges && (

              Unsaved changes

          )}
          {showSaveSuccess && (

              Saved successfully

          )}

          {hasChanges && (
            <button
              onClick={() => setShowDiff(!showDiff)}
              className={`px-3 py-1.5 text-sm border rounded transition-colors ${
                showDiff
                  ? 'border-accent text-accent bg-accent/10'
                  : 'border-border hover:bg-surface-elevated'
              }`}
            >
              {showDiff ? 'Hide Diff' : 'Show Diff'}
            </button>
          )}
          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>
          <button
            onClick={handleSave}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {saving ? 'Saving...' : 'Save'}
          </button>

      {/* Error */}
      {error && (

          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {error}

      )}

      {/* Diff View */}
      {showDiff && hasChanges && (

Changes Preview

                  Removed

                  Added

              {/* Original (left side) */}

Original (v{spec?.version})

                  {computeDiff().oldLines.map((line, idx) => (

                        {line.lineNumber || ''}

                      {line.content || '\u00A0'}

                  ))}

              {/* Modified (right side) */}

Modified

                  {computeDiff().newLines.map((line, idx) => (

                        {line.lineNumber || ''}

                      {line.content || '\u00A0'}

                  ))}

      )}

      {/* Editor */}

          <textarea
            value={content}
            onChange={(e) => handleContentChange(e.target.value)}
            className="w-full h-[500px] p-4 font-mono text-sm bg-surface-elevated border border-border rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
            placeholder="# Project Specification

Enter your project specification here...

## Features
- Feature 1
- Feature 2

## Requirements
- Requirement 1
- Requirement 2"
            spellCheck={false}
          />

            {content.split('\n').length} lines | {content.length} characters

        {/* Metadata */}
        {spec && (

Spec Info

                Version:
                {spec.version}

                Last Updated:
                {new Date(spec.created_at).toLocaleString()}

                Last Change:
                {spec.change_summary || 'N/A'}

        )}

  )
}

export default SpecEditor

+  const [showHistory, setShowHistory] = useState(false)

+  const [history, setHistory] = useState<SpecHistoryEntry[]>([])

+  const [loadingHistory, setLoadingHistory] = useState(false)


Now let me add the fetchHistory function after fetchSpec:


    } finally {
      setLoading(false)
    }
  }

  const handleContentChange = (newContent: string) => {

    } finally {
      setLoading(false)
    }
  }

  const fetchHistory = async () => {
    try {
      setLoadingHistory(true)
      const res = await fetch(`/api/projects/${projectName}/spec/history`)
      if (!res.ok) throw new Error('Failed to fetch spec history')
      const data = await res.json()
      setHistory(data.history)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setLoadingHistory(false)
    }
  }

  const handleShowHistory = () => {
    if (!showHistory) {
      fetchHistory()
    }
    setShowHistory(!showHistory)
  }

  const handleContentChange = (newContent: string) => {


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocode-fork\frontend\src\components\SpecEditor.tsx has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
    49→      setContent(data.content)
    50→      setError(null)
    51→    } catch (err) {
    52→      setError(err instanceof Error ? err.message : 'Unknown error')
    53→    } finally {
    54→      setLoading(false)
    55→    }
    56→  }
    57→
    58→  const fetchHistory = async () => {
    59→    try {
    60→      setLoadingHistory(true)
    61→      const res = await fetch(`/api/projects/${projectName}/spec/history`)
    62→      if (!res.ok) throw new Error('Failed to fetch spec history')
    63→      const data = await res.json()
    64→      setHistory(data.history)
    65→    } catch (err) {
    66→      setError(err instanceof Error ? err.message : 'Unknown error')
    67→    } finally {
    68→      setLoadingHistory(false)
    69→    }
    70→  }
    71→
    72→  const handleShowHistory = () => {
    73→    if (!showHistory) {
    74→      fetchHistory()
    75→    }
    76→    setShowHistory(!showHistory)
    77→  }
    78→
    79→  const handleContentChange = (newContent: string) => {
    80→    setContent(newContent)
    81→    setHasChanges(newContent !== spec?.content)
    82→  }
    83→

    } finally {
      setLoading(false)
    }
  }

  const fetchHistory = async () => {
    try {
      setLoadingHistory(true)
      const res = await fetch(`/api/projects/${projectName}/spec/history`)
      if (!res.ok) throw new Error('Failed to fetch spec history')
      const data = await res.json()
      setHistory(data.history)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setLoadingHistory(false)
    }
  }

  const handleShowHistory = () => {
    if (!showHistory) {
      fetchHistory()
    }
    setShowHistory(!showHistory)
  }

  const handleContentChange = (newContent: string) => {

import { useState, useEffect } from 'react'

interface SpecEditorProps {
  projectName: string
  onBack: () => void
}

interface SpecData {
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

interface SpecHistoryEntry {
  id: number
  project_id: number
  version: number
  content: string
  created_at: string
  change_summary: string | null
}

export function SpecEditor({ projectName, onBack }: SpecEditorProps) {
  const [spec, setSpec] = useState<SpecData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const [saving, setSaving] = useState(false)
  const [hasChanges, setHasChanges] = useState(false)
  const [showSaveSuccess, setShowSaveSuccess] = useState(false)
  const [showDiff, setShowDiff] = useState(false)
  const [showHistory, setShowHistory] = useState(false)
  const [history, setHistory] = useState<SpecHistoryEntry[]>([])
  const [loadingHistory, setLoadingHistory] = useState(false)

  useEffect(() => {
    fetchSpec()
  }, [projectName])

  const fetchSpec = async () => {
    try {
      setLoading(true)
      const res = await fetch(`/api/projects/${projectName}/spec`)
      if (!res.ok) throw new Error('Failed to fetch spec')
      const data: SpecData = await res.json()
      setSpec(data)
      setContent(data.content)
      setError(null)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setLoading(false)
    }
  }

  const handleContentChange = (newContent: string) => {
    setContent(newContent)
    setHasChanges(newContent !== spec?.content)
  }

  const handleSave = async () => {
    try {
      setSaving(true)
      setError(null)

      const res = await fetch(`/api/projects/${projectName}/spec`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          content,
          change_summary: 'Manual edit from Spec Editor'
        }),
      })

      if (!res.ok) {
        const data = await res.json()
        throw new Error(data.detail || 'Failed to save spec')
      }

      const updatedSpec: SpecData = await res.json()
      setSpec(updatedSpec)
      setHasChanges(false)
      setShowSaveSuccess(true)
      setTimeout(() => setShowSaveSuccess(false), 3000)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setSaving(false)
    }
  }

  const handleReset = () => {
    if (spec) {
      setContent(spec.content)
      setHasChanges(false)
    }
  }

  // Simple line-by-line diff computation
  interface DiffLine {
    type: 'unchanged' | 'added' | 'removed'
    content: string
    lineNumber?: number
  }

  const computeDiff = (): { oldLines: DiffLine[], newLines: DiffLine[] } => {
    const originalLines = (spec?.content || '').split('\n')
    const currentLines = content.split('\n')

    const oldLines: DiffLine[] = []
    const newLines: DiffLine[] = []

    const maxLen = Math.max(originalLines.length, currentLines.length)

    for (let i = 0; i < maxLen; i++) {
      const oldLine = originalLines[i]
      const newLine = currentLines[i]

      if (oldLine === undefined) {
        // Line was added
        oldLines.push({ type: 'unchanged', content: '' })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else if (newLine === undefined) {
        // Line was removed
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: '' })
      } else if (oldLine !== newLine) {
        // Line was modified
        oldLines.push({ type: 'removed', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'added', content: newLine, lineNumber: i + 1 })
      } else {
        // Line unchanged
        oldLines.push({ type: 'unchanged', content: oldLine, lineNumber: i + 1 })
        newLines.push({ type: 'unchanged', content: newLine, lineNumber: i + 1 })
      }
    }

    return { oldLines, newLines }
  }

  if (loading) {
    return (

          Loading spec...

    )
  }

  return (

      {/* Header */}

          <button
            onClick={onBack}
            className="flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
            </svg>
            Back
          </button>

Spec Editor

          {spec && (

              Version {spec.version}

          )}
          {hasChanges && (

              Unsaved changes

          )}
          {showSaveSuccess && (

              Saved successfully

          )}

          {hasChanges && (
            <button
              onClick={() => setShowDiff(!showDiff)}
              className={`px-3 py-1.5 text-sm border rounded transition-colors ${
                showDiff
                  ? 'border-accent text-accent bg-accent/10'
                  : 'border-border hover:bg-surface-elevated'
              }`}
            >
              {showDiff ? 'Hide Diff' : 'Show Diff'}
            </button>
          )}
          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>
          <button
            onClick={handleSave}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {saving ? 'Saving...' : 'Save'}
          </button>

      {/* Error */}
      {error && (

          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {error}

      )}

      {/* Diff View */}
      {showDiff && hasChanges && (

Changes Preview

                  Removed

                  Added

              {/* Original (left side) */}

Original (v{spec?.version})

                  {computeDiff().oldLines.map((line, idx) => (

                        {line.lineNumber || ''}

                      {line.content || '\u00A0'}

                  ))}

              {/* Modified (right side) */}

Modified

                  {computeDiff().newLines.map((line, idx) => (

                        {line.lineNumber || ''}

                      {line.content || '\u00A0'}

                  ))}

      )}

      {/* Editor */}

          <textarea
            value={content}
            onChange={(e) => handleContentChange(e.target.value)}
            className="w-full h-[500px] p-4 font-mono text-sm bg-surface-elevated border border-border rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
            placeholder="# Project Specification

Enter your project specification here...

## Features
- Feature 1
- Feature 2

## Requirements
- Requirement 1
- Requirement 2"
            spellCheck={false}
          />

            {content.split('\n').length} lines | {content.length} characters

        {/* Metadata */}
        {spec && (

Spec Info

                Version:
                {spec.version}

                Last Updated:
                {new Date(spec.created_at).toLocaleString()}

                Last Change:
                {spec.change_summary || 'N/A'}

        )}

  )
}

export default SpecEditor

+      const res = await fetch(`/api/projects/${projectName}/spec/history`)

+      if (!res.ok) throw new Error('Failed to fetch spec history')

+      setError(err instanceof Error ? err.message : 'Unknown error')


Now let me add the "History" button to the header and the history panel. First, let me find where the header buttons are:


180→          <button
   181→            onClick={onBack}
   182→            className="flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors"
   183→          >
   184→            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
   185→              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
   186→            </svg>
   187→            Back
   188→          </button>
   189→
Spec Editor

   190→          {spec && (
   191→
   192→              Version {spec.version}
   193→
   194→          )}
   195→          {hasChanges && (
   196→
   197→              Unsaved changes
   198→
   199→          )}
   200→          {showSaveSuccess && (
   201→
   202→              Saved successfully
   203→
   204→          )}
   205→

   206→

   207→          {hasChanges && (
   208→            <button
   209→              onClick={() => setShowDiff(!showDiff)}
   210→              className={`px-3 py-1.5 text-sm border rounded transition-colors ${
   211→                showDiff
   212→                  ? 'border-accent text-accent bg-accent/10'
   213→                  : 'border-border hover:bg-surface-elevated'
   214→              }`}
   215→            >
   216→              {showDiff ? 'Hide Diff' : 'Show Diff'}
   217→            </button>
   218→          )}
   219→          <button
   220→            onClick={handleReset}
   221→            disabled={!hasChanges || saving}
   222→            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
   223→          >
   224→            Reset
   225→          </button>
   226→          <button
   227→            onClick={handleSave}
   228→            disabled={!hasChanges || saving}
   229→            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

<button
            onClick={onBack}
            className="flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
            </svg>
            Back
          </button>

Spec Editor

          {spec && (

              Version {spec.version}

          )}
          {hasChanges && (

              Unsaved changes

          )}
          {showSaveSuccess && (

              Saved successfully

          )}

          {hasChanges && (
            <button
              onClick={() => setShowDiff(!showDiff)}
              className={`px-3 py-1.5 text-sm border rounded transition-colors ${
                showDiff
                  ? 'border-accent text-accent bg-accent/10'
                  : 'border-border hover:bg-surface-elevated'
              }`}
            >
              {showDiff ? 'Hide Diff' : 'Show Diff'}
            </button>
          )}
          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>
          <button
            onClick={handleSave}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"


Let me add the History button to the header:


        <div className="flex items-center gap-2">
          {hasChanges && (
            <button
              onClick={() => setShowDiff(!showDiff)}
              className={`px-3 py-1.5 text-sm border rounded transition-colors ${
                showDiff
                  ? 'border-accent text-accent bg-accent/10'
                  : 'border-border hover:bg-surface-elevated'
              }`}
            >
              {showDiff ? 'Hide Diff' : 'Show Diff'}
            </button>
          )}
          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>

        <div className="flex items-center gap-2">
          <button
            onClick={handleShowHistory}
            className={`px-3 py-1.5 text-sm border rounded transition-colors ${
              showHistory
                ? 'border-accent text-accent bg-accent/10'
                : 'border-border hover:bg-surface-elevated'
            }`}
          >
            {showHistory ? 'Hide History' : 'History'}
          </button>
          {hasChanges && (
            <button
              onClick={() => setShowDiff(!showDiff)}
              className={`px-3 py-1.5 text-sm border rounded transition-colors ${
                showDiff
                  ? 'border-accent text-accent bg-accent/10'
                  : 'border-border hover:bg-surface-elevated'
              }`}
            >
              {showDiff ? 'Hide Diff' : 'Show Diff'}
            </button>
          )}
          <button
            onClick={handleReset}
            disabled={!hasChanges || saving}
            className="px-3 py-1.5 text-sm border border-border rounded hover:bg-surface-elevated transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Reset
          </button>