bridgex crate (version 0.2.1). The codebase is intentionally small and modular: each concern lives in its own module, there is no shared global mutable state outside of Freya’s reactive primitives, and the conversion pipeline is a straightforward function call chain. This page describes how the modules fit together, what each key dependency provides, how a file travels from disk to rendered Markdown, and how the release binary is optimised.
Module layout
The source tree undersrc/ is divided into five top-level modules, each with a clear single responsibility:
app
The root application component. Contains
app(), which owns all top-level use_state hooks, initialises the theme, wires together the menu bar and popups, drives the editor, and registers the global keyboard handler.logic
Houses
converter.rs and the single public function convert_from_path(). This is the only place in the codebase that calls into Markitdown-rs; all LLM orchestration for JPEG images also happens here.theme
Declares the
GITHUB_COLORS constant (ColorsSheet), github_app_theme() (returns a Freya Theme), and github_editor_theme() (returns an EditorTheme for the code editor). No mutable state.ui
Contains the individual UI components:
menu (menu bar and open_file() dialog helper), llm (LLM API Key popup), about (About popup), licenses (Licenses popup), and popup (shared popup primitives).utils
A collection of utility modules:
files—FileOwn(path wrapper),Filter(dialog filter type), and dialog helper functions (open_file_dialog,save_file_dialog,write_text_file).llm_config—LLMConfigstruct withload()andsave()for JSON persistence.constants—DOCUMENT_EXTENSIONS(["pdf", "docx"]) andMENU_HEIGHT(30.0px).helpers—open_url()wrapper aroundwebbrowser::open.
Key dependencies
The full dependency set is declared inCargo.toml. The table below covers the dependencies that directly shape the application’s architecture:
The
freya pin uses an exact version (=0.4.0-rc.19) to prevent accidental upgrades to incompatible release candidates. When upgrading Freya, update this pin explicitly and retest the full UI.File conversion pipeline
The journey from user action to rendered Markdown follows a linear path with no background threads involved except for the optional LLM image-description call:1
User picks a file
The user clicks File → Open or presses
Ctrl+O / ⌘+O. This calls open_file() in menu.rs, which builds a Vec<Filter> and delegates to FileOwn::open_file_dialog() (wrapping rfd). The result is an Option<FileOwn> containing the selected path.2
State update triggers a re-render
open_file() returns Some(FileOwn), which is written into the open_file_state: State<Option<FileOwn>> hook in app(). Freya schedules a re-render.3
Conversion is triggered
At the top of the next render pass,
app() reads open_file_state. If it holds Some(file), it immediately calls:4
Markitdown-rs converts the file
Inside
convert_from_path(), a MarkItDown::new() instance is created and .convert(filename, Some(options)) is called with a ConversionOptions struct that carries the normalised file extension, and optionally the LLM client and model strings.5
LLM image description (JPEG only)
If the file extension is
.jpg (.jpeg is normalised to .jpg in code), and all three LLM fields are non-empty, converter.rs sets the appropriate provider environment variable and calls llm::get_llm_description() using a single-threaded tokio runtime built with Builder::new_current_thread().enable_all().build(). The description is appended under a # Description: heading.6
Editor state is updated
The returned
String is written into the CodeEditorData rope:open_file_state is reset to None to prevent re-conversion on the next render.7
Markdown preview re-renders
The
MarkdownViewer component in the right panel reads the rope content reactively and re-renders the formatted preview. The left panel’s CodeEditor reflects the raw Markdown source.UI state management
Bridgex uses Freya’suse_state hook for all mutable runtime state. There is no global singleton, no Arc<Mutex<...>>, and no message-passing channel. All state lives inside the single app() component and is passed into child components by cloning the State<T> handle (which is reference-counted internally by Freya).
Popup visibility is owned inside the respective popup structs (
AboutPopup, LicencesPopup, LLMApiKeyPopup) and exposed as public State<bool> fields. app() clones those handles so that both the menu bar and the keyboard handler can toggle the same piece of state.Release build profile
The[profile.release] section in Cargo.toml applies aggressive size and speed optimisations:
Contributing: where to make changes
Add a new file format
Add a
Filter::new(...) entry in open_file() (src/ui/menu.rs) and verify that Markitdown-rs supports the new extension. No changes to converter.rs are needed unless the format requires special pre-processing.Add a new LLM provider
Add a new
match arm in converter.rs for the environment variable, and extend the llm_client validation if needed. Update the LLM Settings popup in src/ui/llm.rs to expose the new provider string.Change the theme
Edit
GITHUB_COLORS in src/theme.rs. All UI components reference this constant or the derived github_app_theme() / github_editor_theme() functions, so a single edit propagates everywhere.Add a new keyboard shortcut
Add a new
Code::Key* arm inside the on_global_key_down handler in src/app.rs, mirroring the pattern used by the existing six shortcuts.