How the Omarchy Menu's `checked` Field Wires to Shell Conditions

The checked: field in Omarchy's JSONC menu definition contains a Bash expression that gets evaluated once per menu opening, with the Boolean result stored in checkedResults and rendered as a "✓" glyph via MenuModel.labelFor().

The Omarchy desktop environment uses a dynamic menu system where check-marks reflect real-time shell state. This article explains how the checked field in menu definitions connects to underlying shell conditions, tracing the full execution pipeline from JSONC configuration to QML rendering.

Where Menu checked Fields Are Defined

Menu entries live in default/omarchy/omarchy-menu.jsonc. Each entry can specify a checked: property containing arbitrary Bash code.

The syntax is straightforward:

"setup.default.browser.firefox": {
  "icon": "",
  "label": "Firefox",
  "checked": "[[ \"$(omarchy-default-browser)\" == \"firefox\" ]]",
  "action": "omarchy-default-browser firefox"
}

When this menu opens, the Firefox entry displays a check-mark only if omarchy-default-browser currently returns "firefox".

How MenuModel.js Processes checked Guards

The shell/plugins/menu/MenuModel.js module orchestrates evaluation. It collects guards, builds a Bash script, executes it, and maps results for UI consumption.

Step 1: Collect Guard Expressions

Lines 85-88 scan the parsed menu entries and accumulate checked: expressions (alongside when: and disabled:):

if (entry.checked) guards += guardLine(ids[i], "c", entry.checked)

The "c" tag identifies this as a checked guard versus "d" for disabled or "w" for when.

Step 2: Build the Guard Evaluation Script

The guardLine function (lines 69-71) generates Bash that runs each expression in a subshell:

function guardLine(id, tag, expression) {
  return "if { " + substituteGuardReaders(expression) +
         "; } >/dev/null 2>&1; then echo " + id + ":" + tag + ":1; " +
         "else echo " + id + ":" + tag + ":0; fi\n"
}

This produces deterministic output: <id>:<tag>:1 for true, <id>:<tag>:0 for false.

Step 3: Execute and Parse Results

The combined guardScript runs once. Output lines populate two maps:

  • root.checkedResults — id → Boolean for checked guards
  • root.disabledResults — id → Boolean for disabled guards

This single batch evaluation minimizes shell fork overhead during menu rendering.

Step 4: Generate Display Labels with labelFor

The labelFor helper (lines 84-88) appends "✓" when checkedResults shows true:

function labelFor(entry, checkedResults, disabledResults) {
  var marked = (entry.checked && checkedResults && checkedResults[entry.id]) ||
               isDisabled(disabledResults, entry);
  return marked ? entry.label + " ✓" : entry.label;
}

The check-mark is textual (Unicode U+2713), not a separate icon or widget state.

Complete Execution Example

Given the Firefox browser entry above, the generated guard script looks like:


# Prelude: cache expensive readers

__omarchy_read_2=$(omarchy-default-browser 2>/dev/null) || :

# Guard evaluation for checked state

if { [[ "$__omarchy_read_2" == "firefox" ]] ; } >/dev/null 2>&1; then
  echo setup.default.browser.firefox:c:1
else
  echo setup.default.browser.firefox:c:0
fi

The substituteGuardReaders() call replaces $(omarchy-default-browser) with the cached variable, eliminating redundant process spawns.

Parsed result stored in JavaScript:

root.checkedResults = {
  "setup.default.browser.firefox": true   // "Firefox ✓" displayed
}

QML binding for the menu row:

label: MenuModel.labelFor(entry, root.checkedResults, root.disabledResults)

Performance and Caching Characteristics

Aspect Implementation
Evaluation timing Once per menu open, not per-frame
Reader caching substituteGuardReaders() collapses duplicate $() calls
Output parsing Line-oriented, delimiter-separated (:)
UI update Reactive binding to root.checkedResults

This design keeps menu opening latency low even with dozens of checked conditions.

Key Source Files

File Responsibility
default/omarchy/omarchy-menu.jsonc JSONC menu definitions with checked: expressions
shell/plugins/menu/MenuModel.js Guard collection, script generation, result parsing, labelFor()
shell/plugins/menu/Menu.qml QML container consuming checkedResults for rendering

Summary

  • checked: contains Bash expressions evaluated at menu-open time
  • MenuModel.js batches all guards into one script execution
  • guardLine() generates tagged output (:c:1 or :c:0)
  • checkedResults map stores Boolean outcomes by entry id
  • labelFor() appends "✓" based on checkedResults[entry.id]
  • The pipeline is single-batch, cached-reader, reactive-bound for performance

Frequently Asked Questions

What happens if a checked expression fails or returns non-zero?

The guardLine wrapper redirects stderr to /dev/null and treats non-zero exit as false (:0). The menu entry simply appears unchecked; no error propagates to the UI.

Can checked expressions reference environment variables or functions?

Yes. The guard script runs with full shell context, so exported variables, functions from ~/.bashrc, and Omarchy's own shell library are all available.

Why use textual "✓" instead of a QML checked property?

The Omarchy menu uses a unified list widget where styling (icons, labels, shortcuts) is controlled through labelFor(). Embedding the check-mark in the label string keeps the rendering pipeline simple and consistent across entry types.

How do I debug a checked expression that behaves unexpectedly?

Run the generated guard script manually. Extract it from MenuModel.js logging or temporarily add set -x to see actual values. The cached reader variables (like __omarchy_read_2) are visible in the generated output.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →