From 1de4c890302ee25efcef4d05888489bf6c6263ff Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 22 Jul 2026 15:31:01 -0700 Subject: [PATCH] Expand graphical acceptance coverage --- test/acceptance | 25 +++++- test/acceptance.d/apps-test.sh | 26 +++--- test/acceptance.d/base-test.sh | 22 ++++- test/acceptance.d/menu-test.sh | 107 +++++++++++++++++++++++ test/acceptance.d/panels-test.sh | 103 ++++++++++++++++++++++ test/acceptance.d/session-test.sh | 8 +- test/acceptance.d/shell-surfaces-test.sh | 96 +++++++++++++++++--- test/acceptance.d/system-test.sh | 104 ++++++++++++++++++++++ 8 files changed, 460 insertions(+), 31 deletions(-) create mode 100644 test/acceptance.d/menu-test.sh create mode 100644 test/acceptance.d/panels-test.sh create mode 100644 test/acceptance.d/system-test.sh diff --git a/test/acceptance b/test/acceptance index b12c0c35..192e38f0 100755 --- a/test/acceptance +++ b/test/acceptance @@ -22,6 +22,16 @@ export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=$XDG_RUNT export OMARCHY_PATH="${OMARCHY_PATH:-$ROOT}" export PATH="$OMARCHY_PATH/bin:$PATH" +if [[ -z ${DISPLAY:-} ]]; then + display=$(systemctl --user show-environment | sed -n 's/^DISPLAY=//p' | head -1) + export DISPLAY="${display:-:0}" +fi + +if [[ -z ${LANG:-} ]]; then + locale_name=$(systemctl --user show-environment | sed -n 's/^LANG=//p' | head -1) + export LANG="${locale_name:-C.UTF-8}" +fi + # First boot may still be settling; wait for Hyprland to come up. boot_deadline=$((SECONDS + ${OMARCHY_ACCEPTANCE_BOOT_TIMEOUT:-300})) @@ -66,9 +76,20 @@ if (( ${#tests[@]} == 0 )); then exit 1 fi +status=0 +suite_started=$SECONDS for test in "${tests[@]}"; do + test_started=$SECONDS printf '==> %s\n' "${test#$ROOT/}" - bash "$test" + if ! timeout "${OMARCHY_ACCEPTANCE_TEST_TIMEOUT:-420}" bash "$test"; then + status=1 + fi + printf ' completed in %ss\n' "$((SECONDS - test_started))" done -printf 'ok - acceptance suite passed (artifacts in %s)\n' "$OMARCHY_ACCEPTANCE_DIR" +if ((status != 0)); then + printf 'not ok - acceptance suite failed in %ss (artifacts in %s)\n' "$((SECONDS - suite_started))" "$OMARCHY_ACCEPTANCE_DIR" >&2 + exit $status +fi + +printf 'ok - acceptance suite passed in %ss (artifacts in %s)\n' "$((SECONDS - suite_started))" "$OMARCHY_ACCEPTANCE_DIR" diff --git a/test/acceptance.d/apps-test.sh b/test/acceptance.d/apps-test.sh index 1717e0a7..d4f4932a 100644 --- a/test/acceptance.d/apps-test.sh +++ b/test/acceptance.d/apps-test.sh @@ -5,7 +5,7 @@ set -euo pipefail source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" launch_and_verify() { - local name="$1" command="$2" class="$3" + local name="$1" command="$2" class="$3" timeout="${4:-45}" # A pre-existing window makes the test ambiguous (and closing it would be # hostile on a dev machine) — acceptance runs expect a fresh session. @@ -14,9 +14,9 @@ launch_and_verify() { fi launch_app "$command" - wait_until "$name opens a window" 90 window_present "$class" + wait_until "$name opens a window" "$timeout" window_present "$class" sleep 1 - screenshot "app-$name" + screenshot "success-app-$name" local deadline=$((SECONDS + 30)) while window_present "$class" >/dev/null 2>&1; do @@ -31,15 +31,19 @@ launch_and_verify() { pass "$name window closes" } -# name|command|window class regex +# Keep launch coverage to the primary daily-use paths. The system acceptance +# test separately verifies the complete core package manifest. +# name|command|window class regex|launch timeout apps='terminal|foot|^foot$ browser|chromium --new-window|(?i)chromium -files|nautilus --new-window|org.gnome.Nautilus -calculator|gnome-calculator|org.gnome.Calculator -notes|obsidian|(?i)obsidian -office|libreoffice|(?i)(soffice|libreoffice) -pdf-viewer|evince|(?i)evince' +neovim|xdg-terminal-exec --app-id=org.omarchy.nvim nvim|org.omarchy.nvim +writer|omawrite|(?i)omawrite' -while IFS='|' read -r name command class; do - launch_and_verify "$name" "$command" "$class" +status=0 +while IFS='|' read -r name command class timeout; do + if ! (launch_and_verify "$name" "$command" "$class" "$timeout"); then + status=1 + fi done <<<"$apps" + +exit $status diff --git a/test/acceptance.d/base-test.sh b/test/acceptance.d/base-test.sh index 55151835..4262d40a 100644 --- a/test/acceptance.d/base-test.sh +++ b/test/acceptance.d/base-test.sh @@ -17,15 +17,33 @@ pass() { fail() { local description="$1" local detail="${2:-}" + local step=${description,,} + + step=${step// /-} + step=${step//[^a-z0-9-]/} [[ -n $detail ]] && printf '%s\n' "$detail" >&2 - screenshot "failure-$(date +%s)" + screenshot "failure-$step" printf 'not ok - %s\n' "$description" >&2 exit 1 } screenshot() { - grim "$ARTIFACTS/$1.png" 2>/dev/null || true + timeout 10 grim "$ARTIFACTS/$1.png" 2>/dev/null || true +} + +screen_contains() { + local text="$1" + local snapshot="/tmp/omarchy-acceptance-ocr-$$.png" + + if ! timeout 10 grim "$snapshot" 2>/dev/null; then + rm -f "$snapshot" + return 1 + fi + tesseract "$snapshot" stdout --psm 11 2>/dev/null | grep -Fqi -- "$text" + local status=$? + rm -f "$snapshot" + return $status } # Poll a command until it succeeds; screenshot and fail on timeout. diff --git a/test/acceptance.d/menu-test.sh b/test/acceptance.d/menu-test.sh new file mode 100644 index 00000000..39f20d67 --- /dev/null +++ b/test/acceptance.d/menu-test.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +CONFIG_FILE="$HOME/.config/omarchy/shell.json" +DEFAULTS_FILE="$OMARCHY_PATH/config/omarchy/shell.json" +config_backup=$(mktemp) +config_existed=0 + +if [[ -f $CONFIG_FILE ]]; then + cp "$CONFIG_FILE" "$config_backup" + config_existed=1 +fi + +restore_bar_config() { + omarchy-shell shell hide omarchy.menu >/dev/null 2>&1 || true + + if ((config_existed)); then + mkdir -p "$(dirname "$CONFIG_FILE")" + cp "$config_backup" "$CONFIG_FILE" + else + rm -f "$CONFIG_FILE" + fi + + omarchy-shell shell reloadConfig >/dev/null 2>&1 || true + rm -f "$config_backup" +} + +trap restore_bar_config EXIT + +bar_is_vertical() { + local width height + + read -r width height < <(hyprctl -j layers | jq -r ' + [.. | objects | select(.namespace? == "omarchy-bar")][0] + | [.w, .h] | @tsv + ') + + [[ $width =~ ^[0-9]+$ && $height =~ ^[0-9]+$ ]] && ((width < height)) +} + +bar_is_horizontal() { + local width height + + read -r width height < <(hyprctl -j layers | jq -r ' + [.. | objects | select(.namespace? == "omarchy-bar")][0] + | [.w, .h] | @tsv + ') + + [[ $width =~ ^[0-9]+$ && $height =~ ^[0-9]+$ ]] && ((width > height)) +} + +bar_position_is() { + local expected="$1" + + [[ $(jq -r '.bar.position // "top"' "$CONFIG_FILE") == $expected ]] +} + +source_file=$DEFAULTS_FILE +((config_existed)) && source_file=$CONFIG_FILE +original_position=$(jq -r '.bar.position // "top"' "$source_file") + +omarchy-shell shell summon omarchy.menu '{"menu":"root"}' >/dev/null +wait_until "root menu opens" 15 layer_present "omarchy-menu" +wait_until "root menu content is visible" 15 screen_contains "Apps" +screenshot "success-menu-01-root" + +wtype -k Down -k Down -k Down +sleep 1 +screenshot "success-menu-02-style-selected" +wtype -k Return + +wait_until "style submenu is visible" 15 screen_contains "Theme" +screenshot "success-menu-03-style-submenu" + +wtype -k Down -k Down -k Down -k Return +sleep 1 +screenshot "success-menu-04-menu-bar-submenu" + +wtype -k Return +sleep 1 +screenshot "success-menu-05-position-submenu" + +wtype -k Down -k Down -k Return +wait_until "menu bar position changes to left" 20 bar_position_is "left" +wait_until "menu bar becomes vertical" 20 bar_is_vertical +wait_until "menu closes after selecting a position" 15 layer_absent "omarchy-menu" +screenshot "success-menu-06-bar-left" + +if ((config_existed)); then + cp "$config_backup" "$CONFIG_FILE" +else + rm -f "$CONFIG_FILE" +fi +omarchy-shell shell reloadConfig >/dev/null + +if [[ $original_position == "left" || $original_position == "right" ]]; then + wait_until "menu bar restores its original vertical position" 20 bar_is_vertical +else + wait_until "menu bar restores its original horizontal position" 20 bar_is_horizontal +fi +screenshot "success-menu-07-bar-restored" + +trap - EXIT +restore_bar_config diff --git a/test/acceptance.d/panels-test.sh b/test/acceptance.d/panels-test.sh new file mode 100644 index 00000000..28396ec2 --- /dev/null +++ b/test/acceptance.d/panels-test.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +WEATHER_FILE="$HOME/.local/state/omarchy/settings/weather.json" +weather_backup=$(mktemp) +weather_existed=0 + +if [[ -f $WEATHER_FILE ]]; then + cp "$WEATHER_FILE" "$weather_backup" + weather_existed=1 +fi + +hide_panels() { + local plugin + + for plugin in omarchy.weather omarchy.bluetooth omarchy.network omarchy.audio omarchy.monitor omarchy.power; do + omarchy-shell shell hide "$plugin" >/dev/null 2>&1 || true + done +} + +restore_weather() { + hide_panels + + if ((weather_existed)); then + mkdir -p "$(dirname "$WEATHER_FILE")" + cp "$weather_backup" "$WEATHER_FILE" + else + rm -f "$WEATHER_FILE" + fi + + rm -f "$weather_backup" +} + +trap restore_weather EXIT + +open_and_capture_panel() { + local name="$1" plugin="$2" + + omarchy-shell shell summon "$plugin" >/dev/null + wait_until "$name panel opens" 15 layer_present "omarchy-keyboard-panel" + sleep 1 + screenshot "success-panel-$name" + + omarchy-shell shell hide "$plugin" >/dev/null + wait_until "$name panel closes" 15 layer_absent "omarchy-keyboard-panel" +} + +# Give weather deterministic coordinates so this test exercises the real +# Open-Meteo forecast instead of IP geolocation through wttr.in. +omarchy-weather-location --set "San Francisco" "37.7749,-122.4194" +omarchy-shell shell summon omarchy.weather >/dev/null +wait_until "weather panel opens" 15 layer_present "omarchy-keyboard-panel" +wait_until "weather location is visible" 30 screen_contains "SAN FRANCISCO" +wait_until "weather details are visible" 30 screen_contains "WIND" +screenshot "success-panel-weather" +omarchy-shell shell hide omarchy.weather >/dev/null +wait_until "weather panel closes" 15 layer_absent "omarchy-keyboard-panel" + +status=0 +panels='bluetooth|omarchy.bluetooth +network|omarchy.network +audio|omarchy.audio +monitor|omarchy.monitor' + +while IFS='|' read -r name plugin; do + if ! (trap - EXIT; open_and_capture_panel "$name" "$plugin"); then + status=1 + hide_panels + wait_until "$name failed panel is dismissed" 15 layer_absent "omarchy-keyboard-panel" + fi +done <<<"$panels" + +# The power widget intentionally disappears on desktops and VMs without a +# battery. Exercise it on laptops, and verify that hardware-less sessions take +# the supported no-panel path instead of treating that as a shell failure. +if upower -e | grep -q '/battery_'; then + if ! (trap - EXIT; open_and_capture_panel "power" "omarchy.power"); then + status=1 + hide_panels + wait_until "power failed panel is dismissed" 15 layer_absent "omarchy-keyboard-panel" + fi +else + pass "power panel is hidden without battery hardware" + screenshot "success-panel-power-unavailable" +fi + +# The common panel keyboard contract uses Tab to move to the next bar panel. +omarchy-shell shell summon omarchy.bluetooth >/dev/null +wait_until "panel keyboard navigation starts on bluetooth" 15 screen_contains "Bluetooth" +screenshot "success-panel-navigation-01-bluetooth" +wtype -k Tab +sleep 2 +wait_until "Tab keeps a shell panel open" 15 layer_present "omarchy-keyboard-panel" +screenshot "success-panel-navigation-02-next" +hide_panels +wait_until "keyboard-navigated panel closes" 15 layer_absent "omarchy-keyboard-panel" + +trap - EXIT +restore_weather +exit $status diff --git a/test/acceptance.d/session-test.sh b/test/acceptance.d/session-test.sh index e1642dae..8f87dde3 100644 --- a/test/acceptance.d/session-test.sh +++ b/test/acceptance.d/session-test.sh @@ -14,7 +14,11 @@ wait_until "omarchy-shell responds to ping" 60 omarchy-shell shell ping # Core shell plugins are loaded plugins=$(omarchy-shell shell listPlugins) -for plugin in omarchy.bar omarchy.launcher omarchy.emojis omarchy.menu omarchy.notifications; do +for plugin in \ + omarchy.audio omarchy.background omarchy.bar omarchy.bluetooth \ + omarchy.clipboard omarchy.emojis omarchy.launcher omarchy.menu \ + omarchy.monitor omarchy.network omarchy.notifications omarchy.power \ + omarchy.reminders omarchy.weather; do [[ $plugins == *"$plugin"* ]] || fail "shell plugin is loaded: $plugin" "loaded plugins: $plugins" pass "shell plugin is loaded: $plugin" done @@ -53,4 +57,4 @@ if [[ -n $failed_user ]]; then fi pass "no failed user units" -screenshot "desktop" +screenshot "success-desktop" diff --git a/test/acceptance.d/shell-surfaces-test.sh b/test/acceptance.d/shell-surfaces-test.sh index 82e986d2..fa9c9b03 100644 --- a/test/acceptance.d/shell-surfaces-test.sh +++ b/test/acceptance.d/shell-surfaces-test.sh @@ -15,34 +15,102 @@ open_and_close() { wait_until "$name opens" 15 layer_present "$namespace" sleep 1 - screenshot "$name-open" + screenshot "success-$name-open" omarchy-shell shell hide "$plugin" >/dev/null wait_until "$name closes" 15 layer_absent "$namespace" } -# Overlays open and close on IPC command -open_and_close "emoji-picker" omarchy.emojis omarchy-emojis -open_and_close "menu" omarchy.menu omarchy-menu '{"menu":"root"}' -open_and_close "clipboard" omarchy.clipboard omarchy-clipboard +# Search and select an emoji. The host harness separately proves the shortcut +# with a QMP hardware key chord, while this test focuses on UI behavior. +omarchy-shell shell summon omarchy.emojis >/dev/null +wait_until "emoji picker opens" 15 layer_present "omarchy-emojis" +wtype "rocket" +sleep 1 +screenshot "success-emoji-picker-search" +wtype -k Return +wait_until "emoji picker selection closes" 15 layer_absent "omarchy-emojis" -# The launcher does the full loop: open, search by typing, launch the top hit -if window_present "org.gnome.Calculator" >/dev/null 2>&1; then - fail "launcher test starts with no calculator window" "a calculator window is already open" +# Seed two clipboard entries, search for the older one, and copy it back out. +clipboard_token="Omarchy acceptance clipboard $(date +%s)" +printf '%s' "$clipboard_token" | wl-copy +wait_until "clipboard history captures test text" 15 grep -Fq "$clipboard_token" "$HOME/.local/state/omarchy/clipboard-history.json" +printf '%s' "clipboard decoy" | wl-copy +sleep 1 + +omarchy-shell shell summon omarchy.clipboard >/dev/null +wait_until "clipboard opens" 15 layer_present "omarchy-clipboard" +wtype "$clipboard_token" +wait_until "clipboard search finds test text" 15 screen_contains "Omarchy acceptance clipboard" +screenshot "success-clipboard-search" +wtype -M shift -k Return -m shift +wait_until "clipboard selection closes" 15 layer_absent "omarchy-clipboard" +wait_until "clipboard selection restores test text" 15 bash -c '[[ $(wl-paste --no-newline) == "$1" ]]' _ "$clipboard_token" + +# Exercise the system branch without invoking any destructive action. +omarchy-shell shell summon omarchy.menu '{"menu":"system"}' >/dev/null +wait_until "system menu opens" 15 layer_present "omarchy-menu" +wait_until "system menu content is visible" 15 screen_contains "Shutdown" +screenshot "success-system-menu" +wtype -k Escape +wait_until "system menu closes" 15 layer_absent "omarchy-menu" + +# Preview both visual selectors and cancel without changing user state. These +# cover thumbnail generation, the image-grid overlay, and current selection. +launch_app "omarchy-theme-bg-switcher" +wait_until "background selector opens" 30 layer_present "omarchy-image-selector" +sleep 1 +screenshot "success-background-selector" +wtype -k Escape +wait_until "background selector closes" 15 layer_absent "omarchy-image-selector" + +launch_app "omarchy-theme-switcher" +wait_until "theme selector opens" 30 layer_present "omarchy-image-selector" +sleep 1 +screenshot "success-theme-selector" +wtype -k Escape +wait_until "theme selector closes" 15 layer_absent "omarchy-image-selector" + +# Walk the reminder flow through each input screen, but dismiss before it +# schedules a real timer in the test user's session. +omarchy-shell shell summon omarchy.reminders >/dev/null +wait_until "reminder flow opens" 15 layer_present "omarchy-reminders" +screenshot "success-reminder-01-minutes-prompt" +wtype "5" +sleep 1 +screenshot "success-reminder-02-minutes-entered" +wtype -k Return +wait_until "reminder message prompt opens" 15 screen_contains "Reminder message" +screenshot "success-reminder-03-message-prompt" +wtype -k Escape +wait_until "reminder flow closes" 15 layer_absent "omarchy-reminders" + +# Render a real shell notification and clear it through the notification IPC. +omarchy-shell notifications dismissAll >/dev/null +omarchy-notification-send "Acceptance notification" "Shell notification rendering" --expire-time=15000 +wait_until "notification popup opens" 15 layer_present "omarchy-notifications" +wait_until "notification content is visible" 15 screen_contains "Acceptance notification" +screenshot "success-notification-popup" +omarchy-shell notifications dismissAll >/dev/null +wait_until "notification popup closes" 15 layer_absent "omarchy-notifications" + +# The launcher does the full loop: open, search by typing, launch the top hit. +if window_present "(?i)omawrite" >/dev/null 2>&1; then + fail "launcher test starts with no Omawrite window" "an Omawrite window is already open" fi omarchy-shell shell summon omarchy.launcher >/dev/null wait_until "launcher opens" 15 layer_present "omarchy-launcher" sleep 1 -screenshot "launcher-open" +screenshot "success-launcher-open" -wtype "calculator" +wtype "omawrite" sleep 1 -screenshot "launcher-search" +screenshot "success-launcher-search" wtype -k Return -wait_until "launcher launches the top search hit" 60 window_present "org.gnome.Calculator" +wait_until "launcher launches the top search hit" 60 window_present "(?i)omawrite" wait_until "launcher closes after launching" 15 layer_absent "omarchy-launcher" -close_windows "org.gnome.Calculator" -wait_until "calculator window closes" 30 window_absent "org.gnome.Calculator" +close_windows "(?i)omawrite" +wait_until "Omawrite window closes" 30 window_absent "(?i)omawrite" diff --git a/test/acceptance.d/system-test.sh b/test/acceptance.d/system-test.sh new file mode 100644 index 00000000..e8e4944e --- /dev/null +++ b/test/acceptance.d/system-test.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +status=0 + +verify_core_packages() { + local package + local -a missing=() + + while IFS= read -r package; do + [[ -z $package || $package == \#* ]] && continue + pacman -Q "$package" >/dev/null 2>&1 || missing+=("$package") + done <"$OMARCHY_PATH/install/omarchy-base.packages" + + (( ${#missing[@]} == 0 )) || fail "all Omarchy core packages are installed" "missing packages: ${missing[*]}" + pass "all Omarchy core packages are installed (${#missing[@]} missing)" +} + +verify_defaults() { + [[ $(omarchy-default-browser) == "chromium" ]] || fail "Chromium is the default browser" + pass "Chromium is the default browser" + + [[ $(omarchy-default-terminal) == "foot" ]] || fail "Foot is the default terminal" + pass "Foot is the default terminal" + + [[ $(omarchy-default-editor) == "nvim" ]] || fail "Neovim is the default editor" + pass "Neovim is the default editor" + + [[ $(omarchy-theme-current) != "Unknown" ]] || fail "a current theme is configured" + pass "a current theme is configured" + + [[ $(omarchy-theme-bg-current) != "Unknown" ]] || fail "a current background is configured" + pass "a current background is configured" + + [[ -n $(omarchy-font-current) ]] || fail "a monospace font is configured" + pass "a monospace font is configured" + + [[ $(xdg-mime query default x-scheme-handler/http) == "chromium.desktop" ]] || fail "HTTP MIME handling uses Chromium" + [[ $(xdg-mime query default inode/directory) == "org.gnome.Nautilus.desktop" ]] || fail "directory MIME handling uses Nautilus" + pass "desktop MIME handlers are configured" +} + +verify_services() { + local unit + + for unit in \ + avahi-daemon.service cups.service cups-browsed.service docker.socket \ + NetworkManager.service power-profiles-daemon.service sddm.service \ + systemd-resolved.service ufw.service; do + systemctl is-enabled --quiet "$unit" || fail "core system services are enabled" "$unit is not enabled" + done + pass "core system services are enabled" + + for unit in NetworkManager.service systemd-resolved.service ufw.service; do + systemctl is-active --quiet "$unit" || fail "critical system services are running" "$unit is not active" + done + pass "critical system services are running" + + systemctl --user is-active --quiet pipewire.service pipewire-pulse.service wireplumber.service || + fail "user audio services are running" + pass "user audio services are running" +} + +verify_runtime_tools() { + timeout 20 docker info >/dev/null 2>&1 || fail "Docker is usable by the desktop user" + pass "Docker is usable by the desktop user" + + nvim --headless '+qa' >/dev/null 2>&1 || fail "Neovim starts headlessly" + pass "Neovim starts headlessly" + + timeout 10 fastfetch --pipe false >/dev/null 2>&1 || fail "Fastfetch can read system information" + pass "Fastfetch can read system information" + + git --version >/dev/null || fail "Git is installed and runnable" + tmux -V >/dev/null || fail "Tmux is installed and runnable" + mise --version >/dev/null || fail "Mise is installed and runnable" + pass "core terminal tools are runnable" +} + +verify_user_setup() { + local directory + + for directory in DESKTOP DOCUMENTS DOWNLOAD PICTURES; do + [[ -d $(xdg-user-dir "$directory") ]] || fail "XDG user directories exist" "$directory is missing" + done + pass "XDG user directories exist" + + [[ -e $HOME/.local/state/omarchy/current/theme ]] || fail "current theme state exists" + [[ -e $HOME/.local/state/omarchy/current/background ]] || fail "current background state exists" + [[ -s $HOME/.config/omarchy/shell.json ]] || fail "shell configuration exists" + jq empty "$HOME/.config/omarchy/shell.json" || fail "shell configuration is valid JSON" + pass "Omarchy user state and shell configuration exist" +} + +for check in verify_core_packages verify_defaults verify_services verify_runtime_tools verify_user_setup; do + if ! ("$check"); then + status=1 + fi +done + +exit $status