From 64e6e5df861734e2a6fb595c12ba766ea4cef1ae Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 31 May 2026 13:08:10 +0100 Subject: [PATCH 1/4] Prepare Omarchy shell script migration --- scripts/.local/bin/on-resume | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/scripts/.local/bin/on-resume b/scripts/.local/bin/on-resume index 59764c1e..06a700ba 100755 --- a/scripts/.local/bin/on-resume +++ b/scripts/.local/bin/on-resume @@ -1,21 +1,23 @@ #!/bin/bash -# Called by hypridle after_sleep_cmd on system resume from sleep. +# Called after system resume from sleep. # Restarts services that don't recover well after suspend. LOG_FILE="/tmp/on-resume.log" CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" WAYBAR_CACHE_DIR="$CACHE_HOME/waybar" -DOT_FETCH_CACHE_DIR="$CACHE_HOME/dot/fetch-upstream" log() { echo "[$(date '+%H:%M:%S')] $1" >> "$LOG_FILE" } -clear_git_caches() { - rm -f "$WAYBAR_CACHE_DIR"/git-*-waybar.json "$WAYBAR_CACHE_DIR"/git-*-waybar.json.tmp +clear_git_refresh_locks() { + rm -f "$WAYBAR_CACHE_DIR"/git-*-waybar.json.tmp rmdir "$WAYBAR_CACHE_DIR"/git-*-waybar.lock 2>/dev/null || true - rm -rf "$DOT_FETCH_CACHE_DIR" +} + +refresh_shell_indicators() { + omarchy-shell -q omarchy.indicators refresh || true } if [[ "${ON_RESUME_DETACHED:-0}" != "1" ]]; then @@ -31,15 +33,13 @@ log "Resume started" pkill -f '/usr/bin/[t]witch-notifications( |$)' >/dev/null 2>&1 || pkill -f '(^| )[t]witch-notifications( |$)' >/dev/null 2>&1 || true log "Killed twitch-notifications" -# Clear git caches and restart waybar so modules refresh after network resume -clear_git_caches -pkill -x waybar >/dev/null 2>&1 || true -sleep 0.2 -pkill -9 -x waybar >/dev/null 2>&1 || true -log "Killed waybar, cleared git caches" +# Clear stale git watcher locks while keeping cached JSON for restart efficiency. +clear_git_refresh_locks +log "Cleared stale git watcher locks and temporary cache files" -uwsm-app -- waybar >/dev/null 2>&1 & -log "Waybar restarted" +# Ask the shell to repaint state that may have changed while suspended. +refresh_shell_indicators +log "Requested Omarchy shell indicator refresh" # Restart twitch-notifications after brief delay sleep 1 From 610ea4857e3398b55f3fa292aaf41f1232435cab Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 31 May 2026 15:10:59 +0100 Subject: [PATCH 2/4] Add Omarchy shell resume monitor --- scripts/.local/bin/on-resume | 8 ++++-- scripts/.local/bin/on-resume-monitor | 28 +++++++++++++++++++ .../user/dot-on-resume-monitor.service | 13 +++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100755 scripts/.local/bin/on-resume-monitor create mode 100644 systemd/.config/systemd/user/dot-on-resume-monitor.service diff --git a/scripts/.local/bin/on-resume b/scripts/.local/bin/on-resume index 06a700ba..9ba939d2 100755 --- a/scripts/.local/bin/on-resume +++ b/scripts/.local/bin/on-resume @@ -5,15 +5,17 @@ LOG_FILE="/tmp/on-resume.log" CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" -WAYBAR_CACHE_DIR="$CACHE_HOME/waybar" +# The git status modules keep the legacy Waybar cache namespace until their +# shell module port lands. +GIT_MODULE_CACHE_DIR="$CACHE_HOME/waybar" log() { echo "[$(date '+%H:%M:%S')] $1" >> "$LOG_FILE" } clear_git_refresh_locks() { - rm -f "$WAYBAR_CACHE_DIR"/git-*-waybar.json.tmp - rmdir "$WAYBAR_CACHE_DIR"/git-*-waybar.lock 2>/dev/null || true + rm -f "$GIT_MODULE_CACHE_DIR"/git-*-waybar.json.tmp + rmdir "$GIT_MODULE_CACHE_DIR"/git-*-waybar.lock 2>/dev/null || true } refresh_shell_indicators() { diff --git a/scripts/.local/bin/on-resume-monitor b/scripts/.local/bin/on-resume-monitor new file mode 100755 index 00000000..c0806b53 --- /dev/null +++ b/scripts/.local/bin/on-resume-monitor @@ -0,0 +1,28 @@ +#!/bin/bash + +set -euo pipefail + +sleep_signal="type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'" +saw_sleep=0 + +run_resume_recovery() { + sleep 1 + "$HOME/.local/bin/on-resume" +} + +while IFS= read -r line; do + case "$line" in + *"boolean true"*) + saw_sleep=1 + ;; + *"boolean false"*) + if (( saw_sleep )); then + run_resume_recovery + fi + saw_sleep=0 + ;; + esac +done < <(dbus-monitor --system "$sleep_signal") + +# Restart the user service if dbus-monitor exits unexpectedly. +exit 1 diff --git a/systemd/.config/systemd/user/dot-on-resume-monitor.service b/systemd/.config/systemd/user/dot-on-resume-monitor.service new file mode 100644 index 00000000..8fa886bc --- /dev/null +++ b/systemd/.config/systemd/user/dot-on-resume-monitor.service @@ -0,0 +1,13 @@ +[Unit] +Description=Run dot resume recovery after system resume +After=dbus.socket +Requires=dbus.socket + +[Service] +Type=simple +ExecStart=%h/.local/bin/on-resume-monitor +Restart=always +RestartSec=2 + +[Install] +WantedBy=graphical-session.target From 92a43110bf383c56084e0a12b7f68d6ad58897a7 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 31 May 2026 15:23:32 +0100 Subject: [PATCH 3/4] Check resume monitor in dot doctor --- dot/src/doctor/checks/systemd.ts | 203 +++++++++++++++++++++---------- dot/src/doctor/runner.ts | 2 + 2 files changed, 141 insertions(+), 64 deletions(-) diff --git a/dot/src/doctor/checks/systemd.ts b/dot/src/doctor/checks/systemd.ts index 19a495bc..36e8f600 100644 --- a/dot/src/doctor/checks/systemd.ts +++ b/dot/src/doctor/checks/systemd.ts @@ -21,6 +21,7 @@ const LEGACY_WORKFLOW_WATCH_SERVICE_UNIT = "git-workflow-watch.service"; const LEGACY_WORKFLOW_WATCH_TIMER_UNIT = "git-workflow-watch.timer"; const DOCTOR_STARTUP_TIMER_UNIT = "dot-doctor-startup.timer"; const DAILY_VOLUME_ZERO_TIMER_UNIT = "daily-volume-zero.timer"; +const RESUME_MONITOR_SERVICE_UNIT = "dot-on-resume-monitor.service"; function pathExistsOrSymlink(path: string): boolean { try { @@ -67,6 +68,87 @@ function addObsoletePathCheck( } } +function addExecutablePresenceCheck( + results: CheckResult[], + path: string, + okMessage: string, + warnMessage: string, + detail?: string, +): void { + results.push( + executableExists(path) + ? { severity: "ok", message: okMessage } + : { + severity: "warn", + message: warnMessage, + ...(detail && { detail }), + }, + ); +} + +function addFilePresenceCheck( + results: CheckResult[], + path: string, + okMessage: string, + warnMessage: string, + detail: string, +): void { + results.push( + existsSync(path) + ? { severity: "ok", message: okMessage } + : { severity: "warn", message: warnMessage, detail }, + ); +} + +const checkRequiredUserUnit = ( + results: CheckResult[], + executor: CommandExecutorService, + unit: string, + label: string, + enableDetail: string, +) => + Effect.gen(function* () { + const hasSystemctl = + (yield* executor.exitCode("which", ["systemctl"])) === 0; + if (!hasSystemctl) { + results.push({ + severity: "warn", + message: `Skipping ${label.toLowerCase()} checks (systemctl not found)`, + }); + return; + } + + const enabled = yield* executor.exitCode("systemctl", [ + "--user", + "is-enabled", + unit, + ]); + if (enabled === 0) { + results.push({ severity: "ok", message: `${label} enabled: ${unit}` }); + } else { + results.push({ + severity: "warn", + message: `${label} is disabled: ${unit}`, + detail: enableDetail, + }); + } + + const active = yield* executor.exitCode("systemctl", [ + "--user", + "is-active", + unit, + ]); + if (active === 0) { + results.push({ severity: "ok", message: `${label} active: ${unit}` }); + } else { + results.push({ + severity: "warn", + message: `${label} is not active: ${unit}`, + detail: enableDetail, + }); + } + }); + const checkObsoleteUserUnit = ( results: CheckResult[], executor: CommandExecutorService, @@ -564,74 +646,67 @@ export const checkDoctorStartup = Effect.gen(function* () { "user", DOCTOR_STARTUP_TIMER_UNIT, ); + const enableDetail = `Enable with: systemctl --user enable --now ${DOCTOR_STARTUP_TIMER_UNIT}`; - if (existsSync(notifyScript)) { - results.push({ - severity: "ok", - message: `Doctor startup notify script found: ${displayPath(notifyScript)}`, - }); - } else { - results.push({ - severity: "warn", - message: `Doctor startup notify script missing or not executable: ${displayPath(notifyScript)}`, - }); - } + addExecutablePresenceCheck( + results, + notifyScript, + `Doctor startup notify script found: ${displayPath(notifyScript)}`, + `Doctor startup notify script missing or not executable: ${displayPath(notifyScript)}`, + ); + addFilePresenceCheck( + results, + unitPath, + `Doctor startup timer unit file found: ${displayPath(unitPath)}`, + `Doctor startup timer unit file missing: ${displayPath(unitPath)}`, + "Run dot stow (or dot install) to link systemd user units", + ); + yield* checkRequiredUserUnit( + results, + executor, + DOCTOR_STARTUP_TIMER_UNIT, + "Doctor startup timer", + enableDetail, + ); - if (existsSync(unitPath)) { - results.push({ - severity: "ok", - message: `Doctor startup timer unit file found: ${displayPath(unitPath)}`, - }); - } else { - results.push({ - severity: "warn", - message: `Doctor startup timer unit file missing: ${displayPath(unitPath)}`, - detail: "Run dot stow (or dot install) to link systemd user units", - }); - } + return results; +}); - const hasSystemctl = (yield* executor.exitCode("which", ["systemctl"])) === 0; - if (hasSystemctl) { - const enabled = yield* executor.exitCode("systemctl", [ - "--user", - "is-enabled", - DOCTOR_STARTUP_TIMER_UNIT, - ]); - if (enabled === 0) { - results.push({ - severity: "ok", - message: `Doctor startup timer enabled: ${DOCTOR_STARTUP_TIMER_UNIT}`, - }); - } else { - results.push({ - severity: "warn", - message: `Doctor startup timer is disabled: ${DOCTOR_STARTUP_TIMER_UNIT}`, - detail: `Enable with: systemctl --user enable --now ${DOCTOR_STARTUP_TIMER_UNIT}`, - }); - } +/** Check resume recovery monitor service used after hypridle is removed. */ +export const checkResumeMonitor = Effect.gen(function* () { + const executor = yield* CommandExecutor; + const results: CheckResult[] = []; - const active = yield* executor.exitCode("systemctl", [ - "--user", - "is-active", - DOCTOR_STARTUP_TIMER_UNIT, - ]); - if (active === 0) { - results.push({ - severity: "ok", - message: `Doctor startup timer active: ${DOCTOR_STARTUP_TIMER_UNIT}`, - }); - } else { - results.push({ - severity: "warn", - message: `Doctor startup timer is not active: ${DOCTOR_STARTUP_TIMER_UNIT}`, - }); - } - } else { - results.push({ - severity: "warn", - message: "Skipping doctor startup timer checks (systemctl not found)", - }); - } + const monitorScript = join(HOME, ".local", "bin", "on-resume-monitor"); + const unitPath = join( + XDG_CONFIG_HOME, + "systemd", + "user", + RESUME_MONITOR_SERVICE_UNIT, + ); + const enableDetail = `Enable with: systemctl --user enable --now ${RESUME_MONITOR_SERVICE_UNIT}`; + + addExecutablePresenceCheck( + results, + monitorScript, + `Resume monitor script is executable: ${displayPath(monitorScript)}`, + `Resume monitor script is missing or not executable: ${displayPath(monitorScript)}`, + "Run dot stow (or dot install) to link the resume monitor script", + ); + addFilePresenceCheck( + results, + unitPath, + `Resume monitor service unit file found: ${displayPath(unitPath)}`, + `Resume monitor service unit file missing: ${displayPath(unitPath)}`, + "Run dot stow (or dot install) to link systemd user units", + ); + yield* checkRequiredUserUnit( + results, + executor, + RESUME_MONITOR_SERVICE_UNIT, + "Resume monitor service", + enableDetail, + ); return results; }); diff --git a/dot/src/doctor/runner.ts b/dot/src/doctor/runner.ts index fb2c61fc..4562c2a0 100644 --- a/dot/src/doctor/runner.ts +++ b/dot/src/doctor/runner.ts @@ -12,6 +12,7 @@ import { checkGitNotifications, checkWorkflowRuns, checkDoctorStartup, + checkResumeMonitor, checkDailyVolumeReset, } from "./checks/systemd.js"; import { checkOmarchy } from "./checks/omarchy.js"; @@ -49,6 +50,7 @@ const sections: readonly SectionDef[] = [ { name: "Workflow runs checks", check: checkWorkflowRuns }, { name: "Git notification checks", check: checkGitNotifications }, { name: "Doctor startup notification", check: checkDoctorStartup }, + { name: "Resume recovery monitor", check: checkResumeMonitor }, { name: "Daily volume reset", check: checkDailyVolumeReset }, { name: "Omarchy repository checks", check: checkOmarchy }, { name: "Private access", check: checkPrivateAccess }, From a16ca41c53ab44a72895dbac464afc87d1589a12 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 31 May 2026 17:38:15 +0100 Subject: [PATCH 4/4] Document Omarchy Quickshell migration plan --- dot-migration/omarchy-quickshell/README.md | 212 +++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 dot-migration/omarchy-quickshell/README.md diff --git a/dot-migration/omarchy-quickshell/README.md b/dot-migration/omarchy-quickshell/README.md new file mode 100644 index 00000000..968a4eee --- /dev/null +++ b/dot-migration/omarchy-quickshell/README.md @@ -0,0 +1,212 @@ +# Omarchy Quickshell Migration Plan + +This file is temporary planning material for the Omarchy 4 Quickshell migration. It lives in `dot-migration/` because that directory is ignored by stow, so these notes and draft files can exist on the dotfiles branch without being installed into the live home directory. + +The likely long-term home is a separate `omarchy-quickshell` repository. Do not create that repository yet. Treat this directory as the staging area for the VM migration plan, draft shell config, and checklist. + +## Current Action + +For this PR branch, the active work is: + +1. Record the upstream Omarchy Quickshell extension point. +2. Keep a single tracked plan and checklist in this file. +3. Keep the PR body short and link here instead of duplicating the migration plan. +4. Avoid live activation on the host machine. + +No file in this directory should be stowed. The host remains on Omarchy 3 until the Omarchy 4 work passes VM validation. + +## Upstream Baseline + +- Upstream PR: +- Upstream branch: `omarchy-shell` +- Extension commit: `1bb439476af2a7b2bdee03c682ae002655c3523c` +- Default shell config at that commit: `config/omarchy/shell.json` +- Local comparison worktree used during planning: `/tmp/opencode/omarchy-shell` + +The Omarchy PR default config is the baseline. Do not derive the Quickshell user config directly from the current Waybar config. Use the upstream `shell.json` shape first, then layer custom modules and host-specific choices into a user config for VM testing. + +If `basecamp/omarchy#5856` advances, compare the new head against `1bb439476af2a7b2bdee03c682ae002655c3523c` before applying this checklist. + +## Scope + +This plan covers dotfiles-side preparation for Omarchy 4 and VM validation. + +In scope: + +1. Draft an Omarchy Quickshell migration plan in this ignored dotfiles path. +2. Use the Omarchy PR default `shell.json` as the extension point. +3. Keep command and script changes compatible with the current Omarchy 3 host where practical. +4. Prepare VM checklist and validation criteria for Omarchy 4. +5. Identify which current Waybar modules become built-ins, command modules, QML modules, or deferred work. +6. Keep Hypr Lua migration requirements visible alongside the shell migration. + +Out of scope for this PR branch: + +1. Creating the future `omarchy-quickshell` repository. +2. Writing live `~/.config/omarchy/shell.json` on the host. +3. Switching the host machine to Omarchy 4. +4. Deleting the current Waybar, hypridle, or hyprlock configs before VM validation. +5. Editing `~/.local/share/omarchy`. + +## Quickshell Extension Model + +The PR makes Omarchy's desktop shell intentionally extensible. + +Important rules: + +1. `~/.config/omarchy/shell.json` becomes canonical once it exists. It does not deep-merge with the default config. +2. Built-in widget IDs use the reserved `omarchy.*` namespace. +3. Custom IDs should use a separate namespace such as `timmo.*`. +4. One-off command modules can be declared inline in `bar.layout.left`, `bar.layout.center`, or `bar.layout.right`. +5. Command module output can be plain text or Waybar-style JSON containing `text`, `tooltip`, and `class`. +6. Command modules run on an interval and parse output after the command exits. They are suitable for polling, not long-running streams. +7. Custom QML modules can live under `~/.config/omarchy/bar/modules/.qml`, or an entry can provide an explicit `source` path. +8. Full plugins live under `~/.config/omarchy/plugins//manifest.json` and can declare `bar-widget`, `panel`, `overlay`, `menu`, or `service` kinds. +9. Plugins are unsandboxed code running inside `omarchy-shell`, so custom QML should stay small and auditable. +10. Waybar `signal = RTMIN+N` refresh behavior does not map directly. Future modules should use polling or shell IPC instead. +11. Waybar CSS class semantics do not map directly. Command modules currently use `class` or `alt` mainly to determine active styling. + +## Temporary File Layout + +Use this ignored staging layout while the future repository does not exist: + +```text +dot-migration/omarchy-quickshell/ + README.md # this plan and checklist + shell.json # optional future draft copied from Omarchy PR default + modules.md # optional deeper module inventory if this file gets too large + bar/modules/ # optional future one-off QML module prototypes + plugins/ # optional future full plugin prototypes +``` + +Only create the optional files when they are needed. This PR starts with the plan file. + +## Waybar To Shell Mapping + +Start with the Omarchy PR default bar layout, then map current custom behavior onto it. + +| Current Waybar module | Omarchy 4 target | Notes | +| --- | --- | --- | +| `custom/omarchy` | `omarchy.menu` | Built-in menu widget. | +| `hyprland/workspaces` | `omarchy.workspaces` | Built-in workspaces widget. | +| `clock` | `omarchy.clock` | Built-in clock widget; configure format inline. | +| `custom/update` | `omarchy.system-update` | Built-in update widget. | +| `group/tray-expander`, `tray`, `custom/expand-icon` | `omarchy.tray` | Built-in tray handles drawer behavior. | +| `bluetooth` | `omarchy.bluetooth` | Built-in panel/widget. | +| `network` | `omarchy.network` | Built-in panel/widget; verify NetworkManager behavior in VM. | +| `pulseaudio` | `omarchy.audio` | Built-in audio panel/widget. | +| `cpu`, `memory`, `battery` | `omarchy.monitor` / `omarchy.power` | Use built-ins first; only add custom output if needed. | +| `custom/screenrecording-indicator` | `omarchy.indicators` item | Built-in indicator support. | +| `custom/idle-indicator` | `omarchy.indicators` item | Maps to shell idle/stay-awake behavior. | +| `custom/notification-silencing-indicator` | `omarchy.indicators` item | Maps to DND indicator. | +| `custom/voxtype` | `omarchy.indicators` item | Built-in dictation indicator; verify click behavior differences. | +| `custom/git-diff` | `timmo.git-diff` command module | Polling command module; remove Waybar signal dependency. | +| `custom/git-notifications` | `timmo.git-notifications` command module | Polling command module; open/refresh clicks need shell-compatible behavior. | +| `custom/git-workflows` | `timmo.git-workflows` command module | Polling command module; no RTMIN refresh equivalent. | +| `custom/twitch-notifications-active` | `timmo.twitch-notifications` command module | Good command-module candidate using `--status-bar-json`. | +| `custom/temperature` | `timmo.temperature` command module | Polling Home Assistant status. | +| `custom/co2-alert` | `timmo.co2-alert` command module | Polling Home Assistant status; class styling may need adjustment. | +| `custom/voc-alert` | `timmo.voc-alert` command module or omit | Desktop currently hides this permanently; verify need. | +| `custom/nas-activity` | `timmo.nas-activity` command module | Polling Home Assistant status. | +| `custom/current-next-event` | `timmo.current-next-event` command module | Polling Home Assistant status. | +| `custom/in-a-call` | QML/service or rewritten one-shot command | Current behavior is a streaming watcher. | +| `custom/time-check` | QML/service or rewritten one-shot command | Current behavior is a streaming watcher. | +| `custom/heating` | QML/service or rewritten one-shot command | Current behavior is a streaming watcher. | +| `custom/rain` | QML/service or rewritten one-shot command | Current behavior is a streaming watcher. | +| `custom/doorbell` | QML/service plugin | Stateful stream, trigger command, cooldown, and host-specific monitor geometry. | + +## Script Changes Needed + +Script work should preserve current host behavior while making the Omarchy 4 VM path clear. + +1. Replace direct `omarchy-launch-walker` assumptions with an abstraction that uses `omarchy-menu-select` when available and falls back to the current Walker launcher while the host remains on Omarchy 3. +2. Apply that to `twitch-menu`, `workspace-menu`, and `workspace-relayout`. +3. Split generic status-bar JSON output from Waybar-specific refresh behavior in the git status scripts. +4. Keep Waybar-specific wrappers working until the host leaves Omarchy 3. +5. Avoid adding new `--waybar` or `--status-waybar` aliases. Use `--bar-json` and `--status-bar-json` only. + +## Hypr Lua Checklist + +Current Hypr Lua prep exists in `timmo001/omarchy-hypr`, but Omarchy 4 migration still needs VM validation. + +Before enabling Omarchy 4 in the VM: + +1. Preserve prepared root Lua files from upstream migration clobbering, especially `hyprland.lua`, `bindings.lua`, `autostart.lua`, `input.lua`, `looknfeel.lua`, and `monitors.lua`. +2. Confirm `hyprland.lua` still requires the local `hypr.envs` module if needed. +3. Re-check local binding conflicts against Omarchy 4 defaults and add explicit `hl.unbind(...)` calls where local bindings intentionally replace defaults. +4. Keep host-specific monitor behavior in `hosts/desktop` and `hosts/laptop`. +5. Treat `hypridle.conf` and `hyprlock.conf` as Omarchy 3-only once shell idle/lock are active. +6. Move idle timing to `shell.json` after Omarchy 4 shell idle service is active. +7. Verify `hyprsunset` laptop-specific behavior does not conflict with Omarchy 4 night-light behavior. + +## Theme Checklist + +Omarchy 4 moves more visual behavior into shell theme tokens. + +For each custom theme that should survive the migration: + +1. Keep `colors.toml` as the palette source where available. +2. Add or update `shell.toml` for bar, menu, launcher, notification, lock, and shared control tokens. +3. Convert Hypr theme overrides from `hyprland.conf` to `hyprland.lua` where needed. +4. Revisit Mako settings because shell notifications replace Mako. +5. Revisit Waybar CSS because it will not apply to Quickshell widgets. +6. Verify current background and unlock assets still match Omarchy 4 background and lock behavior. + +## VM Test Checklist + +Use the VM as the first activation target. + +Base setup: + +1. Install or switch the VM to the Omarchy 4 branch represented by the chosen upstream commit. +2. Confirm the VM's Omarchy checkout matches or intentionally supersedes `1bb439476af2a7b2bdee03c682ae002655c3523c`. +3. Apply dotfiles branches needed for this PR. +4. Apply Hypr Lua branch/config needed for host override testing. +5. Copy or adapt the draft `shell.json` from this migration directory only after reviewing the current upstream default. + +Shell validation: + +1. `omarchy-shell` starts without QML errors. +2. `omarchy shell shell ping` or equivalent IPC health check succeeds. +3. Default built-in widgets render before adding custom modules. +4. Command modules render last-line JSON correctly. +5. Click handlers work with `onClick`, `onRightClick`, and `onMiddleClick` naming. +6. Missing secrets or services degrade gracefully for Home Assistant, GitHub, and Twitch modules. +7. No custom module relies on Waybar RTMIN signals. +8. No custom module relies on Waybar CSS classes for essential behavior. + +Hypr validation: + +1. Hyprland starts from Lua config. +2. `hyprctl reload` succeeds. +3. `hyprctl configerrors` is clean or contains only understood upstream issues. +4. Desktop and laptop host-selection behavior is still clear. +5. Resume recovery still works without `hypridle` `after_sleep_cmd`. +6. Lock, idle, screensaver, and wake behavior are verified through Omarchy shell services. + +Desktop behavior validation: + +1. Menus open through the shell menu path instead of Walker-only paths. +2. Workspace menu and relayout flows work. +3. Twitch menu works. +4. Git diff, GitHub notification, and workflow modules show expected status. +5. Doorbell behavior is either explicitly deferred or implemented through a QML/service plugin. +6. Network and Wi-Fi behavior works with NetworkManager. +7. Notification and OSD behavior match the intended theme. + +## Promotion Criteria + +Promote this work out of `dot-migration/` only when all of these are true: + +1. The VM passes the shell and Hypr validation checklist. +2. The selected upstream Omarchy commit is either merged, tagged, or intentionally accepted as the target. +3. The future `omarchy-quickshell` repository layout is decided. +4. Draft files are moved from this temporary directory into the final repo or replaced with links to that repo. +5. Host activation is reviewed separately from the PR that only prepares dotfiles. + +## Open Decisions + +1. Whether git status modules should remain shell command modules or become a small shared QML plugin with explicit refresh IPC. +2. Whether Home Assistant streaming modules should be rewritten as one-shot polling commands or implemented as a QML service plugin. +3. Whether desktop and laptop should share a single `shell.json` with host-specific scripts, or separate generated user configs in the future `omarchy-quickshell` repo. +4. Which custom themes are worth fully porting to `shell.toml` before host activation.