Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test-ml4w-setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 14 additions & 4 deletions docs/configuration/ml4w.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions docs/sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

## Quick path — re-pin an upstream ref

1. Resolve the current remote HEAD: `git ls-remote https://github.com/<upstream>.git HEAD`
1. Resolve the intended upstream ref: `git ls-remote https://github.com/<upstream>.git HEAD` for a rolling pin, or `git ls-remote https://github.com/<upstream>.git refs/tags/<tag> 'refs/tags/<tag>^{}'` 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`

Expand Down Expand Up @@ -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 | <https://github.com/mylinuxforwork/dotfiles.git> | `3960570f47f4f691c424ff46b387d389a8e69bca` (tag `2.16`) | Pinned — verified against tag 2.16 and remote HEAD |
| ML4W (Hyprland desktop dotfiles) | Desktop base environment | <https://github.com/mylinuxforwork/dotfiles.git> | `b95bd010eabc9c58a1a275ab0cca179b358aa9e3` (tag `2.16.1`) | Pinned — verified against release tag 2.16.1 |
| Gentleman.Dots | Shell / editor / terminal base configuration | <https://github.com/Gentleman-Programming/Gentleman.Dots.git> | `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 <url> 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.
Expand Down
10 changes: 5 additions & 5 deletions docs/upstream-manifest.json
Original file line number Diff line number Diff line change
@@ -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)",
Expand Down
4 changes: 2 additions & 2 deletions ml4w_assets/hypr/conf/keybindings/dreamcoder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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" })
Expand Down
29 changes: 29 additions & 0 deletions tests/fixtures/ml4w/capture-keybindings.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- Load the public variant with a recording Hyprland API; never execute commands.
-- Usage: lua capture-keybindings.lua <variant.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
28 changes: 26 additions & 2 deletions tests/ml4w/keybindings_variant.bats
Original file line number Diff line number Diff line change
@@ -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"; }
Expand All @@ -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 ]
Expand Down
9 changes: 6 additions & 3 deletions tests/test_verify_repo_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading