Repository navigation
Novos recursos e correções: Comunidades, Carrinhos/Pedidos, Transcrição e Acessibilidade - #490
Merged
Merged
Conversation
…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>
…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
- 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:
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)
Carrinho (cliente)
Acessibilidade (NVDA/JAWS/Narrador)
Comunidades e grupos
Protocolo
Transcrição
macOS
Servidor Node / WPPConnect 2.10.55
Não portado de propósito do upstream
Generated by Claude Code |
2 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resumo
Esta Pull Request consolida o conjunto de novas funcionalidades, melhorias de acessibilidade e correções desenvolvidas na branch
fixes-and-features-oct-2026em relação à branchmain(origin/main).As principais frentes contempladas são:
chat.whatsapp.comdiretamente no aplicativo e associação opcional ao protocolowhatsapp:no Windows.whisper.cpp(whisper-server.exe) e opção de transcrição em segundo plano sem janela modal de progresso.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.O que muda para o usuário
👥 1. Comunidades do WhatsApp
Alt+8para 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.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
Alt+Shift+B):Alt+Shift+B), listando produtos, fotos/descrições, preços formatados e disponibilidade.productMessage) agora são formatadas com título, descrição, preço e moeda, eliminando mensagens ilegíveis ou dados crus de imagem.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.WAWebMobilePlatforms.isSMB()).Ctrl+Shift+N) e configurar se o catálogo aceita novos pedidos via carrinho (set-cart-enabled).🚪 3. Solicitações de Entrada em Grupos
@lid), canal de origem da solicitação (link de convite, entrada via comunidade ou adicionado por participante) e data/hora.🔗 4. Links de Convite de Grupo e Associação de Protocolo
chat.whatsapp.comem 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).?mode=....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
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.Alt+Shift+Deleteapaga a transcrição da mensagem ou status focado.Ctrl+Alt+Shift+Deletee item no submenu "Ações em massa" para exclusão em lote das mensagens selecionadas.whisper.cppatravés do serviçowhisper-server.exegerenciado em Windows Job Object, eliminando o atraso de carregamento do modelo a cada áudio.🗑️ 6. Controle de Exclusão de Mídia no Aparelho Celular
♿ 7. Acessibilidade e Teclado
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).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.Ctrl+Shift+G) e áudio do computador (Ctrl+Shift+H) anunciam aos leitores de tela seus respectivos atalhos e nãoCtrl+R.ℹ️ 8. "Novidades Desde a Última Vez Que Você Viu" (#488)
changelog_seen.json).Detalhes Técnicos e Arquitetura
sessionUtil,sessionController,createSessionUtil,functions,config,deviceController).commerceController.ts: manipulação do catálogo comercial, gerenciamento da sessão do carrinho e rotas de ordens.communityOverviewController.tsecommunityAdminController.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.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.pyeclient/core/changelog_seen.py: comparador de histórico de versões.tests/test_no_undefined_names.py: verificação estática preventiva viasymtablepara assegurar que nenhum nome indefinido exista em nenhum módulo do cliente.Testes e Validação
tests/test_communities.pytests/test_commerce.pyetests/test_commerce_shortcuts.pytests/test_group_membership_requests.pytests/test_whatsapp_link_association.pytests/test_status_transcription.pyetests/test_transcription_delete_shortcuts.pytests/test_whisper_cpp_server.pytests/test_delete_media_option.pytests/test_data_dialog_mnemonics.pytests/test_no_undefined_names.pytests/test_changelog_diff.pyetests/test_changelog_seen.pydocs/reference/(communities.md,carts-and-orders.md,group-join-requests.md,keyboard-shortcuts.md) e atualização dos traps emdocs/traps/.winzapp.po) e arquivos de changelog atualizados nos 7 idiomas do projeto.