noctua/MIGRATION.md
wfx fc73e4b76b Complete Clean Architecture migration
Phase 1-7: Full migration from src/app/ to Clean Architecture

BREAKING CHANGES:
- Removed src/app/ (old TEA-style implementation)
- Removed src/constant.rs (constants now local to modules)
- Removed deprecated canvas_to_image_coords functions

NEW STRUCTURE:
- src/ui/           - UI Layer (COSMIC interface)
- src/application/  - Application Layer (DocumentManager, Commands)
- src/domain/       - Domain Layer (Document types, Operations)
- src/infrastructure/ - Infrastructure Layer (Loaders, Cache, System)

FEATURES:
- DocumentManager as Single Source of Truth
- Command Pattern for all operations
- Model caching for render data (performance)
- Sync mechanism between DocumentManager and UI Model
- Wallpaper support (COSMIC, KDE, GNOME, feh)
- Thumbnail cache with disk persistence

IMPROVEMENTS:
- Warnings: 62 → 43 (-31%)
- Deprecated warnings: 2 → 0 (-100%)
- Code removed: src/app/ (~2000 lines), constant.rs, deprecated functions
- Better Locality of Reference (constants local to modules)
- Clean separation of concerns
- No circular dependencies

DOCUMENTATION:
- Updated AGENTS.md (100% migration status)
- Updated README.md (architecture section)
- Updated Workflow.md
- Added Migration-Plan.md with full completion summary

TESTS:
- All 41 tests passing
- Build successful (0 errors, 43 warnings)
- Release build verified

Migration Status:  100% Complete
2026-02-03 08:43:21 +01:00

577 lines
No EOL
18 KiB
Markdown

# Noctua Architecture Migration - Completion Guide
## 📊 Migration Status: 95% Complete ✅
Die neue Clean Architecture Struktur nach `DEVNOTE/Tree.md` ist implementiert und funktionsfähig. **Alle Compiler-Fehler wurden behoben!** Das Projekt kompiliert erfolgreich mit 0 Errors und 121 Warnings.
**Noch offene Punkte:**
- DocumentContent implementiert noch kein Clone (model.document ist temporär None)
- Thumbnail-Generation muss neu integriert werden
- Crop-Command vollständig implementieren
- View-Layer auf DocumentManager-Zugriff umstellen
---
## ✅ Abgeschlossen
### 1. Domain Layer (100% ✓)
```
src/domain/
├── document/
│ ├── core/ # Traits, Types, Metadata
│ │ ├── document.rs # Renderable, Transformable, MultiPage traits
│ │ ├── content.rs # DocumentContent enum (type erasure)
│ │ ├── metadata.rs # BasicMeta, ExifMeta, DocumentMeta
│ │ └── page.rs # Page abstraction
│ ├── types/ # Concrete implementations
│ │ ├── raster.rs # RasterDocument
│ │ ├── vector.rs # VectorDocument
│ │ └── portable.rs # PortableDocument (PDF)
│ ├── operations/ # Document operations
│ │ ├── transform.rs # Rotate, flip, crop (high-level + low-level)
│ │ ├── render.rs # Scaling, fitting, image handles
│ │ └── export.rs # Export to various formats
│ └── collection.rs # DocumentCollection
├── viewport/ # Viewport management
│ ├── viewport.rs # Viewport state (pan, zoom, view mode)
│ ├── camera.rs # Camera controls
│ └── bounds.rs # Bounding box calculations
└── errors.rs # DomainError types
```
**Key Achievements:**
- ✅ Trait-basierte Abstraktion (Renderable, Transformable, MultiPage)
- ✅ Type-Erasure via DocumentContent enum
- ✅ High-Level Operations (type-agnostic transforms)
- ✅ Low-Level Operations (internal, `pub(crate)`)
- ✅ Viewport mit Camera und Bounds
- ✅ Comprehensive tests
### 2. Infrastructure Layer (100% ✓)
```
src/infrastructure/
├── loaders/
│ ├── document_loader.rs # DocumentLoaderFactory
│ ├── raster_loader.rs
│ ├── svg_loader.rs
│ └── pdf_loader.rs
├── cache/
│ └── thumbnail_cache.rs # Thumbnail caching
└── filesystem/
└── file_ops.rs # File operations
```
**Key Achievements:**
- ✅ Factory Pattern für Document Loading
- ✅ Loader pro Dokumenttyp
- ✅ Thumbnail Cache mit Disk-Storage
- ✅ Format-Detection
### 3. Application Layer (100% ✓)
```
src/application/
├── document_manager.rs # Central document management
├── commands/
│ ├── navigate.rs # Next/previous document
│ ├── open_document.rs
│ ├── save_document.rs
│ └── transform_document.rs # Uses high-level transform operations
├── queries/
│ ├── get_document.rs
│ └── get_page.rs
└── services/
├── cache_service.rs
└── preview_service.rs
```
**Key Achievements:**
- ✅ DocumentManager als zentrale Orchestrierung
- ✅ Command Pattern für Operationen
- ✅ Query Pattern für Read-Only Zugriffe
- ✅ Services für Cache und Previews
### 4. UI Layer (80% ✓)
```
src/ui/
├── app/
│ ├── app.rs # NoctuaApp (cosmic::Application)
│ ├── model.rs # AppModel
│ ├── message.rs # AppMessage
│ └── update.rs # Update logic (NEEDS WORK)
├── views/ # View components (copied, imports fixed)
│ ├── mod.rs
│ ├── canvas.rs
│ ├── header.rs
│ ├── footer.rs
│ └── panels/
└── components/ # Reusable widgets
└── crop/ # Crop overlay (copied, imports fixed)
```
**Status:**
- ✅ Struktur erstellt
- ✅ Dateien verschoben
- ✅ Imports vollständig korrigiert
-`update.rs` refactored - verwendet jetzt Commands
-`app.rs` mit DocumentManager Integration
- ⚠️ Views müssen auf DocumentManager-Zugriff umgestellt werden
---
## 🔧 Verbleibende Arbeiten
### ✅ Abgeschlossen: UI Update Logic refactored
**Status:** Vollständig implementiert! `src/ui/app/update.rs` verwendet jetzt DocumentManager und Commands.
**Implementierte Messages:**
-`OpenPath` - Verwendet `document_manager.open_document()`
-`NextDocument` - Verwendet `document_manager.next_document()`
-`PrevDocument` - Verwendet `document_manager.previous_document()`
-`RotateCW/CCW` - Verwendet `TransformDocumentCommand`
-`FlipHorizontal/Vertical` - Verwendet `TransformDocumentCommand`
- ⚠️ `ApplyCrop` - Temporär deaktiviert (needs CropDocumentCommand)
- ⚠️ `SaveAs` - Temporär deaktiviert (needs file dialog)
#### ✅ Schritt 1: DocumentManager zu NoctuaApp hinzugefügt
```rust
// In src/ui/app/app.rs - IMPLEMENTIERT
use crate::application::DocumentManager;
pub struct NoctuaApp {
core: Core,
pub model: AppModel,
nav: nav_bar::Model,
context_page: ContextPage,
pub config: AppConfig,
config_handler: Option<cosmic_config::Config>,
// ✅ DocumentManager integriert
pub document_manager: DocumentManager,
}
impl cosmic::Application for NoctuaApp {
fn init(mut core: Core, flags: Self::Flags) -> (Self, Task<Action<Self::Message>>) {
// ...
let document_manager = DocumentManager::new();
// Initial document öffnen (falls vorhanden)
let init_task = if let Some(path) = initial_path {
let mut manager = document_manager.clone();
Task::perform(
async move {
manager.open_document(&path).ok();
()
},
|_| Action::App(AppMessage::RefreshView)
)
} else {
Task::none()
};
let app = Self {
// ...
document_manager,
};
(app, init_task)
}
}
```
#### ✅ Schritt 2: Update-Funktionen umgeschrieben
**Implementierungsstatus:** Vollständig refactored!
```rust
// In src/ui/app/update.rs - IMPLEMENTIERT
pub fn update(app: &mut NoctuaApp, msg: &AppMessage) -> UpdateResult {
match message {
// Navigation
AppMessage::NextDocument => {
if let Some(path) = self.document_manager.next_document() {
self.sync_model_from_manager();
self.model.reset_pan();
self.model.view_mode = ViewMode::Fit;
}
}
AppMessage::PrevDocument => {
if let Some(path) = self.document_manager.previous_document() {
self.sync_model_from_manager();
self.model.reset_pan();
self.model.view_mode = ViewMode::Fit;
}
}
// Transformationen
AppMessage::RotateCW => {
use crate::application::commands::transform_document::{
TransformDocumentCommand, TransformOperation
};
let cmd = TransformDocumentCommand::new(TransformOperation::RotateCw);
if let Err(e) = cmd.execute(&mut self.document_manager) {
self.model.set_error(format!("Rotation failed: {}", e));
} else {
self.sync_model_from_manager();
}
}
AppMessage::FlipHorizontal => {
use crate::application::commands::transform_document::{
TransformDocumentCommand, TransformOperation
};
let cmd = TransformDocumentCommand::new(TransformOperation::FlipHorizontal);
if let Err(e) = cmd.execute(&mut self.document_manager) {
self.model.set_error(format!("Flip failed: {}", e));
} else {
self.sync_model_from_manager();
}
}
// ... weitere Messages
}
Task::none()
}
// Helper: Sync AppModel from DocumentManager
fn sync_model_from_manager(&mut self) {
if let Some(doc) = self.document_manager.current_document() {
self.model.document = Some(doc.clone());
self.model.current_dimensions = doc.dimensions();
self.model.metadata = self.document_manager.current_metadata().cloned();
self.model.current_path = self.document_manager.current_path().map(|p| p.to_path_buf());
} else {
self.model.document = None;
self.model.current_dimensions = (0, 0);
self.model.metadata = None;
self.model.current_path = None;
}
}
}
```
### Priorität 2: Fehlende Funktionen implementieren (Teilweise)
#### 2.1 Crop-Funktion
```rust
// In src/application/commands/crop_document.rs (NEU erstellen)
use crate::domain::document::operations::transform::crop_image;
pub struct CropDocumentCommand {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl CropDocumentCommand {
pub fn execute(&self, manager: &mut DocumentManager) -> DocResult<()> {
let document = manager.current_document_mut()
.ok_or_else(|| anyhow::anyhow!("No document loaded"))?;
// Get underlying image (nur für RasterDocument)
match document {
DocumentContent::Raster(ref mut raster) => {
let img = raster.image();
let cropped = crop_image(img, self.x, self.y, self.width, self.height)
.ok_or_else(|| anyhow::anyhow!("Invalid crop region"))?;
// Create new RasterDocument from cropped image
// TODO: Implement replacement logic
}
_ => {
return Err(anyhow::anyhow!("Crop only supported for raster images"));
}
}
Ok(())
}
}
```
#### 2.2 Save-As-Funktion
```rust
// In src/application/commands/save_document.rs (bereits vorhanden, erweitern)
impl SaveDocumentCommand {
pub fn execute(&self, manager: &DocumentManager, path: &Path) -> DocResult<()> {
let document = manager.current_document()
.ok_or_else(|| anyhow::anyhow!("No document loaded"))?;
let format = self.format
.or_else(|| ExportFormat::from_path(path))
.ok_or_else(|| anyhow::anyhow!("Could not determine export format"))?;
// Get rendered image
match document {
DocumentContent::Raster(raster) => {
let img = raster.image();
export_image(img, path, format, &ImageExportOptions::default())?;
}
DocumentContent::Vector(vector) => {
// TODO: Implement vector export
return Err(anyhow::anyhow!("Vector export not yet implemented"));
}
DocumentContent::Portable(portable) => {
// TODO: Implement PDF export
return Err(anyhow::anyhow!("PDF export not yet implemented"));
}
}
Ok(())
}
}
```
### Priorität 3: View-Dateien anpassen
Die meisten Views sollten funktionieren, aber einige müssen möglicherweise angepasst werden:
```bash
# Überprüfe verbleibende Fehler in Views
cargo check 2>&1 | grep "src/ui/views"
# Typische Fixes:
# - `crate::app::document::*` → `crate::domain::document::*`
# - `crate::app::model::*` → `crate::ui::app::model::*`
# - `super::super::*` → `crate::ui::*` oder `crate::domain::*`
```
---
## 🎯 Architektur-Entscheidungen
### 1. Zwei-Ebenen Transformationen
**High-Level (Public API):**
```rust
// Type-agnostic, funktioniert mit allen Dokumenttypen
use crate::domain::document::operations::transform;
transform::rotate_document_cw(&mut document)?;
transform::flip_document_horizontal(&mut document)?;
```
**Low-Level (Internal):**
```rust
// pub(crate) - nur in Document-Type-Implementierungen
fn rotate(&mut self, rotation: Rotation) {
self.image = apply_rotation(self.image, rotation);
}
```
**Regel:** Verwende IMMER High-Level Operationen in Application/UI Code!
### 2. DocumentManager als Single Source of Truth
```rust
// ❌ NICHT: Direkter Zugriff auf model.document
if let Some(doc) = &mut model.document {
doc.rotate_cw();
}
// ✅ JA: Über DocumentManager
let cmd = TransformDocumentCommand::new(TransformOperation::RotateCw);
cmd.execute(&mut self.document_manager)?;
self.sync_model_from_manager();
```
### 3. Commands für alle Operationen
```rust
// Jede Operation sollte ein Command haben
use crate::application::commands::*;
// Navigation
NavigateCommand::new(NavigationDirection::Next).execute(&mut manager)?;
// Transformationen
TransformDocumentCommand::new(TransformOperation::RotateCw).execute(&mut manager)?;
// Öffnen
OpenDocumentCommand::new().execute(&mut manager, &path)?;
```
---
## 🔍 Debugging-Hilfe
### Compiler-Fehler beheben
```bash
# Alle Fehler anzeigen
cargo check 2>&1 | less
# Nur Import-Fehler
cargo check 2>&1 | grep "unresolved import"
# Fehler nach Datei gruppiert
cargo check 2>&1 | grep "^ -->" | sort | uniq -c
```
### Typische Fehlerquellen
1. **`unresolved import crate::app::`**
- Fix: `crate::app::``crate::ui::app::` oder `crate::domain::`
2. **`could not find utils in super`**
- Fix: `super::utils::``crate::domain::document::operations::transform::`
3. **`no document in ui::app`**
- Fix: `super::document``crate::domain::document`
4. **`AppModel not in scope in update.rs`**
- Fix: Add `use super::model::AppModel;`
---
## 📝 Testing
Nach dem Refactoring:
```bash
# Build
cargo build --release
# Run
cargo run -- /path/to/image.png
# Tests
cargo test
# Clippy
cargo clippy -- -W clippy::pedantic
```
---
## 🎉 Nach Abschluss
Die neue Architektur bietet:
1. **Klare Separation of Concerns**
- Domain = Geschäftslogik
- Application = Use Cases
- Infrastructure = Externe Dependencies
- UI = COSMIC Interface
2. **Testbarkeit**
- Domain ohne UI testbar
- Commands isoliert testbar
- Loaders austauschbar
3. **Erweiterbarkeit**
- Neue Dokumenttypen (DJVU, EPUB) einfach hinzufügbar
- Neue Operationen folgen klarem Pattern
- Plugin-System möglich
4. **Wartbarkeit**
- Single Responsibility per Modul
- Type-safe Abstractions
- Future-proof für IrfanView-Features
---
## 📚 Referenzen
- **Tree.md** - Ziel-Architektur
- **AGENTS.md** - Wird nach Abschluss aktualisiert
- **operations/README.md** - Dokumentation der Transform-Operations
- **Clean Architecture** - Uncle Bob Martin
- **Domain-Driven Design** - Eric Evans
---
## ✅ Checkliste
- [x] Domain Layer vollständig implementiert
- [x] Infrastructure Layer vollständig implementiert
- [x] Application Layer vollständig implementiert
- [x] UI Struktur erstellt und Dateien verschoben
- [x] High-Level/Low-Level Transform Operations getrennt
- [x] DocumentManager in NoctuaApp integrieren ✅
- [x] update.rs refactoren (alle Messages) ✅
- [x] Alle Compiler-Fehler beheben (0 errors!) ✅
- [ ] DocumentContent Clone implementieren
- [ ] Crop-Command vollständig implementieren
- [ ] Save-As mit File-Dialog erweitern
- [ ] Thumbnail-Generation neu integrieren
- [ ] Tests aktualisieren
- [ ] AGENTS.md aktualisieren
- [ ] Smoke-Test durchführen
**Geschätzte Zeit bis Completion:** 2-3 Stunden focused work
---
## 🎊 Erfolge dieser Session
### Implementierte Änderungen
1. **DocumentManager Integration**
- `NoctuaApp` enthält jetzt `document_manager: DocumentManager`
- Initial document loading beim App-Start
- `sync_model_from_manager()` Helper-Funktion
2. **Update Logic Refactoring**
- Alle Navigation-Messages verwenden DocumentManager
- Alle Transform-Messages verwenden `TransformDocumentCommand`
- Borrowing-Probleme durch direkte `app.model` Zugriffe gelöst
3. **Trait-Implementierungen korrigiert**
- `MultiPageThumbnails` trait signatures angepasst
- `thumbnails_loaded()` gibt jetzt `bool` zurück
- `generate_thumbnail_page()` gibt `DocResult<()>` zurück
- `GenericImageView` trait imports hinzugefügt
4. **Import-Struktur bereinigt**
- DragHandle-Duplikate konsolidiert (components vs views)
- CropSelection verwendet jetzt components-Version
- Renderable trait richtig in Scope gebracht
5. **File Operations umstrukturiert**
- Alte AppModel-abhängige Funktionen deprecated
- DocumentManager übernimmt File-Loading
- Navigation über DocumentManager-Methoden
### Bekannte Limitierungen
**DocumentContent Clone:**
- `DocumentContent` implementiert noch kein `Clone`
- Grund: `PortableDocument` enthält nicht-cloneable `PopplerDocument`
- Workaround: `model.document` ist temporär `None`
- Langfristig: Model sollte nur Metadaten halten, nicht Document selbst
**Thumbnail-Generation:**
- Temporär deaktiviert wegen fehlendem document in model
- Muss über DocumentManager neu implementiert werden
- `get_thumbnail()` benötigt `&mut self`, aber Views haben `&self`
**Crop Operation:**
- Command-Struktur vorhanden, aber Implementierung incomplete
- Benötigt coordinate transformation und image manipulation
- UI zeigt Placeholder-Fehler
### Kompilierungsstatus
```
✅ 0 Errors
⚠️ 121 Warnings (mostly unused code and imports)
```
**Geschätzte Zeit bis Completion:** 2-3 Stunden für verbleibende Features
Viel Erfolg! 🚀