{"id":50451240,"url":"https://github.com/codingfragments/zellij-delaylock","last_synced_at":"2026-06-01T00:03:04.049Z","repository":{"id":357704230,"uuid":"1238192612","full_name":"codingfragments/zellij-delaylock","owner":"codingfragments","description":"Zellij plugin that auto-returns the session to Locked mode after a short idle timeout.","archived":false,"fork":false,"pushed_at":"2026-05-13T22:50:34.000Z","size":28,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-14T01:04:06.754Z","etag":null,"topics":["rust","terminal-multiplexer","wasm","zellij","zellij-plugin"],"latest_commit_sha":null,"homepage":"https://github.com/codingfragments/zellij-delaylock","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codingfragments.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-13T22:44:25.000Z","updated_at":"2026-05-13T22:50:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/codingfragments/zellij-delaylock","commit_stats":null,"previous_names":["codingfragments/zellij-delaylock"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/codingfragments/zellij-delaylock","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codingfragments%2Fzellij-delaylock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codingfragments%2Fzellij-delaylock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codingfragments%2Fzellij-delaylock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codingfragments%2Fzellij-delaylock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codingfragments","download_url":"https://codeload.github.com/codingfragments/zellij-delaylock/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codingfragments%2Fzellij-delaylock/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33753931,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rust","terminal-multiplexer","wasm","zellij","zellij-plugin"],"created_at":"2026-06-01T00:03:02.456Z","updated_at":"2026-06-01T00:03:04.036Z","avatar_url":"https://github.com/codingfragments.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# delayLock\n\nA [Zellij](https://zellij.dev) plugin that auto-returns your session to `Locked`\nmode after a short idle timeout. It exists as a safety net for **leader-key\nmistakes**: you press the leader to enter `Tmux` (or `Normal`) mode, miss the\nfollow-up action, and now Zellij is silently eating your keystrokes. delayLock\nsnaps the session back to `Locked` after a couple of seconds so your typing\ngoes back to the underlying terminal.\n\n## How it works\n\nThree rules, one timer:\n\n1. **On any mode change**, if you've just left the target mode and entered a\n   mode in the *active list* (default: `Normal`, `Tmux`), a timer starts\n   (default: 2 seconds).\n2. **On any keystroke** (anywhere in the Zellij session), the timer is reset\n   to a fresh full interval — so as long as you're typing actions, you stay\n   in the unlocked mode.\n3. **When the timer expires** with no input since it was armed, the plugin\n   calls `switch_to_input_mode(Locked)` and you're back in `Locked` mode.\n\nModes that are *not* in the active list (e.g. `Scroll`, `Search`, `Prompt`,\n`RenameTab`) are left alone — those are modes where you legitimately want to\nsit idle for a while.\n\nOn first load the plugin switches the session to the target mode immediately,\nso you start locked.\n\n### Why a timer that \"re-arms\" instead of resetting on each keypress\n\nZellij's plugin API does not let you cancel a `set_timeout`. delayLock instead\ncounts input events and snapshots the count when arming. When the timer fires,\nit compares: if the count moved, the user was typing, so it re-arms with a\nfresh full interval. If the count is unchanged, the user really was idle, and\nit locks. The user-visible effect is \"every keystroke gives you a fresh full\ntimeout\", which is the right UX for a 2-second leader-key safety net.\n\n## Requirements\n\n- Zellij (any recent version — tested against `zellij-tile = 0.44`).\n- Rust toolchain with the `wasm32-wasip1` target:\n  ```fish\n  rustup target add wasm32-wasip1\n  ```\n\n(Older Zellij documentation says `wasm32-wasi`. That target name was removed\nin Rust 1.84; `wasm32-wasip1` is the same ABI under the new name. Zellij\nloads either fine.)\n\n## Build\n\n```fish\ngit clone \u003cthis repo\u003e delaylock\ncd delaylock\ncargo build --release\n```\n\nThe artifact lands at `target/wasm32-wasip1/release/delaylock.wasm`. Copy it\nsomewhere stable — e.g.:\n\n```fish\nmkdir -p ~/.config/zellij/plugins\ncp target/wasm32-wasip1/release/delaylock.wasm ~/.config/zellij/plugins/\n```\n\n## Install in your Zellij config\n\ndelayLock has no UI — it just needs to be running. The right place for that\nis your `~/.config/zellij/config.kdl`, not a layout. Layouts are\nsession-scoped (you'd have to add it to every layout) and require a pane to\nhost the plugin; the config-level `load_plugins` block runs the plugin\n**headless** in the background, automatically, for every session.\n\nAdd an alias in the `plugins` block (with your config), then reference it in\n`load_plugins`:\n\n```kdl\nplugins {\n    // ... any existing aliases ...\n    delaylock location=\"file:~/.config/zellij/plugins/delaylock.wasm\" {\n        timeout_seconds \"2.0\"\n        target_mode \"locked\"\n        active_modes \"normal,tmux\"\n    }\n}\n\nload_plugins {\n    \"delaylock\"\n}\n```\n\nThat's it. The next time you start a Zellij session, delayLock loads in the\nbackground. The first time, Zellij prompts once to grant\n`ReadApplicationState` and `ChangeApplicationState` — accept both. After\nthat it's silent.\n\n### Alternatives\n\n- **Load ad-hoc** (e.g. to try it without editing config):\n  ```fish\n  zellij action launch-or-focus-plugin file:~/.config/zellij/plugins/delaylock.wasm --floating\n  ```\n- **Load via a layout** — only useful if you specifically want delayLock\n  active in *some* layouts but not others. Same `plugin { ... }` block as\n  above, nested inside a `pane { }` or `floating_panes { }`.\n\n## Configuration\n\nAll settings go in the `plugin { ... }` block in the KDL.\n\n| Key               | Default        | Meaning                                                                    |\n|-------------------|----------------|----------------------------------------------------------------------------|\n| `timeout_seconds` | `2.0`          | How long to wait, in seconds. Float. Must be positive.                     |\n| `target_mode`     | `locked`       | The mode to return to. Any `InputMode` name, case-insensitive.             |\n| `active_modes`    | `normal,tmux`  | Comma-separated list of modes that should auto-return. Case-insensitive.   |\n| `logging`         | `false`        | If `true`, emit verbose state-machine logs. Accepts `true/false/yes/no/1/0/on/off`. |\n\nValid mode names: `normal`, `locked`, `resize`, `pane`, `tab`, `scroll`,\n`entersearch`, `search`, `renametab`, `renamepane`, `session`, `move`,\n`prompt`, `tmux`.\n\nUnknown names are silently dropped. An empty (or all-unknown) `active_modes`\nfalls back to the default — a typo won't accidentally disable the plugin.\n\n### Debugging with `logging`\n\nSet `logging \"true\"` in the plugin config, then tail the Zellij log.\n\n#### Where is the log file?\n\nZellij writes one log file per UID under its temp directory:\n\n| Platform | Path |\n|---|---|\n| macOS   | `$TMPDIR/zellij-\u003cuid\u003e/zellij-log/zellij.log` |\n| Linux   | `/tmp/zellij-\u003cuid\u003e/zellij-log/zellij.log` |\n\n`\u003cuid\u003e` is your numeric user id (`id -u`). On macOS `$TMPDIR` is something\nlike `/var/folders/.../T/`; on Linux it's usually unset and `/tmp` is used.\n\nIf you're unsure, just locate it:\n\n```fish\nfind $TMPDIR /tmp -name zellij.log 2\u003e/dev/null\n```\n\n#### Tail it\n\nThe path expands at the shell, so the exact command depends on which shell\nyou're in.\n\n**fish** (note: no `$` before the subshell):\n\n```fish\ntail -f $TMPDIR/zellij-(id -u)/zellij-log/zellij.log | grep delaylock\n```\n\n**bash / zsh**:\n\n```bash\ntail -f \"$TMPDIR/zellij-$(id -u)/zellij-log/zellij.log\" | grep delaylock\n```\n\nOn Linux, replace `$TMPDIR` with `/tmp` in either shell.\n\n#### What you'll see\n\nLines prefixed with `[delaylock]` covering: config loaded, permission\ngrant, every `ModeUpdate`, every `InputReceived`, every timer fire, and\nevery forced switch.\n\nTurn it off (`logging \"false\"` or omit the key) for normal use — when\ndisabled, the logging code paths don't even format their arguments.\n\n### Recommended setups\n\n**Strict leader-key safety net** (the default):\n```kdl\ntimeout_seconds \"2.0\"\ntarget_mode \"locked\"\nactive_modes \"normal,tmux\"\n```\n\n**More forgiving** — give yourself longer to think:\n```kdl\ntimeout_seconds \"5.0\"\ntarget_mode \"locked\"\nactive_modes \"normal,tmux\"\n```\n\n**Auto-return to Normal instead of Locked** (less aggressive — your shell\nkeys are still eaten by Zellij when Normal is the target, so this only makes\nsense if Locked isn't your usual base mode):\n```kdl\ntimeout_seconds \"3.0\"\ntarget_mode \"normal\"\nactive_modes \"tmux,pane,tab,resize,move,session\"\n```\n\n## Development\n\nA dev layout is included: `zellij.kdl`. It opens the source file and loads the\nplugin from the `debug/` build directory, so you can hot-reload as you edit.\n\n```fish\nzellij -l zellij.kdl\n# in another shell, after each edit:\ncargo build \u0026\u0026 zellij action start-or-reload-plugin file:target/wasm32-wasip1/debug/delaylock.wasm\n```\n\n`CLAUDE.md` has notes on the internals (state machine, event scoping, why\ncertain events were chosen over others) for anyone — human or AI — who wants\nto extend the plugin.\n\n## License\n\n[MIT](LICENSE). © 2026 Stefan Marx.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodingfragments%2Fzellij-delaylock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodingfragments%2Fzellij-delaylock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodingfragments%2Fzellij-delaylock/lists"}