# jonathanlotan/swivel architecture

> Architecture diagram of the GitHub repository jonathanlotan/swivel, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-11-11). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/jonathanlotan/swivel
- Repository: https://github.com/jonathanlotan/swivel

## Overview

Swivel is an offline macOS file-conversion and file-action app, with a command-line entry point sharing its conversion engine. The main gesture flow is drag files, choose a ring and target, then run a queued conversion or file action; the app also offers menu-bar, Shortcuts, Finder Services, and Quick Action entry points. Conversion work is staged beside the original and published without overwriting it. The README documents crop, metadata, sharing, archive, media, and document capabilities; several are represented at their source directories because their implementations were not sampled.

Main entry points: [`Packages/ConverterCore/Sources/ConverterCore/JobRunner.swift`](https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/JobRunner.swift) (Job runner), [`Packages/ConverterCore/Sources/ConverterCore/ConverterRegistry.swift`](https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/ConverterRegistry.swift) (Converter registry), [`Converter/App/ShiftDrag/ShiftDragController.swift`](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/ShiftDrag/ShiftDragController.swift) (Drag wheel), [`Converter/App/JobQueue.swift`](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/JobQueue.swift) (Job queue)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_experience["App experience"]
  node_wheel["Drag wheel"]
  node_wheelmodel["Wheel model"]
  node_watcher["Drag watcher<br/>[DragWatcher.swift]"]
  node_queue["Job queue<br/>[JobQueue.swift]"]
  node_actions["File actions<br/>[FileActions.swift]"]
  node_status["Menu bar"]
  node_settings["Settings<br/>[AppSettings.swift]"]
  node_crop["Crop editor"]
  node_metadata["Metadata view"]
end

subgraph group_entrypoints["Other entry points"]
  node_shortcuts["Shortcuts action"]
  node_services["Finder Services"]
  node_quickaction["Quick Action"]
  node_cli["CLI<br/>[CLI.swift]"]
end

subgraph group_engine["Conversion engine"]
  node_registry["Converter registry"]
  node_input["Input file<br/>[InputFile.swift]"]
  node_options["Conversion options"]
  node_runner["Job runner<br/>[JobRunner.swift]"]
end

subgraph group_formats["Conversion domains"]
  node_images["Image converters"]
  node_media["Audio/video converters"]
  node_documents["Document converters"]
  node_archives["Archive converters"]
  node_metadataengine["File metadata<br/>[FileMetadata.swift]"]
end

node_user(("Mac user"))

node_user -->|"drags files"| node_wheel
node_wheel -->|"observes drag"| node_watcher
node_wheel -->|"builds menu"| node_wheelmodel
node_wheel -->|"queues conversion"| node_queue
node_wheel -->|"performs action"| node_actions
node_wheelmodel -->|"queries formats"| node_registry
node_wheelmodel -->|"creates inputs"| node_input
node_queue -->|"runs jobs"| node_runner
node_queue -.->|"passes options"| node_options
node_status -->|"queues conversions"| node_queue
node_status -->|"offers actions"| node_actions
node_settings -->|"stores settings"| node_options
node_shortcuts -->|"finds converters"| node_registry
node_shortcuts -->|"runs conversion"| node_runner
node_services -.->|"offers conversions"| node_registry
node_quickaction -->|"uses support"| node_services
node_cli -->|"selects converter"| node_registry
node_cli -->|"runs conversion"| node_runner
node_registry -->|"registers converters"| node_images
node_registry -->|"registers converters"| node_media
node_registry -->|"registers converters"| node_documents
node_registry -->|"registers converters"| node_archives
node_runner -->|"reads input"| node_input
node_runner -.->|"invokes converter"| node_images
node_runner -.->|"invokes converter"| node_media
node_runner -.->|"invokes converter"| node_documents
node_runner -.->|"invokes converter"| node_archives
node_crop -->|"edits queued crop"| node_queue
node_metadata -.->|"displays metadata"| node_metadataengine

click node_wheel "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/ShiftDrag/ShiftDragController.swift"
click node_wheelmodel "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/ShiftDrag/RadialMenuModel.swift"
click node_watcher "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/ShiftDrag/DragWatcher.swift"
click node_queue "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/JobQueue.swift"
click node_actions "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/Actions/FileActions.swift"
click node_status "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/StatusItemController.swift"
click node_settings "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/Settings/AppSettings.swift"
click node_crop "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/Crop/CropEditorView.swift"
click node_metadata "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/Info/MetadataWindow.swift"
click node_shortcuts "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/Intents/ConvertFileIntent.swift"
click node_services "https://github.com/jonathanlotan/swivel/blob/main/Converter/App/ServicesProvider.swift"
click node_quickaction "https://github.com/jonathanlotan/swivel/blob/main/Converter/QuickAction/ActionRequestHandler.swift"
click node_cli "https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/swivel/CLI.swift"
click node_registry "https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/ConverterRegistry.swift"
click node_input "https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/InputFile.swift"
click node_options "https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/ConversionOptions.swift"
click node_runner "https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/JobRunner.swift"
click node_images "https://github.com/jonathanlotan/swivel/tree/main/Packages/ConverterCore/Sources/ConverterCore/Image"
click node_media "https://github.com/jonathanlotan/swivel/tree/main/Packages/ConverterCore/Sources/ConverterCore/Media"
click node_documents "https://github.com/jonathanlotan/swivel/tree/main/Packages/ConverterCore/Sources/ConverterCore/Documents"
click node_archives "https://github.com/jonathanlotan/swivel/tree/main/Packages/ConverterCore/Sources/ConverterCore/Archives"
click node_metadataengine "https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/Metadata/FileMetadata.swift"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_wheel,node_wheelmodel,node_watcher,node_queue,node_actions,node_status,node_settings,node_crop,node_metadata,node_user toneBlue
class node_shortcuts,node_services,node_quickaction,node_cli toneAmber
class node_registry,node_input,node_options,node_runner toneMint
class node_images,node_media,node_documents,node_archives,node_metadataengine toneRose
```

## Components

### App experience

- [Drag wheel](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/ShiftDrag/ShiftDragController.swift): `Converter/App/ShiftDrag/ShiftDragController.swift`
- [Wheel model](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/ShiftDrag/RadialMenuModel.swift): `Converter/App/ShiftDrag/RadialMenuModel.swift`
- [Drag watcher](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/ShiftDrag/DragWatcher.swift): `Converter/App/ShiftDrag/DragWatcher.swift`
- [Job queue](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/JobQueue.swift): `Converter/App/JobQueue.swift`
- [File actions](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/Actions/FileActions.swift): `Converter/App/Actions/FileActions.swift`
- [Menu bar](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/StatusItemController.swift): `Converter/App/StatusItemController.swift`
- [Settings](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/Settings/AppSettings.swift): `Converter/App/Settings/AppSettings.swift`
- [Crop editor](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/Crop/CropEditorView.swift): `Converter/App/Crop/CropEditorView.swift`
- [Metadata view](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/Info/MetadataWindow.swift): `Converter/App/Info/MetadataWindow.swift`

### Other entry points

- [Shortcuts action](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/Intents/ConvertFileIntent.swift): `Converter/App/Intents/ConvertFileIntent.swift`
- [Finder Services](https://github.com/jonathanlotan/swivel/blob/main/Converter/App/ServicesProvider.swift): `Converter/App/ServicesProvider.swift`
- [Quick Action](https://github.com/jonathanlotan/swivel/blob/main/Converter/QuickAction/ActionRequestHandler.swift): `Converter/QuickAction/ActionRequestHandler.swift`
- [CLI](https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/swivel/CLI.swift): `Packages/ConverterCore/Sources/swivel/CLI.swift`

### Conversion engine

- [Converter registry](https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/ConverterRegistry.swift): `Packages/ConverterCore/Sources/ConverterCore/ConverterRegistry.swift`
- [Input file](https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/InputFile.swift): `Packages/ConverterCore/Sources/ConverterCore/InputFile.swift`
- [Conversion options](https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/ConversionOptions.swift): `Packages/ConverterCore/Sources/ConverterCore/ConversionOptions.swift`
- [Job runner](https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/JobRunner.swift): `Packages/ConverterCore/Sources/ConverterCore/JobRunner.swift`

### Conversion domains

- [Image converters](https://github.com/jonathanlotan/swivel/tree/main/Packages/ConverterCore/Sources/ConverterCore/Image): `Packages/ConverterCore/Sources/ConverterCore/Image`
- [Audio/video converters](https://github.com/jonathanlotan/swivel/tree/main/Packages/ConverterCore/Sources/ConverterCore/Media): `Packages/ConverterCore/Sources/ConverterCore/Media`
- [Document converters](https://github.com/jonathanlotan/swivel/tree/main/Packages/ConverterCore/Sources/ConverterCore/Documents): `Packages/ConverterCore/Sources/ConverterCore/Documents`
- [Archive converters](https://github.com/jonathanlotan/swivel/tree/main/Packages/ConverterCore/Sources/ConverterCore/Archives): `Packages/ConverterCore/Sources/ConverterCore/Archives`
- [File metadata](https://github.com/jonathanlotan/swivel/blob/main/Packages/ConverterCore/Sources/ConverterCore/Metadata/FileMetadata.swift): `Packages/ConverterCore/Sources/ConverterCore/Metadata/FileMetadata.swift`

### Other components

- **Mac user**

## Connections

- Mac user → Drag wheel: drags files (evidence: `README.md`)
- Drag wheel → Drag watcher: observes drag (evidence: `Converter/App/ShiftDrag/ShiftDragController.swift`)
- Drag wheel → Wheel model: builds menu (evidence: `Converter/App/ShiftDrag/ShiftDragController.swift`)
- Drag wheel → Job queue: queues conversion (evidence: `Converter/App/ShiftDrag/ShiftDragController.swift`)
- Drag wheel → File actions: performs action (evidence: `Converter/App/ShiftDrag/ShiftDragController.swift`)
- Wheel model → Converter registry: queries formats (evidence: `Converter/App/ShiftDrag/RadialMenuModel.swift`)
- Wheel model → Input file: creates inputs (evidence: `Converter/App/ShiftDrag/RadialMenuModel.swift`)
- Job queue → Job runner: runs jobs (evidence: `Converter/App/JobQueue.swift`)
- Job queue → Conversion options: passes options (evidence: `Converter/App/JobQueue.swift`)
- Menu bar → Job queue: queues conversions (evidence: `Converter/App/StatusItemController.swift`)
- Menu bar → File actions: offers actions (evidence: `Converter/App/StatusItemController.swift`)
- Settings → Conversion options: stores settings (evidence: `Converter/App/Settings/AppSettings.swift`)
- Shortcuts action → Converter registry: finds converters (evidence: `Converter/App/Intents/ConvertFileIntent.swift`)
- Shortcuts action → Job runner: runs conversion (evidence: `Converter/App/Intents/ConvertFileIntent.swift`)
- Finder Services → Converter registry: offers conversions
- Quick Action → Finder Services: uses support (evidence: `Converter/QuickAction/ActionRequestHandler.swift`)
- CLI → Converter registry: selects converter (evidence: `Packages/ConverterCore/Sources/swivel/CLI.swift`)
- CLI → Job runner: runs conversion (evidence: `Packages/ConverterCore/Sources/swivel/CLI.swift`)
- Converter registry → Image converters: registers converters (evidence: `Packages/ConverterCore/Sources/ConverterCore/ConverterRegistry.swift`)
- Converter registry → Audio/video converters: registers converters (evidence: `Packages/ConverterCore/Sources/ConverterCore/ConverterRegistry.swift`)
- Converter registry → Document converters: registers converters (evidence: `Packages/ConverterCore/Sources/ConverterCore/ConverterRegistry.swift`)
- Converter registry → Archive converters: registers converters (evidence: `Packages/ConverterCore/Sources/ConverterCore/ConverterRegistry.swift`)
- Job runner → Input file: reads input (evidence: `Packages/ConverterCore/Sources/ConverterCore/JobRunner.swift`)
- Job runner → Image converters: invokes converter
- Job runner → Audio/video converters: invokes converter
- Job runner → Document converters: invokes converter
- Job runner → Archive converters: invokes converter
- Crop editor → Job queue: edits queued crop (evidence: `Converter/App/Crop/CropEditorView.swift`)
- Metadata view → File metadata: displays metadata
