diff --git a/AGENT.md b/AGENT.md
new file mode 100644
index 0000000..5e140e6
--- /dev/null
+++ b/AGENT.md
@@ -0,0 +1,103 @@
+# AGENT.md — Patchy Codegen Agent Contract (Python 3.11, PyQt6)
+
+> Single source of truth for converting codegen-ready pseudocode in `/modules/*.pseudocode.md` into production Python in `/src/` with tests in `/tests/`.
+> This file is loaded for every Codex Cloud task.
+
+## 0. Operating Mode
+- **Language**: Python 3.11
+- **UI stack**: PyQt6 (widgets; no async GUI frameworks)
+- **Test**: pytest
+- **Lint/Format**: ruff (`ruff check .` and `ruff format --check .`)
+- **Filesystem layout**:
+ - Input pseudocode: `/modules/{slug}.pseudocode.md`
+ - Output code: `/src/{path}.py` (exact path comes from META.target_file)
+ - Output tests: `/tests/test_{slug}.py`
+
+## 1. Pseudocode DSL the agent must follow
+The source files contain this structure (sections are mandatory unless noted):
+- ` ... ` — machine-readable. Must be parsed and used verbatim.
+ - Keys: `slug`, `target_file`, `language`, `runtime.python`, `index_base`, `newline`, `dependencies`, `acceptance.lint`, `acceptance.tests`
+- `## PURPOSE` — produce a module that does exactly this and nothing more.
+- `## SCOPE` — enforce **in-scope** and reject **out-of-scope** behavior.
+- `## IMPORTS - ALLOWED ONLY` — only import from these modules. Prefer absolute imports under `src/` layout (e.g., `from core.contracts import ...`) mapped to package root.
+- `## CONSTANTS` — copy verbatim; do not modify names or values.
+- `## TYPES - USE ONLY SHARED TYPES` — use types from `core.contracts` where referenced.
+- `## INTERFACES` — treat each bullet as a concrete signature; implement exactly. Respect pre/post/error contracts.
+- `## STATE` — implement state exactly as specified; no hidden globals.
+- `## THREADING` — adhere to policy. UI thread only mutates widgets; heavy work in worker threads.
+- `## I/O` — only the specified inputs/outputs/side effects. Newline policy: normalize to **LF** on read; write **LF**; preserve final newline.
+- `## LOGGING` — use `logging.getLogger("patchy")`. Log INFO at start, WARNING for recoverable issues, ERROR before raising exceptions.
+- `## ALGORITHM`, `### EDGE CASES`, `## ERRORS`, `## COMPLEXITY`, `## PERFORMANCE`, `## TESTS - ACCEPTANCE HOOKS`, `## EXTENSIBILITY`, `## NON-FUNCTIONAL` — implement and honor all items.
+
+## 2. Global Conventions (enforced)
+- **Dataclasses**: use Python `@dataclass` for `FilePatch`, `Hunk`, `HunkLine`, `ApplyResult` (declared in `core.contracts`).
+- **Exceptions**: raise only from the hierarchy (`PatchyError`, `ParseError`, `ApplyError`, `ValidationError`, `IOErrorCompat`).
+- **Indices**: all line indices are **0-based** (`INDEX_BASE=0`). Document in code.
+- **EOL**: read-normalize to LF; write LF; preserve trailing newline where applicable.
+- **Determinism**: no randomness, no time-sensitive formatting in code paths under test.
+- **Threading**: UI widgets and signals on GUI thread; background work via `concurrent.futures.ThreadPoolExecutor` with marshaling back to the UI thread using Qt queued calls (`QMetaObject.invokeMethod`/`QTimer.singleShot` or an equivalent signal bridge). Never block the UI thread with long work.
+- **Logging**: do not print; use `logging`. Include start/warn/error markers noted in pseudocode.
+- **No hidden dependencies**: only imports listed under `IMPORTS - ALLOWED ONLY`.
+- **No API drift**: function/class names and signatures must match pseudocode exactly.
+
+## 3. File and Package Mapping
+- Treat `src/` as the project root package. Example: `src/core/contracts.py` is importable as `core.contracts`.
+- Do not create new top-level packages or nested packages not implied by `target_file`.
+- For each pseudocode file:
+ - Write exactly **one** `.py` module to `META.target_file`.
+ - Write exactly **one** test file to `/tests/test_{slug}.py`.
+
+## 4. Test Generation Rules
+- Convert **## TESTS - ACCEPTANCE HOOKS** bullets into concrete `pytest` tests.
+- Use deterministic fixtures (small inline content). Do not hit the network or filesystem unless allowed by the module’s I/O.
+- For UI tests, focus on logic-level verification (signals emitted, function outputs) rather than rendering.
+- Ensure full test isolation and idempotence. No global state leaks across tests.
+- Respect linting: generated tests must also pass ruff.
+
+## 5. Module-Specific Guidance (common patterns)
+- **core.contracts**: define dataclasses, constants (e.g., `SKIP_PREFIXES`, `UNIFIED_HUNK_HEADER_REGEX`, `CONTEXT_HUNK_HEADER_REGEX`, `INDEX_BASE`, `NEWLINE_POLICY`), exception classes, and a `get_logger()` helper returning `logging.getLogger("patchy")` configured once.
+- **core.diff_parser**: implement exact unified/context diff grammar using constants; build `FilePatch`/`Hunk`/`HunkLine`; keep order stable; strict validation; no filesystem writes.
+- **core.diff_applier**: implement strict-and-fuzzy application with bounded search; produce `ApplyResult` with `added_lines`, `removed_original_indices`, and `origin_map`; enforce 0-based, sorted indices.
+- **utils.state**: JSON-only, atomic writes (temp + replace), schema-lite validation per key; no YAML.
+- **utils.theme**: single source of palette; publish changes via callbacks or a small signal bridge (no OS polling here).
+- **ui.code_editor**: define explicit signals `on_content_changed(str)`, `on_cursor_moved(int,int)`, `on_scroll(int,int)`; validate indices; LF-only content.
+- **ui.highlighters**: consume palette from Theme; batch large updates; no OS detection.
+- **ui.navigation**: compute contiguous blocks from `ApplyResult.added_lines` and `.removed_original_indices`; provide wrap-around next/prev.
+- **ui.main_window**: orchestrate collaborators; all heavy work in workers; safe error surfaces; save/restore state; connect/disconnect signals cleanly.
+- **app.entry_point**: parse CLI, bootstrap logging, create `QApplication` before widgets, safe shutdown with exit code.
+
+## 6. Failure Policy
+- If a pseudocode section contradicts another, prefer: CONSTANTS > INTERFACES > ALGORITHM > SCOPE > PURPOSE.
+- If a section is missing, **fail the task** with a clear explanation rather than inventing behavior.
+- If allowed imports are insufficient to satisfy the interfaces, fail with a deterministic list of missing imports.
+
+## 7. Output Requirements
+- The generated module file must be **complete and runnable** without manual edits.
+- Every public function/class must have a short docstring reflecting pre/post/error conditions.
+- Include `from __future__ import annotations` where useful.
+- **All** logging, warnings, and significant actions must be issued via the module logger.
+- Tests must pass: run `pytest -q` for the specific file and ensure `ruff check .` passes.
+
+## 8. Command Map (what Codex Cloud should run)
+- Lint: `python -m ruff check . && python -m ruff format --check .`
+- Tests (single module): `pytest -q tests/test_{slug}.py`
+- Full suite (optional): `pytest -q`
+
+## 9. Examples of transforming acceptance hooks to tests
+Given a hook: `- assert len(result.origin_map) == len(result.text.splitlines())`
+Produce a pytest test:
+```python
+def test_origin_map_length_matches_output(apply_sample):
+ result = apply_sample()
+ assert len(result.origin_map) == len(result.text.splitlines())
+```
+
+## 10. Prohibited Behaviors
+- No third-party dependencies not listed in `IMPORTS - ALLOWED ONLY`.
+- No network or disk access outside declared I/O.
+- No random sleeps, time-based variability, or OS-specific assumptions without guards.
+- No mutation of other modules unless explicitly allowed by SCOPE.
+
+---
+
+**End of AGENT.md.** The agent must treat this document as normative and refuse ambiguous generation rather than guessing.
\ No newline at end of file
diff --git a/modules/app.entry_point.pseudocode.md b/modules/app.entry_point.pseudocode.md
index 7493c02..67df07f 100644
--- a/modules/app.entry_point.pseudocode.md
+++ b/modules/app.entry_point.pseudocode.md
@@ -1,37 +1,122 @@
-# Application Entry Point Pseudocode
-
-## main()
-1. CREATE QApplication
-2. SET application metadata:
- - Name: "Patchy"
- - Version: "2.0.0"
- - Organization: "Patchy Project"
-3. INITIALIZE exception handler
-4. CREATE MainWindow
-5. HANDLE command line arguments:
- - IF file path provided:
- - LOAD file
- - IF diff path provided:
- - LOAD diff
-6. SHOW main window
-7. START event loop
-8. ON exit:
- - SAVE state
- - CLEANUP resources
-
-## Exception Handler
-1. SET global exception handler
-2. ON unhandled exception:
- - LOG to console
- - SHOW error dialog
- - SAVE crash report
- - GRACEFUL exit
-
-## CLI Arguments
-1. PARSE arguments:
- - --file: Open specific file
- - --diff: Open specific diff
- - --folder: Open specific folder
- - --theme: Force theme (light/dark)
- - --debug: Enable debug mode
-2. APPLY parsed arguments
\ No newline at end of file
+# app.entry_point - Codegen-Ready Pseudocode Template
+
+
+
+{
+ "slug": "app.entry_point",
+ "target_file": "src/main.py",
+ "language": "python",
+ "runtime": {
+ "python": "3.11"
+ },
+ "index_base": 0,
+ "newline": "LF",
+ "dependencies": [
+ "core.contracts"
+ ],
+ "acceptance": {
+ "lint": [
+ "ruff check .",
+ "ruff format --check ."
+ ],
+ "tests": [
+ "pytest -q"
+ ]
+ }
+}
+
+
+## PURPOSE
+- Start application: parse CLI, initialize logging, create main window, run event loop with graceful shutdown.
+
+## SCOPE
+- In-scope:
+ - CLI parse
+ - Global exception handler
+ - Startup banner
+ - Exit code propagation
+- Out-of-scope:
+ - Business logic
+ - Heavy computation
+
+## IMPORTS - ALLOWED ONLY
+
+- - import sys
+- - import argparse
+- - from core.contracts import PatchyError
+- from PyQt6.QtWidgets import QApplication
+
+## CONSTANTS
+- APP_NAME = 'Patchy'
+
+## TYPES - USE ONLY SHARED TYPES
+
+- Uses: FilePatch, Hunk, HunkLine, ApplyResult, ParseError, ApplyError // from core.contracts
+
+## INTERFACES
+- def main(argv: list[str]) -> int
+ - pre: argv is list[str]
+ - post: returns 0 on success else non-zero
+ - errors: none
+
+
+## STATE
+- none: stateless
+
+## THREADING
+- ui_thread_only: true
+- worker_policy: none
+- handoff: n/a
+
+## I/O
+- inputs: ["argv"]
+- outputs: ["exit code"]
+- encoding: utf-8
+- atomic_write: false // temp file + replace
+
+## LOGGING
+- logger: patchy
+- on_start: INFO "start app.entry_point"
+- on_warn: WARNING "condition"
+- on_error: ERROR "condition raises ErrorType"
+
+## ALGORITHM
+1) Parse CLI args
+2) Initialize logging
+3) Create QApplication and MainWindow
+4) Run event loop
+5) Catch top-level exceptions, log, set exit code
+
+### EDGE CASES
+- - No args → default behavior
+- - Unknown arg → exit with usage 2
+101) Create QApplication before constructing MainWindow
+
+## ERRORS
+- - ValidationError on bad CLI combinations
+
+## COMPLEXITY
+- time: O(1)
+- memory: O(1)
+- notes: none
+
+## PERFORMANCE
+- max input size: n/a
+- max iterations: n/a
+- timeout: none
+- instrumentation:
+- record startup time
+
+## TESTS - ACCEPTANCE HOOKS
+- assert exit code semantics respected
+
+## EXTENSIBILITY
+- - Subcommands can be added later
+
+## NON-FUNCTIONAL
+- security: no elevated privileges
+- i18n: UTF-8
\ No newline at end of file
diff --git a/modules/core.contracts.pseudocode.md b/modules/core.contracts.pseudocode.md
new file mode 100644
index 0000000..7b0d9e4
--- /dev/null
+++ b/modules/core.contracts.pseudocode.md
@@ -0,0 +1,126 @@
+# core.contracts - Codegen-Ready Pseudocode Template
+
+
+
+{
+ "slug": "core.contracts",
+ "target_file": "src/core/contracts.py",
+ "language": "python",
+ "runtime": {
+ "python": "3.11"
+ },
+ "index_base": 0,
+ "newline": "LF",
+ "dependencies": [
+ "core.contracts"
+ ],
+ "acceptance": {
+ "lint": [
+ "ruff check .",
+ "ruff format --check ."
+ ],
+ "tests": [
+ "pytest -q"
+ ]
+ }
+}
+
+
+## PURPOSE
+- Provide shared dataclasses, exceptions, constants, logging bootstrap for Patchy modules.
+
+## SCOPE
+- In-scope:
+ - Define FilePatch, Hunk, HunkLine, ApplyResult dataclasses
+ - Provide exception hierarchy: PatchyError, ParseError, ApplyError, ValidationError, IOErrorCompat
+ - Expose constants: SKIP_PREFIXES, UNIFIED_HUNK_HEADER_REGEX, CONTEXT_HUNK_HEADER_REGEX, INDEX_BASE, NEWLINE_POLICY
+ - Initialize module logger 'patchy'
+- Out-of-scope:
+ - Business logic
+ - Qt widgets
+ - Disk I/O beyond reading config
+
+## IMPORTS - ALLOWED ONLY
+
+- - from dataclasses import dataclass
+- - from typing import Optional, List, Tuple, Dict, Any
+- - import logging
+
+## CONSTANTS
+- SKIP_PREFIXES = ["diff --git ", "index ", "new file mode ", "deleted file mode ", "--- ", "+++ ", "*** ", "rename from ", "rename to ", "similarity index ", "Binary files "]
+- UNIFIED_HUNK_HEADER_REGEX = "^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@.*$"
+- CONTEXT_HUNK_HEADER_REGEX = "^\*\*\* (\d+),(\d+) \*\*\*\*$"
+- INDEX_BASE = 0
+
+## TYPES - USE ONLY SHARED TYPES
+
+- Uses: FilePatch, Hunk, HunkLine, ApplyResult, ParseError, ApplyError // from core.contracts
+
+## INTERFACES
+- class Contracts: ...
+ - pre: none
+ - post: types and constants are importable
+ - errors: ValidationError on inconsistent constants
+
+
+## STATE
+- none: stateless
+
+## THREADING
+- ui_thread_only: false
+- worker_policy: none
+- handoff: callback
+
+## I/O
+- inputs: ["importers"]
+- outputs: ["dataclasses", "exceptions", "constants"]
+- encoding: utf-8
+- atomic_write: false // temp file + replace
+
+## LOGGING
+- logger: patchy
+- on_start: INFO "start core.contracts"
+- on_warn: WARNING "condition"
+- on_error: ERROR "condition raises ErrorType"
+
+## ALGORITHM
+1) Define dataclasses and exceptions
+2) Define constants and export in __all__
+3) Configure logger with INFO level by default
+4) Return nothing
+
+### EDGE CASES
+- - Constants values must be import-safe strings or lists
+
+## ERRORS
+- - ValidationError when constants conflict (e.g., index_base not 0)
+
+## COMPLEXITY
+- time: O(1)
+- memory: O(1)
+- notes: none
+
+## PERFORMANCE
+- max input size: n/a
+- max iterations: n/a
+- timeout: none
+- instrumentation:
+- logging configured exactly once
+
+## TESTS - ACCEPTANCE HOOKS
+- assert types importable
+- assert logger name equals 'patchy'
+- assert INDEX_BASE == 0
+- assert isinstance(SKIP_PREFIXES, list) and 'rename from ' in SKIP_PREFIXES and 'deleted file mode ' in SKIP_PREFIXES
+
+## EXTENSIBILITY
+- - New constants can be added without breaking imports
+
+## NON-FUNCTIONAL
+- security: no secrets
+- i18n: UTF-8
+- compliance: no telemetry
\ No newline at end of file
diff --git a/modules/core.diff_applier.pseudocode.md b/modules/core.diff_applier.pseudocode.md
index 10e2320..ff9ee9c 100644
--- a/modules/core.diff_applier.pseudocode.md
+++ b/modules/core.diff_applier.pseudocode.md
@@ -1,80 +1,138 @@
-# Diff Applier Pseudocode
-
-## Main Function: apply_patch
-1. INPUT: original_text (string), file_patch (FilePatch)
-2. SPLIT original_text into lines
-3. INITIALIZE:
- - result_lines = copy of original_lines
- - added_lines = empty list
- - removed_original_indices = empty list
- - line_bias = 0
- - origin_map = [0, 1, 2, ...] # Maps result index to original index
-4. FOR each hunk in file_patch.hunks:
- - guess_index = calculate_guess_index(hunk, line_bias)
- - anchor_index = find_anchor_index(result_lines, hunk, guess_index)
- - IF anchor_index is None:
- - RAISE ApplyError("Cannot locate hunk")
- - VERIFY hunk matches at anchor_index:
- - IF verification fails:
- - RAISE ApplyError("Context mismatch")
- - APPLY hunk:
- - FOR each hunk_line in hunk.lines:
- - IF hunk_line.kind == ' ':
- - IF hunk_line.text is empty:
- - SKIP any blank lines
- - ELSE:
- - ADVANCE cursor
- - ELIF hunk_line.kind == '-':
- - RECORD original index from origin_map
- - DELETE line from result_lines
- - DELETE from origin_map
- - DECREMENT line_bias
- - ELIF hunk_line.kind == '+':
- - INSERT line into result_lines
- - INSERT None into origin_map
- - ADD current index to added_lines
- - INCREMENT line_bias
-5. RETURN ApplyResult(joined_lines, added_lines, sorted(removed_original_indices))
-
-## Function: find_anchor_index
-1. INPUT: lines, hunk, guess_index
-2. consuming_lines = filter hunk.lines for [' ', '-'] types
-3. IF no consuming_lines:
- - RETURN max(0, min(length(lines), guess_index))
-4. min_needed = calculate_min_needed(consuming_lines)
-5. max_start = max(0, length(lines) - min_needed)
-6. guess = clamp(guess_index, 0, max_start)
-7. IF hunk_matches_at(lines, consuming_lines, guess):
- - RETURN guess
-8. TRY fuzzy search within fuzzy_context of guess
-9. IF found, RETURN found index
-10. TRY global scan of entire file
-11. IF found, RETURN found index
-12. RETURN None
-
-## Function: hunk_matches_at
-1. INPUT: lines, consuming_lines, start_pos
-2. cursor = start_pos
-3. FOR each line in consuming_lines:
- - IF line.kind == ' ':
- - IF line.text is empty:
- - SKIP any blank lines
- - ELSE:
- - IF cursor >= length(lines) OR lines[cursor] != line.text:
- - RETURN False
- - cursor += 1
- - ELIF line.kind == '-':
- - IF cursor >= length(lines) OR lines[cursor] != line.text:
- - RETURN False
- - cursor += 1
-4. RETURN True
-
-## Function: calculate_min_needed
-1. INPUT: consuming_lines
-2. needed = 0
-3. FOR each line in consuming_lines:
- - IF line.kind == '-':
- - needed += 1
- - ELIF line.kind == ' ' AND line.text != '':
- - needed += 1
-4. RETURN needed
\ No newline at end of file
+# core.diff_applier - Codegen-Ready Pseudocode Template
+
+
+
+{
+ "slug": "core.diff_applier",
+ "target_file": "src/core/diff_applier.py",
+ "language": "python",
+ "runtime": {
+ "python": "3.11"
+ },
+ "index_base": 0,
+ "newline": "LF",
+ "dependencies": [
+ "core.contracts"
+ ],
+ "acceptance": {
+ "lint": [
+ "ruff check .",
+ "ruff format --check ."
+ ],
+ "tests": [
+ "pytest -q"
+ ]
+ }
+}
+
+
+## PURPOSE
+- Apply a parsed FilePatch to original text using strict or fuzzy anchoring and report line mappings.
+
+## SCOPE
+- In-scope:
+ - Strict application of hunks
+ - Fuzzy recovery within bounded window
+ - Origin map generation
+- Out-of-scope:
+ - Parsing diffs
+ - UI rendering
+
+## IMPORTS - ALLOWED ONLY
+
+- - from core.contracts import FilePatch, Hunk, ApplyResult, ApplyError
+- - from typing import List, Optional
+
+## CONSTANTS
+- INDEX_BASE = 0
+
+## TYPES - USE ONLY SHARED TYPES
+
+- Uses: FilePatch, Hunk, HunkLine, ApplyResult, ParseError, ApplyError // from core.contracts
+
+## INTERFACES
+- def apply(original: str, patch: FilePatch, strict: bool = True) -> ApplyResult
+ - pre: original LF normalized; patch well-formed
+ - post: returns ApplyResult; origin_map length equals output line count
+ - errors: ApplyError on context mismatch
+- def preview(original: str, patch: FilePatch) -> ApplyResult
+ - pre: same as apply
+ - post: returns ApplyResult without writing
+ - errors: none
+- def calculate_guess_index(hunk: Hunk, prior_offset: int) -> int
+ - pre: prior_offset is int
+ - post: returns non-negative int
+ - errors: ApplyError on negative result
+- def fuzzy_context() -> int
+ - pre: none
+ - post: returns default fuzz window in lines
+ - errors: none
+- ApplyResult fields are 0-based and sorted: added_lines, removed_original_indices
+
+## STATE
+- window_bias: int - current guessed offset
+
+## THREADING
+- ui_thread_only: false
+- worker_policy: none
+- handoff: callback
+
+## I/O
+- inputs: ["original string", "FilePatch"]
+- outputs: ["ApplyResult"]
+- encoding: utf-8
+- atomic_write: false // temp file + replace
+
+## LOGGING
+- logger: patchy
+- on_start: INFO "start core.diff_applier"
+- on_warn: WARNING "condition"
+- on_error: ERROR "condition raises ErrorType"
+
+## ALGORITHM
+1) Split original into lines
+2) For each hunk, locate anchor line using strict match else fuzzy within window
+3) Apply additions and deletions, updating running offset
+4) Track added_lines and removed_original_indices (0-based)
+5) Build origin_map mapping output line to original or null
+6) Return ApplyResult
+
+### EDGE CASES
+- - Empty patch → return original unchanged with empty deltas
+- - Overlapping hunks → raise ApplyError
+- - Excess fuzz window → clamp to limit
+
+## ERRORS
+- - ApplyError when context cannot be located
+- - ValidationError when patch inconsistent
+
+## COMPLEXITY
+- time: O(n + m)
+- memory: O(n + m)
+- notes: none
+
+## PERFORMANCE
+- max input size: 50MB
+- max iterations: bounded by window per hunk
+- timeout: none
+- instrumentation:
+- count fuzzy attempts
+- measure per-hunk search time
+
+## TESTS - ACCEPTANCE HOOKS
+- assert origin_map length equals output lines
+- assert removed_original_indices sorted and unique
+- assert sorted(result.removed_original_indices) == result.removed_original_indices
+- assert len(result.origin_map) == len(result.text.splitlines())
+
+## EXTENSIBILITY
+- - Alternative search strategies can be injected later
+
+## NON-FUNCTIONAL
+- security: pure in-memory
+- i18n: UTF-8
+- compliance: no telemetry
\ No newline at end of file
diff --git a/modules/core.diff_parser.pseudocode.md b/modules/core.diff_parser.pseudocode.md
index 4b0993c..c2d82a3 100644
--- a/modules/core.diff_parser.pseudocode.md
+++ b/modules/core.diff_parser.pseudocode.md
@@ -1,93 +1,134 @@
-# Diff Parser Pseudocode
-
-## Main Function: parse_diff_content
-1. INPUT: diff_content (string)
-2. IF diff_content is empty or only whitespace:
- RETURN empty list
-3. SPLIT diff_content into lines
-4. INITIALIZE:
- - patches = empty list
- - current_patch = None
- - index = 0
-5. WHILE index < length(lines):
- - line = lines[index]
- - IF line should be skipped (matches skip prefixes):
- - index += 1
- - CONTINUE
- - IF line starts file header:
- - patch = parse_file_header(lines, index)
- - IF patch is valid:
- - APPEND patch to patches
- - SET current_patch = patch
- - index += 2 # Skip header pair
- - CONTINUE
- - IF line starts hunk header ('@@'):
- - IF current_patch is None:
- - RAISE ParseError("Hunk before file header")
- - hunk, consumed = parse_hunk(lines, index)
- - APPEND hunk to current_patch.hunks
- - index += consumed
- - CONTINUE
- - index += 1
-6. RETURN patches
-
-## Function: parse_file_header
-1. INPUT: lines, start_index
-2. IF start_index + 1 >= length(lines):
- RETURN None
-3. DETECT header style:
- - IF line matches unified pattern ('---'):
- - old_path = extract path after '---'
- - EXPECT next line starts with '+++'
- - new_path = extract path after '+++'
- - ELIF line matches context pattern ('***'):
- - old_path = extract path after '***'
- - EXPECT next line starts with '---'
- - new_path = extract path after '---'
- - ELSE:
- RETURN None
-4. CLEAN both paths:
- - Remove timestamp after tab
- - Remove 'a/' or 'b/' prefixes
- - Strip whitespace
-5. RETURN FilePatch(old_path, new_path, [])
-
-## Function: parse_hunk
-1. INPUT: lines, start_index
-2. header_line = lines[start_index]
-3. PARSE header using regex:
- - Extract old_start (default 1)
- - Extract old_len (default 0)
- - Extract new_start (default 1)
- - Extract new_len (default 0)
-4. CREATE hunk object with parsed values
-5. index = start_index + 1
-6. WHILE index < length(lines):
- - line = lines[index]
- - IF line starts new section:
- BREAK
- - IF line is empty:
- - ADD HunkLine(' ', '')
- - ELIF first character is in [' ', '+', '-']:
- - ADD HunkLine(kind, line[1:])
- - ELIF line is '\\ No newline...':
- - SKIP
- - ELSE:
- - RAISE ParseError("Invalid hunk line")
- - index += 1
-7. RETURN (hunk, index - start_index)
-
-## Function: should_skip_line
-1. INPUT: line
-2. FOR each skip_prefix in skip_prefixes:
- - IF line starts with skip_prefix:
- RETURN True
-3. RETURN False
-
-## Function: clean_path
-1. INPUT: path_string
-2. REMOVE everything after tab character
-3. IF path starts with 'a/' or 'b/':
- - REMOVE first 2 characters
-4. STRIP whitespace
-5. RETURN cleaned path
\ No newline at end of file
+# core.diff_parser - Codegen-Ready Pseudocode Template
+
+
+
+{
+ "slug": "core.diff_parser",
+ "target_file": "src/core/diff_parser.py",
+ "language": "python",
+ "runtime": {
+ "python": "3.11"
+ },
+ "index_base": 0,
+ "newline": "LF",
+ "dependencies": [
+ "core.contracts"
+ ],
+ "acceptance": {
+ "lint": [
+ "ruff check .",
+ "ruff format --check ."
+ ],
+ "tests": [
+ "pytest -q"
+ ]
+ }
+}
+
+
+## PURPOSE
+- Parse unified or context diff text into structured FilePatch and Hunk objects.
+
+## SCOPE
+- In-scope:
+ - Parse headers, file sections, and hunks
+ - Skip noise lines using SKIP_PREFIXES
+ - Normalize EOL to LF
+- Out-of-scope:
+ - Applying patches
+ - Filesystem writes
+
+## IMPORTS - ALLOWED ONLY
+
+- - from core.contracts import FilePatch, Hunk, HunkLine, ParseError, UNIFIED_HUNK_HEADER_REGEX, CONTEXT_HUNK_HEADER_REGEX, SKIP_PREFIXES
+- - from typing import List, Tuple
+- - import re
+
+## CONSTANTS
+- UNIFIED_HUNK_HEADER_REGEX = "^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@.*$"
+- CONTEXT_HUNK_HEADER_REGEX = "^\*\*\* (\d+),(\d+) \*\*\*\*$"
+- SKIP_PREFIXES = ["diff --git ", "index ", "new file mode ", "deleted file mode ", "--- ", "+++ ", "*** ", "rename from ", "rename to ", "similarity index ", "Binary files "]
+- INDEX_BASE = 0
+
+## TYPES - USE ONLY SHARED TYPES
+
+- Uses: FilePatch, Hunk, HunkLine, ApplyResult, ParseError, ApplyError // from core.contracts
+
+## INTERFACES
+- def parse(content: str) -> list[FilePatch]
+ - pre: content is str; not None; LF normalized
+ - post: returns list of FilePatch; stable order by appearance
+ - errors: ParseError on grammar violation
+- def validate(content: str) -> tuple[bool, list[tuple[int,str]]]
+ - pre: content is str
+ - post: returns validity and sorted error list
+ - errors: none
+- def split_lines(content: str) -> list[str] - pre: content is str; post: LF-only lines; errors: none
+
+## STATE
+- line_no: int - current line index
+
+## THREADING
+- ui_thread_only: false
+- worker_policy: none
+- handoff: callback
+
+## I/O
+- inputs: ["diff content string"]
+- outputs: ["list[FilePatch]"]
+- encoding: utf-8
+- atomic_write: false // temp file + replace
+
+## LOGGING
+- logger: patchy
+- on_start: INFO "start core.diff_parser"
+- on_warn: WARNING "condition"
+- on_error: ERROR "condition raises ErrorType"
+
+## ALGORITHM
+1) Split content into lines with LF
+2) Iterate, skipping SKIP_PREFIXES and non-hunk noise
+3) Detect file boundaries and create FilePatch entries
+4) Match hunk headers via UNIFIED_HUNK_HEADER_REGEX or CONTEXT_HUNK_HEADER_REGEX
+5) Accumulate HunkLine with kinds ' ', '+', '-'
+6) Validate counts against header spans
+7) Return FilePatch list
+
+### EDGE CASES
+- - Empty content → return []
+- - Malformed header → raise ParseError with line number
+- - Unknown line kind → raise ParseError
+
+## ERRORS
+- - ParseError when grammar is violated
+
+## COMPLEXITY
+- time: O(n)
+- memory: O(n)
+- notes: none
+
+## PERFORMANCE
+- max input size: 50MB
+- max iterations: n
+- timeout: none
+- instrumentation:
+- count lines processed
+- record header matches
+
+## TESTS - ACCEPTANCE HOOKS
+- assert lines count equals sum of hunks
+- assert only kinds in {' ', '+', '-'}
+- assert re.compile(UNIFIED_HUNK_HEADER_REGEX)
+- assert all(k in {' ', '+', '-'} for fp in parse(sample) for h in fp.hunks for k in [ln.kind for ln in h.lines])
+
+## EXTENSIBILITY
+- - Add context-diff support without changing parse signature
+
+## NON-FUNCTIONAL
+- security: no file paths are executed
+- i18n: UTF-8 only
+- compliance: no telemetry
\ No newline at end of file
diff --git a/modules/ui.code_editor.pseudocode.md b/modules/ui.code_editor.pseudocode.md
index 1a56825..11a9ff2 100644
--- a/modules/ui.code_editor.pseudocode.md
+++ b/modules/ui.code_editor.pseudocode.md
@@ -1,46 +1,132 @@
-# Code Editor Widget Pseudocode
-
-## Initialization
-1. INHERIT from QPlainTextEdit
-2. SET monospace font
-3. CREATE LineNumberArea widget
-4. CONNECT signals:
- - blockCountChanged -> update_line_number_area_width
- - updateRequest -> update_line_number_area
-5. INITIALIZE line number area
-
-## Line Number Area
-1. CREATE custom QWidget
-2. OVERRIDE paintEvent:
- - GET first visible block
- - FOR each visible block:
- - DRAW line number
- - MOVE to next block
-3. OVERRIDE sizeHint:
- - CALCULATE width based on digit count
-
-## Methods
-
-### update_line_number_area_width
-1. CALCULATE required width:
- - GET max line count
- - COUNT digits
- - ADD padding
-2. SET viewport margins
-
-### update_line_number_area
-1. IF scroll delta provided:
- - SCROLL line number area
-2. ELSE:
- - UPDATE line number area
-3. IF viewport rect contains update rect:
- - UPDATE line number area width
-
-### line_number_area_paint_event
-1. CREATE painter for line number area
-2. FILL background with alternate base color
-3. GET first visible block
-4. WHILE block is valid and within paint rect:
- - IF block is visible:
- - DRAW line number
- - MOVE to next block
\ No newline at end of file
+# ui.code_editor - Codegen-Ready Pseudocode Template
+
+
+
+{
+ "slug": "ui.code_editor",
+ "target_file": "src/ui/code_editor.py",
+ "language": "python",
+ "runtime": {
+ "python": "3.11"
+ },
+ "index_base": 0,
+ "newline": "LF",
+ "dependencies": [
+ "core.contracts"
+ ],
+ "acceptance": {
+ "lint": [
+ "ruff check .",
+ "ruff format --check ."
+ ],
+ "tests": [
+ "pytest -q"
+ ]
+ }
+}
+
+
+## PURPOSE
+- Provide a text editor widget API with line navigation, highlights, and change notifications.
+
+## SCOPE
+- In-scope:
+ - Set/get content
+ - Scroll to line
+ - Highlight lines
+ - Emit signals on edits
+- Out-of-scope:
+ - Disk I/O
+ - Diff parsing
+
+## IMPORTS - ALLOWED ONLY
+
+- - from typing import List
+- - from core.contracts import ValidationError
+
+## CONSTANTS
+- HIGHLIGHT_LIMIT = 20000
+
+## TYPES - USE ONLY SHARED TYPES
+
+- Uses: FilePatch, Hunk, HunkLine, ApplyResult, ParseError, ApplyError // from core.contracts
+
+## INTERFACES
+- def set_content(content: str) -> None
+ - pre: content is str; LF normalized
+ - post: content stored; signals emitted
+ - errors: ValidationError on None
+- def get_content() -> str
+ - pre: content may be empty
+ - post: returns string
+ - errors: none
+- def scroll_to_line(line: int, centered: bool = True) -> None
+ - pre: line >= 0
+ - post: viewport moved
+ - errors: ValidationError if out of range
+- def highlight_lines(lines: list[int]) -> None
+ - pre: all >= 0
+ - post: applies highlight up to HIGHLIGHT_LIMIT
+ - errors: ValidationError on negatives
+- Signals: on_content_changed(str), on_cursor_moved(int,int), on_scroll(int,int)
+
+## STATE
+- content: str
+- highlights: list[int]
+
+## THREADING
+- ui_thread_only: true
+- worker_policy: none
+- handoff: queued signal
+
+## I/O
+- inputs: ["strings", "line indices"]
+- outputs: ["signals"]
+- encoding: utf-8
+- atomic_write: false // temp file + replace
+
+## LOGGING
+- logger: patchy
+- on_start: INFO "start ui.code_editor"
+- on_warn: WARNING "condition"
+- on_error: ERROR "condition raises ErrorType"
+
+## ALGORITHM
+1) Normalize input newlines to LF
+2) Set internal buffer
+3) Emit onContentChanged
+4) Scroll and highlight as requested with bounds checks
+
+### EDGE CASES
+- - Empty content → still valid
+- - Out-of-range line → ValidationError
+
+## ERRORS
+- - ValidationError on bad params
+
+## COMPLEXITY
+- time: O(n)
+- memory: O(n)
+- notes: none
+
+## PERFORMANCE
+- max input size: 10MB
+- max iterations: n
+- timeout: none
+- instrumentation:
+- count highlights applied
+
+## TESTS - ACCEPTANCE HOOKS
+- assert no ERROR logs during typical operations
+- assert set(['on_content_changed','on_cursor_moved','on_scroll']) == set(defined_signals)
+
+## EXTENSIBILITY
+- - Future syntax highlight strategies can inject color providers
+
+## NON-FUNCTIONAL
+- security: none
+- i18n: UTF-8 only
\ No newline at end of file
diff --git a/modules/ui.highlighters.pseudocode.md b/modules/ui.highlighters.pseudocode.md
index bf8a749..e71d038 100644
--- a/modules/ui.highlighters.pseudocode.md
+++ b/modules/ui.highlighters.pseudocode.md
@@ -1,48 +1,124 @@
-# Syntax Highlighters Pseudocode
-
-## DiffHighlighter (QSyntaxHighlighter)
-1. INITIALIZE with color palette
-2. DEFINE formats:
- - Added lines: green foreground
- - Removed lines: red foreground
- - Headers: blue foreground
-3. OVERRIDE highlightBlock:
- - IF line starts with '@@', '+++', '---', or '***':
- - APPLY header format
- - ELIF line starts with '+':
- - APPLY added format
- - ELIF line starts with '-':
- - APPLY removed format
-
-## PatchHighlighter (QSyntaxHighlighter)
-1. INITIALIZE with color palette
-2. DEFINE format:
- - Added lines: green background
-3. METHOD set_added_lines(indices):
- - STORE indices
- - REHIGHLIGHT
-4. OVERRIDE highlightBlock:
- - GET current line number
- - IF line in added indices:
- - APPLY added format
-
-## RemovedHighlighter (QSyntaxHighlighter)
-1. INITIALIZE with color palette
-2. DEFINE format:
- - Removed lines: red background (transparent)
-3. METHOD set_removed_lines(indices):
- - STORE indices
- - REHIGHLIGHT
-4. OVERRIDE highlightBlock:
- - GET current line number
- - IF line in removed indices:
- - APPLY removed format
-
-## Color Palette Detection
-1. DETECT if dark mode:
- - Windows: Check registry
- - Other: Check palette brightness
-2. IF dark mode:
- - SET dark color scheme
-3. ELSE:
- - SET light color scheme
\ No newline at end of file
+# ui.highlighters - Codegen-Ready Pseudocode Template
+
+
+
+{
+ "slug": "ui.highlighters",
+ "target_file": "src/ui/highlighters.py",
+ "language": "python",
+ "runtime": {
+ "python": "3.11"
+ },
+ "index_base": 0,
+ "newline": "LF",
+ "dependencies": [
+ "core.contracts"
+ ],
+ "acceptance": {
+ "lint": [
+ "ruff check .",
+ "ruff format --check ."
+ ],
+ "tests": [
+ "pytest -q"
+ ]
+ }
+}
+
+
+## PURPOSE
+- Render visual styles for diff and code regions based on Theme palette; no OS detection here.
+
+## SCOPE
+- In-scope:
+ - Apply styles for added, removed, headers, line numbers
+ - Batch updates to avoid jank
+- Out-of-scope:
+ - Theme detection
+ - File I/O
+
+## IMPORTS - ALLOWED ONLY
+
+- - from typing import List
+- - from core.contracts import ValidationError
+
+## CONSTANTS
+- BATCH_SIZE = 1000
+- DELAY_MS = 0
+
+## TYPES - USE ONLY SHARED TYPES
+
+- Uses: FilePatch, Hunk, HunkLine, ApplyResult, ParseError, ApplyError // from core.contracts
+
+## INTERFACES
+- def set_palette(palette: dict) -> None
+ - pre: required keys present: {'background','foreground','added','removed','header','lineNumber','selection'}
+ - post: palette stored
+ - errors: ValidationError on missing keys
+- def highlight_diff(lines: list[str]) -> None
+ - pre: lines is list[str]
+ - post: styles applied
+ - errors: none
+- def highlight_patch(added: list[int], removed: list[int]) -> None
+ - pre: indices >= 0
+ - post: styles applied
+ - errors: ValidationError on negatives
+
+
+## STATE
+- palette: dict
+
+## THREADING
+- ui_thread_only: true
+- worker_policy: none
+- handoff: queued signal
+
+## I/O
+- inputs: ["palette dict", "text lines", "indices"]
+- outputs: ["styled regions"]
+- encoding: utf-8
+- atomic_write: false // temp file + replace
+
+## LOGGING
+- logger: patchy
+- on_start: INFO "start ui.highlighters"
+- on_warn: WARNING "condition"
+- on_error: ERROR "condition raises ErrorType"
+
+## ALGORITHM
+1) Validate palette keys exist
+2) Apply styles in batches of BATCH_SIZE
+3) Optionally delay between batches via DELAY_MS
+
+### EDGE CASES
+- - Empty lines → no-op
+- - Missing palette key → ValidationError
+
+## ERRORS
+- - ValidationError on bad inputs
+
+## COMPLEXITY
+- time: O(n)
+- memory: O(1)
+- notes: none
+
+## PERFORMANCE
+- max input size: 200k lines
+- max iterations: n
+- timeout: none
+- instrumentation:
+- count batches
+
+## TESTS - ACCEPTANCE HOOKS
+- assert indices are 0-based and sorted
+
+## EXTENSIBILITY
+- - New token categories can be added by extending key map
+
+## NON-FUNCTIONAL
+- security: none
+- i18n: UTF-8
\ No newline at end of file
diff --git a/modules/ui.main_window.pseudocode.md b/modules/ui.main_window.pseudocode.md
index 45da4dd..d440772 100644
--- a/modules/ui.main_window.pseudocode.md
+++ b/modules/ui.main_window.pseudocode.md
@@ -1,115 +1,148 @@
-# Main Window Pseudocode
-
-## Initialization
-1. CREATE QApplication instance
-2. INITIALIZE ThemeManager
-3. INITIALIZE StateManager
-4. CREATE MainWindow instance
-5. SETUP UI:
- - CREATE central widget
- - CREATE toolbar
- - CREATE content area (3-panel splitter)
- - CONNECT all signals
-6. RESTORE saved state
-7. SHOW window
-8. START event loop
-
-## Toolbar Setup
-1. CREATE horizontal layout
-2. ADD buttons:
- - Open File button
- - Open Folder button
- - Open Diff button
- - Apply Patch button
- - Save As button
-3. ADD checkboxes:
- - Create backups
- - Live preview
-4. CONNECT button clicks to handlers
-
-## Content Area Setup
-1. CREATE horizontal splitter
-2. CREATE left panel:
- - QLabel "Files"
- - QListWidget for file list
-3. CREATE middle panel:
- - QLabel "Original"
- - CodeEditor widget
-4. CREATE right panel:
- - QTabWidget
- - ADD tabs:
- - Diff editor
- - Preview editor
- - Navigation widget
-5. SET splitter sizes [200, 600, 800]
-
-## Event Handlers
-
-### on_open_file
-1. OPEN file dialog
-2. IF file selected:
- - SET current_file
- - SET root_folder = None
- - LOAD file content into original editor
- - CLEAR file list
- - CLEAR preview
-
-### on_open_folder
-1. OPEN folder dialog
-2. IF folder selected:
- - SET root_folder
- - SET current_file = None
- - CLEAR editors
- - SCAN folder for files
-
-### on_open_diff
-1. OPEN file dialog for diff
-2. IF file selected:
- - LOAD diff content into diff editor
- - PARSE diff using DiffParser
- - UPDATE file list
- - SELECT first file
-
-### on_diff_changed
-1. GET diff content
-2. IF empty:
- - CLEAR file list
- - RETURN
-3. TRY:
- - PARSE diff using DiffParser
- - UPDATE file list
- - IF files exist:
- - SELECT first file
-4. CATCH ParseError:
- - SHOW warning dialog
-
-### on_file_selected
-1. GET selected patch from list item
-2. IF no patch:
- - RETURN
-3. LOAD original content:
- - TRY relative to root folder
- - TRY absolute path
-4. IF loaded:
- - SET original editor content
- - APPLY patch using DiffApplier
- - UPDATE preview editor
- - UPDATE navigation
-
-## State Management
-
-### save_state
-1. CREATE state dict:
- - window_size
- - window_position
- - root_folder
- - current_file
- - splitter_sizes
-2. CALL StateManager.save(state)
-
-### restore_state
-1. CALL StateManager.load()
-2. IF state exists:
- - RESTORE window size
- - RESTORE window position
- - RESTORE splitter sizes
\ No newline at end of file
+# ui.main_window - Codegen-Ready Pseudocode Template
+
+
+
+{
+ "slug": "ui.main_window",
+ "target_file": "src/ui/main_window.py",
+ "language": "python",
+ "runtime": {
+ "python": "3.11"
+ },
+ "index_base": 0,
+ "newline": "LF",
+ "dependencies": [
+ "core.contracts"
+ ],
+ "acceptance": {
+ "lint": [
+ "ruff check .",
+ "ruff format --check ."
+ ],
+ "tests": [
+ "pytest -q"
+ ]
+ }
+}
+
+
+## PURPOSE
+- Assemble main UI, wire signals, orchestrate file open, diff parse/apply, navigation, and state/theme integration.
+
+## SCOPE
+- In-scope:
+ - Toolbar actions
+ - Split panes
+ - Open file/folder/diff
+ - Apply patch
+ - Persist and restore state
+- Out-of-scope:
+ - Implement diff parsing or applying logic inside
+ - Network access
+
+## IMPORTS - ALLOWED ONLY
+
+- - from core.contracts import PatchyError
+- - from typing import Optional
+- from core.diff_parser import parse
+- from core.diff_applier import apply
+- from utils.state import load, save
+- from utils.theme import current, palette
+- from ui.code_editor import set_content, get_content, scroll_to_line, highlight_lines
+- from ui.highlighters import set_palette, highlight_diff, highlight_patch
+- from ui.navigation import analyze_changes, next_change, prev_change
+
+## CONSTANTS
+- SPLITTER_DEFAULTS = [300, 400, 300]
+
+## TYPES - USE ONLY SHARED TYPES
+
+- Uses: FilePatch, Hunk, HunkLine, ApplyResult, ParseError, ApplyError // from core.contracts
+
+## INTERFACES
+- def init_ui() -> None
+ - pre: called once
+ - post: widgets constructed and connected
+ - errors: none
+- def load_state() -> None
+ - pre: state accessible
+ - post: restore splitter sizes and theme
+ - errors: none
+- def save_state() -> None
+ - pre: on close
+ - post: persist state
+ - errors: none
+- def on_open_file(path: str) -> None
+ - pre: path exists
+ - post: file loaded and editors updated
+ - errors: IOErrorCompat on fail
+- def on_apply_diff() -> None
+ - pre: diff present
+ - post: apply result updates preview and nav
+ - errors: ApplyError on failure
+
+
+## STATE
+- widgets: dict - references to editors and panels
+
+## THREADING
+- ui_thread_only: true
+- worker_policy: threadpool
+- handoff: queued signal
+
+## I/O
+- inputs: ["user actions", "paths"]
+- outputs: ["signals", "editor content updates"]
+- encoding: utf-8
+- atomic_write: false // temp file + replace
+
+## LOGGING
+- logger: patchy
+- on_start: INFO "start ui.main_window"
+- on_warn: WARNING "condition"
+- on_error: ERROR "condition raises ErrorType"
+
+## ALGORITHM
+1) Construct widgets and connect signals
+2) Wire state load and theme hookup
+3) Handle file open and update editors
+4) Invoke parser and applier in worker then post results to UI
+5) Persist state on exit
+
+### EDGE CASES
+- - Missing file → show error
+- - Parse error → show message and keep state stable
+- - Apply error → revert preview
+
+## ERRORS
+- - IOErrorCompat, ParseError, ApplyError
+
+## COMPLEXITY
+- time: O(1) per event
+- memory: O(1) steady aside from loaded files
+- notes: none
+
+## PERFORMANCE
+- max input size: files up to 50MB
+- max iterations: n/a
+- timeout: none
+- instrumentation:
+- count UI events
+- latency per apply
+
+## TESTS - ACCEPTANCE HOOKS
+- assert no ERROR logs during normal operations
+- assert call order: open -> parse -> apply -> navigation -> editors updated
+- assert no ERROR logs on success path
+
+## EXTENSIBILITY
+- - New panes can be added via widget registry
+
+## NON-FUNCTIONAL
+- security: path validation
+- i18n: UTF-8 only
+- compliance: no telemetry
\ No newline at end of file
diff --git a/modules/ui.navigation.pseudocode.md b/modules/ui.navigation.pseudocode.md
index 9c13aa2..40007c5 100644
--- a/modules/ui.navigation.pseudocode.md
+++ b/modules/ui.navigation.pseudocode.md
@@ -1,61 +1,127 @@
-# Navigation System Pseudocode
-
-## NavigationManager Class
-
-### Properties
-- current_patch: FilePatch or None
-- result: ApplyResult or None
-- added_blocks: List[contiguous block starts]
-- removed_blocks: List[contiguous block starts]
-
-### Methods
-
-#### update_for_patch(patch, apply_result)
-1. STORE patch and result
-2. BUILD added_blocks:
- - SORT added_lines
- - FIND contiguous blocks
- - STORE block start positions
-3. BUILD removed_blocks:
- - SORT removed_lines_original
- - FIND contiguous blocks
- - STORE block start positions
-
-#### create_widget()
-1. CREATE QWidget
-2. CREATE vertical layout
-3. ADD navigation buttons:
- - Previous change
- - Next change
- - Jump to first/last
-4. ADD change counter label
-5. ADD keyboard shortcuts
-6. RETURN widget
-
-#### navigate_prev()
-1. DETERMINES active editor (original or preview)
-2. GET current cursor line
-3. FIND previous block start before cursor
-4. IF found:
- - MOVE cursor to block start
-5. ELSE:
- - WRAP to last block
-
-#### navigate_next()
-1. DETERMINES active editor
-2. GET current cursor line
-3. FIND next block start after cursor
-4. IF found:
- - MOVE cursor to block start
-5. ELSE:
- - WRAP to first block
-
-#### find_contiguous_blocks(indices)
-1. INPUT: sorted list of indices
-2. IF empty:
- RETURN empty list
-3. INITIALIZE blocks = [indices[0]]
-4. FOR each index starting from second:
- - IF index != previous + 1:
- - ADD index to blocks
-5. RETURN blocks
\ No newline at end of file
+# ui.navigation - Codegen-Ready Pseudocode Template
+
+
+
+{
+ "slug": "ui.navigation",
+ "target_file": "src/ui/navigation.py",
+ "language": "python",
+ "runtime": {
+ "python": "3.11"
+ },
+ "index_base": 0,
+ "newline": "LF",
+ "dependencies": [
+ "core.contracts"
+ ],
+ "acceptance": {
+ "lint": [
+ "ruff check .",
+ "ruff format --check ."
+ ],
+ "tests": [
+ "pytest -q"
+ ]
+ }
+}
+
+
+## PURPOSE
+- Compute contiguous change blocks from ApplyResult and provide next/prev navigation with wrap.
+
+## SCOPE
+- In-scope:
+ - Build blocks of added or removed lines
+ - Provide navigation methods with wrap semantics
+- Out-of-scope:
+ - Parsing diffs
+ - UI painting
+
+## IMPORTS - ALLOWED ONLY
+
+- - from core.contracts import ApplyResult
+- - from typing import List, Tuple
+
+## CONSTANTS
+- INDEX_BASE = 0
+
+## TYPES - USE ONLY SHARED TYPES
+
+- Uses: FilePatch, Hunk, HunkLine, ApplyResult, ParseError, ApplyError // from core.contracts
+
+## INTERFACES
+- def analyze_changes(result: ApplyResult) -> list[tuple[int,int,str]]
+ - pre: result valid; indices 0-based
+ - post: returns blocks as (start,end,type)
+ - errors: ValidationError on bad indices
+- def next_change(current_line: int) -> int
+ - pre: current_line >= 0
+ - post: returns target line
+ - errors: ValidationError on negatives
+- def prev_change(current_line: int) -> int
+ - pre: current_line >= 0
+ - post: returns target line
+ - errors: ValidationError on negatives
+- analyze_changes consumes only result.added_lines and result.removed_original_indices (0-based)
+
+## STATE
+- blocks: list[tuple[int,int,str]]
+
+## THREADING
+- ui_thread_only: false
+- worker_policy: none
+- handoff: callback
+
+## I/O
+- inputs: ["ApplyResult"]
+- outputs: ["blocks", "line indices"]
+- encoding: utf-8
+- atomic_write: false // temp file + replace
+
+## LOGGING
+- logger: patchy
+- on_start: INFO "start ui.navigation"
+- on_warn: WARNING "condition"
+- on_error: ERROR "condition raises ErrorType"
+
+## ALGORITHM
+1) Merge adjacent indices of same type into blocks
+2) Sort blocks by start
+3) Implement wrap-around for next and previous
+
+### EDGE CASES
+- - No changes → return empty list
+- - Current line beyond end → clamp to end
+101) Merge from result.added_lines and result.removed_original_indices only
+
+## ERRORS
+- - ValidationError on unsorted indices
+
+## COMPLEXITY
+- time: O(n log n)
+- memory: O(n)
+- notes: none
+
+## PERFORMANCE
+- max input size: 200k indices
+- max iterations: n
+- timeout: none
+- instrumentation:
+- count blocks generated
+
+## TESTS - ACCEPTANCE HOOKS
+- assert wrap behavior consistent
+- assert blocks non-overlapping
+- assert all(a>=0 for a in result.added_lines)
+- assert all(r>=0 for r in result.removed_original_indices)
+
+## EXTENSIBILITY
+- - Filtering by type can be added later
+
+## NON-FUNCTIONAL
+- security: none
+- i18n: UTF-8
\ No newline at end of file
diff --git a/modules/utils.state.pseudocode.md b/modules/utils.state.pseudocode.md
index 57b7050..db328f9 100644
--- a/modules/utils.state.pseudocode.md
+++ b/modules/utils.state.pseudocode.md
@@ -1,62 +1,137 @@
-# State Manager Pseudocode
-
-## StateManager Class
-
-### Properties
-- config_dir: Path to config directory
-- config_file: Path to state file
-- state: Dict of current state
-
-### Methods
-
-#### __init__()
-1. DETERMINE config directory:
- - Windows: %APPDATA%/Patchy
- - macOS: ~/Library/Application Support/Patchy
- - Linux: ~/.config/Patchy
-2. CREATE directory if not exists
-3. SET config_file = config_dir / 'state.json'
-
-#### save_state(state_dict)
-1. INPUT: state_dict containing:
- - window_size
- - window_position
- - root_folder
- - current_file
- - splitter_states
- - recent_files
- - theme_preference
-2. TRY:
- - SERIALIZE state_dict to JSON
- - WRITE to config_file
-3. CATCH IOError:
- - LOG warning
- - CONTINUE
-
-#### load_state()
-1. TRY:
- - IF config_file exists:
- - READ file content
- - PARSE JSON
- - RETURN state_dict
-2. CATCH (FileNotFoundError, JSONDecodeError):
- - RETURN default state:
- - window_size: 1600x1000
- - window_position: center
- - theme: 'auto'
- - recent_files: []
-3. RETURN empty dict on any other error
-
-#### clear_state()
-1. IF config_file exists:
- - DELETE file
-2. RESET to default state
-
-#### add_recent_file(file_path)
-1. LOAD current recent_files
-2. IF file_path exists in list:
- - MOVE to top
-3. ELSE:
- - INSERT at top
- - TRUNCATE to max 10 files
-4. SAVE state
\ No newline at end of file
+# utils.state - Codegen-Ready Pseudocode Template
+
+
+
+{
+ "slug": "utils.state",
+ "target_file": "src/utils/state.py",
+ "language": "python",
+ "runtime": {
+ "python": "3.11"
+ },
+ "index_base": 0,
+ "newline": "LF",
+ "dependencies": [
+ "core.contracts"
+ ],
+ "acceptance": {
+ "lint": [
+ "ruff check .",
+ "ruff format --check ."
+ ],
+ "tests": [
+ "pytest -q"
+ ]
+ }
+}
+
+
+## PURPOSE
+- Persist and retrieve JSON state with atomic writes and schema validation.
+
+## SCOPE
+- In-scope:
+ - Load/save JSON
+ - Atomic temp-write and replace
+ - Keyed sections: window, ui, files
+- Out-of-scope:
+ - YAML parsing
+ - Secrets storage
+
+## IMPORTS - ALLOWED ONLY
+
+- - from typing import Any, Optional, Dict
+- - import json
+- - from pathlib import Path
+- - from core.contracts import ValidationError
+
+## CONSTANTS
+- STATE_FILE = 'patchy.state.json'
+- INDEX_BASE = 0
+
+## TYPES - USE ONLY SHARED TYPES
+
+- Uses: FilePatch, Hunk, HunkLine, ApplyResult, ParseError, ApplyError // from core.contracts
+
+## INTERFACES
+- def load(key: str) -> dict | None
+ - pre: key in {'window','ui','files'}
+ - post: returns dict or None
+ - errors: ValidationError on unknown key
+- def save(key: str, value: dict) -> None
+ - pre: value JSON-serializable
+ - post: file updated atomically
+ - errors: IOErrorCompat on write fail
+- def delete(key: str) -> None
+ - pre: key valid
+ - post: removes key
+ - errors: IOErrorCompat on write fail
+- def clear() -> None
+ - pre: none
+ - post: empties state file
+ - errors: IOErrorCompat
+
+
+## STATE
+- cache: dict - optional in-memory cache
+
+## THREADING
+- ui_thread_only: false
+- worker_policy: none
+- handoff: callback
+
+## I/O
+- inputs: ["state path", "dict values"]
+- outputs: ["state file"]
+- encoding: utf-8
+- atomic_write: true // temp file + replace
+
+## LOGGING
+- logger: patchy
+- on_start: INFO "start utils.state"
+- on_warn: WARNING "condition"
+- on_error: ERROR "condition raises ErrorType"
+
+## ALGORITHM
+1) Resolve state file path
+2) Read JSON if exists else default {}
+3) Validate schema per key
+4) Write via temp file then replace
+
+### EDGE CASES
+- - Missing file → return defaults
+- - Corrupt JSON → raise ValidationError
+- - Permission denied → IOErrorCompat
+
+## ERRORS
+- - ValidationError on schema mismatch
+- - IOErrorCompat on filesystem errors
+
+## COMPLEXITY
+- time: O(n)
+- memory: O(n)
+- notes: none
+
+## PERFORMANCE
+- max input size: 1MB
+- max iterations: n
+- timeout: none
+- instrumentation:
+- count reads and writes
+
+## TESTS - ACCEPTANCE HOOKS
+- assert round-trip save-load yields identical dict
+- assert save()->load() round-trip equals input dict
+- assert atomic write leaves valid file after simulated crash
+
+## EXTENSIBILITY
+- - New keys can be added without breaking existing state
+
+## NON-FUNCTIONAL
+- security: prevent path traversal
+- i18n: UTF-8
+- compliance: no secrets in state
\ No newline at end of file
diff --git a/modules/utils.theme.pseudocode.md b/modules/utils.theme.pseudocode.md
index 0693b9a..4fcf33d 100644
--- a/modules/utils.theme.pseudocode.md
+++ b/modules/utils.theme.pseudocode.md
@@ -1,60 +1,122 @@
-# Theme Manager Pseudocode
-
-## ThemeManager Class
-
-### Properties
-- app: QApplication reference
-- current_theme: 'light' or 'dark'
-- timer: QTimer for theme monitoring
-
-### Methods
-
-#### __init__(app)
-1. STORE app reference
-2. INITIALIZE timer (30s interval)
-3. CONNECT timer timeout to check_theme
-4. START timer
-5. CALL apply_theme
-
-#### detect_system_theme()
-1. IF Windows:
- - READ registry key:
- - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize
- - Value: AppsUseLightTheme
- - RETURN 'dark' if value == 0, 'light' otherwise
-2. ELSE:
- - CALCULATE palette brightness
- - RETURN 'dark' if brightness < 0.5
-
-#### apply_theme()
-1. new_theme = detect_system_theme()
-2. IF new_theme == current_theme:
- - RETURN
-3. SAVE scroll positions
-4. IF dark theme:
- - SET Fusion style
- - APPLY dark palette:
- - Window: dark gray
- - Text: white
- - Base: darker gray
- - Highlight: blue
-5. ELSE:
- - RESET to system palette
-6. RESTORE scroll positions
-7. EMIT theme_changed signal
-
-#### create_dark_palette()
-1. CREATE QPalette
-2. SET colors:
- - Window: QColor(53, 53, 53)
- - WindowText: white
- - Base: QColor(35, 35, 35)
- - Text: white
- - Button: QColor(53, 53, 53)
- - Highlight: QColor(42, 130, 218)
-3. RETURN palette
-
-#### check_theme()
-1. new_theme = detect_system_theme()
-2. IF new_theme != current_theme:
- - CALL apply_theme()
\ No newline at end of file
+# utils.theme - Codegen-Ready Pseudocode Template
+
+
+
+{
+ "slug": "utils.theme",
+ "target_file": "src/utils/theme.py",
+ "language": "python",
+ "runtime": {
+ "python": "3.11"
+ },
+ "index_base": 0,
+ "newline": "LF",
+ "dependencies": [
+ "core.contracts"
+ ],
+ "acceptance": {
+ "lint": [
+ "ruff check .",
+ "ruff format --check ."
+ ],
+ "tests": [
+ "pytest -q"
+ ]
+ }
+}
+
+
+## PURPOSE
+- Manage theme preference 'light'|'dark'|'auto' and provide palette to UI consumers.
+
+## SCOPE
+- In-scope:
+ - Store current theme
+ - Expose palette dict
+ - Signal listeners via callback hooks
+- Out-of-scope:
+ - OS polling inside highlighters or editors
+
+## IMPORTS - ALLOWED ONLY
+
+- - from typing import Literal, Dict
+
+## CONSTANTS
+- DEFAULT_THEME = 'auto'
+
+## TYPES - USE ONLY SHARED TYPES
+
+- Uses: FilePatch, Hunk, HunkLine, ApplyResult, ParseError, ApplyError // from core.contracts
+
+## INTERFACES
+- def current() -> str
+ - pre: state initialized
+ - post: returns 'light'|'dark'|'auto'
+ - errors: none
+- def set(name: str) -> None
+ - pre: name in {'light','dark','auto'}
+ - post: updates theme and emits callback
+ - errors: ValidationError on bad name
+- def palette() -> dict
+ - pre: current theme set
+ - post: returns stable color mapping
+ - errors: none
+
+
+## STATE
+- subs: list[callable] - registered listeners
+
+## THREADING
+- ui_thread_only: false
+- worker_policy: none
+- handoff: callback
+
+## I/O
+- inputs: ["theme name"]
+- outputs: ["palette dict", "callbacks"]
+- encoding: utf-8
+- atomic_write: false // temp file + replace
+
+## LOGGING
+- logger: patchy
+- on_start: INFO "start utils.theme"
+- on_warn: WARNING "condition"
+- on_error: ERROR "condition raises ErrorType"
+
+## ALGORITHM
+1) If name invalid raise ValidationError
+2) Set internal theme
+3) Notify subscribers
+4) Return palette on request
+
+### EDGE CASES
+- - Unknown theme → ValidationError
+
+## ERRORS
+- - ValidationError on bad theme name
+
+## COMPLEXITY
+- time: O(1)
+- memory: O(1)
+- notes: none
+
+## PERFORMANCE
+- max input size: n/a
+- max iterations: n/a
+- timeout: none
+- instrumentation:
+- count theme changes
+
+## TESTS - ACCEPTANCE HOOKS
+- assert palette keys include background, foreground, added, removed
+
+## EXTENSIBILITY
+- - New palettes can be added by extending mapping
+
+## NON-FUNCTIONAL
+- security: no external IO
+- i18n: UTF-8
\ No newline at end of file