From 209425063af52c6c503464312729c1df886581a2 Mon Sep 17 00:00:00 2001 From: Sal Date: Thu, 3 Sep 2026 03:48:39 +0100 Subject: [PATCH] docs(annexes): document @zi-unregister-annex The overview documented @zi-register-annex with no counterpart, so an annex had no documented way to satisfy the Plugin Standard unload contract. Removing a handler while its registration survives leaves Zi dispatching to a function that no longer exists. The resulting 127 is folded into the return value and shifts the argument list, so later plug-ins stop loading for the rest of the session and the first one fails with `Error: No plugin or snippet ID given.' That consequence is called out in a warning admonition, because the symptom appears far from its cause. Covers the signature, that unregistering something never registered is a no-op, placement inside the annex's unload function, and the one documented limit: the ice-modifiers contributed at registration are not withdrawn, since registration does not record which annex contributed which entry. API added in z-shell/zi#503. Closes #912 --- ecosystem/annexes/0_overview.mdx | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/ecosystem/annexes/0_overview.mdx b/ecosystem/annexes/0_overview.mdx index 60c96862..e1248658 100644 --- a/ecosystem/annexes/0_overview.mdx +++ b/ecosystem/annexes/0_overview.mdx @@ -133,6 +133,41 @@ zi load some/plugin Check out the project which fully implements this idea, [z-a-submods][submods]. It e.g. also implements the `atpull` hook, i.e. supports the automatic update of the submodules. The `z-a-*` prefix is recommended for projects which indicate annexes. +## Unregistering an annex {/* #unregistering-an-annex */} + +An annex that follows the [Zsh Plugin Standard][zsh-plugin-standard] unload contract removes its handler functions when it is unloaded. Removing a handler without also removing its registration leaves Zi dispatching to a function that no longer exists, so the registration has to go first: + +`@zi-unregister-annex`: + +```zi showLineNumbers +myproject_plugin_unload() { + @zi-unregister-annex myproject hook:atclone + unfunction →za-myproject-atclone-handler \ + →za-myproject-atclone-help-handler \ + myproject_plugin_unload +} +``` + +The general syntax mirrors the registration call, using the same project name and hook type that were given to `@zi-register-annex`: + +```zi showLineNumbers +@zi-unregister-annex {project-name} { hook: < hook-type >| subcommand: < subcommand-name > } +``` + +Unregistering something that was never registered is a no-op, not an error, so an unload function does not need to guard the call. + +:::warning + +Removing a handler without unregistering it corrupts later loads. Zi calls the stored handler unconditionally, a missing function returns `127`, and Zi folds that error into its return value and shifts its argument list. Every plugin loaded afterwards in that session is affected, and the first one fails with `Error: No plugin or snippet ID given.` + +::: + +:::info + +The ice-modifiers an annex contributed at registration are not withdrawn. Registration appends them to a shared list without recording which annex contributed which entry, so a leftover ice name stays recognised but dispatches to nothing. + +::: + ## Summary {/* #summary */} There are 2 or 3 subtypes for each of the hooks: @@ -148,6 +183,7 @@ There are 2 or 3 subtypes for each of the hooks: [command]: /docs/guides/commands [ice-modifiers]: /docs/guides/syntax/ice-modifiers [the-proposed-function-name-prefixes]: /community/zsh_plugin_standard#the-proposed-function-name-prefixes +[zsh-plugin-standard]: /community/zsh_plugin_standard#unload-function {/* external */}