Skip to content

Novos recursos e correções: Comunidades, Carrinhos/Pedidos, Transcrição e Acessibilidade - #490

Merged
gabrielhhaber merged 38 commits into
mainfrom
fixes-and-features-oct-2026
Oct 14, 2026
Merged

gabrielhhaber merged 38 commits into
mainfrom
fixes-and-features-oct-2026

Conversation

@gustavo-barrios2006

Copy link
Copy Markdown
Collaborator

Resumo

Esta Pull Request consolida o conjunto de novas funcionalidades, melhorias de acessibilidade e correções desenvolvidas na branch fixes-and-features-oct-2026 em relação à branch main (origin/main).

As principais frentes contempladas são:

  1. Comunidades do WhatsApp: visualização completa, navegação e ferramentas de administração com confirmação ativa via releitura.
  2. Carrinhos, Pedidos e Ferramentas Comerciais: visualização de catálogo de empresas, montagem e envio de carrinho, renderização de produtos no chat e ferramentas de vendedor para contas WhatsApp Business (aceitar, recusar e atualizar pedidos).
  3. Solicitações de Entrada em Grupos: diálogo dedicado para administradores listarem, aprovarem ou rejeitarem pedidos pendentes de novos membros.
  4. Links de Convite de Grupo: captura de links chat.whatsapp.com diretamente no aplicativo e associação opcional ao protocolo whatsapp: no Windows.
  5. Transcrição Local Aprimorada: transcrição de status de áudio/vídeo, exclusão de transcrições por atalho de teclado, modelo residente no whisper.cpp (whisper-server.exe) e opção de transcrição em segundo plano sem janela modal de progresso.
  6. Exclusão de Mídia no Dispositivo: opção para apagar a mensagem no chat mantendo o arquivo de mídia salvo no telefone móvel.
  7. Acessibilidade e Leitores de Tela: mnemônicos completos (Alt+letra) nos novos diálogos, correção de rótulos estáticos para leitores de tela, anúncio correto dos atalhos nos botões de gravação e tons de pele nas reações rápidas.
  8. Novidades e Atualizador (add what is new to each alpah version #488): histórico comparativo em linguagem natural do changelog ("Novidades desde a última vez que você viu") e exibição detalhada de novidades no diálogo de atualização.
  9. WPPConnect Server 2.10.52: sincronização com a release upstream mantendo os patches customizados do WinZapp.

O que muda para o usuário

👥 1. Comunidades do WhatsApp

  • Acesso rápido: Novo atalho Alt+8 para abrir a janela de Comunidades a partir de qualquer ponto da janela principal, além do novo botão "Comunidades" no diálogo de Nova Conversa.
  • Estrutura e grupos: Exibe as comunidades da conta, suas descrições, administradores e os grupos vinculados que você já ingressou, identificando claramente o Grupo de Avisos e o Grupo Geral. Pressionar Enter em um grupo abre a conversa correspondente (respeitando grupos trancados por PIN).
  • Administração de Comunidade (para administradores):
    • "Nova comunidade": criação de comunidade com nome, descrição e grupos iniciais.
    • "Adicionar grupos" e "Remover grupo da comunidade".
    • "Promover a administrador da comunidade" e "Rebaixar administrador da comunidade".
    • "Desativar comunidade".
  • Garantia de consistência (read_back): Cada comando é disparado uma única vez e verificado por releituras sucessivas no WhatsApp Web, assegurando que o status final anunciado reflita o estado real no servidor.

🛒 2. Catálogo Comercial, Carrinhos e Pedidos

  • Catálogo e Carrinho de Compras (Alt+Shift+B):
    • Em conversas com contatos comerciais, o menu da conversa oferece "Catálogo e carrinho da empresa" (também acessível por Alt+Shift+B), listando produtos, fotos/descrições, preços formatados e disponibilidade.
    • Montagem de carrinho com opções de adicionar, alterar quantidades, remover itens e esvaziar.
    • "Enviar pedido" envia o pedido para a empresa após confirmação detalhada de destinatário e resumo de itens.
  • Mensagens de Produto e Pedido no Chat:
    • Mensagens de catálogo enviadas como produto (productMessage) agora são formatadas com título, descrição, preço e moeda, eliminando mensagens ilegíveis ou dados crus de imagem.
    • Mensagens de pedido (orderMessage) e atualizações de pedido (orderUpdateMessage) são integradas à lista de mensagens, prévias e notificações com contagem de itens, valor total e status.
    • Pressionar Enter ou escolher "Ver pedido" abre os detalhes completos do pedido.
  • Recursos para Contas WhatsApp Business:
    • Detectadas automaticamente via API (WAWebMobilePlatforms.isSMB()).
    • Na visualização de pedidos recebidos: opções de Aceitar pedido, Recusar pedido (com nota explicativa) e Atualizar estado (status do pedido, pagamento e anotações).
    • Submenu "Ferramentas comerciais": permite criar pedidos diretamente para um contato (Ctrl+Shift+N) e configurar se o catálogo aceita novos pedidos via carrinho (set-cart-enabled).

🚪 3. Solicitações de Entrada em Grupos

  • Para administradores de grupos com aprovação ativada, a janela Dados do Grupo ganha o botão "Solicitações de entrada que aguardam aprovação".
  • Lista os solicitantes com nome de contato, nome do perfil ou telefone resolvido (inclusive convertendo identificadores @lid), canal de origem da solicitação (link de convite, entrada via comunidade ou adicionado por participante) e data/hora.
  • Permite Aprovar ou Rejeitar a solicitação selecionada, com confirmação ativa no WhatsApp Web.

🔗 4. Links de Convite de Grupo e Associação de Protocolo

  • Ao teclar Enter/Espaço em um link chat.whatsapp.com em uma mensagem, clicar no link ou teclar Enter na mensagem, o WinZapp abre sua janela nativa "Entrar no grupo por link", com link preenchido e prévia carregada (evitando redirecionamento para o navegador).
  • Suporte a links modernos terminados em parâmetros como ?mode=....
  • Em Configurações > Geral, a opção "Permitir que o Windows abra links de convite de grupo do WhatsApp com o WinZapp" associa o aplicativo ao protocolo whatsapp:, capturando convites disparados de navegadores e outros programas (com repasse via IPC para instâncias já abertas).

🎙️ 5. Transcrição Local de Áudio e Vídeo

  • Transcrição na aba Status: Suporte a status de áudio e vídeo com Alt+Shift+T (janela de resultado), Alt+Shift+N (transcrição na linha), fila automática de status recebidos e atalho Enter/Espaço. Transcrições salvas nos status são limpas automaticamente quando o status expira.
  • Exclusão de Transcrições por Atalho:
    • Alt+Shift+Delete apaga a transcrição da mensagem ou status focado.
    • Ctrl+Alt+Shift+Delete e item no submenu "Ações em massa" para exclusão em lote das mensagens selecionadas.
    • Suporte às teclas de transcrição e exclusão na janela "Meu status".
  • Modelo Residente no whisper.cpp: A opção "Manter o modelo carregado na memória" agora contempla o backend whisper.cpp através do serviço whisper-server.exe gerenciado em Windows Job Object, eliminando o atraso de carregamento do modelo a cada áudio.
  • Transcrição em Segundo Plano: Nova opção para transcrever sem abrir a janela modal de progresso, permitindo navegar livremente pelas mensagens enquanto a transcrição é concluída.
  • Tratamento de Fila Automática: Alertas sonoros de pausa da fila anunciados uma única vez por causa (ex.: ausência de modelos instalados), sem repetições contínuas a cada tentativa, e retomada imediata ao salvar configurações na aba Transcrição.

🗑️ 6. Controle de Exclusão de Mídia no Aparelho Celular

  • Ao apagar fotos, vídeos, áudios ou documentos (individualmente ou em seleção múltipla), o diálogo de confirmação agora disponibiliza a caixa de seleção "Apagar também a mídia do celular" (marcada por padrão).
  • Permite apagar a mensagem no chat e manter o arquivo armazenado no telefone móvel. Configuração em Configurações > Interface do Usuário define o comportamento inicial da caixa.

♿ 7. Acessibilidade e Teclado

  • Mnemônicos (Alt+letra): Letras de atalho atribuídas a todos os controles dos novos diálogos (Comunidades, Carrinhos/Pedidos, Solicitações de Entrada) e dos diálogos existentes (Dados do Grupo, Dados da Conversa, Convite para o Grupo e Entrar no Grupo por Link).
  • Rótulos Estáticos (add_labelled): Corrigida a ordem de criação de controles e textos estáticos para que leitores de tela (NVDA, JAWS e Narrador) leiam o nome correto do próprio controle.
  • Atalhos nos Botões de Gravação: Botões de gravação de áudio alternativo (Ctrl+Shift+G) e áudio do computador (Ctrl+Shift+H) anunciam aos leitores de tela seus respectivos atalhos e não Ctrl+R.
  • Tons de Pele nas Reações Rápidas: Setas Esquerda/Direita alternam o tom de pele de emojis que possuem variantes, anunciando por voz o novo emoji selecionado.
  • Repintura na Reprodução Encadeada: Durante reproduções sequenciais de mensagens de voz, o congelamento de repintura é restrito à linha focada, permitindo que as confirmações de envio/entrega/leitura sejam atualizadas nas demais mensagens em tempo real.

ℹ️ 8. "Novidades Desde a Última Vez Que Você Viu" (#488)

  • Em Ajuda > "Novidades desde a última vez que você viu", o WinZapp descreve em frases simples o que mudou no changelog desde a última leitura daquele usuário (changelog_seen.json).
  • Na janela de atualizações, o botão "Quais as novidades?" exibe o diff do changelog da versão disponibilizada e lista os PRs intermediários para versões alpha.

Detalhes Técnicos e Arquitetura

  • Atualização WPPConnect Server (2.10.52):
    • Port e sincronização limpa das alterações upstream mantendo os patches do WinZapp (sessionUtil, sessionController, createSessionUtil, functions, config, deviceController).
  • Novas Rotas e Controladores no Backend:
    • commerceController.ts: manipulação do catálogo comercial, gerenciamento da sessão do carrinho e rotas de ordens.
    • communityOverviewController.ts e communityAdminController.ts: rotas de leitura e administração de comunidades.
    • groupMembershipRequestsController.ts: consulta e resposta de solicitações de entrada com atualização síncrona pelo WhatsApp Web.
  • Módulos no Cliente:
    • client/core/read_back.py: ciclo padronizado de validação pós-ação (run_action).
    • client/core/transcription/whisper_cpp_server.py: ciclo de vida do servidor HTTP local com encerramento automático em Job Object.
    • client/core/whatsapp_link_association.py: gerenciamento no Registro do Windows com testes unitários em dicionário simulado.
    • client/core/changelog_diff.py e client/core/changelog_seen.py: comparador de histórico de versões.
    • tests/test_no_undefined_names.py: verificação estática preventiva via symtable para assegurar que nenhum nome indefinido exista em nenhum módulo do cliente.

Testes e Validação

  • Mais de 4.000 linhas de novos testes automatizados cobrindo todas as áreas alteradas:
    • tests/test_communities.py
    • tests/test_commerce.py e tests/test_commerce_shortcuts.py
    • tests/test_group_membership_requests.py
    • tests/test_whatsapp_link_association.py
    • tests/test_status_transcription.py e tests/test_transcription_delete_shortcuts.py
    • tests/test_whisper_cpp_server.py
    • tests/test_delete_media_option.py
    • tests/test_data_dialog_mnemonics.py
    • tests/test_no_undefined_names.py
    • tests/test_changelog_diff.py e tests/test_changelog_seen.py
    • Testes Jest para os novos endpoints do WPPConnect Server.
  • Documentação de referência em docs/reference/ (communities.md, carts-and-orders.md, group-join-requests.md, keyboard-shortcuts.md) e atualização dos traps em docs/traps/.
  • Todos os catálogos gettext (winzapp.po) e arquivos de changelog atualizados nos 7 idiomas do projeto.

gustavo-barrios2006 and others added 30 commits October 13, 2026 00:03
…le conversation

While a sequence of voice notes was alive, every status repaint of the
conversation was parked until it ended, and a sequence stays alive while
its audio is paused. Pausing a chained voice note to record a reply left
every message sent meanwhile with no "sent", "delivered" or "read" in the
list, while the message data window (which asks WhatsApp directly) showed
the right state. Seen in a session log: the hold armed at 23:27:41, the
audio paused at 23:29:01, and the receipts of the two replies sent next
written to no row until a later playback ended the sequence.

The rule is now core/chain_repaint_guard.py: during a sequence a row about
to be rewritten is parked only when it has list focus, whichever message is
playing (the user can leave the audio playing and walk the list, or turn
the focus move off), or when the sequence has just moved focus off it.
Every other row is written when its receipt arrives.

- A row parked for having focus gets a second look when focus moves, through
  the coalescing timer, and on every later repaint pass.
- Pausing releases the hold; the next voice note to end arms it again.
- _auto_chain_next_audio()'s early exits release too, so the hold cannot
  outlive a sequence the user walked away from.
- The chain tells the hold which row it is leaving before calling Focus().

Changelog in every locale; docs/traps/screen-reader-speech.md and the rule
short form describe the new rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-by-link dialogs

The buttons of the group data dialog, some of the contact data dialog's and
every control of the group invitation dialog had no Alt+letter.

Most of them showed a label shared with a menu item, a window title or a
button of another window (add_member, open, save_as, copy_link,
select_group, preview_sound_play, conversation_data), where a mnemonic
would collide somewhere else, so these dialogs' controls get keys of their
own with the same words: group_data_add_member_btn,
contact_data_add_to_group_btn, conversation_data_overview_label,
conversation_sound_preview_play/_stop, group_media_open_btn,
group_media_save_as_btn, group_invitation_copy_link. Labels used only here
gain the mnemonic in place.

- Where such a label is also a window title, a message box caption or an
  accessible name, the marker is stripped with split_mnemonic().
- The invitation dialog's stock Close button said "Close" in every language
  (the client never creates a wx.Locale); it now takes the app's label.
- Letters are per locale, all ASCII, and unique inside each window;
  tests/test_data_dialog_mnemonics.py checks every locale.

Changelog in every locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nk forms are accepted

Enter or Space on an invitation link (the Tab stop below the message), a
click on it and Enter on the message row used to send
https://chat.whatsapp.com/... to the browser, whose page only offers a
button that asks for a WhatsApp app. They now open WinZapp's own join
dialog with the link filled in and the preview requested; nothing is joined
until the user presses the join button. The link's context menu offers
"Join group by link" first and keeps "Open link" for the browser.

invitation_code() rejected anything after the code. Read through the
page's own store over CDP: 2 of the 11 invitation links in loaded messages
carry "?mode=...", so pasting one into "Join group by link" answered
"invalid link". A query string, a fragment and a trailing slash are now
ignored, and whatsapp://chat?code=... (what the web page's "Join chat"
button asks the system to open) is accepted too. Bare codes, other hosts,
longer paths, http and any other whatsapp: link are still refused.

join_group_by_link() is the shared entry point; New conversation's button
goes through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…: association)

Settings > General gains an opt-in box that registers WinZapp, for the
current user, as a handler of whatsapp: links. The page behind an
https://chat.whatsapp.com/... link has one button, which asks the system
to open whatsapp://chat?code=...; with the association on, that reaches
WinZapp's join dialog (preview first, nothing joined until confirmed).
Windows gives https links directly only to packaged apps, so the web link
itself cannot be claimed.

- core/whatsapp_link_association.py: the handler, its Capabilities and
  RegisteredApplications entries (what lists WinZapp in "Default apps"),
  and the whatsapp scheme key only when it is free or already WinZapp's.
  Another program's handler is never overwritten, and turning the option
  off removes exactly what turning it on wrote. The registry is reached
  through a three-method object, tested against a dict.
- core/group_invitation_link.py: the pure link reading, moved out of
  group_join so the process start and ipc can use it; also finds the link
  on a command line.
- A second launch passes the link to the running process with a new IPC
  command, open_link, which only accepts a group invitation link and falls
  back to a plain activation; a first launch hands it to its own window. A
  source checkout changes to its folder first, since its data lives under
  the working directory.
- MainWindow.open_group_invitation_link() waits for WhatsApp to be
  connected (a link that starts WinZapp arrives before the session is up),
  says so, gives up after three minutes, and shows one dialog at a time.

The server's own reading of a pasted link (preview and join routes) now
accepts the same forms as the client: a query string, fragment or trailing
slash after the code, and whatsapp://chat?code=. The client only ever sends
the bare code, so nothing it does depends on this; it keeps the two
readings from drifting. Jest cases added for accepted and refused forms.

Changelog in every locale, covering this and the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WhatsApp Web's delete dialog has a "Delete file from your phone" box for
media messages. Read from the page over CDP (WhatsApp Web 2.3000.1049387914,
wa-js 4.6.1):

- the dialog's state is useWAWebToggle(!0), so the box starts checked;
- it is shown when a message of the selection is a media message that is
  not a sticker (getAsMms(msg) && type !== STICKER);
- its value is the clearMedia argument of Cmd.sendDeleteMsgs /
  Cmd.sendRevokeMsgs, and becomes deleteMedia in the
  DeleteMessageForMeAction that syncs to the phone;
- WPP.chat.deleteMessage(chat, ids, deleteMediaInDevice, revoke) is the
  wa-js entry to it.

WinZapp never sent deleteMediaInDevice, and the library's
deleteMessage(chatId, messageId, onlyLocal = false, deleteMediaInDevice =
true) fills a missing one with true: every delete also removed the file
from the phone, with no way to keep it.

The delete confirmations (one message, several selected, and the plain
prompt of the "Me" chat) now show "Also delete the media from your phone"
when a photo, video, audio or document is among the messages. It starts
checked, so nothing changes for someone who never touches it, and the value
is always sent explicitly instead of being left to that default. The
server route already accepted the field and is unchanged.

Changelog in every locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings > Interface: "When deleting a media message, start with 'Also
delete the media from your phone' checked" (user_interface.
delete_media_from_phone, on by default). On is WhatsApp Web's own default
and what WinZapp did before the box existed, so existing installations keep
their behaviour; off makes every delete confirmation start on "keep the
file", for someone who usually wants that. The confirmation still lets each
delete decide.

Changelog sentence added to the delete-media entry in every locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The conversation's transcription, on the Status tab, with the same pieces
mirrored: Alt+Shift+T (the text in the result window, the saved one when
there is one), Alt+Shift+N (the text in the status's line, read aloud),
"Enter and Space transcribe" with the same alternation, transcription on
arrival, and a cache switch with its clear button.

- core/transcription/statuses.py: the rules that differ from a message's.
- main_window/status_transcription.py: storing, deleting and clearing; the
  media of a run; the automatic runs. The text is kept under _transcription
  in the status's own record (status_updates), so the periodic prune of
  expired statuses deletes it with the status and no second store exists.
- ui/status_transcription_flow.py: a subclass of MessageTranscriptionFlow.
  The base gained four hooks (_save, _delete, _title_name, _offers_insert)
  whose default is exactly what it did; a status has no vault, no
  withdrawal and no "insert into the message" (the result window hides
  that button through a new can_insert option).
- status_tab/status_transcription.py: the two keys, the row text and
  focus. The keys act on the status the focused row is showing.
- The tab's list is rebuilt from the API's answer, whose copy of a status
  wins the dedup and knows nothing of a saved text: statuses.carry_over()
  puts it back on every reload, and storing writes both kinds of copy.
- A status is never on disk: a run downloads it, Fernet-encrypted like
  every local media file, into a private folder that lives as long as the
  run (statuses.TEMP_PREFIX, added to temp_sweep.PREFIXES).
- Automatic runs share the received messages' queue, one transcription at
  a time; a status arriving queues itself through _store_status_update().
- Settings > Local transcription: transcription.auto_transcribe_statuses
  (off), transcription.status_cache_enabled (on), user_interface.
  enter_transcribes_statuses (off) and "Clear saved local transcriptions of
  statuses". The per-model cache choice is shared with the conversation.
- Shortcut catalogue: status.ID_ALT_SHIFT_T and status.ID_ALT_SHIFT_N,
  customisable like the rest. The test that Alt+Shift+T has one owner now
  names its two, both meaning "transcribe what the list is on".

Changelog in every locale; docs/traps/transcription.md has the section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	translations/pl/LC_MESSAGES/winzapp.po
…the "my status" window

A message's saved local transcription could only be deleted from its
context menu, one message at a time, and a status's not at all (the flow
for it existed but nothing called it).

- Alt+Shift+Delete deletes the focused message's saved transcription, by
  the menu item's own path. With messages selected and "Substituir atalhos
  por ações em massa" on it acts on the selection, like every other
  single-message key. Nothing saved: one sentence, never silence.
- Ctrl+Alt+Shift+Delete and a new "Ações em massa" item delete the saved
  transcriptions of the selection after one question that counts them.
  Only messages holding a result are counted or touched.
- Alt+Shift+Delete on the Status tab does the same for the status the
  focused row shows; the flow asks a question of its own, since the
  message's says "the message and its audio stay in the conversation".
- The "my status" window takes Alt+Shift+T, Alt+Shift+N and
  Alt+Shift+Delete on the account's own statuses. The list's keys never
  reached it: its "my status" row is a summary that opens that window.
  status_tab/my_status_transcription.py is a base class of MyStatusDialog
  whose table is built with make_shortcut_table(self, 'status', ...), so it
  shares the tab's catalogue rows and the user's preference. An own status
  names no poster and is filed under the account when its text is stored.
- Shortcut catalogue: messages.ID_ALT_SHIFT_DELETE,
  messages.ID_BULK_DELETE_TRANSCRIPTIONS and status.ID_ALT_SHIFT_DELETE.
  tests/test_transcription_delete_shortcuts.py checks that these and the
  two status transcription keys are catalogue rows, are listed by the
  Keyboard shortcuts tab, have a label in every locale naming their default
  key, and sit in configurable tables.

Changelog in every locale; docs/traps/transcription.md has the section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /api/:session/communities, a route of WinZapp's own, because nothing
upstream answers it. What exists upstream is used as it is: the community
actions (create, deactivate, link and unlink groups, promote and demote,
participants) are wppconnect-server routes over the library's
WPP.community functions, and none of them is patched.

Two things read over CDP (WhatsApp Web 2.3000.1049387914, wa-js 4.6.1,
an account in 39 communities) decided what the route does itself:

- WPP.community.getAnnouncementGroup() tests
  groupType === "LINKED_ANNOUNCEMENT_GROUP", and groupType is undefined on
  every one of the 273 loaded groups, so it answered undefined for all 39
  communities. The announcement group is the joined subgroup whose metadata
  has defaultSubgroup, which is also what the community-comments runtime
  keys on. Run against the real page, the reader finds one for all 39.
- A community's own participant list holds its admins, not its members
  (WPP.group.iAmMember() is false for a community whose groups the account
  is in). So "am I an admin" is read from that list, and unreadable means
  "not an admin": an admin action is never offered on a guess.

The answer carries ids, names, the closed flag, the admins and the joined
subgroups with the announcement and general ones marked; nothing that is
not a group id leaves the page, and a failure never carries native text.

Registered in the three patch lists; Jest cases for the reader and route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The New chat dialog has a "Communities" button. Its dialog lists the
account's communities, describes the selected one (description, admins,
"you are not an admin") and lists the groups of it the account joined, with
the announcement and general groups named as such; Enter opens the group.
For a community the account administers it offers: add groups, remove a
group, promote and demote an admin, deactivate, plus "New community".

The admin actions are wppconnect-server's own routes over the library's
WPP.community functions, unpatched. They answer the library's raw result (an
object, a success flag, or no body), and they could not be exercised live:
the account read over CDP administers none of its 39 communities, and
nothing that changes an account is run there. So no answer is trusted as
"it happened". core.communities.run_action() sends an action once, never
retries it, and reads the communities again:

- done: the reading shows the effect;
- unconfirmed: accepted, not visible after three readings;
- failed: refused, and one reading shows no effect. A refusal is still read
  back, because creating is two requests and the second can fail after the
  first made the community.

Who can be promoted comes from the announcement group's participants (the
existing group info), not from community-participants, which failed with
SmaxParsingFailure on the real page. One person is matched across @lid,
@c.us and 8/9-digit forms with contact_dedup_key. The announcement group is
never offered for unlinking; the creator is never offered for demotion; a
community is only created over a list that was read, since "created" means
"was not there before".

New chat's buttons now wrap: five did not fit one row. open_group_chat() is
the join dialog's chat-opening step, shared.

41 strings in the seven locales, with a letter of its own for every control
of the three windows; changelog entry in every locale;
docs/reference/communities.md records what was read and what was not
verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…roup

Communities, run for real
-------------------------
With the maintainer's leave the community actions were run over CDP on the
real account (WhatsApp Web 2.3000.1049387914, wa-js 4.6.1): two throwaway
communities, a throwaway group and one borrowed member, all removed again.
docs/reference/communities.md has the table. Three things changed for it:

- Promotion has a route of WinZapp's own
  (POST community-admin/:communityId/promote). The library's
  promoteParticipants() looks the person up in the community's own
  participant list, which holds the admins only, also on an admin's
  account: it threw group_participant_not_found for a member who was in the
  announcement group and in getParticipants(). The route calls what
  WhatsApp Web's interface calls
  (WAWebGroupModifyParticipantsJob.promoteCommunityParticipants), which
  answered status 207 / code "200" and showed the new admin at once.
  Demotion stays with the library, where it worked.
- A refused action is now read back as patiently as an accepted one.
  Creating with no group answers an error (the library links an empty list
  next, which WhatsApp refuses) after the community exists, and the page
  showed it about 500 ms later: one immediate reading would have said
  "failed" for a community that was created.
- The reading-back moved to core/read_back.py, shared with the requests
  below. It earned its keep in the run: linking a group created 1.5 s
  earlier answered success and WhatsApp had not linked it.

Create, link, unlink, demote and deactivate work through the upstream routes
unpatched; their real answers are now the fixtures of the tests.

Requests to join a group
------------------------
The library has getGroupMembershipRequests, approve and reject, and
wppconnect-server has no route for them, so the routes are WinZapp's own
over those functions. The group data dialog shows an admin a "Pending join
requests" button: who is waiting (saved name, else profile name with the
number, else the number), how the request came and when; Approve and Reject
answer the selected one.

getMembershipRequests() only reads the page's local table, so the route
asks the server first with the job WhatsApp Web's interface uses, which
also drops from the table the requests that are no longer pending. Run for
real: the reading for a group the account administers (empty) and for one
it does not (403, answered as not_admin). Not run: a real request, which
needs a second account. So an entry is checked field by field, the answer
of approve/reject is not interpreted, and an answer is believed when the
person has left a list the server confirmed; an unrefreshed list proves
nothing. docs/reference/group-join-requests.md says what was and was not
run.

person_name() moved to core/participant_names.py and the dialogs' thread
plumbing to ui/dialogs/background_work.py, both now shared by two dialogs.

19 strings in the seven locales; the new data-dialog button has a letter no
other control of that dialog uses, in every locale. Changelog entry in
every locale. Jest: 30 cases for the two new controllers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t-2026

# Conflicts:
#	client/changelog_en-US.txt
#	client/changelog_es-ES.txt
#	client/changelog_pl.txt
#	client/changelog_pt-BR.txt
#	client/changelog_pt-PT.txt
#	client/changelog_ro.txt
#	client/changelog_tr-TR.txt
Routes of WinZapp's own under /api/:session/commerce: wppconnect-server has
none for WPP.cart.* nor for WPP.order.accept/decline/update, and its only
order route passes on a model that does not survive being returned by
value. The library functions are called as they are. Creating an order for
a customer (send-order-message) and the business's own cart setting
(set-cart-enabled) stay the upstream routes.

Who may do what follows WhatsApp Web's own program text
(2.3000.1049387914), read from the page:

- the cart is the buyer's: any account, in a chat with a business that has
  a catalog (profileOptions.commerceExperience). Whether the business takes
  carts is its own setting (profileOptions.cartEnabled); the library does
  not look at it, so the route does, and neither fills nor sends a cart to
  a business that turned it off;
- an order message opens for both sides ("View sent cart" / "View received
  cart");
- accepting, declining and updating an order are the seller's: only a
  WhatsApp Business account (WAWebMobilePlatforms.isSMB(), the test behind
  WhatsApp Web's "Orders" menu) and only for an order it received.

Run over CDP on the real, non-business account, with the function as it
ships and nothing sent to anyone: the catalog of a business; set 2, set 5,
read, remove and clear on the cart of a business that takes carts (the
page's own cart table, left empty); the refusal for a business that turned
its cart off; an unknown product; an empty cart not being sent; and the
seller's actions answering not_business. That run is where these came from:
a product's price is a string of digits in thousandths; WPP.cart.get()
returned by value carries no items, so they are read from the collection;
WPP.cart.clear() throws on an empty cart; the library skips a product the
catalog no longer has without a word, which is answered as
product_unavailable.

Not run: the seller's side, which needs a business account and a real
order. It follows the library's and WhatsApp Web's program text, and its
answer claims no more than "accepted" plus the message's status as the page
then holds it.

Jest: 33 cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cs yet)

Order messages and the seller's follow-ups are normalised, shown in the
list, the chat preview and notifications (core/commerce.py). A business
chat's menu opens its catalog and cart; an order message's menu opens the
order; a WhatsApp Business account gets its own tools (create an order,
accept/decline/update one it received, the cart setting). Who may do what
comes from the server (commerce/account, canAnswer/canUpdate), never from a
guess. 100 strings in the seven locales.

Still owed before this is done: Python tests for core/commerce.py, the
three dialogs and the mixins; changelog entries; docs/reference; a live run
of the account route's list of business chats. The buyer's side of the
page function was run live; the seller's side could not be.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/test_commerce.py (139 cases): the contracts with the server, the
order and order-update messages through the normaliser, the list row, the
chat preview, the notification and the unread count, the account's facts,
the menus, and the three windows on stubs. Two things they caught:

- the cart window's handlers relied on the buttons being greyed out; they
  now hold the rule themselves (no cart for a business that turned it off,
  and none after an order went out from the window);
- a typed price took '12,345,6' for 12345.60; groups of thousands must
  share one separator and the decimals use the other.

Changelog entry in the seven locales. docs/reference/carts-and-orders.md
says who sees what and why, what was run on a real account and what was
not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the account owner's leave one order (a cart with one item) was sent to
a business of their choosing, read back and deleted for everyone. It
arrived, with the fields order_payload() reads. Two library faults showed:

- WPP.cart.submit() throws msg_not_found after sending: it looks the message
  up before the page has it. The route no longer takes a throw for 'not
  sent'; it reads the chat for a new order message of the account's own. A
  throw with nothing new there stays unknown.
- The library calls WhatsApp Web's order query with positional arguments;
  it now takes one object and needs the seller's id, so WPP.order.get(),
  accept(), decline() and update() all failed with ServerStatusCodeError.
  Reading an order calls the query in its current form (run on the real
  order). The three seller functions run with the library's own reference
  to the query swapped, for the length of the call, for one that
  translates; with that swap WPP.order.get() read the real order and the
  reference was put back.

Cancelling follows WhatsApp Web, read from its order drawer's program text:
the buyer has no cancel (a sent cart is read-only; taking it back is the
ordinary delete message, which the test used), the seller declines with a
cancellation note or sets the status Canceled. So no cancel button was
added for the buyer, and the decline prompt now says what WhatsApp Web's
does, in the seven locales.

Jest: 37 cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A WhatsApp 'product' message (one item of a business's catalog) had no
branch in the normaliser. Its body is the picture's thumbnail, so the text
fallback turned it into a row of base64 or 'unsupported message'. It is now
productMessage: title, description, price (the sale price when there is
one) and currency, as WhatsApp Web's own parser fills them
(WAWebParseProductMessageProto); the thumbnail is not read. Listed,
previewed, notified and counted as unread, with the same sentence
everywhere: 'Product: Bread, R$ 7.50. Fresh every morning'.

Not seen on a real account (the one read had none); the fields come from
WhatsApp Web's program text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reported live: NameError: name 'can_insert' is not defined.
TranscriptionResultDialog._build_ui() read a parameter of __init__ (added
with the status transcriptions, where the result window has no Insert
button). Every local transcription that opened the result window failed.
The dialog's own tests build a real window, so they run in CI only, and the
line had never run before it reached a user.

The value is kept on the dialog. And since a window builder is exactly the
code a plain pytest cannot execute, tests/test_no_undefined_names.py reads
every client module instead (symtable): a name a function loads must be a
local, an enclosing function's, the module's or a builtin. Over the 417
modules it found this one and nothing else, the windows added on this
branch included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sent with the account owner's leave through the page function as it ships:
'sent: true' with the id of the message in the chat about a second after
the call, the order read back whole, a seller's action on it refused as
not_business, then deleted for everyone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Asked for: a setting so that Alt+Shift+T and Alt+Shift+N do not open the
window with Cancel, and the user stays on the messages or statuses until
the text is ready.

Settings > Local transcription: 'Transcribe without the progress window'
(transcription.run_without_dialog, off by default). With it on, the flow
says the transcription started, starts the same run with nothing on screen
and reports it when it ends exactly as it would have behind the window
(the result window, the text in the line, or the failure's sentence). The
same shortcut on the same message cancels; on another message it says one
is still running. A sentence said at the end no longer moves the focus,
since the user may be typing or in another chat by then. Quitting cancels
the run and waits for it, as it does for the automatic and bulk ones.

The flow's start() is split so both ways share everything after the run:
_prepare() / _finish() / _cleanup(). A status's private download folder is
now held from _prepare() to _cleanup(), since a run without the window
outlives start().

Four strings in the seven locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Asked for: the reactions list had no way to change the skin tone of an
emoji that has one (the thumbs up), which the emoji picker already offers.

Left and Right on a row of the quick reactions list now walk that emoji's
tones, as they do in the picker, through the one ladder both share
(emoji_picker.skin_tone_step); the new emoji is said aloud, since a row
that changes is silent to a screen reader. The hint at the top of the list
says so.

Choosing the reaction already sent still removes it, but only that very
emoji: another tone of it is another reaction and replaces it
(reaction_shortcuts.reaction_to_send).

One string in the seven locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reported from users' logs: with 'Keep the model loaded in memory' on and a
graphics card, every transcription still reloaded the model. The logs were
whisper.cpp (backend=whisper_cpp ... device=cuda), where the setting did
nothing: it only reached faster-whisper, and whisper.cpp ran one whisper-cli
process per note, 0.8 to 2.9 s of each being the program starting and
loading the model.

The release zip WinZapp already downloads and verifies ships
whisper-server.exe, which loads the model once and answers one HTTP request
per recording. With the setting on, WhisperCppBackend asks it
(core/transcription/whisper_cpp_server.py); release(), which the residency
timer and quitting call, stops it.

- Any doubt goes back to whisper-cli: everything but a cancellation raises
  ServerUnavailable and the note runs the old way, where failures are
  classified. The server can make a run faster, never fail one. With the
  setting off nothing changes at all.
- The program is pinned by RuntimeBuild.server_sha256, apart from
  pinned_files so that a reused folder without it still runs. The CPU
  digest is the installed, verified copy's; the CUDA one was read off the
  pinned zip (its own sha256 and size checked first).
- 127.0.0.1, a port the system picked, local files, stdout to the null
  device, stderr to a file in a private winzapp-whisper- folder: the text
  exists only in the HTTP answer.
- It never outlives WinZapp: the process is put in a Windows job object
  with kill-on-close. A server that cannot be put in the job is killed and
  not used.
- Same decoding as whisper-cli (-bs 5 -bo 5: the server's defaults differ).
  The language code is read from stderr; the answer's own is a name.
- Cancel kills the server, the only way to stop what it has begun.

Run end to end on a verified CPU build, the backend as the job calls it:
same text and segment times as whisper-cli, cancel and restart, release()
leaving no process and no temporary folder. Not run on a card: the gain
there is the point and it was not measured.

The checkbox's label now names both backends. Changelog entries, in every
locale, for this and for the two changes before it (transcribing without
the progress window, skin tones in the quick reactions), and the settings
tab's round-trip test now knows the option added there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t-2026

# Conflicts:
#	client/changelog_en-US.txt
#	client/changelog_es-ES.txt
#	client/changelog_pl.txt
#	client/changelog_pt-BR.txt
#	client/changelog_pt-PT.txt
#	client/changelog_ro.txt
#	client/changelog_tr-TR.txt
#	client/ui/conversation_panel/message_actions.py
#	translations/en-US/LC_MESSAGES/winzapp.po
#	translations/es-ES/LC_MESSAGES/winzapp.po
#	translations/pl/LC_MESSAGES/winzapp.po
#	translations/pt-BR/LC_MESSAGES/winzapp.po
#	translations/pt-PT/LC_MESSAGES/winzapp.po
#	translations/ro/LC_MESSAGES/winzapp.po
#	translations/tr-TR/LC_MESSAGES/winzapp.po
#	translations/winzapp.pot
A user heard, every now and then and with nothing being transcribed,
that automatic transcription was paused because local transcription
was not ready. The queue pauses 15 minutes on a setup fault and said
so "once" per pause: when the pause ended, the next voice message or
status tried again, failed the same way and said it again.

- auto_queue.PauseNotices: each fault is said once; it is news again
  only after a run worked or the Transcription tab was applied, which
  also resumes the queue at once (transcription_settings_applied).
- "Automatic" finding no model that fits the memory free at that
  moment came back as MODEL_NOT_INSTALLED and paused as if nothing
  were installed; pauses_the_queue(error, resolution) now lets it pass
  as a fault of that moment. Unmeasured memory still pauses.
- The notice no longer says "received voice messages" (statuses share
  the queue) and gives the reason a manual run gives.

Also two checks of the full suite that this branch had broken: the
whisper.cpp server reason is logged scrubbed, and the no-dialog
checkbox is read like every other one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pp overrides

2.10.49-2.10.52 are the first releases since 2.10.27 to change source.
Upstream's miscController.ts (not overridden) now imports cleanupSession
from sessionUtil.ts (overridden), so restoring our 2.10.48-era copy made
the type build fail: a server update past 2.10.48 could not build with
the patches WinZapp ships.

Merged three ways per file (base 2.10.48, ours, 2.10.52):
- sessionUtil, routes: upstream's, cleanly (cleanupSession; logout-session
  no longer behind statusConnection).
- sessionController: closeSession stays ours; logOutSession is upstream's.
- createSessionUtil: ours, plus upstream's session-name check.
- functions: webhook allowlist and retries, autoDownloadMaxSize; our lazy
  S3 require kept.
- config: the new webhook fields as constants (this file reads no env).
- deviceController: onlyNewsletter, onlyCommunities, onlyArchived forwarded.

tsc and npm run build pass on 2.10.52; every WinZapp Jest test passes,
upstream's failing ones are the documented divergences
(docs/traps/wppconnect-upgrade.md). Library pair unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AccessibleRecordVoiceMessage looked the configured key up under the
Ctrl+R ids for all three recording buttons, so whenever a main window
could answer, the alternate-mode (Ctrl+Shift+G) and computer-audio
(Ctrl+Shift+H) buttons announced Ctrl+R. Each now passes its own ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st looked"

The updater's "What's new?" filtered the installed changelog between the
two version numbers. That is the old version's file, and an alpha's
number is past every heading in it, so it showed the whole old file; the
GitHub notes of each alpha were only used when no changelog shipped.

- core/changelog_diff.py: two changelogs compared item by item and said
  in sentences (new, changed as it now reads, removed; a new version
  whole), never as + and - lines.
- core/release_notes.py + updater.whats_new_text(): the offered tag's
  changelog (raw.githubusercontent.com) compared with the installed one,
  and for an alpha the GitHub notes of every alpha since the installed
  version, as pull-request titles and numbers. Falls back to the old
  text offline.
- Help > "What's New Since You Last Looked...": the same comparison
  against a snapshot kept once per install (global/changelog_seen.json,
  every language, written atomically), so it is shared by all accounts.

Closes #488

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows names an unlabelled control after the static text created just
before it. The label helpers of the catalog, order, order item,
communities and create-community dialogs were handed controls already
built, so each label was created after its control and read on the next
one: the products list had no name and the empty cart list was read as
"Catalog products". add_labelled() takes a function and builds the
control after its label; a static check refuses the old pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and creating an order (Ctrl+Shift+N)

Each is a row of the shortcut catalog, so it can be changed on the
Keyboard shortcuts tab; the catalog and order rows share one preference
between the chat list and the open conversation. They follow the menu's
rules (one person's chat, a business with a catalog, a Business account)
and say why when they do not apply. Enter on an order message opens it,
as Enter on a poll opens the poll.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gustavo-barrios2006 and others added 5 commits October 14, 2026 01:30
…y is its own answer

The cart submit read-back takes its attempts and pause from the request
(the route sets neither, so about four seconds as before) and the Jest
case runs with no pause. A community the page does not hold is answered
404 not_a_community instead of being called a refusal by WhatsApp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…setting is read back

- open_group_chat goes through navigate_to_conversation_jid(): the PIN
  of a locked group, an archived group from its list, the main panel
  shown before focus moves (Alt+8 reaches Communities from any panel).
  Only a group with no row yet is opened directly, never a locked one.
- The group pickers never name a locked group.
- The cart setting is sent once and read back (core/read_back), so a
  lost answer after the change is not announced as a failure.
- Tests: BackgroundWork's contract; Enter on an order and the F1 list
  called instead of read from the source.
- Nits: focus leaves a hidden answer button; the order confirmation
  has no '1 items'; tr 'sepeti'; ro formal 'Deschideti'; the shortcut
  reference lists the new rows; the preview label computed once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he communities doc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… HKLM handler check

- api_patches: return-after-respond guards in the message, session and status controllers; statusConnection answers 502 when a number lookup throws and looks each recipient up once.
- wpp_minimum_version.txt 2.10.55, upgrade audit written in docs/traps/wppconnect-upgrade.md.
- Cart: an unconfirmed send keeps Send blocked across a refresh, and reads the cart back once: empty means the order went out.
- whatsapp: link association checks the merged HKCR view so a machine-wide handler is not overridden.
- Changelog entries (all locales) for WPPConnect 2.10.55 and the abandoned-session logout.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KoBWYaQGDmdXiH1Cqh1tV8
claude and others added 3 commits October 14, 2026 14:29
- macOS keymap: sections are Alt+1..8 (Alt+8 is Communities), so it no longer collides with Alt+Shift+8.
- tests: contact-message stub gets _can_view_order; group join test follows _go_to_group.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KoBWYaQGDmdXiH1Cqh1tV8
send_failure_is_ambiguous() drops any >= 500 as possibly sent; nothing was sent here, and 408 is in the retryable set. Docs and changelogs follow.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KoBWYaQGDmdXiH1Cqh1tV8
Correções da revisão do #490: WPPConnect 2.10.55, carrinho, protocolo whatsapp:

Copy link
Copy Markdown
Owner

O que NÃO foi testado de verdade (para conferir com contas reais)

Revisão feita por leitura de código e testes automatizados; nada abaixo foi exercitado numa sessão real. Quem testar pode usar esta lista.

Conta WhatsApp Business (vendedor)

  • Aceitar, recusar e atualizar estado de pedido (orderAccept, orderDecline, orderUpdate em commerceController.ts). O próprio código admite que nunca rodou numa conta Business, e withOrderQuery troca W.functions.queryOrder na página durante a chamada: se não for configurável, vira 503 genérico.
  • Criar pedido para um contato (Ctrl+Shift+N) e ligar/desligar o carrinho do catálogo.

Carrinho (cliente)

  • Enviar pedido e o caso de resposta não confirmada: depois do 503 order_not_seen, o diálogo agora lê o carrinho de volta (vazio = "Pedido enviado"). Um carrinho esvaziado por outro meio (ex.: pelo celular) também aparece como "Pedido enviado".
  • Catálogo, preços, montagem do carrinho, mensagens de produto/pedido no chat.

Acessibilidade (NVDA/JAWS/Narrador)

  • Repintura na reprodução encadeada de mensagens de voz (list_refresh.py, chain_repaint_guard.py): docs/traps/screen-reader-speech.md registra que duas tentativas anteriores falharam; precisa de teste manual com leitor de tela.
  • Mnemônicos (Alt+letra) e ordem dos rótulos nos diálogos novos; tons de pele com as setas nas reações rápidas.

Comunidades e grupos

  • Todas as ações de administração de comunidade e as solicitações de entrada em grupo.

Protocolo whatsapp: e convites

  • Associação no registro do Windows (opcional, HKCU), abrir convite vindo do navegador com WinZapp já aberto e fechado. Agora o registro consulta HKEY_CLASSES_ROOT para não sobrescrever um WhatsApp Desktop instalado para todos os usuários, mas isso também não foi testado numa máquina real.

Transcrição

  • whisper-server.exe residente: o SHA-256 do build CPU foi conferido contra o zip oficial; o do build CUDA não foi conferido (zip de ~671 MB).
  • Transcrição de status, em segundo plano, exclusão por atalho.

macOS

  • Atalhos: Alt+1..8 → Command-1..8 (Alt+8 = Comunidades). Só os testes automatizados rodaram.

Servidor Node / WPPConnect 2.10.55

  • tsc e jest não foram executados (a instalação do upstream falhou no ambiente da revisão). Os patches foram portados à mão de 2.10.52→2.10.55 (ver docs/traps/wppconnect-upgrade.md). Vale rodar o build na primeira alpha. Em especial, statusConnection agora responde 408 quando a consulta de um número falha (antes dizia "o número não existe").

Não portado de propósito do upstream

  • instrumentation.ts (já era equivalente) e a mudança de contactToArray (manter o sufixo explícito do JID), pelos motivos em docs/traps/wppconnect-upgrade.md.

Generated by Claude Code

@gabrielhhaber
gabrielhhaber merged commit 7743842 into main Oct 14, 2026
4 checks passed
@github-actions
github-actions Bot deleted the fixes-and-features-oct-2026 branch October 14, 2026 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants