diff --git a/.github/workflows/test-ml4w-setup.yml b/.github/workflows/test-ml4w-setup.yml index 52d41951..6f599061 100644 --- a/.github/workflows/test-ml4w-setup.yml +++ b/.github/workflows/test-ml4w-setup.yml @@ -64,8 +64,8 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y jq bats - # luac may not be available on Ubuntu CI — that's fine, tests handle it + sudo apt-get install -y jq bats lua5.4 + # The public keybinding capture fixture requires the Lua interpreter. # Run the whole directory, not an enumerated file list: naming each # file means a new test file silently never executes in CI. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fef9808..6a061643 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ ### Fixed +- Repository sync regression tests derive the current upstream pin instead of relying on a historical hash. - Zsh sources Bun completions only from nonempty regular files, preserving support for symlinks to regular files. - Pi and Gentle Shell Light use cream chrome surfaces and brown focus roles, including the medium-thinking editor border, without changing Dark. - Zsh checks the optional Starship transience function with native syntax instead of Fish's unsupported `functions -q` option. diff --git a/docs/configuration/ml4w.md b/docs/configuration/ml4w.md index 9b8f1cc0..dc370a97 100644 --- a/docs/configuration/ml4w.md +++ b/docs/configuration/ml4w.md @@ -75,19 +75,29 @@ updates, so custom bindings live in a separate **variant**. `conf/keybinding.lua If you add a keybinding, add it to the profile JSON and re-run the generator — never to `dreamcoder.lua` unless it is a native-only bind. -### Tracking upstream `default.lua` (ML4W 2.16) +### Tracking upstream `default.lua` (ML4W 2.16.1) `dreamcoder.lua` follows upstream `default.lua` minus the profile-owned binds. -The ML4W 2.16 delta is ported as follows: +The verified release pin and upstream sources are recorded in the +[source manifest](../sources.md). The integration retains the +2.16 changes below and updates the confirmed 2.16.1 entry points: -| Upstream 2.16 change | In `dreamcoder.lua` | +| Upstream change | In `dreamcoder.lua` | | --- | --- | +| Standalone power menu (2.16.1) | `SUPER + CTRL + P` runs `ml4w-powermenu toggle`, replacing `qs ipc call power toggle` | +| Launcher selection (2.16.1) | `SUPER + CTRL + RETURN` runs `~/.config/ml4w/scripts/ml4w-launcher`, replacing the compatibility shim `hypr/scripts/launcher.sh` | | Overview moved to `~/.local/share/quickshell-overview` | `SUPER + Tab` runs `qs -p ~/.local/share/quickshell-overview ipc call overview toggle` | | `SUPER + ALT + B` statusbar autohide | Ported | | `SUPER + ALT + D` dock autohide | Ported | | Reload Dock moved to `SUPER + SHIFT + D` | Not bound — the profile owns that combo (theme toggle) | | Rewritten AZERTY detection (`fr`, `be`) | Ported; binds the AZERTY keysyms only on AZERTY layouts, since the profile owns the digit workspace binds | +Waybar and Rofi remain supported, not obsolete: ML4W's statusbar dispatcher +selects Waybar or Quickshell, and `ml4w-launcher` selects Walker or Rofi with a +Rofi fallback when Walker is unavailable. The Waybar theme-switcher binding +remains unchanged. The standalone power-menu command must be installed by the +upstream setup; this repository does not install or start it during tests. + `tests/ml4w/keybindings_variant.bats` fails on any new collision between the variant and the profile; there are no exceptions. The profile owns `SUPER + SHIFT + arrows` (move window), so the variant's keyboard resize sits on @@ -233,7 +243,7 @@ tests/ml4w/ ├── apply_ml4w_hooks.bats # wallpaper + GTK listener hooks (ML4W 2.16 fixtures) ├── args.bats # script argument handling ├── generate_custom_lua.bats # generator, incl. the dreamcoder-colors loader -├── keybindings_variant.bats # dreamcoder.lua vs ML4W 2.16 and the profile +├── keybindings_variant.bats # dreamcoder.lua vs ML4W 2.16.1 and the profile ├── ml4w_managed.bats # ML4W ownership + colour-file predicates ├── setup_hyprland.bats # orchestrator ├── profile_validation.bats # JSON profiles diff --git a/docs/sources.md b/docs/sources.md index afbb985a..9d6de48b 100644 --- a/docs/sources.md +++ b/docs/sources.md @@ -8,7 +8,7 @@ ## Quick path — re-pin an upstream ref -1. Resolve the current remote HEAD: `git ls-remote https://github.com/.git HEAD` +1. Resolve the intended upstream ref: `git ls-remote https://github.com/.git HEAD` for a rolling pin, or `git ls-remote https://github.com/.git refs/tags/ 'refs/tags/^{}'` for a release (use the peeled commit for annotated tags) 2. Update the pinned ref in [`upstream-manifest.json`](upstream-manifest.json) and the table below 3. Confirm everything agrees: `python3 scripts/verify-repo-sync.py` @@ -43,15 +43,19 @@ repository that ships the dotfiles. Gentleman.Dots is hosted at | Upstream | Kind | Verified remote (HTTPS) | Pinned ref | Status | | --- | --- | --- | --- | --- | -| ML4W (Hyprland desktop dotfiles) | Desktop base environment | | `3960570f47f4f691c424ff46b387d389a8e69bca` (tag `2.16`) | Pinned — verified against tag 2.16 and remote HEAD | +| ML4W (Hyprland desktop dotfiles) | Desktop base environment | | `b95bd010eabc9c58a1a275ab0cca179b358aa9e3` (tag `2.16.1`) | Pinned — verified against release tag 2.16.1 | | Gentleman.Dots | Shell / editor / terminal base configuration | | `5b13e07b5a6fde799b65953e92cec323f684408f` | Pinned — verified against remote HEAD | ### Pin mechanism Each ref above was verified on the date recorded in [`upstream-manifest.json`](upstream-manifest.json) by resolving the exact -HTTPS remote listed in the table with `git ls-remote HEAD` and recording -the exact returned commit. Pinning is a two-step process: +HTTPS remote listed in the table with `git ls-remote` and recording the exact +returned commit (release tag for ML4W, remote HEAD for Gentleman.Dots). +The [ML4W 2.16.1 release notes](https://github.com/mylinuxforwork/dotfiles/releases/tag/2.16.1) +and [shipped keybindings](https://github.com/mylinuxforwork/dotfiles/blob/b95bd010eabc9c58a1a275ab0cca179b358aa9e3/dotfiles/.config/hypr/conf/keybindings/default.lua) +were also inspected; Gentleman.Dots retains its earlier verification unchanged. +Pinning is a two-step process: 1. Verify an upstream commit against its canonical repository and record the resolved reference in the machine-readable manifest. diff --git a/docs/upstream-manifest.json b/docs/upstream-manifest.json index 0e6dfd9c..e6a5b919 100644 --- a/docs/upstream-manifest.json +++ b/docs/upstream-manifest.json @@ -1,17 +1,17 @@ { "version": 1, "provenance": { - "verified_on": "2026-09-30T07:27:46Z", - "method": "Resolved each upstream HTTPS remote with git ls-remote and recorded the exact returned commit. ML4W is pinned to tag 2.16 (lightweight tag, 3960570f47f4f691c424ff46b387d389a8e69bca), which was also the remote HEAD at verification; Gentleman.Dots is pinned to main HEAD. No ref was ever filled without verification.", - "command": "git ls-remote https://github.com/mylinuxforwork/dotfiles.git HEAD refs/tags/2.16; git ls-remote https://github.com/Gentleman-Programming/Gentleman.Dots.git HEAD refs/heads/main" + "verified_on": "2026-10-24T20:53:20Z", + "method": "Resolved ML4W tag 2.16.1 with git ls-remote: b95bd010eabc9c58a1a275ab0cca179b358aa9e3, with no peeled tag ref returned. Verified release notes at https://github.com/mylinuxforwork/dotfiles/releases/tag/2.16.1 and shipped interfaces at that commit. Gentleman.Dots retains its 2026-09-30 main HEAD verification and pin; it was not re-resolved. No ref was ever filled without verification.", + "command": "git ls-remote https://github.com/mylinuxforwork/dotfiles.git refs/tags/2.16.1 'refs/tags/2.16.1^{}'" }, "upstreams": { "ml4w": { "name": "ML4W (Hyprland desktop dotfiles)", "url": "https://github.com/mylinuxforwork/dotfiles.git", "status": "pinned", - "pinned_ref": "3960570f47f4f691c424ff46b387d389a8e69bca", - "verified_on": "2026-09-30T07:27:46Z" + "pinned_ref": "b95bd010eabc9c58a1a275ab0cca179b358aa9e3", + "verified_on": "2026-10-24T20:53:20Z" }, "gentleman-dots": { "name": "Gentleman.Dots (shell / editor / terminal base configuration)", diff --git a/ml4w_assets/hypr/conf/keybindings/dreamcoder.lua b/ml4w_assets/hypr/conf/keybindings/dreamcoder.lua index 671f439d..e0d68ed1 100644 --- a/ml4w_assets/hypr/conf/keybindings/dreamcoder.lua +++ b/ml4w_assets/hypr/conf/keybindings/dreamcoder.lua @@ -42,11 +42,11 @@ hl.bind(mainMod .. " + PRINT", hl.dsp.exec_cmd("~/.config/hypr/scripts/screensho hl.bind(mainMod .. " + ALT + F", hl.dsp.exec_cmd("~/.config/hypr/scripts/screenshot.sh --instant"), { description = "Take an instant full-screen screenshot" }) hl.bind(mainMod .. " + ALT + S", hl.dsp.exec_cmd("~/.config/hypr/scripts/screenshot.sh --instant-area"), { description = "Take an instant area screenshot" }) hl.bind(mainMod .. " + ALT + A", hl.dsp.exec_cmd("~/.config/hypr/scripts/text-extractor.sh"), { description = "Extract text from an area" }) -hl.bind(mainMod .. " + CTRL + P", hl.dsp.exec_cmd("qs ipc call power toggle"), { description = "Start Power Menu" }) +hl.bind(mainMod .. " + CTRL + P", hl.dsp.exec_cmd("ml4w-powermenu toggle"), { description = "Start Power Menu" }) hl.bind(mainMod .. " + SHIFT + W", hl.dsp.exec_cmd("~/.config/ml4w/scripts/ml4w-wallpaper-app --random"), { description = "Change the wallpaper" }) hl.bind(mainMod .. " + CTRL + W", hl.dsp.exec_cmd("~/.config/ml4w/scripts/ml4w-wallpaper-app"), { description = "Open wallpaper selector" }) hl.bind(mainMod .. " + ALT + W", hl.dsp.exec_cmd("~/.config/ml4w/scripts/ml4w-wallpaper-automation"), { description = "Start random wallpaper script" }) -hl.bind(mainMod .. " + CTRL + RETURN", hl.dsp.exec_cmd("~/.config/hypr/scripts/launcher.sh"), { description = "Open application launcher" }) +hl.bind(mainMod .. " + CTRL + RETURN", hl.dsp.exec_cmd("~/.config/ml4w/scripts/ml4w-launcher"), { description = "Open application launcher" }) hl.bind(mainMod .. " + SHIFT + B", hl.dsp.exec_cmd("~/.config/ml4w/scripts/ml4w-reload-statusbar"), { description = "Reload Status Bar" }) hl.bind(mainMod .. " + CTRL + B", hl.dsp.exec_cmd("~/.config/ml4w/scripts/ml4w-toggle-statusbar"), { description = "Toggle Status Bar" }) hl.bind(mainMod .. " + ALT + B", hl.dsp.exec_cmd("~/.config/ml4w/scripts/ml4w-toggle-statusbar-autohide"), { description = "Toggle Status Bar Autohide" }) diff --git a/tests/fixtures/ml4w/capture-keybindings.lua b/tests/fixtures/ml4w/capture-keybindings.lua new file mode 100644 index 00000000..a07c1208 --- /dev/null +++ b/tests/fixtures/ml4w/capture-keybindings.lua @@ -0,0 +1,29 @@ +-- Load the public variant with a recording Hyprland API; never execute commands. +-- Usage: lua capture-keybindings.lua [key combination] +local commands = {} +local noop = function() return {} end +hl = { + bind = function(key, action) + if type(action) == "table" and action.command then + assert(not commands[key], "duplicate command binding: " .. key) + commands[key] = action.command + end + end, + dsp = { + exec_cmd = function(command) return { command = command } end, + window = setmetatable({}, { __index = function() return noop end }), + workspace = { toggle_special = noop }, + group = { toggle = noop }, + focus = noop, + }, +} +dofile(arg[1]) +if arg[2] then + assert(commands[arg[2]], "missing command binding: " .. arg[2]) + print(commands[arg[2]]) +else + local keys = {} + for key in pairs(commands) do keys[#keys + 1] = key end + table.sort(keys) + for _, key in ipairs(keys) do print(key .. "\t" .. commands[key]) end +end diff --git a/tests/ml4w/keybindings_variant.bats b/tests/ml4w/keybindings_variant.bats index 466e241d..81def0e1 100644 --- a/tests/ml4w/keybindings_variant.bats +++ b/tests/ml4w/keybindings_variant.bats @@ -1,11 +1,12 @@ # ============================================================================ # BATS tests: ml4w_assets/hypr/conf/keybindings/dreamcoder.lua # ============================================================================ -# The curated variant tracks upstream ML4W default.lua (tag 2.16) minus the -# binds the machine profile owns. These checks keep it aligned with the 2.16 +# The curated variant tracks upstream ML4W default.lua (tag 2.16.1) minus the +# binds the machine profile owns. These checks keep it aligned with the 2.16.1 # layout and free of collisions with the profile. load '../helpers/setup' +bats_require_minimum_version 1.5.0 variant() { printf '%s' "${DREAMCODER_DOTS_DIR}/ml4w_assets/hypr/conf/keybindings/dreamcoder.lua"; } profile() { printf '%s' "${DREAMCODER_DOTS_DIR}/DreamcoderProfiles/dreamcoder/asus-vivobook15.json"; } @@ -16,6 +17,29 @@ profile() { printf '%s' "${DREAMCODER_DOTS_DIR}/DreamcoderProfiles/dreamcoder/as [ "$status" -eq 0 ] } +capture_command() { + run --separate-stderr lua "${DREAMCODER_DOTS_DIR}/tests/fixtures/ml4w/capture-keybindings.lua" "$(variant)" "$1" + [ "$status" -eq 0 ] + [ -z "$stderr" ] +} + +@test "keybindings variant: SUPER+CTRL+P uses the standalone 2.16.1 power menu" { + capture_command 'SUPER + CTRL + P' + [ "$output" = 'ml4w-powermenu toggle' ] +} + +@test "keybindings variant: SUPER+CTRL+RETURN uses the supported launcher entry point" { + capture_command 'SUPER + CTRL + RETURN' + [ "$output" = '~/.config/ml4w/scripts/ml4w-launcher' ] +} + +@test "keybindings variant: preserves supported Waybar and statusbar commands" { + capture_command 'SUPER + CTRL + T' + [ "$output" = '~/.config/waybar/themeswitcher.sh' ] + capture_command 'SUPER + CTRL + B' + [ "$output" = '~/.config/ml4w/scripts/ml4w-toggle-statusbar' ] +} + @test "keybindings variant: SUPER+Tab opens the 2.16 overview location" { run grep -F 'qs -p ~/.local/share/quickshell-overview ipc call overview toggle' "$(variant)" [ "$status" -eq 0 ] diff --git a/tests/test_verify_repo_sync.py b/tests/test_verify_repo_sync.py index a9bdc13b..bc7ac2f6 100644 --- a/tests/test_verify_repo_sync.py +++ b/tests/test_verify_repo_sync.py @@ -375,10 +375,13 @@ def test_manifest_docs_mismatch_is_rejected( _point_module_at(module, monkeypatch, tmp_path) monkeypatch.setattr(module.shutil, "which", lambda _: None) sources = tmp_path / "docs/sources.md" + manifest = json.loads(module.UPSTREAM_MANIFEST.read_text()) + pinned_ref = manifest["upstreams"]["ml4w"]["pinned_ref"] stale_ref = "1" * 40 - sources.write_text( - sources.read_text().replace("3960570f47f4f691c424ff46b387d389a8e69bca", stale_ref) - ) + original = sources.read_text() + assert pinned_ref in original + assert stale_ref not in module.UPSTREAM_MANIFEST.read_text() + sources.write_text(original.replace(pinned_ref, stale_ref)) code, output = _run(module, capsys)