Hi, I stumbled upon a bug in the completion for openstack command introduced in https://www.zsh.org/mla/workers/2016/msg01936.html I noticed that the suggestions stop for further subcommands at a certain point. An example: `openstack vpn <tab>` offers "endpoint", "ike", "ipsec" and "service" – perfect, selecting "ipsec"! `openstack vpn ipsec <tab>` offers nothing – It should offer "policy", "site" as can be seen here: # I removed flag suggestions -* for readability reasons ❯ echo $_cache_openstack_clnt_outputs |grep cmds_vpn_ipsec |sed "s/='-.*/='...'/" cmds_vpn_ipsec='policy site' cmds_vpn_ipsec_policy='create delete list set show' cmds_vpn_ipsec_policy_create='...' cmds_vpn_ipsec_policy_delete='...' cmds_vpn_ipsec_policy_list='...' cmds_vpn_ipsec_policy_set='...' cmds_vpn_ipsec_policy_show='...' cmds_vpn_ipsec_site='connection' cmds_vpn_ipsec_site_connection='create delete list set show' cmds_vpn_ipsec_site_connection_create='...' cmds_vpn_ipsec_site_connection_delete='...' cmds_vpn_ipsec_site_connection_list='...' cmds_vpn_ipsec_site_connection_set='...' cmds_vpn_ipsec_site_connection_show='...' This issue continues after selecting "site" `openstack vpn ipsec site <tab>` , no offers – should be "connection" (only one, see above) `openstack vpn ipsec site connection <tab>` no offers – should be "create", "delete", "list", "set" and "show" In the thread it was introduced in, there were some discussions whether it should be included in zsh or in the openstack project. Is there interest in a patch to fix this? I have some idea how to fix this by making some assumptions but I am not certain it's the right way. I would probably get rid of $subcmd and just take the longest string of words connected by underscores as $cmd to find the right suggestions. Some barriers I currently have: * There is also some substitution going on for colon which I just don't get * Why is there a check for not prefix-needed? Some comments from the original author would be quite helpful if he still remembers why it was done a certain way :) Best, Christian
Hi Christian, On 07/03/2021 20.11, Christian K wrote: > > I stumbled upon a bug in the completion for openstack command > introduced in https://www.zsh.org/mla/workers/2016/msg01936.html > > I noticed that the suggestions stop for further subcommands at a certain point. > An example: > `openstack vpn <tab>` offers "endpoint", "ike", "ipsec" and "service" > – perfect, selecting "ipsec"! > `openstack vpn ipsec <tab>` offers nothing – It should offer "policy", > "site" as can be seen here: > > [...] > > I have some idea how to fix this by making some assumptions but I am > not certain it's the right way. I would probably get rid of $subcmd > and just take the longest string of words connected by underscores as > $cmd to find the right suggestions. > Some barriers I currently have: > * There is also some substitution going on for colon which I just don't get I have a vague recollection that some (sub)command(s) had a slightly inconsistent output with colons somewhere which had to be removed for completions. Could have been just one or two such commands but unfortunately I don't remember exactly anymore. > * Why is there a check for not prefix-needed? > > Some comments from the original author would be quite helpful if he > still remembers why it was done a certain way :) Hmm, this one I don't remember, I guess this might be a common convention or something like that, probably not specific to _openstack. I haven't dealt with OpenStack recently so I can't actually test anything around it anymore but if you could fix the issue that would be great. Thanks, -- Marko Myllynen
[-- Attachment #1: Type: text/plain, Size: 1281 bytes --] Hey, > I have a vague recollection that some (sub)command(s) had a slightly > inconsistent output with colons somewhere which had to be removed for > completions. Could have been just one or two such commands but > unfortunately I don't remember exactly anymore. @Marko, I tried all clients I could find easily and could not find any suggestions including colons. I cannot handle an error I don't know how to throw. Please let me know if you remember an example. > > * Why is there a check for not prefix-needed? > > > > Some comments from the original author would be quite helpful if he > > still remembers why it was done a certain way :) > > Hmm, this one I don't remember, I guess this might be a common > convention or something like that, probably not specific to _openstack. I read up on it and I think it has something to do with how people configure their completion preferences with `zstyle`. If you are a nice completion writer you honor the setting. I hope I did it justice. > I haven't dealt with OpenStack recently so I can't actually test > anything around it anymore but if you could fix the issue that would be > great. I gave it a try. I ignored any colon issues for now. Would be great if someone could. So @list what do I need to do to get this merged? [-- Attachment #2: 0001-Fix-_openstack-completion-for-new-style-clients.patch --] [-- Type: text/x-patch, Size: 8533 bytes --] From ecb7cbe49bce946bb4d6e919794af72f3d43014d Mon Sep 17 00:00:00 2001 From: Syphdias <syphdias+git@gmail.com> Date: Thu, 18 Mar 2021 23:23:16 +0100 Subject: [PATCH] Fix _openstack completion for new style clients MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I have rewritten the completion function for new style clients. It no longer differentiates between command and sub command since this lead to a limited depth for completion lookups. This gets rid of the extra global caching variable `_cache_openstack_clnt_cmds`. I tried to stay true to the prefix-needed setting. First I process the words left of the cursor. `words` did not provide this granularity I needed so I opted to parse `LBUFFER` also for saving a partial input/match for later. I remove `help` since everything after it is normal (sub) command, so matches are identical to proper commands. Also I filter out every flag/option. To find the proper completion options I try one level at a time. * $service * $service $some_command * $service $some_command $some_command_for_some_command * etc. You could probably do this in reverse to save time but I don't think it is worth the effort. I add the global options if `-` is used as a prefix at the current position. Caveats: * I know there are options like `--file`. The new implementation does not handle this – neither did the old one. For this level, I'd suggest the OpenStack team to provide official zsh completions * Ignores everything right of the cursor so you can end up with command suggestion that are already on the right * `openstack complete` gets completed now, old implementation ignored it --- Completion/Unix/Command/_openstack | 119 +++++++++++++++-------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/Completion/Unix/Command/_openstack b/Completion/Unix/Command/_openstack index fcb704ac8..c12f25985 100644 --- a/Completion/Unix/Command/_openstack +++ b/Completion/Unix/Command/_openstack @@ -34,8 +34,6 @@ if (( ! $+_cache_openstack_clnt_opts )); then typeset -gA _cache_openstack_clnt_opts typeset -gA _cache_openstack_clnt_cmds typeset -gA _cache_openstack_clnt_cmds_opts - typeset -gA _cache_openstack_clnt_cmds_subcmds - typeset -gA _cache_openstack_clnt_cmds_subcmd_opts fi local -a conn_opts @@ -61,65 +59,74 @@ if [[ -n ${clnts_compl_new[(r)$service]} ]]; then # Populate caches - clnt_outputs is command raw output used later _cache_openstack_clnt_outputs[$service]=${:-"$($service ${(Q)conn_opts} complete 2>/dev/null)"} _cache_openstack_clnt_opts[$service]=${${${${(M)${${${${=${(f)"$($service help 2>/dev/null)"}}/\[}/\]}/\;}:#-[-0-9A-Za-z]*}/,}/\.}%--os-} - _cache_openstack_clnt_cmds[$service]=${${${${_cache_openstack_clnt_outputs[$service]}/* cmds=\'}/\'*}/complete} fi - local cmd subcmd - # Determine the command - for word in ${words:1}; do - local s=${_cache_openstack_clnt_cmds[$service]} - [[ $s[(wI)$word] -gt 0 ]] && cmd=$word && break + + # get worlds left of the curser into an array + local -a left_words + left_words=(${=LBUFFER}) + + # if curser is directly at a word (no space at the end), + # exclude the last word to offer right matches + # the last word could be a partial match that is later checked (prefix-needed) + local partial="" + if [[ "${LBUFFER[-1]}" != " " ]]; then + partial=${(@)left_words[-1]} + left_words=(${(@)left_words[1,$#left_words-1]}) + fi + # remove $service + left_words=(${left_words:1}) + + # filter out "help" + if [[ $left_words[1] == help ]]; then + left_words=(${(@)left_words[2,$#left_words]}) + fi + + # filter out options (-*) + left_words=(${left_words//-*}) + + local -a subcmd_array cmds_array cache_key_array cache_values + subcmd_array=() + cmds_array=(cmds) + cache_key_array=(${service}) + cache_values=() + local cache_key cmds + cache_key="" + cmds="" + + # Check for matches one level at a time + # example: "" server create + for word in "" ${(@)left_words}; do # first loop second loop third loop + subcmd_array=(${(@)subcmd_array} ${word}) # () (server) (server create) + cmds_array=(${(@)cmds_array} ${word}) # (cmds) (cmds server) (cmds server create) + cmds=${${(j:_:)cmds_array}/-/_} # cmds cmds_openstack cmds_server_create + cache_key_array=(${(@)cache_key_array} ${word}) # (openstack) (openstack server) (openstack server create) + cache_key=${${(j:_:)cache_key_array}/-/_} # openstack openstack_server openstack_server_create + + # lookup if current word is in cache_values of last elements + if [[ ${cache_values[(wI)${word}]} -gt 0 || $word == "" ]]; then + _cache_openstack_clnt_cmds[${cache_key}]=${${${_cache_openstack_clnt_outputs[${service}]}/* ${cmds}=\'}/\'*} + fi + # set cache_values for next loop + cache_values=${_cache_openstack_clnt_cmds[${cache_key}]} done - # Populate the subcommand cache - if [[ -n $cmd && -z $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]]; then - local t=cmds_${cmd//-/_} - _cache_openstack_clnt_cmds_subcmds[$service$cmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*} - fi - # Determine the subcommand - if [[ -n $cmd ]]; then - for word in ${words:2}; do - local s=${_cache_openstack_clnt_cmds_subcmds[$service$cmd]} - [[ $s[(wI)$word] -gt 0 ]] && subcmd=$word && break - done - # Populate subcommand option cache - if [[ -n $subcmd && -z $_cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd] ]]; then - local t=cmds_${cmd//-/_}_${subcmd//-/_} - _cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*} - fi - fi - # Special treatment for the help command - if [[ $cmd == help ]]; then - if [[ $words[CURRENT-1] == $cmd && $words[CURRENT] != -* ]]; then - # Offer commands - [[ -n $_cache_openstack_clnt_cmds[$service] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0 - elif [[ $words[CURRENT-2] == $cmd && $words[CURRENT-1] != -* && $words[CURRENT] != -* ]]; then - # Offer subcommands - local cmd=$words[CURRENT-1] - local t=cmds_${cmd//-/_} - [[ -z $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _cache_openstack_clnt_cmds_subcmds[$service$cmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*} - [[ -n $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmds[$service$cmd]} && ret=0 - else - # Handle help<TAB> properly - _values -w option help && ret=0 - fi - # Client options - elif [[ -z $cmd && $words[CURRENT] == -* ]]; then - _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0 - # Commands - elif [[ -z $cmd ]]; then - if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then - _message "missing authentication options" - else - _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0 - fi - # Subcommands - elif [[ -z $subcmd ]]; then - [[ -n $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmds[$service$cmd]} && ret=0 - # Subcommand options + + # Populate the command cache + if [[ -z $_cache_openstack_clnt_cmds[${cache_key}] ]]; then + _message "missing authentication options" else - { ! zstyle -T ":completion:${curcontext}:options" prefix-needed || [[ -prefix - ]] } && \ - [[ -n $_cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd]//\:/\\\:} && ret=0 + # add global options to completion list if current word start with -* + local extra_opts + if [[ $words[CURRENT] == -* ]]; then; + extra_opts=${_cache_openstack_clnt_opts[$service]} + fi + + { ! zstyle -T ":completion:${curcontext}:options" prefix-needed \ + || [[ -n "${partial}" && ${${_cache_openstack_clnt_cmds[${cache_key}]}[(Iw)${partial}*]} -gt 0 || -prefix - ]] } \ + && _values -w option ${(u)=_cache_openstack_clnt_cmds[${cache_key}]} ${(u)=extra_opts} \ + && ret=0 fi + # Old style clients elif [[ -n ${clnts_compl_old[(r)$service]} ]]; then if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then -- 2.30.1
Hi,
On 19/03/2021 00.45, Christian K wrote:
>
>> I have a vague recollection that some (sub)command(s) had a slightly
>> inconsistent output with colons somewhere which had to be removed for
>> completions. Could have been just one or two such commands but
>> unfortunately I don't remember exactly anymore.
>
> @Marko, I tried all clients I could find easily and could not find any
> suggestions including colons. I cannot handle an error I don't know how to
> throw. Please let me know if you remember an example.
I think I remember swift and perhaps one or two being somewhat different
that the rest. But it might be also that it was a client which has since
then become obsolete. Or perhaps it was added needlessly in the first place.
Given that you've tested it with all recent client version I'd say it
should be safe to drop today.
Thanks,
--
Marko Myllynen
[-- Attachment #1: Type: text/plain, Size: 706 bytes --] > > @Marko, I tried all clients I could find easily and could not find any > > suggestions including colons. I cannot handle an error I don't know how to > > throw. Please let me know if you remember an example. > I think I remember swift and perhaps one or two being somewhat different > that the rest. But it might be also that it was a client which has since > then become obsolete. Or perhaps it was added needlessly in the first place. swift should not be affected since it is in the "swift like" category and not new style. > Given that you've tested it with all recent client version I'd say it > should be safe to drop today. I'd prefer more testers. :) I found another small bug and fixed it. [-- Attachment #2: 0001-Fix-_openstack-completion-for-new-style-clients.patch --] [-- Type: text/x-patch, Size: 8675 bytes --] From d408532eb84988d2e565f4b097571b832b8ed172 Mon Sep 17 00:00:00 2001 From: Syphdias <syphdias+git@gmail.com> Date: Thu, 18 Mar 2021 23:23:16 +0100 Subject: [PATCH] Fix _openstack completion for new style clients MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I have rewritten the completion function for new style clients. It no longer differentiates between command and sub command since this lead to a limited depth for completion lookups. This gets rid of the extra global caching variable `_cache_openstack_clnt_cmds`. I tried to stay true to the prefix-needed setting. First I process the words left of the cursor. `words` did not provide this granularity I needed so I opted to parse `LBUFFER` also for saving a partial input/match for later. I remove `help` since everything after it is normal (sub) command, so matches are identical to proper commands. Also I filter out every flag/option. To find the proper completion options I try one level at a time. * $service * $service $some_command * $service $some_command $some_command_for_some_command * etc. You could probably do this in reverse to save time but I don't think it is worth the effort. I add the global options if `-` is used as a prefix at the current position. Caveats: * I know there are options like `--file`. The new implementation does not handle this – neither did the old one. For this level, I'd suggest the OpenStack team to provide official zsh completions * Ignores everything right of the cursor so you can end up with command suggestion that are already on the right * `openstack complete` gets completed now, old implementation ignored it --- Completion/Unix/Command/_openstack | 117 ++++++++++++++++------------- 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/Completion/Unix/Command/_openstack b/Completion/Unix/Command/_openstack index fcb704ac8..d55686b80 100644 --- a/Completion/Unix/Command/_openstack +++ b/Completion/Unix/Command/_openstack @@ -34,8 +34,6 @@ if (( ! $+_cache_openstack_clnt_opts )); then typeset -gA _cache_openstack_clnt_opts typeset -gA _cache_openstack_clnt_cmds typeset -gA _cache_openstack_clnt_cmds_opts - typeset -gA _cache_openstack_clnt_cmds_subcmds - typeset -gA _cache_openstack_clnt_cmds_subcmd_opts fi local -a conn_opts @@ -61,65 +59,78 @@ if [[ -n ${clnts_compl_new[(r)$service]} ]]; then # Populate caches - clnt_outputs is command raw output used later _cache_openstack_clnt_outputs[$service]=${:-"$($service ${(Q)conn_opts} complete 2>/dev/null)"} _cache_openstack_clnt_opts[$service]=${${${${(M)${${${${=${(f)"$($service help 2>/dev/null)"}}/\[}/\]}/\;}:#-[-0-9A-Za-z]*}/,}/\.}%--os-} - _cache_openstack_clnt_cmds[$service]=${${${${_cache_openstack_clnt_outputs[$service]}/* cmds=\'}/\'*}/complete} fi - local cmd subcmd - # Determine the command - for word in ${words:1}; do - local s=${_cache_openstack_clnt_cmds[$service]} - [[ $s[(wI)$word] -gt 0 ]] && cmd=$word && break - done - # Populate the subcommand cache - if [[ -n $cmd && -z $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]]; then - local t=cmds_${cmd//-/_} - _cache_openstack_clnt_cmds_subcmds[$service$cmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*} + + # get worlds left of the curser into an array + local -a left_words + left_words=(${=LBUFFER}) + + # if curser is directly at a word (no space at the end), + # exclude the last word to offer right matches + # the last word could be a partial match that is later checked (prefix-needed) + local partial="" + if [[ "${LBUFFER[-1]}" != " " ]]; then + partial=${(@)left_words[-1]} + left_words=(${(@)left_words[1,$#left_words-1]}) fi - # Determine the subcommand - if [[ -n $cmd ]]; then - for word in ${words:2}; do - local s=${_cache_openstack_clnt_cmds_subcmds[$service$cmd]} - [[ $s[(wI)$word] -gt 0 ]] && subcmd=$word && break - done - # Populate subcommand option cache - if [[ -n $subcmd && -z $_cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd] ]]; then - local t=cmds_${cmd//-/_}_${subcmd//-/_} - _cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*} - fi + # remove $service + left_words=(${left_words:1}) + + # filter out "help" + if [[ $left_words[1] == help ]]; then + left_words=(${(@)left_words[2,$#left_words]}) fi - # Special treatment for the help command - if [[ $cmd == help ]]; then - if [[ $words[CURRENT-1] == $cmd && $words[CURRENT] != -* ]]; then - # Offer commands - [[ -n $_cache_openstack_clnt_cmds[$service] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0 - elif [[ $words[CURRENT-2] == $cmd && $words[CURRENT-1] != -* && $words[CURRENT] != -* ]]; then - # Offer subcommands - local cmd=$words[CURRENT-1] - local t=cmds_${cmd//-/_} - [[ -z $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _cache_openstack_clnt_cmds_subcmds[$service$cmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*} - [[ -n $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmds[$service$cmd]} && ret=0 - else - # Handle help<TAB> properly - _values -w option help && ret=0 - fi - # Client options - elif [[ -z $cmd && $words[CURRENT] == -* ]]; then - _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0 - # Commands - elif [[ -z $cmd ]]; then - if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then - _message "missing authentication options" + + # filter out options (-*) + left_words=(${left_words//-*}) + + local -a subcmd_array cmds_array cache_key_array cache_values + subcmd_array=() + cmds_array=(cmds) + cache_key_array=(${service}) + cache_values=() + local cache_key cmds + cache_key="" + cmds="" + + # Check for matches one level at a time + # example: "" server create + for word in "" ${(@)left_words}; do # first loop second loop third loop + subcmd_array=(${(@)subcmd_array} ${word}) # () (server) (server create) + cmds_array=(${(@)cmds_array} ${word}) # (cmds) (cmds server) (cmds server create) + cmds=${${(j:_:)cmds_array}/-/_} # cmds cmds_openstack cmds_server_create + cache_key_array=(${(@)cache_key_array} ${word}) # (openstack) (openstack server) (openstack server create) + cache_key=${${(j:_:)cache_key_array}/-/_} # openstack openstack_server openstack_server_create + + # lookup if current word is in cache_values of last elements + if [[ ${cache_values[(wI)${word}]} -gt 0 || $word == "" ]]; then + _cache_openstack_clnt_cmds[${cache_key}]=${${${_cache_openstack_clnt_outputs[${service}]}/* ${cmds}=\'}/\'*} else - _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0 + # unknown word: set cache_key to last cache_key and break + cache_key=${${(j:_:)${cache_key_array:0:${#cache_key_array}-1}}/-/_} + break fi - # Subcommands - elif [[ -z $subcmd ]]; then - [[ -n $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmds[$service$cmd]} && ret=0 - # Subcommand options + # set cache_values for next loop + cache_values=${_cache_openstack_clnt_cmds[${cache_key}]} + done + + # Populate the command cache + if [[ -z $_cache_openstack_clnt_cmds[${cache_key}] ]]; then + _message "missing authentication options" else - { ! zstyle -T ":completion:${curcontext}:options" prefix-needed || [[ -prefix - ]] } && \ - [[ -n $_cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd]//\:/\\\:} && ret=0 + # add global options to completion list if current word start with -* + local extra_opts + if [[ $words[CURRENT] == -* ]]; then; + extra_opts=${_cache_openstack_clnt_opts[$service]} + fi + + { ! zstyle -T ":completion:${curcontext}:options" prefix-needed \ + || [[ -n "${partial}" && ${${_cache_openstack_clnt_cmds[${cache_key}]}[(Iw)${partial}*]} -gt 0 || -prefix - ]] } \ + && _values -w option ${(u)=_cache_openstack_clnt_cmds[${cache_key}]} ${(u)=extra_opts} \ + && ret=0 fi + # Old style clients elif [[ -n ${clnts_compl_old[(r)$service]} ]]; then if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then -- 2.30.1
On Fri, Mar 19, 2021, at 6:05 PM, Christian K wrote:
> > > @Marko, I tried all clients I could find easily and could not find any
> > > suggestions including colons. I cannot handle an error I don't know how to
> > > throw. Please let me know if you remember an example.
> > I think I remember swift and perhaps one or two being somewhat different
> > that the rest. But it might be also that it was a client which has since
> > then become obsolete. Or perhaps it was added needlessly in the first place.
>
> swift should not be affected since it is in the "swift like" category
> and not new style.
>
> > Given that you've tested it with all recent client version I'd say it
> > should be safe to drop today.
>
> I'd prefer more testers. :)
>
> I found another small bug and fixed it.
ping for review
vq