diff --git a/architecture.mmd b/architecture.mmd
new file mode 100644
index 0000000..0f838dc
--- /dev/null
+++ b/architecture.mmd
@@ -0,0 +1,132 @@
+---
+title: Movies Diary — Hexagonal Architecture
+---
+graph TB
+ subgraph Binaries["Binaries (Composition Root)"]
+ WEB["presentation
Axum web server
Routes, Handlers, Mappers"]
+ WORKER["worker
Event consumer
Concurrent dispatch, graceful shutdown"]
+ TUI["tui
Terminal UI"]
+ end
+
+ subgraph Application["Application Layer"]
+ direction TB
+ CTX["AppContext
Repositories + Services"]
+ subgraph UseCases["Use Cases"]
+ UC_AUTH["auth
login, register"]
+ UC_DIARY["diary
log_review, get_diary,
get_activity_feed, export"]
+ UC_MOVIES["movies
get_movies, get_movie_profile,
enrich_movie, sync_poster,
reindex_search"]
+ UC_IMPORT["import
create_session, apply_mapping,
execute, profiles"]
+ UC_USERS["users
get_users, get_profile,
update_profile"]
+ UC_WATCHLIST["watchlist
add, remove, get"]
+ UC_WRAPUP["wrapup
generate, compute,
list, delete"]
+ UC_INTEGRATIONS["integrations
webhooks, watch_queue,
confirm, dismiss"]
+ UC_SEARCH["search
execute"]
+ UC_PERSON["person
get, get_credits"]
+ end
+ subgraph EventHandlers["Event Handlers"]
+ EH_ENRICH["EnrichmentHandler"]
+ EH_DISCOVER["MovieDiscoveryIndexer"]
+ EH_CLEANUP["SearchCleanupHandler"]
+ EH_REINDEX["SearchReindexHandler"]
+ EH_WRAPUP["WrapUpEventHandler"]
+ end
+ subgraph Jobs["Periodic Jobs"]
+ JOB_IMPORT["ImportSessionCleanup"]
+ JOB_WATCH["WatchEventCleanup"]
+ JOB_STALE["EnrichmentStaleness"]
+ JOB_WRAPGEN["WrapUpAutoGenerate"]
+ end
+ WORKER_SVC["WorkerService
Semaphore(8), JoinSet,
shutdown signal"]
+ end
+
+ subgraph Domain["Domain Layer (0 dependencies)"]
+ direction TB
+ subgraph Models["Models"]
+ M_MOVIE["Movie, MovieSummary,
MovieProfile"]
+ M_REVIEW["Review, DiaryEntry,
FeedEntry"]
+ M_USER["User, UserSummary"]
+ M_PERSON["Person, PersonId,
PersonCredits"]
+ M_WATCHLIST["WatchlistEntry,
WatchEvent"]
+ M_WRAPUP["WrapUpReport,
MovieRef, PersonStat"]
+ M_SEARCH["SearchQuery,
SearchResults"]
+ end
+ subgraph Ports["Port Traits (Interfaces)"]
+ P_REPOS["MovieRepository
ReviewRepository
DiaryRepository
UserRepository
WatchlistRepository
WatchEventRepository
WebhookTokenRepository
ImportSessionRepository
MovieProfileRepository
WrapUpRepository"]
+ P_SERVICES["AuthService
MetadataClient
PosterFetcherClient
ObjectStorage
EventPublisher
EventConsumer
PasswordHasher
DiaryExporter
DocumentParser"]
+ P_SEARCH["SearchPort
SearchCommand
PersonQuery
PersonCommand"]
+ P_FEDERATION["SocialQueryPort
LocalApContentQuery
RemoteWatchlistRepository"]
+ end
+ EVENTS["DomainEvent enum
ReviewLogged, MovieDiscovered,
SearchReindexRequested, ..."]
+ VO["Value Objects
MovieId, UserId, Rating,
Email, Username, ..."]
+ end
+
+ subgraph ApiTypes["api-types (0 domain deps)"]
+ DTO["DTOs
MovieDto, ReviewDto,
FeedEntryDto, UserSummaryDto,
CastMemberDto, ..."]
+ end
+
+ subgraph Adapters["Adapters (implement Port Traits)"]
+ direction TB
+ subgraph Storage["Storage"]
+ A_SQLITE["sqlite
SQLite repos"]
+ A_PG["postgres
PostgreSQL repos"]
+ A_SQLITE_SEARCH["sqlite-search
FTS5"]
+ A_PG_SEARCH["postgres-search
tsvector/GIN"]
+ A_OBJ["object-storage
S3 / filesystem"]
+ end
+ subgraph Messaging["Messaging"]
+ A_NATS["nats
JetStream / Core"]
+ A_PG_QUEUE["postgres-event-queue
Polling, dead-letter"]
+ A_PAYLOAD["event-payload
Serde (de)serialization"]
+ end
+ subgraph External["External Services"]
+ A_METADATA["metadata
TMDB search/details"]
+ A_TMDB["tmdb-enrichment
Credits, keywords, cast"]
+ A_POSTER["poster-fetcher
TMDB image download"]
+ A_AUTH["auth
JWT tokens"]
+ end
+ subgraph Federation["Federation (feature-gated)"]
+ A_AP["activitypub
k_ap library"]
+ A_SQLITE_FED["sqlite-federation"]
+ A_PG_FED["postgres-federation"]
+ end
+ subgraph Media["Media Processing"]
+ A_IMG["image-converter
AVIF/WebP"]
+ A_POSTER_SYNC["poster-sync"]
+ A_WRAPUP_RENDER["wrapup-renderer
ffmpeg video, slides"]
+ end
+ subgraph Presentation["Presentation Helpers"]
+ A_TEMPLATE["template-askama
HTML templates"]
+ A_RSS["rss
Feed generation"]
+ A_EXPORT["export
CSV/JSON diary"]
+ A_IMPORT["importer
CSV/JSON/XLSX parser"]
+ end
+ subgraph Webhooks["Webhook Parsers"]
+ A_JELLYFIN["jellyfin"]
+ A_PLEX["plex"]
+ end
+ end
+
+ %% Dependency arrows
+ WEB -->|"uses"| Application
+ WEB -->|"maps to"| ApiTypes
+ WORKER -->|"uses"| Application
+
+ Application -->|"depends on"| Domain
+
+ Adapters -.->|"implements"| Ports
+
+ %% Key data flows
+ WEB ===|"HTTP"| DTO
+ WORKER ===|"Events"| EVENTS
+
+ classDef domain fill:#1a1a2e,stroke:#e94560,color:#fff
+ classDef app fill:#16213e,stroke:#0f3460,color:#fff
+ classDef adapter fill:#0f3460,stroke:#533483,color:#fff
+ classDef binary fill:#533483,stroke:#e94560,color:#fff
+ classDef api fill:#2a2a4a,stroke:#e94560,color:#fff
+
+ class Domain domain
+ class Application app
+ class Adapters adapter
+ class Binaries binary
+ class ApiTypes api