Architecture & Internals

Quarkus Forge follows a layered architecture that separates HTTP transport, JSON parsing, domain logic, UI state management, and rendering into focused modules.

High-Level Architecture

graph TD
    subgraph Entry["Entry Points"]
        CLI[QuarkusForgeCli]
        HCLI[HeadlessCli]
        HGS[HeadlessGenerationService]
    end

    subgraph TUI["TUI Layer"]
        TBS[TuiBootstrapService]
        CTC[CoreTuiController]
    end

    subgraph Headless["Headless Layer"]
        PRF[ProjectRequestFactory]
        HCC[HeadlessCatalogClient]
        HOP[HeadlessOutputPrinter]
        HT[HeadlessTimeouts]
    end

    subgraph API["API Layer"]
        QAC[QuarkusApiClient]
        APP[ApiPayloadParser]
        JFR[JsonFieldReader]
        CSC[CatalogSnapshotCache]
        CDS[CatalogDataService]
    end

    subgraph Archive["Archive Layer"]
        PAS[ProjectArchiveService]
        SZE[SafeZipExtractor]
    end

    subgraph PostGen["Post-Generation"]
        PTA[PostTuiActionExecutor]
        SE[ShellExecutor]
        ID[IdeDetector]
    end

    CLI --> TBS
    CLI --> HGS
    HCLI --> HGS
    HGS --> PRF
    HGS --> HCC
    HGS --> HOP
    HGS --> HT
    TBS --> CTC
    CTC --> QAC
    HCC --> QAC
    QAC --> APP
    APP --> JFR
    QAC --> CDS
    CDS --> CSC
    QAC --> PAS
    PAS --> SZE
    PTA --> SE
    PTA --> ID

UI Layer Design

The TUI layer uses an immediate-mode rendering architecture built on the TamboUI framework.

Component Decomposition

CoreTuiController (Orchestration Shell)
    │
    ├── UiEventRouter           Preserves key-priority contract
    │       │
    │       ├── Overlay handlers (command palette, help, post-gen menu)
    │       ├── Cancel flow (filter unwind priority chain)
    │       ├── Global shortcuts
    │       ├── Focus navigation
    │       └── Input handling
    │
    ├── UiStateSnapshotMapper    Builds immutable UiState read model from controller internals
    │
    ├── CoreUiReducer            Pure transition logic for migrated slices
    │       ├── post-generation intent handling
    │       ├── generation callback intent handling
    │       └── focus/input intent handling
    │
    ├── UiEffectsRunner          Executes side effects emitted by reducer
    │       ├── generation start/cancel
    │       ├── post-generation menu updates
    │       └── repaint + async coordination hooks
    │
    ├── UiRenderer               State-driven render orchestration
    │       ├── OverlayRenderer          Stateless overlay rendering
    │       ├── BodyPanelRenderer        Layout: metadata + extensions panels
    │       └── FooterLinesComposer      Status bar composition
    │
    ├── State Managers
    │       ├── PostGenerationMenuState  Post-gen menu state machine
    │       ├── MetadataSelectorManager  Platform/build/java selectors
    │       ├── ExtensionCatalogState    Search, filter, favorites, presets
    │       ├── CatalogLoadState         Sealed type: Loading → Loaded | Failed
    │       ├── StartupOverlayTracker    Startup overlay timing
    │       └── GenerationStateTracker   Generation progress tracking
    │
    └── Extracted Helpers
            ├── ExtensionInteractionHandler  Bridge to catalog interactions
            ├── CatalogRowBuilder            Pure function: row building
            ├── UiTextConstants              UI text content
            ├── AsyncFailureHandler          Reusable async exception mapping
            ├── GenerationFlowCoordinator    Async generation lifecycle
            └── UiTextInputKeys              Text-input edit-key normalization

State Machine: Generation Flow

stateDiagram-v2
    [*] --> IDLE
    IDLE --> VALIDATING: Enter / Alt+G
    VALIDATING --> ERROR: validation fails
    VALIDATING --> LOADING: validation passes
    LOADING --> CANCELLED: Esc / Ctrl+C
    LOADING --> SUCCESS: download complete
    ERROR --> IDLE: dismissed
    CANCELLED --> IDLE: dismissed
    SUCCESS --> PostGenMenu: show menu
    PostGenMenu --> IDLE: Generate Again

    state PostGenMenu {
        [*] --> ActionList
        ActionList --> ExportForgefile
        ActionList --> PublishGitHub
        ActionList --> OpenIDE
        ActionList --> OpenTerminal
        ActionList --> Quit
        PublishGitHub --> VisibilitySubMenu
    }

State Machine: Catalog Loading

stateDiagram-v2
    [*] --> Initial: CatalogLoadState.initial()
    Initial --> Loading1: loadExtensionCatalogAsync()
    state Loading1 <>
    Loading1 --> Loaded_live: success
    Loading1 --> Failed: failure
    Loaded_live --> Loading2: reload requested
    state Loading2 <>
    Loading2 --> Loaded_new: success
    Loading2 --> Loaded_live: failure (preserves catalog)

    note right of Initial: Loaded("snapshot", false, false)
    note right of Loading1: Loading(previous=Initial)
    note right of Loading2: Loading(previous=Loaded_live)

UI State-Machine Refactor Invariants (ADR-UI-001)

The UI refactor to explicit UiState/UiIntent/UiReducer/UiEffect must preserve these behavior contracts:

  • Routing precedence remains fixed in UiEventRouter:

    1. help overlay keys / toggle

    2. command palette toggle + keys

    3. post-generation menu

    4. extension cancel flow

    5. quit flow

    6. loading lock handling

    7. global shortcuts

    8. focus navigation

    9. submit flow

    10. extension flow

    11. metadata selectors

    12. text input

  • Generation lifecycle semantics from GenerationFlowCoordinator remain unchanged:

    1. token-based stale completion protection

    2. cancellation semantics (CancellationException and cancel flag)

    3. dropped-callback reconciliation via reconcileCompletionIfDone

  • Post-generation flow semantics from PostGenerationMenuState remain unchanged:

    1. action selection and visibility sub-menu behavior

    2. stable exit-plan generation

    3. Generate again returns to active TUI session without quitting

  • Non-key event behavior remains unchanged:

    1. TickEvent drives repaint/progress updates

    2. ResizeEvent updates status text while preserving state

    3. unknown events are ignored

  • Overlay rendering order remains fixed:

    1. header/body/footer

    2. generation overlay

    3. command palette

    4. help overlay

    5. post-generation menu

    6. startup overlay

  • State updates follow single-writer discipline: reducer transitions execute on the render thread.

  • TUI refactor cannot change headless behavior or CLI semantics.

Extension Cancel Flow (Esc Priority Chain)

When the user presses Esc in the extension search or list, the TUI unwinds filters in priority order:

 Esc pressed in extension search/list
      │
      ├──[1] Search text not empty? ──► Clear search
      │
      ├──[2] Favorites filter on? ──► Disable favorites filter
      │
      ├──[3] Selected-only filter on? ──► Disable selected-only filter
      │
      ├──[4] Preset filter active? ──► Clear preset filter
      │
      ├──[5] Category filter active? ──► Clear category filter
      │
      ├──[6] In search field? ──► Focus extension list
      │
      └──[7] None of the above ──► Fall through to quit flow

API Layer Design

Architectural Guardrails

Headless/TUI boundaries are protected by ArchUnit tests to prevent dependency drift:

  • Headless orchestration classes must not depend on dev.ayagmar.quarkusforge.ui.*.

  • Headless orchestration classes must not depend on dev.tamboui.*.

  • Core non-UI layers (api, archive, domain, diagnostics, util) must not depend on ui.

Transport vs Parsing Separation

graph TD
    REQ[HTTP Request] --> QAC
    subgraph QAC["QuarkusApiClient (Transport)"]
        direction TB
        R[Retry with backoff]
        T[Timeout management]
        H[HTTP status handling]
        C[Cancellation support]
    end
    QAC -->|raw JSON| APP
    subgraph APP["ApiPayloadParser (Pure Parsing)"]
        direction TB
        E[Extension deserialization]
        M[Metadata extraction]
        S[Stream/preset parsing]
    end
    APP --> JFR
    subgraph JFR["JsonFieldReader (Shared)"]
        direction TB
        RS[readString / readInt]
        RL[readStringList]
        RO[readNestedObject]
    end

Catalog Freshness Strategy

graph TD
    START[Application Start] --> LM[Load metadata sync]
    LM -->|success| MCC[MetadataCompatibilityContext]
    LM -->|failure| BSF[Bundled snapshot fallback]
    MCC -->|constrains build/java| LE[Load extensions async]
    LE --> LIVE[Live API]
    LE --> CACHE[Cache fallback]
    LIVE --> CSC[CatalogSnapshotCache]
    CACHE --> CSC
    BSF --> RENDER[TUI renders immediately
with default extensions] CSC --> REPLACE[Catalog replaces defaults
when ready]

Domain Layer

graph LR
    subgraph Domain["Domain Model"]
        PR["ProjectRequest
(immutable)"] PRF["ProjectRequestFactory"] PRV["ProjectRequestValidator"] MCC["MetadataCompatibilityContext"] CPM["CliPrefillMapper"] FF["Forgefile + ForgefileLock"] FFS["ForgefileStore"] end PRF -->|creates| PR PRV -->|validates| PR MCC -->|constrains| PR CPM -->|maps CLI options| PR FFS -->|read/write| FF

Archive Layer

graph TD
    ZIP[ZIP Download] --> PAS[ProjectArchiveService]
    PAS -->|progress reporting| SZE[SafeZipExtractor]
    PAS -->|cancellation check| SZE
    SZE --> ZS["Zip-Slip protection
(path traversal guard)"] SZE --> ZB["Zip-Bomb protection
(size + ratio + entry count)"] SZE --> ROOT["Root directory normalization
(strips single root folder)"]

Design Principles

Principle Application

Single Responsibility

Each class has one reason to change. QuarkusApiClient handles HTTP; ApiPayloadParser handles JSON; OverlayRenderer handles overlay rendering. QuarkusForgeCli owns TUI bootstrap; HeadlessCli owns headless-only entry.

Sealed Types

CatalogLoadState (Loading/Loaded/Failed) and MenuKeyResult (Handled/Quit/ExportRecipe/GenerateAgain) make state transitions explicit and exhaustive.

Immutable Snapshots

MetadataPanelSnapshot, ExtensionsPanelSnapshot, FooterSnapshot are immutable records passed to renderers — no side effects.

DRY

JsonFieldReader deduplicates JSON helpers across 5+ classes. renderCompactField consolidates two near-identical rendering methods. AsyncFailureHandler centralizes 4-exception async failure mapping. applyFavoritesAndPresetFilters eliminates duplicate filter pipeline.

Derived State Elimination

successHint is computed from PostGenerationMenuState rather than stored as a separate mutable field.

Encapsulation

PostGenerationMenuState, StartupOverlayTracker, MetadataSelectorManager each own their state and expose it through focused APIs.

Resource Safety

QuarkusApiClient implements AutoCloseable; all instantiation sites use try-with-resources or whenComplete callbacks to prevent thread pool leaks.

Atomic Persistence

FileBackedExtensionFavoritesStore.saveAll() writes both favorites and recents in a single file operation, preventing data loss from split read-modify-write.

Testing Strategy

  • Extensive unit tests with deterministic async harnesses for TUI state machine testing

  • Integration tests using WireMock for API contract verification

  • Dedicated unit tests for extracted state managers: PostGenerationMenuState, MetadataSelectorManager, CatalogRowBuilder, StartupOverlayTracker

  • Async TUI tests use UiScheduler.immediate() and Duration.ZERO debounce for deterministic behavior

  • Property-based tests for ZIP extraction edge cases

See Testing Strategy for details.