Browse Source

Add WhatsApp relink and pairing workflow documentation

- Updated index.md to include a new section for WhatsApp relink and pairing.
- Created relink-and-pairing.json to define claims related to the relink process.
- Added relink-and-pairing.md to provide detailed documentation on the relink workflow, including steps for initiating a relink, handling QR codes, and managing session states.
softword 1 day ago
parent
commit
dd6298b1e6

+ 108 - 50
openwiki/.claims/architecture/app-state.json

@@ -1,14 +1,14 @@
 {
   "schemaVersion": 1,
-  "pageVersion": "sha256:a11d9385ed0fa92efef8cc9b1d8eb7a4c392af8f751c1b660a60d6044eac8893",
+  "pageVersion": "sha256:5a08c085203eed7c6af86a5ee968fd9f61a42c7dec64787f196a9cfc4a404452",
   "claims": [
     {
       "id": "claim_daef343b267c45a987738a3daf7e404d",
-      "statement": "AppEvent has variants Key, Redraw, Wa, AiReply, ImagesDescribed, ReplyDue, Sent and Web; AiReply carries a Reply or an error and Web carries a WebRequest from the web control panel.",
+      "statement": "AppEvent has variants Key, Redraw, Wa, AiReply, ImagesDescribed, ReplyDue, Sent, Web, ConfigReloaded and Relinked; AiReply carries a Reply or an error, Web carries a WebRequest from the web control panel, ConfigReloaded carries the reloaded Config or the load error, and Relinked carries the relink outcome.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L90-L121",
-          "version": "repo-lines-v1:sha256:f02e9754ecd750b36108a3c72bb9c08065b4ad6856541ca12ce77b0d077a7ed0:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI4OWMyOWE3NjJmOGZhMmEzZmY0NmY5MGZiZDY3N2QyYTI3MjViMjM3NTYxNjIwODdkOTRmMWI2MDg2Yzk5YzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmMGExNjA2YWZlMjU3OTU2NWMyMWNkOGU1ZjUyNzljMmY0ZWMwZWMyMjZmZjM5YjljNmY2MjllZTNkYjk0NTEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJkNTQzZDNkZjU4ZmJmODdkZjViYjZlOGNmZDQwYzBkNjY5MjY3MWZmMTAyOWRmZDM4OWY0MWIyNDY2OTZiY2EifQ"
+          "resource": "repo://src/app.rs#L106-L141",
+          "version": "repo-lines-v1:sha256:de9462259da6519ea1c124deddbc8bb27a8c18b17c07eed5377af48ee5589801:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI4OWMyOWE3NjJmOGZhMmEzZmY0NmY5MGZiZDY3N2QyYTI3MjViMjM3NTYxNjIwODdkOTRmMWI2MDg2Yzk5YzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmMGExNjA2YWZlMjU3OTU2NWMyMWNkOGU1ZjUyNzljMmY0ZWMwZWMyMjZmZjM5YjljNmY2MjllZTNkYjk0NTEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJkNTQzZDNkZjU4ZmJmODdkZjViYjZlOGNmZDQwYzBkNjY5MjY3MWZmMTAyOWRmZDM4OWY0MWIyNDY2OTZiY2EifQ"
         }
       ]
     },
@@ -17,18 +17,18 @@
       "statement": "Screen has Connecting, Pairing and Main; Focus has List and Input; ListMode has Chats and Contacts.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L131-L151",
+          "resource": "repo://src/app.rs#L151-L171",
           "version": "repo-lines-v1:sha256:92aaf2af1678376e51c1ad2567e9dce9ab29a60eb8e773ba16594a42750ff769:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY2YTEyNzUwZTQ5YzY3NDE5NGM1Yjg4ODEzOGQ5Y2JjNGI0Y2M2OTM2NjdiYmZjMzIxYjgzM2MxMWZlMTdlY2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY3NmE1NDRjYzAyM2VkNDA1ZTNiOThiOGEzNTQwOTBmYWQxNmU5ZTE4ZDVlZTlmN2JhNmMzZTcwMDUzMDA5ZDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFmY2RhYjQwOGQyMDcwM2JmNTU2ZWJmZjk2NGM3M2I4NGQzYWUzYzdiMzUwZTFiODNmNTA3OTE4MDUxYjA0ZjYifQ"
         }
       ]
     },
     {
       "id": "claim_0a2882d788054b5586b16e6e990f77ee",
-      "statement": "App::new builds the AI client, loads chats, contacts and the persisted global autoreply flag, and starts on the Connecting screen with an empty filter and a ReplyGate created at the current time.",
+      "statement": "App::new builds the AI client, loads chats, contacts and the persisted global autoreply flag, and starts on the Connecting screen with an empty filter, no pending deletion or relink, and a ReplyGate created at the current time.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L222-L260",
-          "version": "repo-lines-v1:sha256:40d548eaea0ec6ea72e016a3adeab2e0d8b2c9a0039d4f3bf03310b7e72917d0:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmNTYzZjU4ZTBlOWQyYjgxMjgwMTZhOWEyZGZhMTU2YjgzNTM0NWYzOTkzYTVhYjdkNjVhNWU4ODFmMDg0Y2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBhOGJlZTA0NjIzODBmZjdmN2Y0ZDZhYzE2MGViODRmYjk3YjgwZGRjMjM2ZGFmZWY4NTQ4OTYxYjcwZDQ4ZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU3M2U3ZTFjNDIxZDk5NzQ5ZWVmNzNjNjJiNmE4ODAyODhjNGNhZDM4ZTYwZWQyMTNjNmM1ZDI1ZjAyMDU3OGMifQ"
+          "resource": "repo://src/app.rs#L246-L286",
+          "version": "repo-lines-v1:sha256:94b043b31861b7f2e517e87907e09881fb6d2f163bbb08ec7784e45a44e7d767:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmNTYzZjU4ZTBlOWQyYjgxMjgwMTZhOWEyZGZhMTU2YjgzNTM0NWYzOTkzYTVhYjdkNjVhNWU4ODFmMDg0Y2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBhOGJlZTA0NjIzODBmZjdmN2Y0ZDZhYzE2MGViODRmYjk3YjgwZGRjMjM2ZGFmZWY4NTQ4OTYxYjcwZDQ4ZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU3M2U3ZTFjNDIxZDk5NzQ5ZWVmNzNjNjJiNmE4ODAyODhjNGNhZDM4ZTYwZWQyMTNjNmM1ZDI1ZjAyMDU3OGMifQ"
         }
       ]
     },
@@ -37,7 +37,7 @@
       "statement": "handle_wa_event maps Qr to Pairing, Connected to Main with status Connesso, LoggedOut to Connecting, OfflineSyncCompleted to the startup catch-up, and routes Contact and Message { message, image } events.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L289-L312",
+          "resource": "repo://src/app.rs#L406-L429",
           "version": "repo-lines-v1:sha256:5d1dce3b181c3675faeb3b14606340ed867acb60a349b69ef26369d3856c49c6:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZkOGNmMDJkODU2OWY1YTQ3ZjRiNmY0Zjc5MzBmNzkzMTczM2VmNGE4NDMwODNmYmZjMzAzNmNiM2JiMjc5MGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM5NDkyZWM5YTM2ZThkODU0NjE1MmI4MjJkNTdmZDIwMDk4OTE1MDMwYTIwZDliZGExZDVmYWQ0NGY4ZjFiNWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI2ZDczY2I1NTUwODdmZGQwZTc4MzhjZjM4MWZmZmRmMzljMzk2M2VmZWJhMGE0ZjQyNzg4OWE0MGUwODYwZWQifQ"
         }
       ]
@@ -47,22 +47,22 @@
       "statement": "on_message returns early when History::save_message reports the message was not new.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L333-L342",
+          "resource": "repo://src/app.rs#L450-L459",
           "version": "repo-lines-v1:sha256:39169d7955b19430b175fe5273033b79acede8ec3c2de6b4a8998c3201160caa:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk2ZjNjMTFjYTY5OWY4OTA5MTUwMjAyYzhkZTVlMjdlZjAwNTIyOGRmZmNjMzAzOTlmNDg4OTdmYzdhOTVlMTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdmYzAyNDRhNGJkOTI4YWRmMDE3YjljOWQ0ODIxYjU4NjgwZGRhMmI2YTBhZDEyYWU2MWVhNDBlOWZkMjM3OGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJhNDYyZmZiOTg5MjlkZTUyYmFlMTM0ZWVmMWRkZjYzNTRiODVlNGQ4MzE2MzlkMzZhMDIyMzA5NDcwNDYyZDYifQ"
         }
       ]
     },
     {
       "id": "claim_a5e4a2575e2a4890b96ffeed57e9f621",
-      "statement": "F10 quits on any screen and non-main screens ignore other keys; on the main screen F2 switches the list mode, F3 toggles the open chat's autoreply and F4 toggles the global autoreply regardless of focus, Tab switches focus, and Ctrl command chords are swallowed.",
+      "statement": "F10 quits and F8 requests or confirms a relink on any screen; any other key cancels a pending relink and an Esc doing so is consumed; non-main screens ignore other keys; on the main screen F2 switches the list mode, F3 toggles the focused chat's autoreply and F4 the global autoreply, Tab switches focus, and Ctrl command chords are swallowed.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L23-L30",
-          "version": "repo-lines-v1:sha256:1b77b4b729c472be09a45c5473cf8594fc6554a18cc8f686ece6245ff2aa26ec:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmQxOTM5NWQwMDUxZDZhMDgzMGQyZDk5ZmE1OThkNTg3YTI5ZmQ1MDQ5OTA0ZDU5Y2I1MjZiZDdjNzg4MGIwZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWY2MjM3MDg5YjkxZDEwN2I2NjQ1Nzk3M2E4YmEyYTcxZDg3MDQ0ZWE5MWU5MThjZGUyMDVmODZhY2Q0NmM5MyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWVmZGQwMDhhMTYzNjZhM2YxN2I2NjUzYjI3NzIxOGRjNDY1NGIwYWNhMTkwYjViYWJkNzA2YjIzYjEwNDA3NyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYjZmYjdiOWM1MTM4MWJmY2U2YjBjNThlNjRmZmEyOGM4MzI2NTk1OTI5ZDUwM2VhNTBiMGUwNGI1OTQ3ZDA1MyJ9"
+          "resource": "repo://src/app.rs#L23-L32",
+          "version": "repo-lines-v1:sha256:683c16567f4074fbe88d85ef8581c16739d5ca0531826b0fb49c5df95ac18f20:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJkMTkzOTVkMDA1MWQ2YTA4MzBkMmQ5OWZhNTk4ZDU4N2EyOWZkNTA0OTkwNGQ1OWNiNTI2YmQ3Yzc4ODBiMGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFmNjIzNzA4OWI5MWQxMDdiNjY0NTc5NzNhOGJhMmE3MWQ4NzA0NGVhOTFlOTE4Y2RlMjA1Zjg2YWNkNDZjOTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFlZmRkMDA4YTE2MzY2YTNmMTdiNjY1M2IyNzcyMThkYzQ2NTRiMGFjYTE5MGI1YmFiZDcwNmIyM2IxMDQwNzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI2ZmI3YjljNTEzODFiZmNlNmIwYzU4ZTY0ZmZhMjhjODMyNjU5NTkyOWQ1MDNlYTUwYjBlMDRiNTk0N2QwNTMifQ"
         },
         {
-          "resource": "repo://src/app.rs#L774-L804",
-          "version": "repo-lines-v1:sha256:37edcb8b1691dbaa467f7d59b80cdc3781376ce6ce1fc1783f167c52ae5ce169:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmODVjMjY1ZjBmMjNkOTA0NDcwMjYxNWZiM2NkMzQ2ODMxYTFiZWNiOTRlYzNkODYyNDYxYzFkNWEwMGQzNDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5OTRiMGIxZDNhNDA1MzRjYTFiZTVmNDdiNTRlNWY3ZDg3ZTZlMGMxMWE3ZjU5ZjA0NGU2ZGJmNTkyYzNlZDYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk2ZWU5ZDllODg2ODFmN2JmM2M4ZmZiZjEzZDgyODk2ODM4MGY2Y2E0M2Y1ZGE4ODI5MmU1MjY3ZDRjMDhlOWYifQ"
+          "resource": "repo://src/app.rs#L884-L929",
+          "version": "repo-lines-v1:sha256:6e737f1163f7fc1e0f873a9756a9a2c32d247edaf022b24c9d62e4f5d3ecdc71:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZGRmNjgzMjdhYTA4ZWZjNDYxNGFmZDIwOTkzZTFkNzZiZjUxNmQ3NzRjM2QyMmIzOTUyOTEwNmNkOTBhZmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk2ZWU5ZDllODg2ODFmN2JmM2M4ZmZiZjEzZDgyODk2ODM4MGY2Y2E0M2Y1ZGE4ODI5MmU1MjY3ZDRjMDhlOWYifQ"
         }
       ]
     },
@@ -71,7 +71,7 @@
       "statement": "In input focus characters are appended, Backspace deletes, Esc returns to the list and Enter submits the trimmed text to the open chat.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L939-L963",
+          "resource": "repo://src/app.rs#L1064-L1088",
           "version": "repo-lines-v1:sha256:93f1a991412191cf1e47a90542afc809bc43aaab50594660e57b57012282758d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc1ZWM1NzZmZGY5ZTZhYjIyYmM3ZTgyYWM2NzNiY2EyM2MxNTZkZjE0MTRmZGFjZjRlNzcwOTEzYmI3Yzg1OTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUxNzg4NWVhNGE5MGJjODU2N2NiZDFmYjM0M2FlNTQ0Y2EwMTU5ZDZkNTA0ZGU3ZjcyNWFiMWIyYjg0YzYwZGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBlYTM2NTI1MWJlYzc3N2JhYTBmYTZjMGEzZTZmOGRiYzliNzhiNmQ5YTNmNmI4ODVmODBmY2RkMmQzYWFhMmEifQ"
         }
       ]
@@ -81,26 +81,26 @@
       "statement": "Enabling a chat's autoreply requires an AI client first and then a chat accepted by supports_autoreply (private chats and groups); disabling is always allowed and the flag is persisted with History::set_autoreply.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1408-L1416",
+          "resource": "repo://src/app.rs#L1549-L1557",
           "version": "repo-lines-v1:sha256:ba5bea3063970ee3b66b2d54fc0529a98bf3972bf7e9a5cab37c6d0bf4659ff7:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZmFmNzM0NDVmZTU0ZTJjMzIyMDBiMGRiNDhlM2MyOGZlZDgyZDk3NWY0Mzg4MjM0NjA5NjhlZmM4ZmU1ZjJmOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOWM5Yjc1NjM5OGI3MTI0NGNmYThkOTk4ZGRlOTQwZWRmODk1MmNkMDEwMTYyMzM1YmEwMzRjYjkxZGZmMjRiZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDQwM2Q5ZmY0OGI3ZmI2ZDcyM2RlNWM5YjMxODkzMTNlMjQ2NzBhMWE5NjJhYWUxZTYxODRhMjllZmM3MWM1ZCJ9"
         },
         {
-          "resource": "repo://src/app.rs#L994-L1007",
+          "resource": "repo://src/app.rs#L1124-L1137",
           "version": "repo-lines-v1:sha256:cbcb0d80e707fedd21b3de7081436677468c63c025abb23dab5e5a6ab0ad90cc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0ZTRlYmRmYjA5YmI0NTM3YzBhZDM0MWUxNGY5NTkzYjEwYWE5ZWJiOTM0ZTNiOGJhMGViY2ZiYjlhYjBkZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI1Zjg5YTg3NjBkYWQ3NjUxYmZiZGQ4ZGFkYTM4OGRmZDUwZmU2NDczOGUwMzM4YjIzZTM5NzM4Y2Y1NTRmOWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzZWNmNGIzNzRlN2M0ODFlZjUzODQ1Y2Y1YjlmNDI5MDk3NmQxYTlmNDlkOWY5NWJjNjIxNWEyY2VhMDYyOTAifQ"
         }
       ]
     },
     {
       "id": "claim_4989af934f564cf789a6028f36e19728",
-      "statement": "Opening the selected entry of the filtered list loads its display name, autoreply flag and the last 200 messages and moves focus to the input.",
+      "statement": "Opening the selected entry of the filtered list (resolved by selected_jid) loads its display name, autoreply flag and the last 200 messages and moves focus to the input.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1075-L1091",
-          "version": "repo-lines-v1:sha256:0aceaa3d72a442f277babfd7a811f8fa14bea8944c8dee6f9657dcad479fd9d8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU0NmM2MGMyMDFmZDM3MThkMzVjZTZhNzRlMTdkMmM2Y2E2Yjg5N2ZmMDc3Y2RkMDcyZGZkZDgzODFhZjVhNWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzMjlhMjljNjFlZGI3Y2E3NTEzZTQ0NTgxMDJhZTAwNWU1MGM2N2QyYWFhMTdjMjRmMTBhMTRmN2MxOTExNzIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNjYzVlMzg4ZThmOTY0ZGY1OTFjZDM1Y2FlZjk5ODM0MmRjNTllNGU5OTMwMmMyYmI0MjMyZmMxMzdhM2Q1ZjkifQ"
+          "resource": "repo://src/app.rs#L1205-L1232",
+          "version": "repo-lines-v1:sha256:e52bd5fba06c8fa9c17319299e938b8922b2b431f8015138d79e1dca4d4c26d6:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQwZDJhY2QwNjZiMWU0MDM4ZGMyYzllOTI0YTAwNzlkZDNmOGNlNjllZjY4OTZlMTYzYzYyZThhMDlmYTQzYjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNjYzVlMzg4ZThmOTY0ZGY1OTFjZDM1Y2FlZjk5ODM0MmRjNTllNGU5OTMwMmMyYmI0MjMyZmMxMzdhM2Q1ZjkifQ"
         },
         {
           "resource": "repo://src/app.rs#L20-L21",
-          "version": "repo-lines-v1:sha256:052992c9f8e9b996b43554cee0a2351140f142d6d9781a2b3217356c40bf6cb1:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODdkOGI3ZDU2NTM2Yjg3MDE0OGY1Y2U5YzQ2YWQxN2FmMzI4ZDQ5NzRjMmNlMTVhODNhOWQ0ODllZTBjNDY5MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMjQwNDg4Y2VlODM4MmUwZTUzMWI5ZjEzZmQwYWE5NDFjNzJkMDM5MGU3ZTg5YzQ0MzA4MTFjYmM3NmFhNjYxMCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMGY4MjlhNmQ5ZTNmMmZjNjc2ZTdkYTAzNzA2YjY4ZjQ1OTE5ZjhiMTkyNTIyN2JiYTMwMDkwZGZmYzYwN2Q0MyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDcyMjFlYzQ5YThiY2FiYTFkNzUwNTE0MWZhYzY0MDhkOGZmMmUwMjUyMDgwZTZlMGYwNzQ1MDMzMjY3ZjQwZSJ9"
+          "version": "repo-lines-v1:sha256:052992c9f8e9b996b43554cee0a2351140f142d6d9781a2b3217356c40bf6cb1:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODdkOGI3ZDU2NTM2Yjg3MDE0OGY1Y2U5YzQ2YWQxN2FmMzI4ZDQ5NzRjMmNlMTVhODNhOWQ0ODllZTBjNDY5MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMjQwNDg4Y2VlODM4MmUwZTUzMWI5ZjEzZmQwYWE5NDFjNzJkMDM5MGU3ZTg5YzQ0MzA4MTFjYmM3NmFhNjYxMCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOWY5NTFjZjJlOTBkY2Y1MDJiMGFhYWNmODA0ODQxY2UyZjk3NjUyMmZlNWI2ZDI1OGY3YjliMDY2ZTY5MDNiNyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDcyMjFlYzQ5YThiY2FiYTFkNzUwNTE0MWZhYzY0MDhkOGZmMmUwMjUyMDgwZTZlMGYwNzQ1MDMzMjY3ZjQwZSJ9"
         }
       ]
     },
@@ -109,7 +109,7 @@
       "statement": "spawn_send sends the text via WaHandle::send_text and emits AppEvent::Sent.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1377-L1386",
+          "resource": "repo://src/app.rs#L1518-L1527",
           "version": "repo-lines-v1:sha256:946a66552037dc4ae7eff9cf6c602520fd019e2700d8d6cc4dc93c7db36bb837:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFlOWMwM2FlM2RiZTlkZDA3YjYxNTZkZWFkNzBhNmRhYjc4MzI3M2I2Y2E0Y2JlZGI0M2E0MTI4YmJlNTdiZjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMwZDEyZTUxMmY3N2FlNTEwYWQwZTk5M2I3NGNmNjdmY2ExYmRiOTllYmE3OWY3OTdjZDkxMWYyN2Y2MWQ1ODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUyM2IxZmMxYjIzYjFmMWZhZWVhY2E4MTM2Yzc1YmFkOGEwNGEzMGQ5Nzk0MTc5ZDNjZjUxYjJkNGVhMDI1NTIifQ"
         }
       ]
@@ -119,7 +119,7 @@
       "statement": "An AI text reply is sent only if the autoreply is still enabled for the chat (globally or per chat) and the owner did not answer by hand after the answered context; otherwise it is discarded with a status message.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L709-L735",
+          "resource": "repo://src/app.rs#L826-L852",
           "version": "repo-lines-v1:sha256:a05110c5a383be3518256a8f574d7c2b6dfa7ab90b8f3a2d19fbed62b9ec6e14:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVlODZiMzQ5MjhkN2ZlZWQzNTRiNmY1OGM3MjJlNTk5YzI3ODhhNWE0Yzc2MjUxN2YzYzExYzIzMDM0OGVjOTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc3ODkyYWI4MWM1MjU3OGQ5NjU4YWE4MmNmNDU0NjAyY2VmYTA4MDJiNmMzYzRkYWE4OWM4MjUwZDBlZDBjMjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVlYjQ1ODA1ZTI5MzcwMzMyZTY2ZDA3NzAwZjlhMDg3MDgyNWE3ZDM2NWFmYmJmNzYxOWNiYjNlMjNiNTViMWQifQ"
         }
       ]
@@ -129,7 +129,7 @@
       "statement": "build_ai returns no client when the system prompt is missing.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1393-L1401",
+          "resource": "repo://src/app.rs#L1534-L1542",
           "version": "repo-lines-v1:sha256:02a6b90c8547969f5229ba908e64ec2f9fd8d0547f3a7b34404429320b337fe4:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzcyNTRhNjcwYWJhNGU4ZWFiNDlmM2U1OWYzNjc4NTk0OWM2ZDk1OTFlN2ZkMWY1NjA4NGU3NzBlOWM3NDJlNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMGQ3OWM4ZDU3OGFjNjM3NDFmOTgwMTlmMzlmOWRmMDA4M2RkN2YzM2U0ZDNlOWU3ODU1YzZmNmQ2MWQzNjg0MCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTA2ZDU0NTM1OGJmMGQyNTFiNDhiNWZhZTE2YzIyZjkzYzgzNDM5ZTQwNzA0YzRkYzg1MzgyN2ZmNWFiNmYyNiJ9"
         }
       ]
@@ -139,7 +139,7 @@
       "statement": "is_command treats Ctrl as a command only when Alt is not held, because Windows reports AltGr as Ctrl+Alt.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1434-L1441",
+          "resource": "repo://src/app.rs#L1587-L1594",
           "version": "repo-lines-v1:sha256:a7e4215d1055bcf132d529dd3c057ef20525fbb22d5a1301c6980d41107b5124:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzllOWIxZTI5MGI4MzMyZDI5YTMwZjc1N2JjN2E3N2ZlYjZmMjAxYjNjMmM5M2ZiNTQ4MDU1YTFhODVmN2MyZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDg4NTkyMTBlYmNiMTJjZjY4YzYxNjIyMmZjNTYwYzFmNjhlMmZhMzFmNjMwNDkzZGY4MDk1ODg4N2Q1N2FmOSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiN2JjOThkNmIwZGM0OTJjNTAxNmRmMTA4MDVhM2E1NGViOWMxODRhNWE2MTM3ZTdmNGJlY2ZhMGMyMjA1NTBkMCJ9"
         }
       ]
@@ -149,7 +149,7 @@
       "statement": "start_reply records the last context message as answered, marks own mentions, reads the chat name and stored image descriptions, builds the turns and spawns the AI request.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L654-L673",
+          "resource": "repo://src/app.rs#L771-L790",
           "version": "repo-lines-v1:sha256:0a937f62450210fb1b4a45c2eb392c8533cab8fc5e7f2ca88caec3ba64669dbb:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkMjA2ZWQ4NGMzNGNhMWE0ZmNmZmQ1NmFlOTBiNDgyZDIyNGM2OWQ0ZmRlMDY4MmI2MTllODNlMDRlNWJhYjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEyZTA5MzVlNDEzNWQ5MWEyZGFlMDUxOGE3ZTY5NjQ0MmIxYmM5ZGYxOThiNDA1ZmVmMGQzMjUwY2VmOWU1ZWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQwMjI2ZGQ4YmZkY2JkMDhlNjUxMjUzOGY4MmU5YWNiOTEyNzg2NTgwNDEwMmQ2YjRlMzQ4NmRmMjU4YjMxNmYifQ"
         }
       ]
@@ -159,11 +159,11 @@
       "statement": "maybe_autoreply schedules a reply when ReplyGate::should_schedule allows it: the chat is marked pending and a timer task emits ReplyDue after reply_delay_seconds.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1204-L1209",
+          "resource": "repo://src/app.rs#L1345-L1350",
           "version": "repo-lines-v1:sha256:9d9b7ef08292560e1f25590a1f38732ed3fd2b45ffe522b5928335427699b373:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWM4Mjk4ODI1Mzg5YTU1NjFkNzUyZjZjZThhMjgyNzlkZTI4MDMyOTVlNTRlY2Y3NGQyODc2NWUwM2E3MDBkNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjE5NGJjNjc2ZDg4NmZhM2MyZDM3ZTNlMGFhM2M4OTEwODc1MWZhYzYzZmQwNzViNDY3MzM3NDZlZGIyM2Q2YiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDVkMmViYWNlOGUxMTQ2MDNlNjJkNzViNWY2ZGM4MWMzMmIzODMyYjhiMGIwZTNkNWFkNjQ5MzIwNDhhNmQzOCJ9"
         },
         {
-          "resource": "repo://src/app.rs#L351-L376",
+          "resource": "repo://src/app.rs#L468-L493",
           "version": "repo-lines-v1:sha256:0f06f0bd1697dfc6eea76bf1331023d0de98cf0face31b358f6defedc38a31d0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5NWFjMWY4MTY1YjkzNmU3MzY3MTRlNTgzM2FmNTc3YWZjMjdkMTUzZGY4NWEwNDRmMzkxNDJiOWI3OTZhODciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRiMmY5MGU2YmE0M2NkMDdhNGMzZWRmMWRiZTJiZWI0MmY4NDBjYzAzYjhjNDI3NWQ1ZGEzNDUxYzhkNDlhNDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBmZDk1MWYzMzMxMmIwZGIxOTkwNzNjZDIzNDA4MWM3MzMyNDIxZjYwZjRjNmQ4MDA3YmU2YjM1YzMwODYxNGUifQ"
         }
       ]
@@ -173,7 +173,7 @@
       "statement": "On ReplyDue the kept images of the chat are taken and, if the chat is still pending, prepare_reply either ends the pending state when no reply is wanted, spawns the description of images still lacking one (the reply follows ImagesDescribed), or starts the reply at once.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L422-L451",
+          "resource": "repo://src/app.rs#L539-L568",
           "version": "repo-lines-v1:sha256:7dc3d726de8b72086eda0835321c1115c4fd2cd81cc43f8ed74a3ad6704b3b61:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIxMGU4Y2Q4NGRkNGVlMzUzOTU2ODAxODRjZTNlYTEwZDlhMTI3Mjg1Yzk2MDVkY2RmNDJiZjkwOWJkMDZlNjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI5NDI0YzQ0ZDRkYWFkZTBkMTA4ZmM3Nzk5MDVlMGI2Mjk4NDJiMjk3ZjRkMWZhYTc5OTIxNTg5NDJlZGY1MzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU3ODQ1MTBjNTZmNWE5YTNiMmY0ZTdkNmZjMDFjNGM4NTEyOWJjYzUzM2U1YTZjZTA2MTYyY2FiMmIwNGY4YzIifQ"
         }
       ]
@@ -183,7 +183,7 @@
       "statement": "The startup catch-up runs once per process on OfflineSyncCompleted, marks pending and prepares a reply without delay for each enabled chat (every chat when the global autoreply is on) whose last message passes ReplyGate::catch_up_due within catch_up_seconds, and sets the status 'Risposte di recupero: N' when N > 0.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L571-L626",
+          "resource": "repo://src/app.rs#L688-L743",
           "version": "repo-lines-v1:sha256:42baaf60c3fa91366d408e42913e5157b90816c6d76274c43152965dd45df15f:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJiYWY4NjRiNzJmMjM1YjJhNzAxNzllOWRiZTlkYTg1ZmY0NWM1OTg4YjY1MTUzNmQyYjNmMDM0MzRkMTA3YjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJkOWY0YzAxNDlmZTYwZWNjMDY0NDU2Y2VhNTgwNWIyMmI4YjgzZTljYzY0YzVmZTc3YzhjZDNjMzQ5ODg1OWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc4OTU5NGE5NzIzN2Y2NjVkMmU0Y2RhMTIyZmY1NTIyNzJjMjVmZTFhMTI0NmZjMGNkNjllMDQ4MjE2ZTNlYWMifQ"
         }
       ]
@@ -193,7 +193,7 @@
       "statement": "The autoreply applies to a chat when the global autoreply is on or the chat's per-chat flag is on.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L521-L527",
+          "resource": "repo://src/app.rs#L638-L644",
           "version": "repo-lines-v1:sha256:9190cc2d950eb45e132eae01c1ae01b5fb4af82470bb42fae8b4e0227a315710:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmMzZGZhMjZjNzdiNTNkZDcyZWZmZGI1MmEyNWFlNGY5YzMwNTdiZTk2NWI0MzRlMTMyOWIwMTM2MGZmNGViNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTYyZWU1N2U0MWE4YzIwYWUwMTY0ZGYyNjE0MzlkY2M0OTlkMzUyMDgzNDE1MTUxNmQwNDdlOGRlYWU4YTQ2NiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTEyYzE5ODI4NjExZDJhZjdmZWFmNjQyN2ZmNzE0MjFiN2JhMTU4MzM5OWY0YWU5ODBiMzJkNThlNDUzYTJiYSJ9"
         }
       ]
@@ -203,15 +203,15 @@
       "statement": "Typing in the list focus edits a case-insensitive name filter that restricts the visible chats and contacts; selection, opening and deletion operate on the filtered list.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1148-L1150",
+          "resource": "repo://src/app.rs#L1289-L1291",
           "version": "repo-lines-v1:sha256:fa265165a7dbaf620c22011a0c3c22d3351ef4c563f2f54f8fcb163c57bd1734:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTBhZGVkYjQzODU5N2ZkNWM0ZTVlZTAxMmE5Y2RiYzY1Yjk4Y2VkYTcxMDc2YWU5YjE4MmFjMDNmMmQ5YWRlNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOWI5ODAxZjkzZGZiYTU3OWNmZTAzOWRiMGFlYTMwNzc1YWRhYTU2ZDM2NGUxZjE3NWJmNjYxYzhhZjg4MDRkYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMmFiZDA1MjI3NzQyOTJhOTE3MDdjZDg0ZjRmMWExYTExNmQ3MWQzNDA3Yjg1Y2NkNDY4OWU1ZDM1MDBiYjAyNiJ9"
         },
         {
-          "resource": "repo://src/app.rs#L812-L848",
+          "resource": "repo://src/app.rs#L937-L973",
           "version": "repo-lines-v1:sha256:ebbdd1522a95030cb283c058e7c767edf7b3297cdfe336683fa94151dd1114b4:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExNTZkYzNmM2MyODVjOTI5NmU1NWNlY2FiOGQzMzczNzg1YTgyOTVkYTZkZjU4Nzk1ZDUyNTZlNDc5MjRiMzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU3NmVkYmFmNjIyMDAzYjdkMGYzOWE0YjE1ZGNkZjJmYTQ0NGNhY2Q5ZWRlMWRmNjc2OGI0ZWZiMzQ3OTVjNGIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdkZWE3NDZmZTI4YTEwOGI2ZDUyNmVkOWJlNzE1NjU3MmI2YjIwYTdmODgxYTI0NGU0OTViN2YzMzg4MWFiMDUifQ"
         },
         {
-          "resource": "repo://src/app.rs#L923-L933",
+          "resource": "repo://src/app.rs#L1048-L1058",
           "version": "repo-lines-v1:sha256:3f82b1885fd37c0a7f8cddf8a1e57280241400b28724250c318d147c0b73a56a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4NDJlYTc1OTQ1NzNkNTFkY2MyY2YzMTUyNDRkODE2Nzc4NWQwYjA1ODUzZDc3NzAzMWQ4ODU5NmVmNzRlZTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY5OTI3ZWJiYWIzOWI4Zjc4ZGM3NzcyYTY3Y2YxNzhjZmY4Y2I5OTBiYWJlZjVjMTc1ZDQwNDRlZTllOWZmMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQzMzk2MDA5NzYxYzQ4YWY3MDRkZDRmZDQzNWM1NTNhOTg2ZGQ5ZWIzMmZiNTYyZmY0MGFkODI0NGIwMWU4ZDQifQ"
         }
       ]
@@ -221,7 +221,7 @@
       "statement": "Deleting a chat requires two Delete presses on the same chat in chat mode; confirmation removes the chat from the local history only, drops its pending reply, kept images and answered snapshot, and closes it if open; any other key cancels the pending deletion.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L854-L906",
+          "resource": "repo://src/app.rs#L979-L1031",
           "version": "repo-lines-v1:sha256:170c2b5d404120c0eeed964795203ed9134af4677755a22c29cc61b36babe747:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1NWJlYTQ4MGM0OWY0N2ZiZDg2MGY3ZThiMDIwMzNmYmZlNGU4ZDJjYmZmMWU4Y2Q3Mjk3NjY2YTc3NDI2ZjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI0MzQxZWJmNGViOTE5ZGQwMmUwYWE3YTVmNjM0MjdhNDI1OGQ4YzZkOTAwNDc4MWY3Zjc5YmI2ZTViNWUwZjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgyNTEwNTY0NjA2ZWRmNDUyODU1YTA4OTFlYmRlN2ZhOGZjYjIyMTBlODQyZjU4NDNjNzlhMmRmZDcyZjU5OTAifQ"
         }
       ]
@@ -231,18 +231,18 @@
       "statement": "toggle_global_autoreply refuses to enable without an AI client, persists the global flag in the history and updates the status; disabling is always allowed.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1052-L1069",
+          "resource": "repo://src/app.rs#L1186-L1203",
           "version": "repo-lines-v1:sha256:05cde78eab7927a8b5c33ce971f85c0e210ce60936eada88f8c9481585c1519e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhMjY2MjVlMDNiMmE0MDg1NDg0YjQ2NjUzOTllOWRmOWFhZDQzYmVkNGQxNWFkODEwODMzMGIyMDhjNmM5ZTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU2ZWQ1MmZlYjhjZTliMjNiYmNiYTkwMGFkYjk0MzE3MjA4ODNhZTk0ODY4NmQyNjhlNmRkMjFlNjljYTU3YjkifQ"
         }
       ]
     },
     {
       "id": "claim_5447e09be26b40afa6cf039ca7dee031",
-      "statement": "handle_web_request applies State, ToggleGlobal, ToggleChat or Quit and always replies with a WebState snapshot; a ToggleChat for a JID not in the panel's chat list only sets the status 'Chat non trovata'.",
+      "statement": "handle_web_request applies State, ToggleGlobal, ToggleChat, Relink or Quit and always replies with a WebState snapshot; a ToggleChat for a JID not in the panel's chat list only sets the status 'Chat non trovata'.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1009-L1050",
-          "version": "repo-lines-v1:sha256:7e50e3ca57a62654a61947e33ba08f34d79beff1c2e3df006a2210c439244f6a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3YTEzOTMxODdkZWNmZmM0NzMzYzYwY2FiYjA2MTkwYjdkYjAxMjJjYWEyYWM3YjRjNGUwNzBiNzIxNTk1NWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM2MGUyOWExOGZkNDE5NDVjMjM3NTgxMzM4ZGI3NWFjOTIzYzYxYjVmYmFiOTNlOTM3ODdlNWJmMGMyMjk4OTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJlODM0YjUzNzA2ODgzYmQzMmMxMmViNmQ0OGEyMmFjZjlkNTIyYzIzOWJlNTE5NDZmNzA5YjIxZDNkNjIwMzIifQ"
+          "resource": "repo://src/app.rs#L1139-L1169",
+          "version": "repo-lines-v1:sha256:64fdd9a3310b49def09cbd68a0103e07c41050b652d90eece3d0e2323f063a71:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3YTEzOTMxODdkZWNmZmM0NzMzYzYwY2FiYjA2MTkwYjdkYjAxMjJjYWEyYWM3YjRjNGUwNzBiNzIxNTk1NWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM2MGUyOWExOGZkNDE5NDVjMjM3NTgxMzM4ZGI3NWFjOTIzYzYxYjVmYmFiOTNlOTM3ODdlNWJmMGMyMjk4OTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVhOGY4ZTE1N2Y4ODViMmUzM2NmZjkyMzAzNTIyY2VlYzYzZTlhZDcwZmI5NDgyMjg2MGI4NmJiZmJlNjc2ZmEifQ"
         }
       ]
     },
@@ -251,7 +251,7 @@
       "statement": "web_chats exposes the TUI chat list in the same order, restricted to chats that support autoreply.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1418-L1432",
+          "resource": "repo://src/app.rs#L1571-L1585",
           "version": "repo-lines-v1:sha256:fef040ce667500067e70dc91e838a73c35752821bb041d4463b4361b4445b2c0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYxMTFhMDk4Y2E0M2I2Zjg1OTYwYTUxMjFiOGVmY2Y5MDVlZDZmNjdmZjE5MjY0NjcxZDhkYTFiYzhjZTEwNWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg5NGUwY2FkODFjNTBiOWY4MWE5ODI2ZjA1YzUyZjY0MGE0ZGM3NzdiYWQyNGM2MGEwMjQ5NzhlZWJmMWRjMGMifQ"
         }
       ]
@@ -261,11 +261,11 @@
       "statement": "When ImagesDescribed arrives, each description whose message still exists is stored in the history, failures set one status with their count and the first error, and the reply is started only when the target asks for it and the chat is still pending.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1321-L1324",
+          "resource": "repo://src/app.rs#L1462-L1465",
           "version": "repo-lines-v1:sha256:22bd4378ebfd0c259aac4089a03d61912cbb17abca836659a5d9c8fc9cde7b15:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjA0MjdkNzcxY2Q5ZTlmY2YxZTMyODc5YWNlMDkzZWY5ODk3NjRiZjE4ZjE3OWY2YzFkZGFjM2M3Nzc1MDMyMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYTQzMjhiMGQzNWY1YmVlOTEzODE3NWRlZTE2ZDRmNmZhM2FmMzc1NDM0ZmIwOTMwYWU4Nzk5MDg2OTk2MTgxYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTViMzliZDc5NmMyYjVjYThiYjhjMWEzNjUzODk5NzY5MGE0OTE5ZDY5NzVlNGMxMGZiMWE1ZGE3MGU1Mjc3OSJ9"
         },
         {
-          "resource": "repo://src/app.rs#L474-L497",
+          "resource": "repo://src/app.rs#L591-L614",
           "version": "repo-lines-v1:sha256:5736af4a9574d77087b2de7f56f7b77e2d96d8861d19b9c42159d91fa560f0f1:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJmNTc4ODJlNGRmYjJmODViYWQyOGRhYWU0Njc1ZjIwODYxYzc2N2E1ZjIxNzg3OTIyYzYyZDgxOGQ5YjJiYjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdjZWI1ZWFiOTZmZDM0MzEzNTI3ZjczMDk2ZjYwMWYzZDZiNWUxYmY1MzE3YTRmMzg0YWM0MTY4OWE0YWI1NTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBhN2YyNDA4YTExOTJiY2VmZTI0ODJhNTg2MGM1MTVhMmQ4NGIxMzQzZTE1ZDdmMGVmNGRhMTA0YzY1ZTJiM2EifQ"
         }
       ]
@@ -275,11 +275,11 @@
       "statement": "finish_reply clears the pending state and, if a newer message that passes should_schedule arrived after the answered one, schedules a new reply; otherwise it drops the kept images of the chat.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1312-L1314",
+          "resource": "repo://src/app.rs#L1453-L1455",
           "version": "repo-lines-v1:sha256:f837e4630ef0ee6fe89e07bf810f5851838ae48e227e0659b648d4ea480392fa:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTk5ZjgxMThmMmE5ODIzYzNmZDQwNDcyYjlhMmUwNDE1ZTRhZGQzMWQ5NTkwNjE0NzY3MzlhYjk4YWIyNTUxMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGQ4NDkzOWY4ODU5NjI0YmY1NGNiOTdjYWJjMDE2ZjRhYmY3OTU0YWUwNGU0MTVmODUzMjQ3ZTRmYWY2Zjc3ZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDlhNTk5ZGY5ZDUwOWVhMzBiNDMzZjkzYjY4ZjE4ZGQ1ODY2ZDVmMGJjMGMzMGEyMTU1OWFkN2U4MWM1ZmMwMCJ9"
         },
         {
-          "resource": "repo://src/app.rs#L505-L519",
+          "resource": "repo://src/app.rs#L622-L636",
           "version": "repo-lines-v1:sha256:084f8b11c8537716d5b5aa7dd03d18d8c925c2c8808ed3bd55605fac82c22a47:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmN2EzMzI2Mzk4MGZiOTQ4OGI5NDcyZTI4ZGU0NjE4ZWVjMzc3MTE2ZmFmZDViODZlNmUzYTZhZGVjYjM2YTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBiZTFkYWFmY2ViYWI1NjdjMWQwYjE3NmVjZmY3N2I5NGNmOTllYmRmYzI0MGZiMjFhYTRjNDIxYzBkYTRiNzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNmMGY4ZWU3NGMyYmNhMjZlNTlmY2NmYjUzZmQyYWI2OGJjNzIwZTY2MGZiZjM3M2IwZjFhNTY4ZmIyNjBjOTUifQ"
         }
       ]
@@ -289,7 +289,7 @@
       "statement": "spawn_ai_request awaits the request task in a second task so that AiReply is always emitted, even when the request task fails unexpectedly, and the chat never stays pending.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1218-L1229",
+          "resource": "repo://src/app.rs#L1359-L1370",
           "version": "repo-lines-v1:sha256:5c8be8846fd13dfd0c92193083af62f733a451585be051143592b7b033237591:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY2OWRlZjlmNmNmMDJlMjJjNGFhMmU4NTllMWY1MWZjYjdhOWUyYzc1NzRmZmFhY2IxNDNmOWIzZmUxMWY1ZmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0ZmM5YzZmMDQxZWE4YTkyNjlhMGIzYmYzMTJjNjYwYjk4OTEyNWI5MDhmN2I1NjE2Y2IwZmZmMjM0NTI2YjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5NTA2Nzc1NGYwNDQ1NDFmZmVlZDA3N2Y3OGMyZjg1ZTY3NjNiM2YzOTZkYjBlYTM5OTJmZDgxMmZjYWRmNDAifQ"
         }
       ]
@@ -299,7 +299,7 @@
       "statement": "When the model calls the no_reply tool, nothing is sent and the status shows 'AI: nessuna risposta necessaria'.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L695-L701",
+          "resource": "repo://src/app.rs#L812-L818",
           "version": "repo-lines-v1:sha256:f27e2fa9423ba6f3c9fb6381dd2ec60346c3c15ed204b861c320fac53ba2a473:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMWY1MGM3ZGZhNzg3MTk0NDViMjRiNDI2NjAyNjQxZDk0MmI1NjFmN2U1MmQ3Y2M2MGNkN2M5YzNiYjI2NWQzNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNzk0ZmJmY2NiNjQ2MWJhYjM5ZWE3YTVmZTJiZDJjMzU3YTQyY2I1NTUxYjE3NzJjNTc2YThjOTVjODkyMTY2ZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZGVkOTlkNjVkMzhkZjFiODYxZTExYTc1MzdlZDk4YWI1NjA5NzEyYTVhZjgzNjdlNWY1Y2FkNjM4MGYyNWZiMSJ9"
         }
       ]
@@ -309,18 +309,76 @@
       "statement": "In list focus PageUp/PageDown move the selection by LIST_PAGE_ROWS (10) rows and Home/End jump to the first/last row, clamped to the filtered list.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L31-L32",
+          "resource": "repo://src/app.rs#L33-L34",
           "version": "repo-lines-v1:sha256:7825efb1211acd5d025f243b3bb470aeb24c370f07856a47d71779b8de576ab3:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMTVlZWU2ODI2YzIzMDYxMTIxNjY4N2UzNjYyM2E1YjA0NThhMGRjNGFjMGIxOTk3ZDZjYmVlNDNhZGJkMDgyYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNGEwYTBlYmUxMGIzNDk4NjVhMzlmY2Y3ZjJlMzY5MDM2MTA2NjdiNWQwYmU5MGU2NWZhYzUwNGJiNmZkNTM5MyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmZiMmY1ZDFiZmZhMTlmNWYxODFhNjBkYTg2NWU5YmY0ODk4MDBkYjUzNDgwMjZjNjE4MGJmNjBkZDk4MjhmZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZjM1MGFjYmZhYzdlNDEzZmFkNWYxMzk0NTQwYWE3MWM3YWY3ZWZmMDA4MDliYmMxYTlhNzQxZGRhNDFiOGYwZCJ9"
         },
         {
-          "resource": "repo://src/app.rs#L812-L839",
+          "resource": "repo://src/app.rs#L937-L964",
           "version": "repo-lines-v1:sha256:acf06960921a82cc9b2dfbc4cd8be73f5ce9d8945326f358e29f727a80dd79f0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExNTZkYzNmM2MyODVjOTI5NmU1NWNlY2FiOGQzMzczNzg1YTgyOTVkYTZkZjU4Nzk1ZDUyNTZlNDc5MjRiMzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU3NmVkYmFmNjIyMDAzYjdkMGYzOWE0YjE1ZGNkZjJmYTQ0NGNhY2Q5ZWRlMWRmNjc2OGI0ZWZiMzQ3OTVjNGIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFlYjhjMzVjMzllMzMxMTJhMGUzZTVlMDg1NjlmOWVkYWJkNDM5ZmNkM2FkZmVkNjQ5MzAwNjcyZWRiYjYwNjEifQ"
         }
       ]
+    },
+    {
+      "id": "claim_44fc6d9b1b484218b8a5d1b81c0a89f4",
+      "statement": "F3 toggles the autoreply of the selected row of the filtered list when the list has focus and of the open chat when the input has focus.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L1101-L1116",
+          "version": "repo-lines-v1:sha256:f3a02114e65f3b57e1e9609cdb5227848e7d4a8d0d2d3abc6b38c40f5286461f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjMWZhZjI0NjQzNTZlZjY0NzAyNjI4YzM0MGYyOGNhZDljMjY3OGYxMTViNTAxNDZkMGMyNTBmNzhmZDk2NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkzNzNhMTgxNzI2YzQ3ZjdjNzlmOTI3ZDI2NzhhYzQ3NjYzY2VhMzI1OGYxOGEwNTM0ZTg2NDVlYmFlOTlkMGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc1YjdhNGU5MWJmNTc3ZDQwNjIyM2IzMjg2ZTFhYmI0NGRkNzUwYmViNmU1MzIwYTMyMGZlZDJkOTBjOWQ3ZDQifQ"
+        },
+        {
+          "resource": "repo://src/app.rs#L1221-L1232",
+          "version": "repo-lines-v1:sha256:b46e0c6074aa70a6a18b87a08237023dd69de8b989ccfb3ce3c5796148164d3b:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZGFkMjlkZWMwNzg5N2ZlNTVmMTZlNGRiNTEzOWEwMzEyZmU3M2M5Mzc5OTk0NTU5ZDg2ZmQyNjVlZWU2NjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNjYzVlMzg4ZThmOTY0ZGY1OTFjZDM1Y2FlZjk5ODM0MmRjNTllNGU5OTMwMmMyYmI0MjMyZmMxMzdhM2Q1ZjkifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_36c6c5a030ac405b87bbe3051c9b7d52",
+      "statement": "A relink needs two F8 presses: the first sets pending_relink and asks for confirmation, the second starts it; start_relink is ignored while a relink is running, otherwise it switches to the Connecting screen, clears the QR, resets own_jids_known and spawns a task running WaHandle::relink that sends AppEvent::Relinked.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L326-L381",
+          "version": "repo-lines-v1:sha256:1874e8b5ab58c3a5e271332f88ac81403b0acf03851aaef445bbd477d21f76c4:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIxZGUyN2ZmNmY2ZmRjOWQxMWZhMDVjOGM5ZTVjNmI1NTc2NjU3NDhhMWRmM2I3YzFkOTVhZTQwNDY3MmJkNDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM3ZWE1ZDExNGEyZjkyZThlZjQwMzQxN2JhMDkxZWRjODhiNTYwODM5OWY2YTFiNmM0ZWVmNTAzZmI1ZTdjZjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBlZjIyYTA2NDVkNDAwY2EyNWVmYzI2Mjg2ZTdjMDZmOWUzZmZhZmQxY2UwMzVjMmZjOTkyYWZhNGI2NGIxY2EifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_67ccf2b70e024c5a8241c0732c7e754c",
+      "statement": "finish_relink clears the relinking flag and shows either 'Sessione cancellata: scansiona il nuovo QR' or 'Nuovo abbinamento fallito: <error>'; the screen then follows the new session's events.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L343-L354",
+          "version": "repo-lines-v1:sha256:741e9b7ade558dddd72ab34aa017552f645c8cc476274a1266dc2b4f27a5860d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYzZjY3YjMxMGI5MjEzMzcwMjdhMWY2YzdiYjA0YjkzNDM5MWZjOWM1ZDAzYTJlNjY1NWI2ZmVjNDE4ZjljMjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEyY2MwZDQ0ZGRlMzkxMDE4YTQ1OWMwNmIwOTBlZWVjOGRmNTA0MTg4Mjk3MzIwMmQwYTc2NWZjOTM1NjRjYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU1ZTg0YjQyYzc5NDMxZTNkOWI4Mjk4MGQwN2Y5NWQxMTBiYjQ1ZGUxZTI0NjlkYjcxMDJlNzJmODRiM2FjOWIifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_41ee5980f5b144f8a6984144ec65e736",
+      "statement": "apply_reloaded_config keeps the current configuration and shows 'Configurazione non ricaricata: <error>' on a load error; on success it replaces config, rebuilds the AI client with build_ai (running replies keep their previous client) and shows 'Configurazione ricaricata' or the build_ai status.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L383-L400",
+          "version": "repo-lines-v1:sha256:11076b2b498b6d19b7a0629886f2cdb37229105049de848490f4e1d9faead710:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVhYTEzNjY2OGNiMzc5NzgyYTUwMTY4ZDNlOWM4Njg4MmM5YTBhZTAxOGQwNTZiNTEzZWU2ODA5OWYxNjc3YzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVmMjAxZDg2ZGM3NzY0YWFmOTA5MGNhNDRkODcxNDM3Y2JiODk4ZGE5ZWJlMGU0ZDhlNjVlNDZiZDJkZGVkYjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyNjE2ZDk0ZDJmZDg4NWViMDZhMGFmZjVkYjQ1YWUyOTdiMWQ5NzViYzU0MjRlZGIyYWUwZmE2NzZkMzE0ZTYifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_1e2cad36bb074b0d9d9976128dd97003",
+      "statement": "web_state returns the global flag, the toggleable chats, the status line, the current screen named connecting/pairing/main and, while a QR payload is stored, the QR rendered as SVG.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L1171-L1184",
+          "version": "repo-lines-v1:sha256:643e53e7eee86239879c37d679254899bc317cdbf3cd55b8314e04ba1657043c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIzN2M0MzFhZmQ5NGMyNzU0NzJkNjliODNlMGM0MmRmMjU0NmQ2MDQ3NmM4ODg3Yzk3MDRmZWM2Y2U0MDkyMDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRkZDFkZTJkMmJiNWE5ODAxZDE4MTAyYTQwYzlmOTc4NjRkNjNkYmEzMzkzMzdiZDVlYTU3Y2FiOGU4YTYwYzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJlODM0YjUzNzA2ODgzYmQzMmMxMmViNmQ0OGEyMmFjZjlkNTIyYzIzOWJlNTE5NDZmNzA5YjIxZDNkNjIwMzIifQ"
+        },
+        {
+          "resource": "repo://src/app.rs#L1563-L1569",
+          "version": "repo-lines-v1:sha256:aeccbfa28dab16bc5a14f9e771c54b3c780209ad3837bba0672ad94aaf49bd5a:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjYzMWVjNWFhMWFjMTI1MDk0ODNlODM1MTA5ZDBkNjMwMDY0YTBhYmU5MjU5NGUzOWUxZjUwOGIxZDUxNzVjNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYWNjYzYzZTY1NWZjNmMxODhkMDhhMzMyMTUyYzY1MTZiNzVlZDdkNTIzYmJmMGFhMTg2MzBkMTRkMjA0NDczOSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDQwM2Q5ZmY0OGI3ZmI2ZDcyM2RlNWM5YjMxODkzMTNlMjQ2NzBhMWE5NjJhYWUxZTYxODRhMjllZmM3MWM1ZCJ9"
+        }
+      ]
     }
   ],
   "verification": {
     "by": "openwiki/0.7.2",
-    "at": "2026-10-21T21:49:12.291Z"
+    "at": "2026-10-23T21:26:24.156Z"
   }
 }

+ 32 - 22
openwiki/.claims/architecture/runtime-and-event-loop.json

@@ -1,14 +1,14 @@
 {
   "schemaVersion": 1,
-  "pageVersion": "sha256:3abe82512bfb153e658472ff7afa50967adbaa76f4efa6bb2af3215c6b83795a",
+  "pageVersion": "sha256:e2f63ba48d127d1aaa5a3dc9ea68ed4b0137c88abb763235e4ed0f305b984766",
   "claims": [
     {
       "id": "claim_a384a56f0ebf4add8c0ab7722b040888",
-      "statement": "Unless a version flag is given, main loads the config, ensures the data dir, opens the history DB, creates AppEvent and WaEvent unbounded channels, starts WhatsApp, spawns the forwarder, starts the optional web panel, builds App, spawns the input thread and runs the TUI.",
+      "statement": "Unless a version flag is given, main loads the config, ensures the data dir, opens the history DB, creates AppEvent and WaEvent unbounded channels, starts WhatsApp, spawns the forwarder, starts the optional web panel, spawns the configuration watcher, builds App, spawns the input thread and runs the TUI.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L36-L64",
-          "version": "repo-lines-v1:sha256:2f5e5e305de372b540e8f2ab32af7faeb163b55a1cea3b4f31696097c921a6a7:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExZDA2ZTA0OGNkMjcyNzM4ZDgzN2IwYzBlYzZhYWIwYjAzMTQ1N2U0NzVmZDQ5YWJiY2JhZmM1ZjczYzAyMGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk3YjIxYjgxYWE2YTcxZmY5NGYxNmE2NGVhMThkMjlkODhiYTE5YzkyYjM2MjUzMDhlNmU4NDM2MTkyNzM0MmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAxZmI2ZDM1MThiYTRjMzUwOWRiMWM5MTAyMTk2NjQwMzI4ZjgzM2M3MDJmMzgwNmNmZjFlZGI5MzU4Y2Q5YTgifQ"
+          "resource": "repo://src/main.rs#L42-L71",
+          "version": "repo-lines-v1:sha256:1f8efd5e596ab7a7d1da0bfd9bc62005e6be9c40accb54842253ec733e4abfe3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExZDA2ZTA0OGNkMjcyNzM4ZDgzN2IwYzBlYzZhYWIwYjAzMTQ1N2U0NzVmZDQ5YWJiY2JhZmM1ZjczYzAyMGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE5NjlkZGFlYmE5MjRhNThlYTY2OGUyMDQzMDFiZWE5ZThjMzZjNjg5NTU3NTc3NmYzZDkzZWNiZmJlNzdjMzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAxZmI2ZDM1MThiYTRjMzUwOWRiMWM5MTAyMTk2NjQwMzI4ZjgzM2M3MDJmMzgwNmNmZjFlZGI5MzU4Y2Q5YTgifQ"
         }
       ]
     },
@@ -17,7 +17,7 @@
       "statement": "The WhatsApp forwarder wraps each WaEvent in AppEvent::Wa and stops when a channel side closes.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L74-L86",
+          "resource": "repo://src/main.rs#L81-L93",
           "version": "repo-lines-v1:sha256:376729d914bfe5462e810010c4e827fb7fc331182e173bf42c14ec815be9c5eb:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRhNjQ2NjQ1ZDg1OTU4N2VjNWRjNmMyZWVjMzJhNjFkODI1ZDVmZjM0MWQzMmVlMmRhNGNhMWQwNzYzNmQ3ZjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFhOGNhMzU1ZmQ1NzA2YTk1OTI3N2EwNDFlM2IxNTIzM2I4YzI0M2M0Njc4MGM4M2Y1ZjkyY2M2ZTU0MjhjN2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNiMWI0ZGU5MmNiYmMwMmUxN2UyYWJmYjc3OGQyZDA0MjJmNjUzODM5YWZiNWE2MTdmNDhjNmE4MTBiZGU3YzIifQ"
         }
       ]
@@ -27,11 +27,11 @@
       "statement": "Terminal input is read on a dedicated OS thread named wh-input; a read error is reported as WaEvent::Error and stops the thread.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L23-L23",
+          "resource": "repo://src/main.rs#L29-L29",
           "version": "repo-lines-v1:sha256:1d14980ebaa2f31e4144fe6b04613dfc3285892428a59dc526be0808a55c1eff:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMWQxNDk4MGViYWEyZjMxZTQxNDRmZTZiMDQ2MTNkZmMzMjg1ODkyNDI4YTU5ZGM1MjZiZTA4MDhhNTVjMWVmZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWQxNDk4MGViYWEyZjMxZTQxNDRmZTZiMDQ2MTNkZmMzMjg1ODkyNDI4YTU5ZGM1MjZiZTA4MDhhNTVjMWVmZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDZmZmE0OWU5ZTQyNmZkYjkwM2I2ZmIyNjYxZDdjZDEwZmM1OTljMjFjZjg2ZDMzMWFlNjY4NDBhMzUwZGNkMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODMyYWE3YjFiZGM3MTY1MmFjMmUwODIxNWJkOTcxYmQ3MGJjMjRjNzQyYWVlYzA5NDA4YjYzMTE3ZDkzMmQyMSJ9"
         },
         {
-          "resource": "repo://src/main.rs#L99-L134",
+          "resource": "repo://src/main.rs#L106-L141",
           "version": "repo-lines-v1:sha256:58d18a88c972035530c97c24224afd69709cd2c5de07f4236969a3cc38d1fa8e:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjliZmVlNTkyZTcyMzMwNTAzZGVmODM4ZWFjZTE0ODNjMTFmNGVkOTk2OTM3YzlhODA3OGM1OGE4MTljYWVmNzUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg0NTAxNDAzZmViNmI2ODk4YjM1MDcxOGQ4MmY3ZmU0YzM4YzBhNTljMjZkY2UzMTRjMzUyMDQ3OTcyYTk4NDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImViNTc2ZTc2ZWM5NDRlZjNmY2IzMGZmZDlkYTJiOTU5NzNiNDUyYTdkYTQ4NjM5ODczNjM1MDExOGQ2MDU3ZDAifQ"
         }
       ]
@@ -41,18 +41,18 @@
       "statement": "Only key press events become AppEvent::Key and resize events become AppEvent::Redraw; other terminal events are dropped.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L136-L147",
+          "resource": "repo://src/main.rs#L143-L154",
           "version": "repo-lines-v1:sha256:d8d9638a5e048622a2f5afb4eb87071516bfc768606487e27c53c03eb11ffa4f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJjNjVkZTMxODE3ZDgzNDIyNDg1NTNiNmZkZTAzN2M1ODI3YTRjOWI1NzA4ODAxMzc4NzYxNWM1NWMwNmU4ZDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY0MWNiMWQ2MWJiYTdkZGQ5ZmJiOGEyOTFmODA3ZjA1YzY2NzY5MjM2NjNkYzNiM2E3MjlkNzMyODQ1ZDQ4Y2QifQ"
         }
       ]
     },
     {
       "id": "claim_64e36e0f00d545d48fc16d79cfaf28dd",
-      "statement": "run_tui always restores the terminal after the event loop returns.",
+      "statement": "run_tui clears the screen and always restores the terminal after the event loop returns, returning the loop error first and otherwise the clear error.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L149-L158",
-          "version": "repo-lines-v1:sha256:8bb99c5e51b2b1b83667961939978ee4834cb3bce0ba00fe956af3a2d48a09d5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU4NjMxYjQ1YTE1ODQ4ZjQ5YTJhYWQyYTMzNDdiZWRkOWZkZGNmNDBhNTM5MTkwMDkzNDk1OTdhNzYwOWYzMmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVkMjRmOWNhMzgxM2IwMmQ1ZGNhODJmNGU3NzUzNDg3OWRhYTNhNDI4ZjJjMzM5YjI1ODU4MjU5NDE4NTYyOGYifQ"
+          "resource": "repo://src/main.rs#L156-L175",
+          "version": "repo-lines-v1:sha256:d25ee631a872cf7bd8251dbd7e2ef5309b07a890ee5181e193fc4b0c81632aa3:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU4NjMxYjQ1YTE1ODQ4ZjQ5YTJhYWQyYTMzNDdiZWRkOWZkZGNmNDBhNTM5MTkwMDkzNDk1OTdhNzYwOWYzMmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVkMjRmOWNhMzgxM2IwMmQ1ZGNhODJmNGU3NzUzNDg3OWRhYTNhNDI4ZjJjMzM5YjI1ODU4MjU5NDE4NTYyOGYifQ"
         }
       ]
     },
@@ -61,18 +61,18 @@
       "statement": "The event loop draws a frame, awaits the next event and dispatches it to App until should_quit is set or the channel closes.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L160-L184",
-          "version": "repo-lines-v1:sha256:c813b17a84a3084eefaa34b8e9284507be8cfac301dd0b8abab485d88d66204a:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNlMTkzYjAzYzI2ZWIzM2EwMjk4MWViY2MyNmM2YzIxNmRlZGQzYTkxMDQ5OTA0ODI5ZjJmNDQ0ZTVmYWYyODUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzYTEwMzdmMDcxOTYxZmIzYjEzYTdkYWZmNjlhY2MyYmFiODEwZmU4YmRhMTZhMmIyMjcxNTU0MzcxNGNjNzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ"
+          "resource": "repo://src/main.rs#L177-L205",
+          "version": "repo-lines-v1:sha256:fe54d25939a4f3603d298eaff2c9a5760f9ae544a1c95627fcbdba35bd563e7f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNlMTkzYjAzYzI2ZWIzM2EwMjk4MWViY2MyNmM2YzIxNmRlZGQzYTkxMDQ5OTA0ODI5ZjJmNDQ0ZTVmYWYyODUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmMmQxOTA3Mzg2ZmZhYmYyMDQzMjNkMGMwMDIyMDE0MTdhNjUwN2IwNGRjYzZlODdjZjFhNDdhZGExNDc0MTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ"
         }
       ]
     },
     {
       "id": "claim_8349159e23ea452480265bcada84d14c",
-      "statement": "event_loop records list_mode_before = app.list_mode before handle_event and calls terminal.clear() when the list mode changed, forcing a full repaint so wide glyphs from the previous list leave no stale cells.",
+      "statement": "event_loop records list_mode_before = app.list_mode before handle_event and, when the list mode changed, resizes the terminal to its current size to force a full repaint without the cursor-position query that Terminal::clear performs.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L160-L184",
-          "version": "repo-lines-v1:sha256:c813b17a84a3084eefaa34b8e9284507be8cfac301dd0b8abab485d88d66204a:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNlMTkzYjAzYzI2ZWIzM2EwMjk4MWViY2MyNmM2YzIxNmRlZGQzYTkxMDQ5OTA0ODI5ZjJmNDQ0ZTVmYWYyODUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzYTEwMzdmMDcxOTYxZmIzYjEzYTdkYWZmNjlhY2MyYmFiODEwZmU4YmRhMTZhMmIyMjcxNTU0MzcxNGNjNzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ"
+          "resource": "repo://src/main.rs#L194-L202",
+          "version": "repo-lines-v1:sha256:d5d2f63f2332914a0e3f33f7bb238c97b54971bd35773182f767422e64442902:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTgwZmU3NzZjMzc0ZWExM2RjYmU5ODhjZjFiMmY0NTQ0MTdkZjA4MGJiNWY4M2Q1N2FhMmRkMmI4YmZiYjA2ZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiODQzMTkxYmFiNjEwYjJjNDZkODgxMjkzMTJhYzQ0ZjUwNjVlODdiYWVjOTVmODg2MDg0YzM2Yzk5ZDg3YTg0NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTVhYjdiNmQ5YjZlMTk1NjUzZmM4YjU1ZjYzZjM2NDYwNjg1NWM0NTRmYzU2NTg1MWYzMWEyZjA5NDVkYTc0MiJ9"
         }
       ]
     },
@@ -81,7 +81,7 @@
       "statement": "start_web_panel does nothing when web_port is unset; otherwise it starts the web server on that port and a bind or spawn failure aborts startup with 'cannot start web server on port <port>'.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L88-L97",
+          "resource": "repo://src/main.rs#L95-L104",
           "version": "repo-lines-v1:sha256:54c6448bd45241fd4b90479b1a91e2fd528e7a38f4ac5d543eda728fbf3756d5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwZjA3MmY1NzA4NTRiMGM0ODg3YmRlZDU4YzU2NWRiZGExOTNlYmY3NGIwODZmMjYyNTFmZjZhOTZiMmJiYWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRmYWI3MTBkZGY3ODU5ZTU3NTc2NGE5MTNhMDAyYzc0NTg1ZmZhNzAwMTUxMGNiODgxMWRhODNkMmNmNzRiMzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI1OTAxZTAwNGRkYTg1ZmUwNjg0OGVkYjhkMWIzM2Q1NTNmYWM4ZjkyMzZlZTFlMjlkZTNmNGUzMWRmNTM4NTkifQ"
         }
       ]
@@ -91,11 +91,11 @@
       "statement": "With a --version or -V argument main only prints 'wh <BUILD_VERSION>' and exits before loading the configuration.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L24-L41",
-          "version": "repo-lines-v1:sha256:ce72f2efa3e3637cbeac509d5672e3b6228a19b2d405cc1402d7f536c98d46dd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzYTMwMjZjMmE4MzdhNTVhMTBlMmU2OTI4NmY3MTVjZDljNGMwNTBiOTJjZmE0MTg0M2YzNTk0OTA4MDgyNGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE4MjYzNjdkZjY0MGI1YzFkZTYyYjFlNDdkZTc4ZGMxMmY2Y2YxNDAyMTI5OTllOTg2YWI1MDVmYmMyYjZkYjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEzNGZhODM0YjVmMWVkZTIwMmNmNGQ0OWVjNDc0NDI0ZTcxNjMxYTJmNzFkOTUzYjQzMjRiODY4NWEzMGQzYTcifQ"
+          "resource": "repo://src/main.rs#L30-L48",
+          "version": "repo-lines-v1:sha256:075df10ec41f8312822982917d8a541ae8fb012a1de865cb86b483225cc1ac34:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzYTMwMjZjMmE4MzdhNTVhMTBlMmU2OTI4NmY3MTVjZDljNGMwNTBiOTJjZmE0MTg0M2YzNTk0OTA4MDgyNGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY2YmM5OTMzNjdjNWY3OGU1ZDZjZjFjY2U0MmMyOTlkMWUxNTQ2MzM1MGU3Mjk1NGIwMDczMGNiMTUzOGZkMjIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE4MjYzNjdkZjY0MGI1YzFkZTYyYjFlNDdkZTc4ZGMxMmY2Y2YxNDAyMTI5OTllOTg2YWI1MDVmYmMyYjZkYjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEyZmFlMzljMjVjZmVlYmZkMTU0MTk0YzA5NjEzZGIzNmY2MmU5YzcwMWNhZGMwMWE4ZDcwMzhmZmIyNjE2OWUifQ"
         },
         {
-          "resource": "repo://src/main.rs#L66-L72",
+          "resource": "repo://src/main.rs#L73-L79",
           "version": "repo-lines-v1:sha256:c7d36e35db0f27324cf4631b197cf7332f8de7e7002d977fead70ef0f6213c51:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjMwMTlkMzNiYmQ2ZmY5MGU3MWNhNWQyYTA0NTI3ZDNhMzg3MzVlM2FhYjA5NjQzMDk5MWVhNDNhZDNjOTVmMyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGI3MGY3Nzk4YzAxYzhmOTQwNTBiZTU0MTQwMWI4MzQ3YWY2MDM2MjdkZDExODBiYjIzZjAxMWZmNjQ1NDhjZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzI4Y2MwYjRkYjM2MWFjMzEwNzhiYzE5NmJkNjUyOTEzNWE2YWZjNDVlY2FlOTIxZjYxNzEyMjVlMmY0Y2Q5OCJ9"
         }
       ]
@@ -109,14 +109,24 @@
           "version": "repo-lines-v1:sha256:862b7d4a061487e5c9a432ace9cadefe3bc590f6eb970126321e7e932fdd4b65:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxNjZlNzY2Mzg4OTM3MmMxNDVhMDYwZDY2Njg0ZTliMGJmYTU5ZjBmN2MzMmRiOGUyMGZiYWI5MWY2MmFlZTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ"
         },
         {
-          "resource": "repo://src/main.rs#L24-L25",
+          "resource": "repo://src/main.rs#L30-L31",
           "version": "repo-lines-v1:sha256:0cf641f44d6658b7b5f5b7157c9d533ac928fa26293fd9f090fcaf80de1ad016:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZTNhMzAyNmMyYTgzN2E1NWExMGUyZTY5Mjg2ZjcxNWNkOWM0YzA1MGI5MmNmYTQxODQzZjM1OTQ5MDgwODI0ZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNmQ0OWQyMmQ3NGM5NWQzZDFmMjJjNGNkN2JmNDA1ZGYxNWU2NTcxNDBjMGMwMWFjNDUzZmEwNjQxOTAzYWRhMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYTgyNjM2N2RmNjQwYjVjMWRlNjJiMWU0N2RlNzhkYzEyZjZjZjE0MDIxMjk5OWU5ODZhYjUwNWZiYzJiNmRiMiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNWEyMmU1NDJiM2M2MWYzMjAzNTI1OTYwZGFhYTNmMjA4NGYxNTkxNDI4OTNhZDhhMjdhZDE3M2QzZWQ0OTRkNSJ9"
         }
       ]
+    },
+    {
+      "id": "claim_9d5addff34a746c7af963b58ee12e992",
+      "statement": "The configuration watcher task polls the modification times of the configuration files every 2 seconds and, on any change including a file appearing or disappearing, reloads Config and sends AppEvent::ConfigReloaded; it stops when the application receiver is dropped.",
+      "evidence": [
+        {
+          "resource": "repo://src/config_watch.rs#L15-L52",
+          "version": "repo-lines-v1:sha256:1501e5e6ec52e197ae84506acd57d2b315d6a1dd434c842b88dc4a4b6d2f1bd3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3NGMwOGQzMzg5NGUyMWQyYzBmNTMyMDkwYmQwNjFjNDNjMzg1ZDU1MzkwMmI0MDkwZDkxZjJjZDY3M2VjMWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE1Y2VkZDkyMzY4ZGI1MDMwYjUxMjQ3ODcwODlmY2FmZTI5YzhmOGQ4MjExZDA0MTE4ZmM4MTViYWM1MDBjOWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ"
+        }
+      ]
     }
   ],
   "verification": {
     "by": "openwiki/0.7.2",
-    "at": "2026-10-21T21:49:12.291Z"
+    "at": "2026-10-23T21:26:24.156Z"
   }
 }

+ 38 - 24
openwiki/.claims/architecture/terminal-ui.json

@@ -1,13 +1,13 @@
 {
   "schemaVersion": 1,
-  "pageVersion": "sha256:d47df0cc7d426e68c11679d15aeb6042a2f2f7ca975321194f44268297b7bc05",
+  "pageVersion": "sha256:63e03e3e582fd585379805751a335bfe7b208a8a64451a79c8c177698950d3a0",
   "claims": [
     {
       "id": "claim_7b6015e24275488a85ba88dd065f2ff8",
       "statement": "render dispatches on app.screen to the connecting, pairing or main screen renderer.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L107-L114",
+          "resource": "repo://src/ui.rs#L109-L116",
           "version": "repo-lines-v1:sha256:44f3697b4f82d6e56108d5b16d3fe4449dc6bc389fe30877ba38781793fb8507:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzg4NTBjODgzNDEyNTY0NDAxZDdlYmI3MWFkMDQ5NzVlMzc2NTA1NjZlNmZlMTU5ODNiZWYzOGVhNGFhMjdhMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjE5YzdkZDRkNTVhMjc2YTc0ZTU0NzA4ODQ0MzQ0MjNlY2JmOGU2OTA5MWNkMjM2ODQ3YTQ5ZTZkNDlmYzAwNyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZmI5MWJlYzI2OWYxNGRiOGY2OGJmZjhjMTY2ODA4ODI2MDc0YjdmOGFjYmI0NWMzZDA0NTg4YTYzZTFlY2FhNyJ9"
         }
       ]
@@ -17,22 +17,22 @@
       "statement": "The QR code is rendered with Dense1x2 half blocks, inverted colors and a quiet zone.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L192-L201",
+          "resource": "repo://src/ui.rs#L196-L205",
           "version": "repo-lines-v1:sha256:e126cd7576186dd9f80551b4f1e1c261f754331d72094cfe860fd19a77e2dfc8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiM2MzZDFlMjNmYmFlNGQ1ZTA1NzMzMjEyNGEyZTk2MjM1NjNhMzBkMzZhYjA0YjljNzFlY2M0YmY2Mjg1OWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMyOWIxMTIwYWEyMmQwM2YwMzBmMjY0NGM3MzZjYWE0ZTZmMzQ2YThiOTBmMDNjNjFhNmE0YzU3YzE5NGMyNDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEyYjI2MDBiMDc3ZWY2NGJmYTgyMGQ3OTc2NTAzMzFlNzI5YWRiMDdhMjhhYTY3OGE3ODQ1YmVlYjExMTNhMmYifQ"
         }
       ]
     },
     {
       "id": "claim_b1b936544af9430ba88b4e8f744f148f",
-      "statement": "qr_block_lines shows a waiting, encoding-error or terminal-too-small message instead of the QR when needed.",
+      "statement": "qr_block_lines shows a waiting, encoding-error or terminal-too-small message instead of the QR when needed, reserving PAIRING_TEXT_ROWS = 6 rows for the other pairing screen lines.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L171-L185",
-          "version": "repo-lines-v1:sha256:f70009d551400b4459543b770a01bcb4ca2cfec3c41a6bd11efe47faa57a3e59:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlkZDQyYTM5MDMyYmQ1ODhiZmNiMTM5NjgyMGY3NmI0ZmIyMmNhMDY3YWU2OGNkZDdjYzhmNjZiZjgzMjMzNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkzMzJjMDRlNjVmOTMyMGRkNzNjY2VlM2IxOTBiZDZkNThmYmE3ZjhmZTEyNzg4MDA3MmRiNmM5YTMwZjg0ZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNlYmI2MGRjYjRjZTI4NzBjZWM3ZGE2YTk5YmI4MDk5NGIyYWIzYWQzZWI5NTc3ZTE2MDFmYjU0NzI3NjZlOWIifQ"
+          "resource": "repo://src/ui.rs#L171-L189",
+          "version": "repo-lines-v1:sha256:901c0f35f9f3bde71cdab5ca8d0e70c30ca4ce67e307e7e3ce30dd88c2e3fbf9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjOGRiMTNkNzViMzQyZDExZDY0ZmEwNzg3MDFmNzYyODA0YTM3YjRjZDI5YzFiOTIyNGI0ZDEwODkwMzJmNjIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OTM3Njc5MWU2OTQ3NmY3YTA3MTg5NmRjMjViOTEzN2VmODQyM2UzZTk2MzQwY2NmYWMzMzIzZDQyY2E1MWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNlYmI2MGRjYjRjZTI4NzBjZWM3ZGE2YTk5YmI4MDk5NGIyYWIzYWQzZWI5NTc3ZTE2MDFmYjU0NzI3NjZlOWIifQ"
         },
         {
-          "resource": "repo://src/ui.rs#L75-L81",
-          "version": "repo-lines-v1:sha256:58a07a9e2caaa4d0fabbb6bb1f5c5dd0d18f3dc926d96b302fa8bdfc5e33644c:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODY5ZTQzNDAyYjczM2JjYTlhOTNhY2EwNjg5ZGQ2ZjkxZjg1M2QwM2E4MDFlMDdlZDMwZGZhYmRkNGM5MDZjYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNGE0ZjM4NDMzZDI5ZmNhNjFmNjJiZTc3NmU2ZGEwNDVhMDZlYjQ0MGI5MzQ5ZmQxYTQ5MjY0ZTgxMTY5ZGMwOCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiM2IzMWVkYmM1ZjI0MWZkZDJjMzA5MThkYmIwODE4YWFjYjRkM2FjZmI2NzgyMjRkYmQxYzBiOGI3ZTU0ZDcxMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODAyNDcyZjBlOGYyYTA1MjI0NGMzYTAzNGU2MGYwOGZkZGNkMjVmYmRmYTg1NmI1NDgxMjQxYTAwYWE2NzU0ZCJ9"
+          "resource": "repo://src/ui.rs#L75-L83",
+          "version": "repo-lines-v1:sha256:9a7bc406d13fbfacf35cdca1871d73120e0b3affae1156398765a95e61d28b33:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODY5ZTQzNDAyYjczM2JjYTlhOTNhY2EwNjg5ZGQ2ZjkxZjg1M2QwM2E4MDFlMDdlZDMwZGZhYmRkNGM5MDZjYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTVlMTc3OTA5ZTVjOGY3NmQ0NWUxMTkwNjNmNTA3MzQ1MDdhOGU1MzRiNDhlNTc1OTJiZTgzYThjYWRiY2U5OSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiM2IzMWVkYmM1ZjI0MWZkZDJjMzA5MThkYmIwODE4YWFjYjRkM2FjZmI2NzgyMjRkYmQxYzBiOGI3ZTU0ZDcxMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODAyNDcyZjBlOGYyYTA1MjI0NGMzYTAzNGU2MGYwOGZkZGNkMjVmYmRmYTg1NmI1NDgxMjQxYTAwYWE2NzU0ZCJ9"
         }
       ]
     },
@@ -41,7 +41,7 @@
       "statement": "The main screen splits into a 30% list pane, a messages pane, a 3-row input pane and a 1-row footer.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L207-L222",
+          "resource": "repo://src/ui.rs#L211-L226",
           "version": "repo-lines-v1:sha256:c7b573159910f69890eba199ba66af477c525be3a8732bcbcd2f9ba7be836466:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM1NWUwYzhkYjY0N2Q0ZDUwZmM0MWY5Mzk1NjgxMmM5OTk4ODQ3NzZmYzVmMjZiOTM1Zjc1MDA1YjQ3M2U1YmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUxODU3ZjE2ZjQ5MDJjOWNhZWM0MDU1NTgxYWI4ZWJiZjFhYzU0NGU3OTMxMjM2ZWY1NDAxOWVkYTkyMDlhNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk2YmI0MWI1YThhOTczMmVhYjg5MTA4MWE4MDRmYjU2ZjEwNGRiYzAzNDVhYzAwOWQ4MzdhYzhiZWVhN2FjMzgifQ"
         },
         {
@@ -55,11 +55,11 @@
       "statement": "The list pane shows the filtered chats or contacts, highlights the clamped selection and marks chats with per-chat autoreply with a green AI marker.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L242-L261",
+          "resource": "repo://src/ui.rs#L246-L265",
           "version": "repo-lines-v1:sha256:732f44ba82916dfa1b3ddde98a73ecbac245a78006147401460b3c1aa17b9b15:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVhYWI1NDY2NTFmMTk2NDNiOTg2MGQzNjBkMmYyZGE2YmIxNTMyYzAzZGRkMzIxNGI3YTg5ZWFiOTFmNjgyZDQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjliZDEwMzhkZWZmODFiYWFlZjU0N2JjMGI2NTk5ODc3YTc5NjhhOWRiZDBkMWM5YzZkODFjOTBhMTJkM2UzM2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJkYjk3MWM3OTZlNTNmZTE5ZDE3OTc4OWIwMDRlZDEzZTdhOWEzN2VhOGNkNGYwNmQ3NzgxNzQ0NmUxNDA5NzYifQ"
         },
         {
-          "resource": "repo://src/ui.rs#L283-L298",
+          "resource": "repo://src/ui.rs#L287-L302",
           "version": "repo-lines-v1:sha256:81e36f0dbffed3b264bf79086b294804a0e2423387d63ef243ec387746d2b84c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBlN2Q5YjRmZmQ1M2VhYjVmMjdhMmFiNGJlZGI5NWNlYzEyZTRlMTRlNTBjMWRhNTdhZWNlZjdlNTBlZDM1NTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBjMmZiOGQ3YjJjMGMwNTI5MmVjNGM4ZWQ0OTYxMzk2ODgzZjYwZjdkYTQ3Yzk5NzdmZTVmZTU1NTMyMDM1ZDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkZTk5NjAxMDljZTU4NjlmNjRmNmFmNGMwNDYzMWU2MmU2ZDFlODU5MGYwMGNlMWNhZGYyY2Q0N2JkYmU5ZTEifQ"
         }
       ]
@@ -69,7 +69,7 @@
       "statement": "The messages pane title shows the chat name and AI ON when the open chat's flag or the global autoreply is on (OFF otherwise), and only the most recent wrapped lines that fit are displayed.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L312-L348",
+          "resource": "repo://src/ui.rs#L316-L352",
           "version": "repo-lines-v1:sha256:c291029f490ad470710b3ac4482737631d1c35ee2eff8d99cde384408b75b573:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwM2M5MTRlZDU2ZmZiODJmYzlhZDJjZjQ0NDQxMzRmZDRhZTkyZTkzZmYxMTQ2NzZlOWI3NjI2NTUwZDVjMzciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBiOWJkNjEyZDA0MDhiOWE2NjFiNGQ4YzJkYThlNzk4OWRmYWIxNmVlOGEyNjBmN2NhMjI2NjBhODRjOWZiNDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJlNWU3NzM0ZjZhNWM1NWJiOTExY2U5ZTNhYjgwYWExZmZmZjljN2FmODgzMTUwMzg1ZDg4ZTVmM2M5ZTJiOWYifQ"
         }
       ]
@@ -79,22 +79,22 @@
       "statement": "The input pane shows the tail of the typed text that fits and places the cursor when focused.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L494-L524",
+          "resource": "repo://src/ui.rs#L498-L528",
           "version": "repo-lines-v1:sha256:4af9cab79ebcb71cd601b44fe7a4b8fcceb1fe16ecac50a20ec643453b72d21d:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM5YzFjMzliNjRjZmUzMjlkNGFjZmM2NTNlYTY1M2RmOTBiMGI5MDQ0NDJlMWU4OTZhMjFhMGUxYmY3ZDRkNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBjOTcwNjYzMjgxOGU3MzliNDJhYWZlZTA2OThlZGQ1N2RkZjc5MmM1ODcxY2I3ZGNiZTliOTBhYWU2YjJhYzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEzYWY3MDc2MmFmY2NkYTcwOTEyZWVkMDUwYzlmZTlmNzVjNzg5YjMwMjVjM2NkOWRhYmMwYTBiYjhkODI1MjQifQ"
         }
       ]
     },
     {
       "id": "claim_6e28bf5fc8004ac8af3412b4f4979d5c",
-      "statement": "The footer shows the bold status followed by dimmed key help in which the F4 global autoreply shortcut is highlighted green while the global autoreply is on.",
+      "statement": "The footer shows the bold status followed by dimmed key help, ending with F8:nuovo QR and F10:esci, in which the F4 global autoreply shortcut is highlighted green while the global autoreply is on.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L526-L566",
-          "version": "repo-lines-v1:sha256:5cc3c702fe6df48a350aa6157256a193b7dd8cef520a91d2deeef73b07e1b798:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJiNzk4OWQ5NWQzZGZlM2YyODliYzI4Njg5OWNjYzAxZWI2YzVkNGQyMzZkNDU3Mjg4YjJiYTA4YjZkMWRkNGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMzZhNmJkYjRhZWM3MTA3YmVjYTFmM2I1OWZhMDBlOWVkYzdlYWZmNmI2YjUwMDlhMzQ4OGM2MjZhMDdlYTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ1MTY0YWM3NTJmMmY2YWE3N2JkMTM0OGI0ZmQ2MTgxZGU4NmQ4OWU0MzY2MzliMDc1ZWIzMmY4YTc1ZWM4NTgifQ"
+          "resource": "repo://src/ui.rs#L53-L59",
+          "version": "repo-lines-v1:sha256:e4d781d38913f5b6a3a88d3129ab6cd4b2bf1ce789788f7d6579fde3c6933237:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWJiMDYzYTdlZmM4YTczMDVhM2NhNjliYjFkYjEyYTliMjg3NTNhNWM5MzY0ZmE1NTUzYWYyOGI1NDY0MTI1MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzAzNmQ3ZTQ4ZTI3NzExY2U0MTZjODAzMTcwMDUyM2M2OWU3NTk0ODIyYzBlMGVlYmRlZDZhOGVlZGUzOTBlMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjU3MWY4OTdkYmU1OGFhZDg5YTBkNzc1MWU0YTIyMTAwZGI5Y2UzYmI4MDllMTRmMjBlNWY4ZDJlYWQ3MGJhNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNWIxODI3ODdlZjdmNzdmNGRhODk4NWY5ZmQ3NGZlMjI0ZTcwNmVmOGY4OTkwZDk3YTVmNDBkZjI3YmEwNDRhZCJ9"
         },
         {
-          "resource": "repo://src/ui.rs#L53-L59",
-          "version": "repo-lines-v1:sha256:6375d8760354f1a87914855b5565f443be78757b62647d467bf23d52b5b1b491:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWJiMDYzYTdlZmM4YTczMDVhM2NhNjliYjFkYjEyYTliMjg3NTNhNWM5MzY0ZmE1NTUzYWYyOGI1NDY0MTI1MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzljMmIzMjhmMGU0MDc5ZDhiN2ZhZjQxOWUyM2Y3YjkzN2QxNGQ2YzZmZjU2NmUwYmRmMjcyZGNkOTVkZDM0YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjU3MWY4OTdkYmU1OGFhZDg5YTBkNzc1MWU0YTIyMTAwZGI5Y2UzYmI4MDllMTRmMjBlNWY4ZDJlYWQ3MGJhNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNWIxODI3ODdlZjdmNzdmNGRhODk4NWY5ZmQ3NGZlMjI0ZTcwNmVmOGY4OTkwZDk3YTVmNDBkZjI3YmEwNDRhZCJ9"
+          "resource": "repo://src/ui.rs#L530-L569",
+          "version": "repo-lines-v1:sha256:8426065f9c38082dfffa5777f230a2765e62f51874d068acf5795050b19ef757:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJiNzk4OWQ5NWQzZGZlM2YyODliYzI4Njg5OWNjYzAxZWI2YzVkNGQyMzZkNDU3Mjg4YjJiYTA4YjZkMWRkNGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMzZhNmJkYjRhZWM3MTA3YmVjYTFmM2I1OWZhMDBlOWVkYzdlYWZmNmI2YjUwMDlhMzQ4OGM2MjZhMDdlYTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM2Y2Y4OGNhYmNhNDI4NjYzNTNhMGRhNDdjNWM2YmYxOTE2NDIzNDg2OGIxNmVmYzUxOTVhMDM4ZTViYTliZTgifQ"
         }
       ]
     },
@@ -103,7 +103,7 @@
       "statement": "Messages are formatted as HH:MM sender: text in local time, with sender Tu, [AI] Tu, the sender name or the JID user part.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L379-L437",
+          "resource": "repo://src/ui.rs#L383-L441",
           "version": "repo-lines-v1:sha256:c499132db2ac5eead3eb62f5ad7bab3b7f8d914d35adb23bfc5ca621870465f2:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM3ZWNmMTZhOWUxM2QxZTA4ZmRhYmUzOGM5ZjU3MmMyZjRmNmUxYTkwNGNiODAxYWY3M2RlNzllOTczN2NhZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMyZDgzZjgxYTNiOWQwZjYzMWZmYzlmMTg5ZGQyYTY4NWNhYzJmOGViODA3NTYwYWQxNzlkZTk2Y2NiNjgzMzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQyOGI1ZWVjNDkzNDMxZWNmZGY2ZTIyZGVlMjc5OThhNDcxOTczN2Q1Mjg1NGI3MGEwZmM3MzUwZjViMjg4YTQifQ"
         }
       ]
@@ -113,7 +113,7 @@
       "statement": "AI messages are cyan, own messages green and incoming messages use the default style.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L367-L373",
+          "resource": "repo://src/ui.rs#L371-L377",
           "version": "repo-lines-v1:sha256:9a0c35114d98982d540867094c84291c906d6d832ab746b9c4f2dec88b905a7c:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDg0NDE5ZmQ0YjVmM2E4YzE4MTM4M2Q4NmQ5ZDc0ZDhiMWUyOWVlZjk5ZGMzNTM3ZGNhNjYzYzI1YjM2Mjk4YSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmVmNTliMjU3ZDI4Yjk0NTA2YTg2YTU0MmQyZThhOWNkODlkNDNjNmIwMmY1YTNhYmZjNWIzYmY2ZmJkZGE3YyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOWVlODhlMWI2M2U2ZWU3M2I1NDcyNTVjN2EyYzQzMzE1NjJkNWE2OGE1NDc3ZGRkMzY0OTZiMzc5ZmYxNWYxYiJ9"
         }
       ]
@@ -123,7 +123,7 @@
       "statement": "Text wrapping splits on newlines, wraps by display-width characters and drops control characters.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L443-L488",
+          "resource": "repo://src/ui.rs#L447-L492",
           "version": "repo-lines-v1:sha256:e966afd08433fba747794711a95f264804c09ac9f81a7c818a6f80be9a2893ad:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwYTJhNzczNmUxMWZkOGUyYjYyYmViMWM5NjNjZjRjNWRmYmNiNTIzNDZkNDdlOTgzNTgwZmE1ZmY1ZjY4OTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNkNWU4MTdmODljYTM3MjE2NTg3NGM0OGRmNzBjMjRiZTc5YTkxZmQxODQyZWE3OTI0YTllZDQzMzg2ZDdlZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1MjY1OTg3OTliMDNjYmQ4ODczY2RjNjRhOWJmOGE2NGQyOGMxNjhmNDkwYTMwM2ExZTkwY2U5ODkzMDc4ZmQifQ"
         }
       ]
@@ -133,7 +133,7 @@
       "statement": "In chat mode the list title appends a global autoreply marker when the global autoreply is on, and any list title appends the search label and filter text when a filter is typed.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L242-L277",
+          "resource": "repo://src/ui.rs#L246-L281",
           "version": "repo-lines-v1:sha256:fc4a9a5029ba905ce7fee869463b44f5f84ba4ff03c0566e28b38b850f9145bb:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVhYWI1NDY2NTFmMTk2NDNiOTg2MGQzNjBkMmYyZGE2YmIxNTMyYzAzZGRkMzIxNGI3YTg5ZWFiOTFmNjgyZDQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjliZDEwMzhkZWZmODFiYWFlZjU0N2JjMGI2NTk5ODc3YTc5NjhhOWRiZDBkMWM5YzZkODFjOTBhMTJkM2UzM2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQyZjY4ZGVhNTk5MTczNjlkNmMwZGJhNTg5NTZhNDI3OWYzMWJhNDMwM2YxNjY0Y2QxY2M1ZGNhNzVmODIxNDQifQ"
         }
       ]
@@ -143,7 +143,7 @@
       "statement": "The footer reserves a right-aligned area exactly as wide as the version label and draws the build version there, dimmed, as 'v' + BUILD_VERSION + a trailing space.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L545-L573",
+          "resource": "repo://src/ui.rs#L549-L577",
           "version": "repo-lines-v1:sha256:d8e5581d97934ea27752d49d8ae47361b347d25a53b08c164b4f7a0f40518099:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3NTc1MjJiMDY1NmVmYzZjNjcyNTk0MTJkZWFlYWRkNjY3ODAxZmFiZjEyMTFjMmIyZWM1NGY4NTJmZDBjMjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk2YTgwOWQ2ZjliZWQyNjAyNzhlYjI2ZGVjOGRmZDVjOWUwMDVhMzAxNzI4MmU1YjNlMmZhYjZkOTBiMGZhYWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ"
         },
         {
@@ -151,10 +151,24 @@
           "version": "repo-lines-v1:sha256:dc21562a3695c6a131645edbb4347b667e125c8e42450e3629835edc7ef58958:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOWEwNmNhOTM5ZTU4YmY5ODk3ZjcxMTQzMWU4MGQ0Mzc3NWYxMWRjZjAxOGVjYjA1Yjg2NTlhZDRkMjhmZDE0ZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMDBjYjlkMmVkNTFiZDg1ZjA4M2Q0ZWFjYTBjZmViNjJkZDQ0ZTNhZTdmNWQxYjZiOTYxYzQ5NTgxNWEwMDQwMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDJiNGUzZDFhN2IxOTVlYzQyYzg5Mzg5OWI4MDA5NDU1ZmM2YzIxMzQ0MzYxMTMyZTViOTIxYWIwMmYzYjQ5YyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMjdlMGVmMGY5NTkwNDBmMzI2YzcxYjJmMjdjMzI3NDQxNDVjZTJiNjBmZTcyNTgzMjAxMDI5MTM1YTIxMDBhZCJ9"
         }
       ]
+    },
+    {
+      "id": "claim_67181b4f819d4c5aba2c4103f451fd11",
+      "statement": "The connecting and pairing screens end with a dimmed key help line 'F8: nuovo QR   F10: esci'.",
+      "evidence": [
+        {
+          "resource": "repo://src/ui.rs#L118-L150",
+          "version": "repo-lines-v1:sha256:32c76a9c419a51d559868af240c4f04704b23815070694dcef8f71467cc59f08:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1NDA5ZDQyNWM0YzFmMjcwMzFmODBjZWQ1NTAwMjM0MDNkNzU5ZGVlZmZmNGM1NmRlZjJlZjVjYjg5YzEwNzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk2NWU2YjJlMjQzNWU2ZTMyOTQxMTZhNjdkNjljMjQzMmE4OTg4ZjYzYzM2NThlZGZmNmY5NjRjZDQ3OTc1ODMifQ"
+        },
+        {
+          "resource": "repo://src/ui.rs#L80-L83",
+          "version": "repo-lines-v1:sha256:24618465d6f9712cc3e763cb836db3bd623dc52d4ad0d775603e70ddab265989:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzg4ZTY2N2EyZTJlOGE4ZGNhZTU3NDNkYTg5NzE1NmVmZGM4OGY5ZDM0MzdlYWI2MjQ3OTg4OTFkYTEwNThjOCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTVlMTc3OTA5ZTVjOGY3NmQ0NWUxMTkwNjNmNTA3MzQ1MDdhOGU1MzRiNDhlNTc1OTJiZTgzYThjYWRiY2U5OSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOGE3MDgxMDljMTAzNDU4MjdjMzM1NjY5MzIzMzMxYzQxODI0MWM5OTVjYWI0NmI0ZTU5NTJlMThmMTBlMDkxYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODAyNDcyZjBlOGYyYTA1MjI0NGMzYTAzNGU2MGYwOGZkZGNkMjVmYmRmYTg1NmI1NDgxMjQxYTAwYWE2NzU0ZCJ9"
+        }
+      ]
     }
   ],
   "verification": {
     "by": "openwiki/0.7.2",
-    "at": "2026-10-21T21:49:12.291Z"
+    "at": "2026-10-23T21:26:24.156Z"
   }
 }

+ 38 - 24
openwiki/.claims/integrations/ai-client.json

@@ -1,6 +1,6 @@
 {
   "schemaVersion": 1,
-  "pageVersion": "sha256:e71947f4ffd79153e75af279f7eade4504e8b9a729d276a1f1938a45a094f7a0",
+  "pageVersion": "sha256:fddcffbcc714b9d61c2a530a57c5b40dbc6dd4279ee7a363ee96b0d7a44fb7bb",
   "claims": [
     {
       "id": "claim_c5dd9ee6003245adaa1701eb40862582",
@@ -27,8 +27,8 @@
       "statement": "AiClient::new requires openai_api_key, uses a 60 second request timeout, builds the endpoint from the base URL without trailing slash plus /chat/completions and copies the retry count from ai_retries.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L169-L197",
-          "version": "repo-lines-v1:sha256:f967f19779a732371d88712967219c8c951d71246e7691599693c2d86eaa50b2:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQyMDRmMzBiYmM1OTNhOGQxOGFiYTY3OWYyMWQ0ZGY1M2JiN2YxYjdlYWIwYTNkNDRmOWJhMjgyZmVhYjFhYjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2YWQyYTE2MzQ0NTcwYjlkY2QwODA0NjQ4Y2NhNzgxYTA0YmRiNWVmMTkwYTQxZGRlOWQ1N2NjYzE0ZjBhY2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY0OGYzMmRjZTI0MWMwNmFjYzBlZjdjZTc4NWM4MGZjM2E5NTk3MTZlODYwNTcwYjdkZmM0MWNiYjg0MWZjNDkifQ"
+          "resource": "repo://src/ai.rs#L170-L202",
+          "version": "repo-lines-v1:sha256:6e3fe2ce9a25f6382482792d28c45b5a27dc933b6a15c50cfddb2c0a56171c57:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQyMDRmMzBiYmM1OTNhOGQxOGFiYTY3OWYyMWQ0ZGY1M2JiN2YxYjdlYWIwYTNkNDRmOWJhMjgyZmVhYjFhYjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI0ZDNiYzY1MjJiYzcwYWVlZDIxYTMzY2RiNmQxNjFjOTg2NzhhOWJlYjlmMGFiMDRjNmZhNjQ5NjZhMjZhNTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY0OGYzMmRjZTI0MWMwNmFjYzBlZjdjZTc4NWM4MGZjM2E5NTk3MTZlODYwNTcwYjdkZmM0MWNiYjg0MWZjNDkifQ"
         },
         {
           "resource": "repo://src/ai.rs#L26-L29",
@@ -51,11 +51,11 @@
       "statement": "complete sends a body with the model, role/content messages and a tools list containing only the parameterless no_reply function.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L199-L208",
+          "resource": "repo://src/ai.rs#L204-L213",
           "version": "repo-lines-v1:sha256:84db9a28e9fbaa3500a8750fd4e0562b0ca94d27cb5b716c579ef27b6d1ab945:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjOTFlMDQ2M2RhZDJlNGQ5NTk1YmYzNDkzMTdiZjFkNDRkY2ExMzI4NGNkZTEwMDIwNmJjYTFiNDFlYjBhNmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImViOWEwOGJjMjlmNmViZWJlOWE1ZjAxOTAzZDk4NDVkYmNiNTg0MmE0NTVmMDdkNWY0MDAyZGFjMzJhNWM2MDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNiZTgwNmFkMjA4NWZmMTAzY2RiYzgzNTg0ZGIzODBkZjQ1OTdmMDZkNGJmOTQ5NDc3NGQ1ZmRlZTg5YmYwYmMifQ"
         },
         {
-          "resource": "repo://src/ai.rs#L343-L379",
+          "resource": "repo://src/ai.rs#L348-L384",
           "version": "repo-lines-v1:sha256:dda6d7007908d587c35f4714ff74812e0a09bc602f912758f08b2a1b5d39175f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM5OGNjOWNlYzY5ZDJiZmQzNThlZTEyMzYxYWNmNWI4Nzc5NjI1NjUzMDRlNDcwOWJjMmUxMDE3ODU5YTJjZDQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU0OWVhZTIzMTIxNDdhOTMxMjNjMGE4ZjJlZTUyYmVjNTgzYTRhMzc5NjQ5MGQ4NmYxMWE3OWIzNWE2ZWViY2EiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc1NDJjMzEwZjQwN2NjOGUxZjNiNzg4MDQzOTM4YTBmNzdkYWVkNDJmZDI4NTI3YTU2ZjRmMzUxNDY5OWE0ZjYifQ"
         }
       ]
@@ -69,11 +69,11 @@
           "version": "repo-lines-v1:sha256:cddeca6fba6fde7769f6cdec95f08d62b34b93c4f1a6822927f4b106a7db0a07:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTkyZDVjNzYwZTNiYmRlNWQ3MDRlNjE4YWY1ZmVmZTY5YWMzODEyMjg0MGU3NTUwYjk1N2RlYTZkMjFiY2YxZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMGJmZTUyOGM4OWM4NTc2YTQxMjYyZDU4NTdkMjE4ZDFjYWIzYTFhYzY4MzVlMGVjMDI4ODI2ZDk5YWVkZWVhNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDdiNTMxMDc0OTNkYTIyZTIzZmQyYTlmZTVjY2EwYjFhOGQzYjM2MmI2YzQ1YjkyMjZiZDM1OWE5MzgxODFiMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYWMzYWNhOGE0NDIxMDNkMDMwNGMzMjYzZmIzMzQxNzBkMjVmN2M3MmZjZjg2ZWZkNGIwZTE2MDY2NDBiYjk2NyJ9"
         },
         {
-          "resource": "repo://src/ai.rs#L218-L236",
+          "resource": "repo://src/ai.rs#L223-L241",
           "version": "repo-lines-v1:sha256:3b94ba30b7e5cb2d330d42ad007c48b0603bfef4b59fed9f017691dc5588825f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUzNTRlMzRjMWYwMTgyN2YyMzk2ZTdlZTc3MWZkOTQ4MDhkOGFiNjlkMzIyOGU3NzQyNWI4MDIyMzM1NWRiYjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiZTViMmQ5MTI2YWViZGRjY2YxOWJlYmFiZTFmNjRkODYzZmQzODA0MWMyY2FkYjk3MzM3ZWFiYmZiNmUzOGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA1ZWY0YzRmMGNjMDcxMzBjOTc1ZWVhM2E3MzdkMGJmOGNjZjY3MThiODUwMDY5NWY0NTU2Y2E2OTk5NmQ0YTcifQ"
         },
         {
-          "resource": "repo://src/ai.rs#L408-L427",
+          "resource": "repo://src/ai.rs#L413-L432",
           "version": "repo-lines-v1:sha256:092f50c0711f3d5bc0d27aec19da222f84f626a7d3a736ad917fcc84a58b6d9f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRmNjM1OTllNGU1ZWMwYTJmMzlmYWEzN2RlYTJlODQwZWIzYjQ2NmJmMGJiNDBlODEzYjdlMTMzYTI0ZGVjZmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUyYjlmYWRmNDM1MTMxNWMwYmMwMTE2YTQzYjNkYmQ5OGE0NWE5YjI0NjExYTg4Y2Q2ODRjZTMyNzlmOGE0N2EiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYwNjNjNGQxYWY4Y2Q4OWJiMGJkZDc2NTdmZmNiZjY4MDZhNjEwZDc5ZDZjOGQ0YzMwN2MyNDFkZjM5MDA1MjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiZTExYTZmMDQxMTdlZjQ2MDZhNTZkNmMxNWZjNjE5N2Q4ODg5ODY1MzcxNTc1ZTk0MDg0ODg4YjBhZTEzOWYifQ"
         }
       ]
@@ -83,7 +83,7 @@
       "statement": "The image description body is a single user message holding the description request text and the image as a base64 data URL, sent to the vision model without tools.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L381-L406",
+          "resource": "repo://src/ai.rs#L386-L411",
           "version": "repo-lines-v1:sha256:504cd9a8e9a12796644c5e64bf2dfaf3e052ae4cdc48d88acb91a8674cd4d994:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkxMWU4YjYyZTMzYmM0ODQxYzg2YTUyYmJmZDQyNGFlNDFlMGJiZWE4MjQyMmQxMGUwZDIwYjlmMzA1ZmI0MGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY4ZTUxYTI1NDgwNDkyN2MwNDc3OTFjYjkyNmUxY2MxNTUwMzY3ZjhlYTA3NWFjNzk0MDc5NGIzOTAwZmUxNGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjMWU2Yzg2MTE1NTc0YWZlM2FhMjAwNDYxYzRkMmUzZmRiNzI5ZjFmYjMyMjQzN2VmNGJhNWZmYzJmMDkxZGIifQ"
         }
       ]
@@ -97,11 +97,11 @@
           "version": "repo-lines-v1:sha256:1e8751e4147e04846b7aafee348637638f2fc85f2f11b79537cc0246feaccc44:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVmOTlmZDhkOTIwYjk2MTFiZmI3MDkxZTJjZTU3ODM3ZTY5MzY2NmVkNjRjMzFmNmIzMDY5MjJhYjVjOTBkMjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0ZGQyM2ZhMzVjMjczYjgwYzEwZGViNzUwYjNmM2M5NDFhNmJlMDg1NDBmNWFlNGQ2ODdkNjQyZmQ0NTQxNjQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJjMDk2M2MxZWY1YzRiMjZkZDNmNGY2MDhlZGRhZmNhMDRkMDVhNjI1ODk1YTcxZTYzMmY2N2Y4OTQxYjc2M2IiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY3Y2VkMGUzMjc4NjE2MjcwZjFiMDY1NjRlYjlhMDUyYTczNTc0ZWJlYjgxODg0ZDM0YjIyZTFlMjBiMDM0ZTYifQ"
         },
         {
-          "resource": "repo://src/ai.rs#L282-L314",
+          "resource": "repo://src/ai.rs#L287-L319",
           "version": "repo-lines-v1:sha256:580ac5298eb243d788d0a8c2ab3c5d4b293ef2288d101b6f46bb06680a3bdbab:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc1MTkyMjFkZDdiYWFiZGNmMTIyYmJmNTU5ZGM5MGNiOWJmY2E2NTcxOGU4NTk1MDIyMGEzOGE1MTk0ODgwZTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEyYWMyZWMxODc1NDdmYTFiNzk2MjZiMGE0NjUyN2IwZDc2N2U0YzE0MzAyMTQyYjQ0NjdiMzY4ZDA2YjcyMTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI2YzllMjYwYjQyZThmYmJiMDRiOWZiM2UwOWEzNmMyZjI1OWI1MDBhOTM1OWE0OTYwMTA2NDYxNWIxY2Q5YjMifQ"
         },
         {
-          "resource": "repo://src/ai.rs#L568-L574",
+          "resource": "repo://src/ai.rs#L573-L579",
           "version": "repo-lines-v1:sha256:366599ee31cc7c09c20fa0ddcff49b42b55831d6b97ede5ec5da7376b9aaa450:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTgxODJmYmI3OGRjMzUzZjJkNjM2ZTc2MjU0YzYzNjIxZmQ2N2IyZjFlNzVmNTM0MzE0ZjFmYmI2N2IwYjJkZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiODI0MTY5N2IzMjdjNjYxODJlYTJhODdjOGJmZDI1Nzk5Y2I5MTk3OGY0MTBhMmI0NjA4ZTM2YjZiZDc4YjUzYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiN2JjOThkNmIwZGM0OTJjNTAxNmRmMTA4MDVhM2E1NGViOWMxODRhNWE2MTM3ZTdmNGJlY2ZhMGMyMjA1NTBkMCJ9"
         }
       ]
@@ -111,7 +111,7 @@
       "statement": "parse_reply returns Reply::NoReply when the first choice calls the no_reply tool (even with text), otherwise Reply::Text with the trimmed first choice content, and fails on missing choices or empty content.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L429-L477",
+          "resource": "repo://src/ai.rs#L434-L482",
           "version": "repo-lines-v1:sha256:b50cbfe36f47a1bcbd306810d81baa272a3fb57a711164de89a741f5818b12ca:eyJzZWxlY3RlZExpbmVDb3VudCI6NDksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjZjNhM2E5Zjk2MTJjZGJmMDZjMjU0YTU2NGY4NTBlOGFiZTEyYTNjMWNmZWRmNWYxMjJiN2IzMTlmZDdmYjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk2MjY0OGRjYWQyYTIyOTY3Y2YzMGUxZDRhMGE2NDA0OGQyZmE4NGY0ODNmNTEyMjU4NjZmODIyMmJmMTVlZWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU0MDRmMmRiY2YyZGQyYjZhMThkYWRkYjg3MTg4Y2E2NGQyMjZhOWZmMTJkMGEzNTNjZGIzMzM3YTcyY2NkODAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyMWNhYWEyZTk5ZTU2MTA0ZWU2MjkxM2JhMTY3ZTc0MmJkYzBmZDg0YmI3ZDhiODg5NzQyMWI2ZTExNzUzYWUifQ"
         }
       ]
@@ -121,15 +121,15 @@
       "statement": "When ai_log is enabled, send_body appends one record per request (header with timestamp, endpoint and model, pretty request body, RISPOSTA or ERRORE outcome) to ai_requests.log in the data directory, ignoring write failures.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L261-L280",
+          "resource": "repo://src/ai.rs#L266-L285",
           "version": "repo-lines-v1:sha256:80e196adb6578d005aa9692b9e78d664b43c74a606ded3b99a348545c412b065:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUxNzY0ZmUzOGM2MTVjODM3MzM2M2ZhM2U2OTVhNTQ3Y2M0MGM5MjhmZjIwYjE0ZTI3NGRjZjQxNDI5NjI5MjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEyYTMyMmRjMWM3OTk2OTEzNTU1MGI0YzRkMzYyMThiNTQ4ZDY2NjE4OWRmNDFkMWM1ODIyYjU1MmE3OTljNjMifQ"
         },
         {
-          "resource": "repo://src/ai.rs#L521-L566",
+          "resource": "repo://src/ai.rs#L526-L571",
           "version": "repo-lines-v1:sha256:f5678d32b8e747a88110624d96ba85e6f0b191d2ff311ce41bd841dd4cd0a2a9:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVhMDJhNmJkMDVjZjBmNWY4NmNlMjk3MTg5ZDk1MjJlYzU4ZWZhMzY1ZjI4NGNjNGYyOWE2NTk2NmQxODhiZWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjZTgwZDQ1MzU3ZWY2NWZiNjUzODgzZWEzOTU1NDMyMDMzN2Q0YjA3MzcxNzQ1ZWE0MGFkYzRmMGFjNTU0YTcifQ"
         },
         {
-          "resource": "repo://src/config.rs#L333-L339",
+          "resource": "repo://src/config.rs#L341-L347",
           "version": "repo-lines-v1:sha256:77a53fa11fa04d405caba6426205b2c57c25edd1b59ce057d5429c3cb0af854d:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzUyM2JkYmRjYjY1ZGIyYThmNWU0OGI0ZmUyMDdjOTUxYjdjNzE3NDYxYzgwNjUzODMxNjExZGI0YjMxZjdhZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDRhMWY2MWFmZTk3ZWM0YWZjODI1OWM4ZGI4MDljZjlmMjkxNWQ3ZWUwYWRlZWJhNzdkZDMxZDBjZmJkM2JhZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYTUyMTYwNGIzNWZjNDBhZjAwY2JhNGVlYTJmZDVlMmMzYWYxODYzYjM5MTE4NWQ3YmY0YmIwOWU3MmQwYzhiMyJ9"
         }
       ]
@@ -139,7 +139,7 @@
       "statement": "Log records replace image base64 data URLs with a placeholder carrying only the payload length and never include the API key.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L473-L547",
+          "resource": "repo://src/ai.rs#L478-L552",
           "version": "repo-lines-v1:sha256:abb31f22842d8a444e09f5ec22b68f5ff8465a79ef1489ef4b124d21b18bcd0e:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNhZmRkOGE1NjBlNzQ4ZmFmNGRlZDhhYjJmOWU4NmUyMTU5NDA1YTAxMGUwYzE4YjI2M2NhNDE4MzAxMzFjZTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBhNjlhODZmMTdiMzAyZWQ5MDgwZTZhMGNjNzY1ZTgxMmFiZmJmYzBmYmZjODNmY2Q5MTA5ZDQ0MWQ3MTExZTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRkNmI1NWM2ODQyOGJkOWU4ZDZiNGU0Y2YwYmJmYmI4Mjc4MWEzMjU4NjEwYWU5ZTAxMjI3NzA0NjM1MGQwN2IiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVkMjc4N2RhNGI5YWFiNTIyZDRmZWQwODQ1MjY2YzFmMzk5ZWEwM2E4NTdjOTE5Njk2Y2MzNjk2Y2I1NjVmNGQifQ"
         }
       ]
@@ -149,7 +149,7 @@
       "statement": "Requests are sent through send_with_retry, which makes up to 1 + ai_retries attempts, retrying only errors accepted by is_retryable and returning the last attempt's error.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L238-L259",
+          "resource": "repo://src/ai.rs#L243-L264",
           "version": "repo-lines-v1:sha256:c006e232dbf853be3e7d3072eeee16260cafa0266b3e5044835b674eb20f8601:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwNzBmYTg1ZjNiN2RjOGYwM2QwYjBmMjA2NWNhYzQ5NWYxNWQyNzQwMWExN2I4YzE2Y2Q4ZmQ5YWIwMWQ0MWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjExYWIwMTYzYTk2YjkzZWI0ODA1NmUxM2Y4ODUzMTJmOGVhNDQ5NjcwYmY3MWU2ZTRiZTAxOTU0ODg1OTk2YjEifQ"
         }
       ]
@@ -159,7 +159,7 @@
       "statement": "is_retryable rejects HTTP 4xx responses other than 429 and accepts 429, 5xx and every non-status failure.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L317-L332",
+          "resource": "repo://src/ai.rs#L322-L337",
           "version": "repo-lines-v1:sha256:29bbd06d8acee1a3e8788f3a5d669981da5c3ff1da3d4ad7b5a66e0cfcd3a69a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxOGVkNTcxN2U3MjlmODcxM2RkZjJiYWE4ZGI1ZGZlZDgzNWYyY2JhZDI3ZGEwNTUwMzgwMWNiNmZlNTAwNGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVjZGFjZmMzNDYzZjY1NDljYjAzNTQ0MzdiNjg3N2YzYmNlZjVhNWQ4NDk2YmMwYjFhYzc0YTE5OWUyY2Q0YTEifQ"
         }
       ]
@@ -169,7 +169,7 @@
       "statement": "The wait before retry number n is 2 seconds times n (linear backoff).",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L334-L341",
+          "resource": "repo://src/ai.rs#L339-L346",
           "version": "repo-lines-v1:sha256:97d05ac13a6dbe53449bb68ab41111ae5cd01fd47dfe333a12720fe3807b20ca:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTIwYTE1YWFhMWYxOTliZDY5OWE4ZDYwMmUxZjBmZTI4Y2EzZjY5MDBiYzdkMzRlNTA5ZjA3MDQ5ZjRkN2FiNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMmIxNGY0OGEzYjA0NzBiZWJhYWYzOTkyZGUzMWVjOTIzNzQ2OGI2NjhiZGYyOTE3NzgzODdiZGZmNmVmZWM3ZiJ9"
         },
         {
@@ -183,25 +183,25 @@
       "statement": "When ai_log is enabled, one log record is appended per attempt, so each retry is logged separately.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L238-L280",
+          "resource": "repo://src/ai.rs#L243-L285",
           "version": "repo-lines-v1:sha256:61675dd2723f8e4314c949dc027ac6e1a543b610794b2f4213100826f4f7eb90:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwNzBmYTg1ZjNiN2RjOGYwM2QwYjBmMjA2NWNhYzQ5NWYxNWQyNzQwMWExN2I4YzE2Y2Q4ZmQ5YWIwMWQ0MWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEyYTMyMmRjMWM3OTk2OTEzNTU1MGI0YzRkMzYyMThiNTQ4ZDY2NjE4OWRmNDFkMWM1ODIyYjU1MmE3OTljNjMifQ"
         }
       ]
     },
     {
       "id": "claim_436ce19bdcf94d028d46da1886350561",
-      "statement": "The image description request comes from vision.md when present, otherwise from the built-in DEFAULT_IMAGE_DESCRIPTION_REQUEST, and a NoReply outcome of a description is turned into an empty-reply error.",
+      "statement": "The image description request comes from VISION.md when present, otherwise from the built-in DEFAULT_IMAGE_DESCRIPTION_REQUEST, and a NoReply outcome of a description is turned into an empty-reply error.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L184-L196",
+          "resource": "repo://src/ai.rs#L189-L201",
           "version": "repo-lines-v1:sha256:e959c04c9811c5e76d9abb77e95982781c484819d9218c76a6cf30f03ac92baa:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAyZDhhNWIxYzc4MTIwMGExYTNiYmQ2NGI4MDU0Yzk3N2E3OWZkNDk0MDk3M2I3M2RlMjE5MTVkZjQ2YTFmMGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY5YjE5ODJlOGRkNzQ4MTE4MTQ2YjM5YTJkMDA2MmY0MDgxZTM0Nzc5Yjg2NzVjMjM0YzRmYjEyNGRjMzkzOTAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI4NjJmMzRhNzU3YWRjNDZhMDMxY2JmY2VjZjI4NTQ1NDJkMDc5ZDEyZjAzNzc4ZGMwMzgwYTI3ODY0YjYyMWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ4NGU3OGMwZTQzNDBjZGJhNzIzZTIwNzdiODQ3NTUzZTg0ZDNlOTkwMjZmZWQxOGIwNDUxNDA1Y2MxNzQxMmEifQ"
         },
         {
           "resource": "repo://src/ai.rs#L22-L23",
-          "version": "repo-lines-v1:sha256:093425babc91b6274b3b8f3a786d50d409a30fc9e71342d62c8cc3f248520993:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjdhOTc4ZWIwYzk2ZDZlMDlhM2MxNzI4ZjNkNDhlZTE5NTA4MDQ2MGEwMjAyOGMxY2MyM2JiMjYzNTllY2RmMSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMjQzZjI1YjQzY2FmYmUxYTg5MDNiMjAwMThiMDEwZWE4NmJiZWQ3N2JhOTk3NjAwZTdjNTAzMmM5Y2M1Y2NmOCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNmY1ZjBkMGY4NWQyYWRmMmE1YjFmODFlNDY4NDJkZGRmMjljZTY2NDQ3NGY0MTllYjQ5NTAzMzMwMTNlYTU4NiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDM4NDJhNGNmOTA1YmJmOWY2NmFkNTY5ODljYjZiYmFhNjNhMzQ0YmIwMjUwNjY4YmU2ODVhNjZlMGRjYjgwZiJ9"
+          "version": "repo-lines-v1:sha256:b16b668c7d3f115f76286312c0ee15219c33a7eb4872a7ec30d5e3ad97a49f8a:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTVmYjlmMDdiN2Q0OTRjZTZiODU1NDBlOGFhZTI1YjE4OGE5NzczZjM2NzMwMDQ3MmIwNzYyYWZjZTAzYjRmMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMjQzZjI1YjQzY2FmYmUxYTg5MDNiMjAwMThiMDEwZWE4NmJiZWQ3N2JhOTk3NjAwZTdjNTAzMmM5Y2M1Y2NmOCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNmY1ZjBkMGY4NWQyYWRmMmE1YjFmODFlNDY4NDJkZGRmMjljZTY2NDQ3NGY0MTllYjQ5NTAzMzMwMTNlYTU4NiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDM4NDJhNGNmOTA1YmJmOWY2NmFkNTY5ODljYjZiYmFhNjNhMzQ0YmIwMjUwNjY4YmU2ODVhNjZlMGRjYjgwZiJ9"
         },
         {
-          "resource": "repo://src/ai.rs#L225-L236",
+          "resource": "repo://src/ai.rs#L230-L241",
           "version": "repo-lines-v1:sha256:bda19d95aafbb2269b1eb5d3138a4ddf5ed88f848b8254487116da95269ad3df:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3ZmUwOTY4MzlmODMzNDkxOTA4ZTQ1NmFhNDI2MDJkZjQzNTc5ZDQyOWExMDg4YmVlNWRhNjE4ZjlkOWYwZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBiYjIyNTU2ZGUzMmVjZTc4YjRlYThhMjUwNjQzODVkMWZhYjdjNjA2ODk5MTQ2NDJmZjk2NjkzNDE4NzA5ZWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA1ZWY0YzRmMGNjMDcxMzBjOTc1ZWVhM2E3MzdkMGJmOGNjZjY3MThiODUwMDY5NWY0NTU2Y2E2OTk5NmQ0YTcifQ"
         }
       ]
@@ -215,10 +215,24 @@
           "version": "repo-lines-v1:sha256:316aee398af311a49e09bb1b511935db92322af15e12d53c4ab9a88707448d9d:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzk0M2Q3YWQzMjNmOTlkMDAzM2JjMTI5ODk3MmI3YmUwNzIxYjAxNDZjMmExYjlhZmZjYzc0NTcyYWFjZjNiNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZDk4ODQ5YjY2YWQzY2FiZTdlZmVjOGU1ZmI5YWI3NjJkYTc4MzViMzg4YzM4YWIyOWVkNDM1MjBiNGI4YTk1NSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZGNkYjE3MDIxM2RkOWVkM2I2OWExZmU5ZWYwNzg1YWRhMzljNzkzMDA3N2E0OGM5ODMxNWE1ZjJmOWFlYTIzNyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzUwZjY3OTE5ZWU2Y2JmNWYxY2VmYjNkZDIyMTViMWFmOTJmMDkwZDdhNWFjMDg3OGZlY2IwOWQ0YTczYzM4NCJ9"
         }
       ]
+    },
+    {
+      "id": "claim_9f3163574a8a4bd4aaf90fe415fde1e7",
+      "statement": "AiClient::new installs the ring rustls crypto provider as the process-wide default before building the reqwest client, ignoring the error returned when a provider is already installed, because reqwest is built with rustls-no-provider.",
+      "evidence": [
+        {
+          "resource": "repo://Cargo.toml#L13-L16",
+          "version": "repo-lines-v1:sha256:ffa1110b9463e2e882dd6ee955610921a6626c1ac714795c354f305349c2de28:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDc1NjE4MjA2Mjk3MDFhYTMxMTUxMTA3NmU1MmU0ZTY4MmNmNmQzMzJhZTgzNTViOWY3NWI1ZmE4ZmNiNjZhNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzMyMGRlNDU2MmQ5YTlkYjc2YTA4YjdhNGMyYjdkZmRjNWQxZDM4YjU3NWUwYzkwMzQ3ZTM2YmRmMjQ1ZWMyMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2E4ODdjMTgwNzVjMTBjMWQwMGNiMTFmYjRjNmE3YWM2MzIyYzJmZjAwMjVmOWZlZWM4M2Y1MDQ5YzE3ZjJmZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOGUyOWE1OWE4ZjIxODhlOTZkOTQwZmI0MzIyNmUzMjY0Njk3NWM5MGU0NTRkOGNjMjU5OTgwNTZhNTU2ZDc0YiJ9"
+        },
+        {
+          "resource": "repo://src/ai.rs#L176-L182",
+          "version": "repo-lines-v1:sha256:87b8c8722b6b4cb10e2721a01357d852406025df2fb45a0970b9d8b91d0599ad:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYTVlYjQ5N2NkM2NkNzJiMDFjYmY3NWI4NWIyNDM2ZDU5MDllYWFhNDcxNGZiMjEwYzljZDA0YmQ2Njg2NWUxNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWQxNjg5NjFhNDM0YjU0MGQ2MGQ5ODkzY2M4N2I5NzgwNmFkMjNjZGY4YmZhZTU4YTdiYWY1NThjNjdkNWE2NiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGE3NmMxNDgwMDcwNTFhZjU2OWRiMzE5MGZlMDcyNGMwYzhkNmQ2MjZkN2NhNmY2YjY3M2JlZDJkYWNhYjRlYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMmFlZmE4Y2FlMzQ5NWJiZTA0MjBkMzhmYTZkY2QyOThhZjRkYmFiYWEyZDYxYjA3MGIyYzBlYmM2YmE5NWNjOSJ9"
+        }
+      ]
     }
   ],
   "verification": {
     "by": "openwiki/0.7.2",
-    "at": "2026-10-21T21:49:12.291Z"
+    "at": "2026-10-23T21:26:24.156Z"
   }
 }

+ 69 - 37
openwiki/.claims/integrations/web-control-panel.json

@@ -1,57 +1,57 @@
 {
   "schemaVersion": 1,
-  "pageVersion": "sha256:3ffa0ef4024e9b6d6363e8a29f878dc08440b9bdb206eb8cbc0e7a43f37d70ed",
+  "pageVersion": "sha256:5fefadb20f2b8cf87afcc027db804249288fd112025b45dd9e740fc8bca85d7e",
   "claims": [
     {
       "id": "claim_3b56098917ee4e02b2f8ede066a9921a",
       "statement": "The web control panel is started only when web_port is configured, binds on 0.0.0.0 with no authentication, and serves requests on a dedicated wh-web OS thread one at a time.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L88-L97",
+          "resource": "repo://src/main.rs#L95-L104",
           "version": "repo-lines-v1:sha256:54c6448bd45241fd4b90479b1a91e2fd528e7a38f4ac5d543eda728fbf3756d5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwZjA3MmY1NzA4NTRiMGM0ODg3YmRlZDU4YzU2NWRiZGExOTNlYmY3NGIwODZmMjYyNTFmZjZhOTZiMmJiYWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRmYWI3MTBkZGY3ODU5ZTU3NTc2NGE5MTNhMDAyYzc0NTg1ZmZhNzAwMTUxMGNiODgxMWRhODNkMmNmNzRiMzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI1OTAxZTAwNGRkYTg1ZmUwNjg0OGVkYjhkMWIzM2Q1NTNmYWM4ZjkyMzZlZTFlMjlkZTNmNGUzMWRmNTM4NTkifQ"
         },
         {
-          "resource": "repo://src/web.rs#L1-L17",
-          "version": "repo-lines-v1:sha256:525008f2abbc8625d414d67af1579c526c618328ba3286794ced9a8b4448a47a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiM2Y3MzZlODVhNTljMGQxNWY5NTM0YmZmM2ZhZjEwYWQ5NDBjZWQwODY3ODBkYWU1YjNlZDk2NjEwMjMyZDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhjNDU3YTVkZDNlNWJkNmQyZjgyZTU5MmRhN2IzOWQ3OTAxN2U0NzZkYjc0YjNhZjA0MDMwNWIwYmIyOGUxZTQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU4ZTEwNzM3YjM4ZjIwMmZmZTRjOTFhYTU0N2IzNmEyNzA4NDQ2ODQ4MTVhYzJiZWZlMzc4MmNkMjBhMmNkZjgifQ"
+          "resource": "repo://src/web.rs#L1-L19",
+          "version": "repo-lines-v1:sha256:7ca0571f2b3a7bab8e55e28c77db9e518e764cccc07f6f3db6ee22483494b39e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiM2Y3MzZlODVhNTljMGQxNWY5NTM0YmZmM2ZhZjEwYWQ5NDBjZWQwODY3ODBkYWU1YjNlZDk2NjEwMjMyZDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhjNDU3YTVkZDNlNWJkNmQyZjgyZTU5MmRhN2IzOWQ3OTAxN2U0NzZkYjc0YjNhZjA0MDMwNWIwYmIyOGUxZTQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU4ZTEwNzM3YjM4ZjIwMmZmZTRjOTFhYTU0N2IzNmEyNzA4NDQ2ODQ4MTVhYzJiZWZlMzc4MmNkMjBhMmNkZjgifQ"
         },
         {
-          "resource": "repo://src/web.rs#L160-L186",
-          "version": "repo-lines-v1:sha256:f8b34287072ae980835326b6b58d084cb50595fbb20e0a5a69a07e2a5107e4a1:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhhZmRhOTQ2N2Y1YzJkNWJhYzg1MjVjYmQxYzgwMzlmZDYyYjk1NDJjNDg0ODlmODE2NTIxZWJlM2U1N2YwM2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjljYTdmZjM2YjFmNjM2MDdlZjI2OTZiMWMwODk5Y2ZiMjlhOTc5NzU4ZGZkMTYwMGYxYzAzMGE3ZDk4NzkyNmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA3ODdlYjhkMTU5MmY2NGVhMmE3M2NhNjA4ZmUyOWRkNjhlZDJiNmYyMDY2MmRlN2MxMmYxNmFjMTEyZWI0ZTIifQ"
+          "resource": "repo://src/web.rs#L185-L212",
+          "version": "repo-lines-v1:sha256:ab494114a58ebd5d3aac2772fa0fad447024157f3749782a39f2e7b5d2db8a0c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMxZTI2MzA4NjJmNWZjZTY0Mjk3NGU4YTg5NmEyMjk3NWE3NDZlNjI5ZTcyMWNlYjY2YjhjZTgyOGM4NDJjMTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA3ODdlYjhkMTU5MmY2NGVhMmE3M2NhNjA4ZmUyOWRkNjhlZDJiNmYyMDY2MmRlN2MxMmYxNmFjMTEyZWI0ZTIifQ"
         }
       ]
     },
     {
       "id": "claim_0efd2e13edc54ed3a8fd6f3d576511ad",
-      "statement": "The server exposes GET /, GET /api/state, POST /api/global, POST /api/chat and POST /api/quit; any other method/path combination returns 404, and the query string is ignored.",
+      "statement": "The server exposes GET /, GET /api/state, POST /api/global, POST /api/chat, POST /api/relink and POST /api/quit; any other method/path combination returns 404, and the query string is ignored.",
       "evidence": [
         {
-          "resource": "repo://src/web.rs#L19-L28",
-          "version": "repo-lines-v1:sha256:549434436d6d39319733236098427e43f3ff8b065cef72fe6bcbe3f13a77b8f9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM5YzE3NTNiNjYzYWNhNDFjNDA1MzRiYzljMGQwZGE0OGQ1NTc0ZGIxMTU2MjdiZmVlYTViY2RmYTJhMjQ5MmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4ZTIzMjgxODM0YjFlNjMyZTQ4NzljY2JmZGIxMjFlMDdhMmNiNGQ5ZGFjMzIxYTdmMGJlYzg1NmRhMmQ4YTciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNkNDMyMjkxNTVmYjM5ODIzZjhkMmY1ZDI1MDFkNzM1NzNiNjI0ODI0ODhlNWRlODg2ZGFiN2EzYTc1YjBmYmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgzYmIyNTJjMjU1NmVmMTM5MWJjNDU1YmEyOTAxZWQ5MTczNmQ2Njc2MmEyZThmYzExMjJlYTFhMWYzYjVmZmQifQ"
+          "resource": "repo://src/web.rs#L21-L32",
+          "version": "repo-lines-v1:sha256:8d68f7263f5184bcacbcc32e3f2b90f72d9c90c4693a2bb0a0efaea433982b46:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM5YzE3NTNiNjYzYWNhNDFjNDA1MzRiYzljMGQwZGE0OGQ1NTc0ZGIxMTU2MjdiZmVlYTViY2RmYTJhMjQ5MmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5NjJjZGY2NDAxOWI1Zjk5NTA1M2JmOTQ4NGU3MjAyM2I5YTgzYzlkMzUyZDhmMGVkNDRjZGFjZTEzOWZiMWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNkNDMyMjkxNTVmYjM5ODIzZjhkMmY1ZDI1MDFkNzM1NzNiNjI0ODI0ODhlNWRlODg2ZGFiN2EzYTc1YjBmYmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1NDU1MTc4NDk2MGFmNDM2ZTM2YmZkOGY2ODg0ZDg2ZWM3YTFlMmVlODA1NjU3OWY5YjMyMTQ5ZDk3OWJlZWIifQ"
         },
         {
-          "resource": "repo://src/web.rs#L313-L334",
-          "version": "repo-lines-v1:sha256:1141f32c78ab436faa724518545a117d98eccf70ff4dc6295b1d0ad5be7cef11:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkxYTU1YzdlMmE3ODBiZDViNzE1ZDY2MTYyYjI5M2M2ZTI2MWY2MmI2Mjk5MzY2ZWJiNTZmZjhhZTNhMDlhNDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzYjZjY2UzZDJjZWYzY2RhMWVkNWEwMzM5Y2QwMmM1ZGViNDRhNzBkMGYxNDA4MmJjZjNlYWYwMDJkN2U3NDkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ0NGIyYWRlMGYzNzRmOGZjZGYxNGM5MTQxYzNkNTMxYjcxMzgwN2ZkZTc0NzI1NTZiOWJlNDI1MjE5NzM4OWYifQ"
+          "resource": "repo://src/web.rs#L340-L362",
+          "version": "repo-lines-v1:sha256:30fb4995bbb20426970a2e31f5f3dd5e5c811e6590f215688cbf4a0fd2662bf7:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkxYTU1YzdlMmE3ODBiZDViNzE1ZDY2MTYyYjI5M2M2ZTI2MWY2MmI2Mjk5MzY2ZWJiNTZmZjhhZTNhMDlhNDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzYjZjY2UzZDJjZWYzY2RhMWVkNWEwMzM5Y2QwMmM1ZGViNDRhNzBkMGYxNDA4MmJjZjNlYWYwMDJkN2U3NDkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMxYmNlM2Y3ZjQ5NjMxNWJjMWQ1NWYzYzE5MjE4MGI4N2NhYjExNTgxZWVjNGQ0ZWRjMWQ2MzYyNzhhOTllYjUifQ"
         }
       ]
     },
     {
       "id": "claim_696ce9a26b74435f8d5439a5f1e4ab6b",
-      "statement": "Every POST route requires the header X-Wh-Request with value 1 (name case-insensitive); without it the server returns 403 and forwards nothing to the application.",
+      "statement": "Every POST route, including /api/relink, requires the header X-Wh-Request with value 1 (name case-insensitive); without it the server returns 403 and forwards nothing to the application.",
       "evidence": [
         {
-          "resource": "repo://src/web.rs#L140-L148",
-          "version": "repo-lines-v1:sha256:988e5ef7a69e7a10a978cd986e4879843f82b485ee9ffcc5ffc85f1844b0348e:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODU5OWJkMWM5YjdhMTQ5ZjczYzQzMjQ2MjljMzczMzFmOGNlZTM1NDVhN2IzYWUwNjk1ZWM0NmFlZjBiZmJjNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGRhMDBiODU3YTc3N2IyNzQwZmQxZjg0MWIxZWQ0MzcwNmYwOTc5YWEyNzE5Zjk1YzJjOTJjMGNkYWEzOWJkYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNGNmODQ4NzQ5MjczZDdkMWJlZDQ1ZTg2ZTNmMTU3ZmY3NTE0MjIyMTFkMWE3YzFlM2RhZDIzYWNkODNiZWRmNCJ9"
+          "resource": "repo://src/web.rs#L166-L174",
+          "version": "repo-lines-v1:sha256:81e9004c1f6afdff65111c92f71cf2a8b9e2fd1f87d5d54d60e8446e7b1dd361:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODU5OWJkMWM5YjdhMTQ5ZjczYzQzMjQ2MjljMzczMzFmOGNlZTM1NDVhN2IzYWUwNjk1ZWM0NmFlZjBiZmJjNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGRhMDBiODU3YTc3N2IyNzQwZmQxZjg0MWIxZWQ0MzcwNmYwOTc5YWEyNzE5Zjk1YzJjOTJjMGNkYWEzOWJkYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNGNmODQ4NzQ5MjczZDdkMWJlZDQ1ZTg2ZTNmMTU3ZmY3NTE0MjIyMTFkMWE3YzFlM2RhZDIzYWNkODNiZWRmNCJ9"
         },
         {
-          "resource": "repo://src/web.rs#L193-L205",
-          "version": "repo-lines-v1:sha256:6f08617663cf012d21e1dd6a16a4f2b6c0c1aeb0e6816a98e6f158ac3e8b7e6a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY1MDQ0NWY4YjI4ZjA5MGJhYWIwOGMyZjcwZGM5NGU5NzE4OGVmNzU4NjYwOTYyNDlkYThkMTcxZTA4MmU3OWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA4ZjhkYWY0NGE3ZDkyZmQzZWFkMGFlODI3NjVhOWQxMGEyN2FmYmEzODhiN2NhYjU2Y2ZlNjk5OGUxODU3ZmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUyNmM2NzRiYmY2YzlkMzNlZjM0NTEwOGJhYmYzMmFmY2UxNzc1OTMwYjU0Y2JkOWU5NzkyNWQ3NDI2NWEyNzAifQ"
+          "resource": "repo://src/web.rs#L214-L231",
+          "version": "repo-lines-v1:sha256:007fa67085367288f4893ce88fd4bad25907592bb8915e1a4649d9de382800fe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkZDUwMWUyNTM5YWM1OTM1ODBjM2RiMTQ5YzBmMWUzMzBiZGJiNzU1YzZkYzJmZjk5Nzg3ODkzZjMwOWQ5ODMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUyNmM2NzRiYmY2YzlkMzNlZjM0NTEwOGJhYmYzMmFmY2UxNzc1OTMwYjU0Y2JkOWU5NzkyNWQ3NDI2NWEyNzAifQ"
         },
         {
-          "resource": "repo://src/web.rs#L336-L345",
-          "version": "repo-lines-v1:sha256:f6fc6a9f07a89bf80251845896e2bb83deb08f3b693960e20ec04eda2ee7cab9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1NzEzZjExZDY3OGYyYmQ4ZDhjMWUyYThjNmRlMDg4ODg4NDQzZWFkYmQ0NTdhZmQyMmJhNzlmMjY2N2RjYzkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiZGE3OWRiOTQ2MjJmMThhNzVjMWU4YmFmNzVlMTFhMWJjOGVmNzg4Yjg2M2VkMmQ2M2RhNTUyZTc0MGQyNDAifQ"
+          "resource": "repo://src/web.rs#L381-L390",
+          "version": "repo-lines-v1:sha256:f6fc6a9f07a89bf80251845896e2bb83deb08f3b693960e20ec04eda2ee7cab9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1NzEzZjExZDY3OGYyYmQ4ZDhjMWUyYThjNmRlMDg4ODg4NDQzZWFkYmQ0NTdhZmQyMmJhNzlmMjY2N2RjYzkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU5NmY4Y2IwODNmYTgwMGE0ZGNkMjk2OTcyMmQ3YjkyNjg2YWRkOWViZTM2NTU0OTJmYTY3MmIxYTdjZWQyNDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiZGE3OWRiOTQ2MjJmMThhNzVjMWU4YmFmNzVlMTFhMWJjOGVmNzg4Yjg2M2VkMmQ2M2RhNTUyZTc0MGQyNDAifQ"
         },
         {
-          "resource": "repo://src/web.rs#L46-L49",
+          "resource": "repo://src/web.rs#L64-L67",
           "version": "repo-lines-v1:sha256:c5e290866be837c820ed79dff719f958fe09c524ae81a8c5816c86113418fbf4:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMWM3MTlmN2I5YmZmYWVhN2I5MjhmMmE5MTg1MTA1NzQ3ZGJmYzg0YTI1NzNhZDFiNmI5NzQ2NzFlZmY0OWNlZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTJlMmMwNmY2ZDg3NDRmYzBkODEyZDc0YTAwNWJmZDhhM2U2MDc1MTFlZmM0ZWMwNDQyY2E3YjQzNGQ5MDYxNyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiODU5M2I4MmRmMTExZjMwMmY3N2U4ZDI4N2EyNGU0NGZhNjVjYTMxYmZmNGYxNjFkZGIyNzgzNDc5MjI1NmRlYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzc4YzAzMWRlMTc3YTZlMjdhMWQxMzY5OTEzNzBlYzJhOTQ3YTBjZTRmZjQ0MmI3NjQzZmYwM2Q2NTZjZmI5ZSJ9"
         }
       ]
@@ -61,15 +61,15 @@
       "statement": "The /api/chat body is the chat JID as text, limited to 1024 bytes; oversized, empty, unreadable or non-UTF-8 bodies return 400.",
       "evidence": [
         {
-          "resource": "repo://src/web.rs#L221-L224",
+          "resource": "repo://src/web.rs#L247-L250",
           "version": "repo-lines-v1:sha256:4ec9fcc7650183cfb21c285788334f7540990a3b6ecc6c0102a5483b1042f772:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTllNmY5YjI2ZmYxNmJlMThiYjFjOTA0Y2MwNWJhZjFiNTEzMzFhNjkwYTNhZjc0ZGYxNzJmOTdiNzQ3NGI1MSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWU2NzNhYmQ0NTdhOTdlZWExN2E2MDA2MmViMGZhZWE2YmRmMDk0ODY4Y2M2YjhmOGMwZWJkYTJmNjFlZDQyZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYmQ3ZTRhZGRhNDk4ZTkwMmJjZDA5ZGI5YzQ5Yjc4ZmE2NGEzNDk0NjgxMGUyZDhiOTBiYThjOTVkNmRkYTNjZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTcxMDE3YWI5NThlNTM3MjhlNmNhMTY0YzEzOGY1NzNmMjRhOWZjZDVhNTQzOWQwNTY2MzA1ZTE0ODQ2ODJhZSJ9"
         },
         {
-          "resource": "repo://src/web.rs#L30-L31",
+          "resource": "repo://src/web.rs#L48-L49",
           "version": "repo-lines-v1:sha256:0a12154e8c7fb736954719a1bc6bbb6919359fd03e35f9e1fc18873bf3953b71:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiM2Q1NzUxODkwZTcxOGQzNTJkMDE1MTI4ZTdhYTNmOGZlYjgwOTg0MGFlMWI4OWFjNDlhNDFlMTViMjZiZjg0NCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiODQ2NTQ3N2VkNjEyOTdmNGI4NTAyYmEzNmU4YzM3Nzg1NGFlM2Q4YzgzMDA1YmExOTI1ZDA2Y2Y3NTBkNWU4ZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNzliYmEwYTMwZjdkZGRiODY5Njk1NTQ4NTc1OWE1ODg3M2YwMjM1NmVlNzQ2YjdhMjFjZmY1YmZjODhlODIyOSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzdlNGFiODViNjRhOWM4YTlkYThkNWI5Yzk0YjViZjgzZTRlZjhlNWNkNWMxNjY1MDhkODYzYTJlYTI1ZDVkMiJ9"
         },
         {
-          "resource": "repo://src/web.rs#L347-L364",
+          "resource": "repo://src/web.rs#L392-L409",
           "version": "repo-lines-v1:sha256:ae5215de4d7affbc92b010adc8ddd1dfad664b5e5f3aeb27dba89330c0f5036d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwODk2MWUyZDgwZWE0ZWQxNThjMDExZjkyYzQ4YjEyYWQ2ZjVhM2FmMjdlMjkwNDA5MDkyMTIxYTVhOGVlOWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEyMzhhMjNmMGFkM2ZkODI4NjY4NzhlZTcwZmI0ZmQwYWVlODlmODFiMDJmZTFmYmQ0YjVmNDAyNzI2MzBiMTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ"
         }
       ]
@@ -79,15 +79,15 @@
       "statement": "API requests are forwarded to App as AppEvent::Web with a oneshot reply channel, and the web thread blocks until App returns the WebState snapshot; a closed channel or dropped reply yields 503.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1009-L1025",
-          "version": "repo-lines-v1:sha256:6905da13ba37beaf073ac6f3f6eb07a12c4b6a34bcf57e4beca54f40fe6b64b0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3YTEzOTMxODdkZWNmZmM0NzMzYzYwY2FiYjA2MTkwYjdkYjAxMjJjYWEyYWM3YjRjNGUwNzBiNzIxNTk1NWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM2MGUyOWExOGZkNDE5NDVjMjM3NTgxMzM4ZGI3NWFjOTIzYzYxYjVmYmFiOTNlOTM3ODdlNWJmMGMyMjk4OTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ0NmM2OGRiNzFjZDkwMmMyZDMxY2QyYjQ5Y2MyM2ZjYzFmNjVlYWRlY2JlZGRmNDFhOWEwZjVhNGIxMzE1MmQifQ"
+          "resource": "repo://src/app.rs#L1139-L1156",
+          "version": "repo-lines-v1:sha256:bbc0ec4cc8f8ab99473d9ab5e429fb3b119ea7a8d7170646720a633d9ab98bc1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3YTEzOTMxODdkZWNmZmM0NzMzYzYwY2FiYjA2MTkwYjdkYjAxMjJjYWEyYWM3YjRjNGUwNzBiNzIxNTk1NWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM2MGUyOWExOGZkNDE5NDVjMjM3NTgxMzM4ZGI3NWFjOTIzYzYxYjVmYmFiOTNlOTM3ODdlNWJmMGMyMjk4OTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ0NmM2OGRiNzFjZDkwMmMyZDMxY2QyYjQ5Y2MyM2ZjYzFmNjVlYWRlY2JlZGRmNDFhOWEwZjVhNGIxMzE1MmQifQ"
         },
         {
-          "resource": "repo://src/web.rs#L114-L121",
-          "version": "repo-lines-v1:sha256:9dff2fd116ff7266dafbe9bcec01fa24bb6ab864bc1c054f5a6fcb6449d60e7d:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDgxZDE0MGYzZjljNjA2NWEyZmE5ZjY1ZTQwMmNjNjA0ZThkYmJhYmZlODc0YjAwODI5ZmNkNDhjZjNhY2ExNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiODZlMGVhYzgyMjFhNzg0OGFhZTUyYTNjNWMwMjM3YzFjMzAyMmU1MzU0NTU2NzgzOGJkMDMyYzhhNWJiYTQzZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTY5YTZmM2ExYjQ0MmYyNDllOTIzNWEzMTUxOWNkM2VkZTFlMDAxZWMzYzgxZmJhMWJiM2FlMTVhMjIwYjhmZSJ9"
+          "resource": "repo://src/web.rs#L138-L145",
+          "version": "repo-lines-v1:sha256:9dff2fd116ff7266dafbe9bcec01fa24bb6ab864bc1c054f5a6fcb6449d60e7d:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDgxZDE0MGYzZjljNjA2NWEyZmE5ZjY1ZTQwMmNjNjA0ZThkYmJhYmZlODc0YjAwODI5ZmNkNDhjZjNhY2ExNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNTgzZjQ1ZWYzZjM2ZTMyODc0MmY3MjgzOWZmODVhOWZlYTc4ZGVkNmNhYWMzZjk0ZjA1YzAxMjFmMWIyODc0MyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTY5YTZmM2ExYjQ0MmYyNDllOTIzNWEzMTUxOWNkM2VkZTFlMDAxZWMzYzgxZmJhMWJiM2FlMTVhMjIwYjhmZSJ9"
         },
         {
-          "resource": "repo://src/web.rs#L248-L261",
+          "resource": "repo://src/web.rs#L275-L288",
           "version": "repo-lines-v1:sha256:e50fde998c7b07b6e3110582bc1a3d6bad6d7a63d11a8ce8afdbe7deee340a4d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3NTc5MmI3MWNhYTk3ZDhhNGNiMzI2NGVkYTgxMGE2ZTkxMmY1NTFlY2JkNzAzMTQxMTg3ZmQ0MTZjMjM4NzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYyYjNmMGVkYWNhNjE2MDgyMThmODc3ZmEzM2NiNzJjZTc5NzMyZWJhMDc0ZjBkNTUxNTc3NWY4OWUwZTAwY2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgxY2RjMWUzZDg3ZmIzMTllNzNjMDBiNTc4YzJiMDNjY2EyNGUyOTM3NTcxYzNhYTczMTI4NDhjNGZmNTFiMjIifQ"
         }
       ]
@@ -97,36 +97,68 @@
       "statement": "The quit route writes its 200 response before sending WebCommand::Quit without waiting for the reply.",
       "evidence": [
         {
-          "resource": "repo://src/web.rs#L229-L246",
+          "resource": "repo://src/web.rs#L256-L273",
           "version": "repo-lines-v1:sha256:155b300223ddf57fcf0446c63d0fe37474bd4a7d5c77f97cb07f527646eac78d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE0YWQxNDUzZDYzOTBhMjY0YWY3NTcxNTJlZmI2ZDdhMDM1YWQ4YmQ4MTE0ZWZkMWRhZDI4MzAzZDM4NGViNmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk2YTMwMjE3MjllMmY0MzA2ZTIwOTY5YTU0YWM4YjFmMTA1MzIwMmM4Mzk4NWM5NzA4OTU3NmM2NzQ4YzE1MjAifQ"
         }
       ]
     },
     {
       "id": "claim_4ce4cb65f51445c9b7ad852328ccfa6f",
-      "statement": "The browser page polls /api/state every 5 seconds while visible, sends the X-Wh-Request header on every POST and orders responses with a ticket counter so stale polls do not overwrite newer toggle results.",
+      "statement": "The browser page polls /api/state every 5 seconds while visible (every 2 seconds while WhatsApp is not connected), sends the X-Wh-Request header on every POST and orders responses with a ticket counter so stale polls do not overwrite newer toggle results.",
       "evidence": [
         {
-          "resource": "repo://src/web_page.html#L330-L339",
-          "version": "repo-lines-v1:sha256:c9aeb2e41ffe90a632e19e8ce8ae720ca511a700fb78ede63aef855669e7593e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhNjA4YjllNWZhNTllMjI0MmFmODdlODAzOTJmMDI1ZmJkNDgxZGFiZGE2YjVjMmZiOTI4N2YyMjk5NzFhNTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmYTJjMTFkNTdhZDg3YjUxODk3NWM0MjEyNmFkZWNjZjI2ZDgwY2NlNjhmM2E1YzEwNDkxNzViNDIxODQzZTIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjliODljNTU5ZmFlMWEzMmMwNjAxNTg0M2VlNmFhMzFlN2Q2NTNiY2VkNmMyNTZjOGRhZTE5ZWFmMDY5ODBiZmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyNjg3MzIzNmVkNThhZGQ4N2E4NWJiMzQ0M2NiNGZiOTY0NmIzOTcyMjcwMzAzZTIyOTM5ZDE0MTI1YWYyMTAifQ"
+          "resource": "repo://src/web_page.html#L382-L383",
+          "version": "repo-lines-v1:sha256:4c8b0e851a2538b88a9455e95c394596dea318f931d8020c1d9573933faff85d:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOWE2MDhiOWU1ZmE1OWUyMjQyYWY4N2U4MDM5MmYwMjVmYmQ0ODFkYWJkYTZiNWMyZmI5Mjg3ZjIyOTk3MWE1OCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNjZhY2FmNWU0ODFjNGJmMzE2YTM4YTZhOTczNmMyYjBhNDFmNGJkYTFmOTQ4YzdkNTU3NWIwYWE3YTE1Y2Y3MCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOWI4OWM1NTlmYWUxYTMyYzA2MDE1ODQzZWU2YWEzMWU3ZDY1M2JjZWQ2YzI1NmM4ZGFlMTllYWYwNjk4MGJmYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYmY1MWRlZTMxZmYyMmU3NTE0YmRmOWYwNjNlYjIxOWMxZWI0MDYyM2ZlY2ZlMTI5ZGNiZjAyMGNjNjZjNTc4ZSJ9"
         },
         {
-          "resource": "repo://src/web_page.html#L373-L382",
+          "resource": "repo://src/web_page.html#L441-L450",
           "version": "repo-lines-v1:sha256:7b6d2b04110398692241256da2fc7b0be08d5ec5f56a928bacf625c9b8bfcb56:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVhZjIyY2YwNzMyNTkyYzI0NzE0MWNiZTYxZWZhMTEwMTI2MTJmNWEzMTIyYjlhZjliYmFmNTJmNTcyNGQxOTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZiNTgwZTk2NGY4M2E0NzNmNDNkNDQzODQ2Mjk0OWQ2OGI1NjAwZmRhNzg3ZjE2Mzc0NWQwMGRlODljYmYzNmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNlNDhiMTQzZTUxMjVjNzBhMjUyNTdmMTY2MzI2MDE1ZTBhYzdiYTBmMzlkOTY1ZTZjMmY1YTczZjlkZDVmYTkifQ"
         },
         {
-          "resource": "repo://src/web_page.html#L534-L577",
-          "version": "repo-lines-v1:sha256:f288c654dec0232c30b73b2211301190731dcaa22175fa8f43cdc6415fa9fd0a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVhZjIyY2YwNzMyNTkyYzI0NzE0MWNiZTYxZWZhMTEwMTI2MTJmNWEzMTIyYjlhZjliYmFmNTJmNTcyNGQxOTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUwNTY3M2NkNWYxZWYwNGEyMTJhOTRmZjljNWU4ZThmZjg2OTI1MzRiMjU5ZGVkYTYyMTNjYjhlMGE5OWRlZGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhjMjA2ZGQ2YzMwMmIxNGFiOTBhYzI0NjM5YzgzNGExNDhlMGMxMDZmMDAyMWRjZjYxZGMyODVhNjk1OThhZGQifQ"
+          "resource": "repo://src/web_page.html#L619-L700",
+          "version": "repo-lines-v1:sha256:c47b354da3f2a2f89fc81593d9acf896016b6125b92c47556517046b70378798:eyJzZWxlY3RlZExpbmVDb3VudCI6ODIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVhZjIyY2YwNzMyNTkyYzI0NzE0MWNiZTYxZWZhMTEwMTI2MTJmNWEzMTIyYjlhZjliYmFmNTJmNTcyNGQxOTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg5MTRjMDFmMzFhZjgzMzczMDk0NGE5MzZjMWJjNTg4MDlkNTkwNjAzNGMyNGNlY2EyOWE5YTU4M2MzYjE4MjUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFjYjQxNTIxYjRlM2FlYmVmYTc3OTBkMzMwY2E3NzE1NzJjNDUxMzU0ZTY2ZjI4Mjc1MGIyY2E1YTE1NmMxNjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTI5ZTkxZTBmOTljM2U4MjVkZTY5YjQzNWZiNjQ4NGQxYjBhMWRmMmY0NjU2MWFiZjFhMjlmMDQxODhiODgifQ"
         },
         {
-          "resource": "repo://src/web_page.html#L612-L635",
-          "version": "repo-lines-v1:sha256:54837e3c49fb90fcadbee23744b947ae4f8ab37b1f75712831b41a54ad5bc7b0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVhZjIyY2YwNzMyNTkyYzI0NzE0MWNiZTYxZWZhMTEwMTI2MTJmNWEzMTIyYjlhZjliYmFmNTJmNTcyNGQxOTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZiNTgwZTk2NGY4M2E0NzNmNDNkNDQzODQ2Mjk0OWQ2OGI1NjAwZmRhNzg3ZjE2Mzc0NWQwMGRlODljYmYzNmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc0YzRlNDU1MWQ1NTYyYjdkNzM1OThiMTYzOTIyNmEyOTE2MDJmYmRjZDE4OWM0NTliYThiMmQ3OTkwYjIzOGIifQ"
+          "resource": "repo://src/web_page.html#L739-L763",
+          "version": "repo-lines-v1:sha256:bdeccd31cb679fe52d1ccf1cfe46b8f36c2c4ca6a2ee5a02f02d5e018246a621:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVhZjIyY2YwNzMyNTkyYzI0NzE0MWNiZTYxZWZhMTEwMTI2MTJmNWEzMTIyYjlhZjliYmFmNTJmNTcyNGQxOTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZiNTgwZTk2NGY4M2E0NzNmNDNkNDQzODQ2Mjk0OWQ2OGI1NjAwZmRhNzg3ZjE2Mzc0NWQwMGRlODljYmYzNmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc0YzRlNDU1MWQ1NTYyYjdkNzM1OThiMTYzOTIyNmEyOTE2MDJmYmRjZDE4OWM0NTliYThiMmQ3OTkwYjIzOGIifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_9c11f6a9f5b946d2b4092f7244c30d4f",
+      "statement": "WebState also reports the current screen as connecting, pairing or main and, while a pairing QR payload is held, the QR code as an SVG document rendered by qr_svg; an unencodable payload yields null.",
+      "evidence": [
+        {
+          "resource": "repo://src/web.rs#L123-L136",
+          "version": "repo-lines-v1:sha256:fbad46fb3a55d90c9fc4c33c1b9c90419816360630851b1b616ef2cbf9f6c491:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3MTZiNTVhZWU2YTVlNjNhM2U3Y2FlOGY2YjlhNGU0MzRhMmZmYTE4NTBhMjhhNTFkNjRjZmUzMDFmZjkxMmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNlN2E5NWQ3ZGMzNzRjNzc1YjVlOWQ0OTE3OTFmYWRjYzA3NjdkYTFhOWFkNmZiMTgyZGNjMjk5NTc5YTcyOTEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE4OGVjMTc0NDhiZWQ3ZmI1YjcxYWYzZWUyZjAwY2M5MjAzMTJlMDM2YzBiODQxZTdmNGE2OTVhNjlmMjViYzMifQ"
+        },
+        {
+          "resource": "repo://src/web.rs#L34-L46",
+          "version": "repo-lines-v1:sha256:5441ac1ffdef7b159cbf366e69f7988f719b0b5b0070fa9c484fc6ece13f85bd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQwY2Q2M2U1YTllNmFjMzM4N2E2OWM1NTBmNWQ5YzIzMGIzN2NhZGM4ZDI2ZDllNTI5MGVkMzU3YzE5NzY0YWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVlMTI1N2EyNDMwZTYyZDRjZTU3ZTQ2MjlmZDg0YzNiYTcyY2RhNDgwMDM3ZjIzZGY4MGVkYzZiNDA0NDg4MDgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYzYmEzMTQwZTgwZjNkNzlkNTQ2YjA5YjgwZTljY2RjMGM4ZDk5ZjllNmYyNjljMjUyZmE0OWQyNzBlMGZiMjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgzYmIyNTJjMjU1NmVmMTM5MWJjNDU1YmEyOTAxZWQ5MTczNmQ2Njc2MmEyZThmYzExMjJlYTFhMWYzYjVmZmQifQ"
+        },
+        {
+          "resource": "repo://src/web.rs#L364-L379",
+          "version": "repo-lines-v1:sha256:e6d58dec7c8a45140e76290e175a7b2f5a9b3c59f60b3e244064813974a3ed1d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMxYjQ2MDBlYjQ4M2JkNTEzNjA1ZTE0ODVkNmRjNmNjMDA3NjJkNzRiODNjNzg4OTQ2MGQzOWNkMGRjMzQ1YTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ0NGIyYWRlMGYzNzRmOGZjZGYxNGM5MTQxYzNkNTMxYjcxMzgwN2ZkZTc0NzI1NTZiOWJlNDI1MjE5NzM4OWYifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_6434b6fdb46f4db6958e31fb3b9a6f55",
+      "statement": "The browser page shows a pairing card with the connection progress or the QR image while the screen is not main, and its 'Nuovo QR' button asks for confirmation before posting /api/relink and applying the returned state.",
+      "evidence": [
+        {
+          "resource": "repo://src/web_page.html#L596-L617",
+          "version": "repo-lines-v1:sha256:2996469d07728fe9a9430abce7e412c174f30205d800ed61434df03a8c0d8913:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYWQ3OTc4ZjRiODU3NDhjOGZiNzEzYzlmZjAyMTM3MDYxMDVkZWY3MjQwMmNjMGQwOThmZTg0MTUwYTRiYWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFhMjkzNzIyNTU4YzUyZGEzMGUzYjhjYTFjMTkyYmE1ZjlmNjA0NTNiOGJkMThmMjhlYjEwYTk5ODgwNGIzZTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjExZTBkOTIwNWRlZGQyZjZkYTM2MTlhODdmOGYzZDY5Nzg2MjQ5YzAyZTVmYjAwNTdkODdkNzE5ZGIwNjMyY2IifQ"
+        },
+        {
+          "resource": "repo://src/web_page.html#L703-L718",
+          "version": "repo-lines-v1:sha256:edcf716f5c77bc9ba443f65a19535e9c1b254b9949a238743590df637a35157e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVhZjIyY2YwNzMyNTkyYzI0NzE0MWNiZTYxZWZhMTEwMTI2MTJmNWEzMTIyYjlhZjliYmFmNTJmNTcyNGQxOTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc2NTNiYWEyY2QxMjM0ZDlmZTgzOGNiZTJhN2EzODc1YTcyMWMxOTRmNGRjMjZiZTgwYjZlOTU5MGY2YzE3MmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4MjJhZjlkZGY3ZTI1ZjQxYTFmZjU1NDA2MDljN2FhNGNhZmZjODBkMjhjM2VjNjkxY2ExY2RmOTMxMzQ0MDMifQ"
         }
       ]
     }
   ],
   "verification": {
     "by": "openwiki/0.7.2",
-    "at": "2026-10-21T21:49:12.291Z"
+    "at": "2026-10-23T21:26:24.156Z"
   }
 }

+ 82 - 28
openwiki/.claims/integrations/whatsapp-client.json

@@ -1,6 +1,6 @@
 {
   "schemaVersion": 1,
-  "pageVersion": "sha256:6a3c8a9d9be1d3bc11179381bdb384623d59cb3e2c026f4f3d62b3bf3a5f84da",
+  "pageVersion": "sha256:18ed8fdf1d8ab4689b600aa550246fd3d6b6b26340ea7a345c775d128f40bc56",
   "claims": [
     {
       "id": "claim_5cd95bce2100497cbe7ac9e4c2cb1856",
@@ -14,11 +14,15 @@
     },
     {
       "id": "claim_6cd32dbca8f44ae0bf204d00ff7a238a",
-      "statement": "start requires a UTF-8 session path, creates its parent directory, opens SqliteStore, builds a Bot with an EventForwarder, spawns the Translator and bot, and emits Disconnected(client stopped) when the bot ends.",
+      "statement": "start wraps the Session built by open_session in a WaHandle; open_session requires a UTF-8 session path, creates its parent directory, opens SqliteStore, builds a Bot with an EventForwarder, spawns the Translator and bot, and when the bot ends emits Disconnected(client stopped) and marks the session stopped.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L208-L241",
-          "version": "repo-lines-v1:sha256:4cd0297c9556cc47b7469013a1a4cd7bc98f9cf55a80ed52c1d041a5958b527e:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM1Yzc3OTRhMTU2OGMwYTkxN2VkZGI5NWIwOWQyNzRkZDQ1ZWJjMmE1NGQ4NjhlYWU1NTJkMTEyMDIzMzMyY2IiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY3YmZiOTBiNDk2OGUyODM0NjAyZTY1OTA0ODBkNWNkY2Y5M2M2YjgyOWYxOWIxODkxYjQ2YjFkZjY0MGY3NDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjIzNjEyMDNhNTJjMmI5YTk4MmJjMjVkNWE3OTU1OWIwNGJlNjAxODcxYzZlZjk1ZWExZTMzNzU4MmM2YTRlZDcifQ"
+          "resource": "repo://src/whatsapp.rs#L298-L312",
+          "version": "repo-lines-v1:sha256:803ade08ccaecd3502b4e412e74510353b9c4495ad8c7ba92531c66c20e7145c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzMWZkNzY1ZDk5ZGViYjBhNjQwNjJjNzhjMzdmYjVkYjZlODdmOWIwMmJmMDVhOTE4NzQ5NGFmYmM4ZjgwYmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjIzZDg5MDg5YWZjZjlkYTg2NDg4ZDFjM2E1NDk2YzEwMDIxZGNlMWE0Mjc2YTYwNWUyNzY3ZWM0YmVjYzliYWUifQ"
+        },
+        {
+          "resource": "repo://src/whatsapp.rs#L369-L416",
+          "version": "repo-lines-v1:sha256:dd10b6e01369abf7053e52f04c2399ba9fe4a096f9593c2658e91f60650f6fbc:eyJzZWxlY3RlZExpbmVDb3VudCI6NDgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRmZDNiZDg2NTQyNDAyYjk5Y2JjNDI1MGY1YmQ3YTE5MmNkM2VhODgyNGFjNjFiMDI4NTc1NzQ1MzZmNDAxYTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjIzNjEyMDNhNTJjMmI5YTk4MmJjMjVkNWE3OTU1OWIwNGJlNjAxODcxYzZlZjk1ZWExZTMzNzU4MmM2YTRlZDcifQ"
         }
       ]
     },
@@ -27,11 +31,11 @@
       "statement": "EventForwarder forwards raw events in order to an internal channel with interest limited to SUBSCRIBED_KINDS, which includes OfflineSyncCompleted.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L244-L265",
+          "resource": "repo://src/whatsapp.rs#L419-L440",
           "version": "repo-lines-v1:sha256:90cc37eed600d805f12bf6cff91abb61379cdfa881ee401bcd902a8e11133f60:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxN2YyYmY0MzZiNzViNGYzNDUyNWY2MTk0NjU4OGE5ZmQ3MjRkNDA2ZDNmODk0NjI4MTE2ZGRlNzg0NDVmNTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZiOGQyYjQ2OTczM2U4ZTNmOWVjNWRhMDk2Njk3NDlkMDJiYTczYjM1YTQzYmQ2YzRkNjNhYTE4M2FkY2U5ZGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU0NTQ4YjU3YzUzZDdhYmNmY2QwY2E0YTJjZGZiZjdhYjk4NWYzMDYzMGM1N2FiYTA2Y2U1MGZmYjc2ZjZiNmMifQ"
         },
         {
-          "resource": "repo://src/whatsapp.rs#L51-L70",
+          "resource": "repo://src/whatsapp.rs#L55-L74",
           "version": "repo-lines-v1:sha256:361053245006ac3f73c75b71c22349865d2290626d5d26e9439e3cedd7193959:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkxN2YxNjJiMTNkNjQ1MzcxOWQ0ZTgwZGQxNzc3NzM5Yzc4Y2M5YTIxNDM4MTBmNDU5ODBhOGQwNWUyOThjNzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwMmQyYzBkNmRjNWU3NGQ5MmZhMTRiOTFkYmVhMjY0MDkwYmViMzVmZGI5MTg4ZDliMzQxMTgxZGNlOWY0Y2UiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNlMzgwNWRiNmFmZTQwNDJiYTcwZTM1YTQyNGFmYWEwZDIyOTUxYzUzZTEyZWU5YThiMTFhMGRlNjZlMDMyMjciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhjODM5ODYwY2FlZWIwMmYwOGMwZTE3NDIwYTNjOTFlYTRhYmY0NTg5OGUwNGI3OTIzZjRjYmFhZTRiNjg3MTIifQ"
         }
       ]
@@ -41,7 +45,7 @@
       "statement": "WaEvent has Qr, Connected, Disconnected, LoggedOut, OfflineSyncCompleted, Message (with an optional image reference), Contact and Error variants.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L81-L108",
+          "resource": "repo://src/whatsapp.rs#L104-L131",
           "version": "repo-lines-v1:sha256:0c68b0b9c76d57c34f906ece59c675e4be58899b2a26f2631d9ee4722c3e8379:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUyMDliMmJhYjE2NjZhZjkyYjQxZWViYjI0MWM1MGQ5ZjI5NmRiMDViOTMyNzI5MzExYzFjM2NkYzY3ZDUxNDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIxNmE2ZjRjMmRkNDg2YjE4NDI2YWUzNGYyMjc5YWViYjM4OTY0OTM3MTE0YmZmZjc1NmY4NGIwY2RmZGM2NzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcwODc1MzQwZmI2YTRmMjNkNmJmMzU5MGU5MzU3ZDBhODNjMjQ0NWM3OGQwYTg4ODc0ZjQxMGM0OGRlMzA1NGIifQ"
         }
       ]
@@ -51,8 +55,8 @@
       "statement": "Translator::handle_event maps raw events to WaEvent, including OfflineSyncCompleted, and turns failures into descriptive Error texts.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L413-L459",
-          "version": "repo-lines-v1:sha256:2e6abdf7a1ad4472d0ad4892b83fcfcab8abaa1e22f9809e3ff9cc172d6d6669:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmNWQyMzJiN2E2YTQ3NzhhZWE4NGU5YTIzNDhjMWU3NDdlZTdjOWY2NzUxNThjNWEwMGMxNDIzMTBlYTAxNTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg2ZDM0ZTcxMTQyZjZhMTJjYmM1NzA1N2QxMTc0ZGE2OGM3MjBkMmRlNGY1YmRjZDMxN2FhMjVjZDljYTE0NDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM2ZTYxOTdhNjY3YmI1ZjgyYzI4ZDdmYWM2OGIzYWE1YzU1YTk5NmQ2NGRkYmYyOTI3ZWQ3YjAyNzk5MDI0NWYifQ"
+          "resource": "repo://src/whatsapp.rs#L585-L634",
+          "version": "repo-lines-v1:sha256:be3833261dc69977863314832b6160d74b61cfd510ba80f67d687d7b53649355:eyJzZWxlY3RlZExpbmVDb3VudCI6NTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUyYWRmNTE3NDJlNmQ4MmEyNDE0NjE5ZmJlOWRhOTMzZGIxYzVjYTZlZGIwZjRhZmJhMGE5NmMyNGEzNjA1OGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM2ZTYxOTdhNjY3YmI1ZjgyYzI4ZDdmYWM2OGIzYWE1YzU1YTk5NmQ2NGRkYmYyOTI3ZWQ3YjAyNzk5MDI0NWYifQ"
         }
       ]
     },
@@ -61,7 +65,7 @@
       "statement": "canonical_jid prefers the phone-number JID, resolving LIDs via the alternate JID or the store LID mapping, and reports lookup failures.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L366-L381",
+          "resource": "repo://src/whatsapp.rs#L541-L556",
           "version": "repo-lines-v1:sha256:c9a3a286dc3e2c684087cd5ca273467ac6be5cc180556477e58883a1587ad161:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjNzhjNDlmYTU0ZmZiOGEwMmNhNDczYzhjYTFmYjY3YjVjNDQyMjBiNDUyMDYzNWI0NmI1MjgyODRkODc1MjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY3ZWQ4MGI0MjM1MDQ2ZTljZWQ4OTEyNjc0ZDRhMTMwMjM3MzJiNTNhYThhNDdhMGYxNzUyNGI2ODM1ZjAxM2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcyNDliZThhYWJlNTBkZDkyNzY5NDkwNzViMDI3NTA1Y2Y0ZWQ3MWE3NmIxYmJiZjg0ZjFhZTFmMjdkZDU4Y2UifQ"
         }
       ]
@@ -71,7 +75,7 @@
       "statement": "handle_live_message skips status broadcasts and silent messages, canonicalizes 1:1 chat JIDs, notes push names and attaches an ImageRef only for image texts.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L475-L521",
+          "resource": "repo://src/whatsapp.rs#L650-L696",
           "version": "repo-lines-v1:sha256:a4d4ee9a079e03eb111e30743b9678dab815a424e54ce5a0be4a0f1ddd68051c:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkMmYxYjJjNzYwNGI3NDk3NmI5YmMyNjRkMDBlYjJlODJlZWM0MTk5NzE4NzJjMWFjOTE2OTJhNDQ1ZjMxYmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdkZTRkYTlhNTUwNmQ1MmI3NWUwOTY1YjY0ZWI2Zjk5ZDhjMWE4MmZjMWU1ZjE1YTZhMjY0MDhlYjc2MmJlOTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY2NDFiNWZlM2M5YmRjNTg2MGQzNmM1MzMzZjMyYTY2OGU4OWUzNWJjZTBiNjU2ZTViYjY2MjVhNjFhNTE3ZWQifQ"
         }
       ]
@@ -81,11 +85,11 @@
       "statement": "Contact names are ranked Push below Saved and note_name never lets a push name override a saved one.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L267-L274",
+          "resource": "repo://src/whatsapp.rs#L442-L449",
           "version": "repo-lines-v1:sha256:63fcb3bcc3fee239b6af98cdaeb31163e5ebb60f59467e221c3df0d186d7af3c:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjU2NmIzYWVmYmU1NWMyMjE5ZTM3MzEzYmFiOWMwZDZkNGNmY2Q3NzUzMjZiMDQ1NWUxY2Y1YmM1MzE3OGZiNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMjg2NmRmMDRmMjc4MGQyOWI2MDQ1YWM1OTM2NmUzZjI2OTk5ZDIxMTdjZmVhZTcxY2ExZGQ5NzcwNGE5YTY1YiJ9"
         },
         {
-          "resource": "repo://src/whatsapp.rs#L329-L347",
+          "resource": "repo://src/whatsapp.rs#L504-L522",
           "version": "repo-lines-v1:sha256:0cb33ca45f9b72198804d2a68dffd870e11f0018eff0f5b4d6e6639f0dabecb2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI3YzZkOTQ3ZWQ4OWVjMDgxZmY1ZDUxZDliMzYxOWRhOWRmMjZhODdlZGYxM2MwNDMwMzhiMDRjN2E5ZTliZWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmYjQ4MjFjNTZhMzYwOTAzMGFlYzM0MWQ4ODI5NDhmZjBlMmYwY2VlNDQ1MzQ0YzczNTJiYmMwYjlhYTA3Y2EiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY1NmEzOWUwZGJjMTIyMGQ0OTg3Mjk2NDBiOTEyNWQ0YjIxMWMzNmVmZGRlODVkMWIzZmM4NjgyZTU2NTE0NWEifQ"
         }
       ]
@@ -95,11 +99,11 @@
       "statement": "History sync chunks are decoded in spawn_blocking and emit push names, inline contacts, conversation names and messages without image references.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L555-L647",
+          "resource": "repo://src/whatsapp.rs#L730-L822",
           "version": "repo-lines-v1:sha256:6c5078dc648f03cc0bba1f6aced5618a9773cf5ce4e6b821cd9284ef4c4268ae:eyJzZWxlY3RlZExpbmVDb3VudCI6OTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAzMDUzNzcyNjU4MGYxZDA2OWQxMjUwNTI3MmRhODg5OWM0M2RiYmZiZmM4NmZiY2IzOGJhMmNhZWE0OWU4NDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgwMmNlYTE4N2JjY2NlOWZhNDkyM2M4NzAzZDliYjk3ZWZhZTZkNTk2YTc4OTQ2YTlhZWMzMzc4MDM3YjU2MWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5NzM0ZmViMmI1ZGU2NjEwOTU2ZjI3N2RjMzAyMWJjMTZhNDVhYzBhMjc2MWM1Y2YwOGM3M2NiZjA0NTU0MWYifQ"
         },
         {
-          "resource": "repo://src/whatsapp.rs#L704-L745",
+          "resource": "repo://src/whatsapp.rs#L879-L920",
           "version": "repo-lines-v1:sha256:8c2114ae474695a3523c19bf0ceac407e82c82d1882bd2d0511a38cab8eeaaa0:eyJzZWxlY3RlZExpbmVDb3VudCI6NDIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1N2FhZjc1ZDg1ZGRhNjUwNDRiYzA5YmE2YjgxN2ExODFlMDNkZmIxNWY5MzIwYWIzNGZmYjkzOTc2MzljZGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjliYjA3YjBjZTBkNjhiMjljNDMxZWQyZjUyODcxN2Y0MDY3ZTA2NzZlNWViZGIzNzQ2N2Y5ZWZjNTM1YmI4YTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE4ZDQ3NzIyZjlhZjNiZmQ2OGYwMzRmYTIzMTgwNGJjMTViOGUxNGIwMzY3Y2FmYmIzZDc2YjI5YjU3ZDQ2YTQifQ"
         }
       ]
@@ -109,32 +113,36 @@
       "statement": "describe_message renders text, image and audio placeholders, captions, and silent or generic placeholders for other message kinds.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L836-L894",
+          "resource": "repo://src/whatsapp.rs#L1011-L1069",
           "version": "repo-lines-v1:sha256:2b1c51b48fce477ab09efb54b0e727a20b91bb212e43c005ffb0ba48ba3f3c2b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNjNjE2NzkwYTVhOWYyMTUyNGQyMTkzMGYwODhiYWNiYjkxZmYwNGQ1YTcxZTQzOTljNjEyMjdiNTVlNmRiODAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkwNzA2MWVjY2M3ZmRhZTJiN2I0MjlhYTAzYTZkMTNmMjNjNTY5NzZmMmRmOWU5Y2UyY2ZhNjM4ZjdlMTQ1ZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcwNzFhZGQ3YWQ1Mzg3NDYwZWJmZWY1YTkyOGQyMTAxZGJkMGQ2ZTU2YzRhMzliNzA0NDViMWM3OTU3NmZlNmEifQ"
         }
       ]
     },
     {
       "id": "claim_53e0859f92954befb4c58eddf9b42402",
-      "statement": "send_text rejects empty text and returns the message id; download_image keeps images in memory with an image/jpeg MIME fallback.",
+      "statement": "send_text rejects empty text and returns the message id; download_image keeps images in memory with an image/jpeg MIME fallback; both fail while no session is running.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L150-L183",
-          "version": "repo-lines-v1:sha256:e1962d0636f891f6df0c21c92dc4855febf775ec84d05389c1d53f37c8afd846:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU1MzA5Y2ExM2M4ZDJlNTg5NTFiYTlhZmJlNzM4OGIxMzQ2MGUwMGUzODU1NGY5ZDE1NWVmODc4ZmJjZGU0NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI0NDRlMWZiZDYyMWQ5MjhjNjUyNDE1NThlNGM0YmIzZjQyZjI1ZGQ1NzY5MjU1YTI5OGZmZTlkZWI3NDE2NjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMwZGZlODgwMmNkNjM3Y2NmMjZjNWIwYjZhOGJhYmNhOWRhYzI5NTNlOTRmYjA4YzFjNzA1NGVkOWUyNmYwMmUifQ"
+          "resource": "repo://src/whatsapp.rs#L200-L210",
+          "version": "repo-lines-v1:sha256:8ed098e34276e2c4b9ff74e786b647375c2d873d70640b609b0ae4bca9505394:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwYjNlNTU4ZDczYzgzNWI1NWVkY2U4NWUxNTMzYjM1ODczM2U1OWU4ZGNmYTJmZTM3N2FmNDBjZTcyZjY4OTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE3M2Y4MjFiY2U5NjYxNDdhMDEyMmRjYjVhZGExZmVjZjgxMWIyZmNiNjIwZDUwZGFiY2U2MTg0OTUzNjVhNWUifQ"
+        },
+        {
+          "resource": "repo://src/whatsapp.rs#L239-L277",
+          "version": "repo-lines-v1:sha256:6876a8217f10e021bfed1374f2f51f88f81c6fd8b45ae385c7fdf5056b93c1bb:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRhZmU4MDgzNzljMjNlNzE5MThmNzNiNmYyZTU3ZGZjYjIxM2U3Y2RhNDhhZDZlYzY5NWFlNGFhODZiODIxNDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMwZGZlODgwMmNkNjM3Y2NmMjZjNWIwYjZhOGJhYmNhOWRhYzI5NTNlOTRmYjA4YzFjNzA1NGVkOWUyNmYwMmUifQ"
         }
       ]
     },
     {
       "id": "claim_295e7481cd9a409e8d2013d1512338d8",
-      "statement": "own_jids returns the account PN and LID without device suffix and is empty before pairing.",
+      "statement": "own_jids returns the account PN and LID without device suffix and is empty before pairing and while no session is running.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L191-L197",
-          "version": "repo-lines-v1:sha256:7ea1e000732e0183f2e0d6671934cf8532875e711139eacf688d202ddfc274f2:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2ZkNDJkOTZmZjZjNWQ3ZDg2YmZjMzI1MDQ4MzYwZWEwMDE4N2VkNzhlNTM1M2Q0NWY0ZGI2NmZiNDBiYjk2NyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOWY1NTZkYTZjNjZkNTE3ZmZkMmYzMzkyOTY0M2Q4Y2UwMTg5YzNmNTJmNDJhYjc3ZTExNDQ3NGYxNzQ0YzZiMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMWJjZmRiYmE1MDBmYjJlYTIxOWFlN2I0NDQ2ZTNlNTU1MDhhZWIxZDdmN2NiYzgwZjE1NTI2MzMzOTlkZjUxYyJ9"
+          "resource": "repo://src/whatsapp.rs#L1000-L1002",
+          "version": "repo-lines-v1:sha256:2dff1120b156dde5cd502a03211b000f533177e7214711f8a8a081955c3663ac:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOWY1MGVlZDdhYWNmZGE2Mjk4Mzg4Yzg2MmI1ZjIyNDNlNzIyYWMxNWEyY2FkNzAwNWUyOWQ1NzIxMmE1YThiOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGNjZjc5MzkxNTY5MjhjYzk4Y2ViZDIwYjJlYTA2OWM0ZWNlZGJlZWE5Nzk2MjBiN2IzNWUzYTY5Mjg3YWE4MyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2I4MWNkZTZlMzVlZWI1MjRlMzM5ZDFlMjM1MzE5Y2Y1NTI5YjFhNTBhMmE2MDUyZmUzNWI1ZDQ4NWRkOWUxMyJ9"
         },
         {
-          "resource": "repo://src/whatsapp.rs#L825-L827",
-          "version": "repo-lines-v1:sha256:2dff1120b156dde5cd502a03211b000f533177e7214711f8a8a081955c3663ac:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOWY1MGVlZDdhYWNmZGE2Mjk4Mzg4Yzg2MmI1ZjIyNDNlNzIyYWMxNWEyY2FkNzAwNWUyOWQ1NzIxMmE1YThiOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGNjZjc5MzkxNTY5MjhjYzk4Y2ViZDIwYjJlYTA2OWM0ZWNlZGJlZWE5Nzk2MjBiN2IzNWUzYTY5Mjg3YWE4MyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2I4MWNkZTZlMzVlZWI1MjRlMzM5ZDFlMjM1MzE5Y2Y1NTI5YjFhNTBhMmE2MDUyZmUzNWI1ZDQ4NWRkOWUxMyJ9"
+          "resource": "repo://src/whatsapp.rs#L279-L295",
+          "version": "repo-lines-v1:sha256:2716d1991bf1de21e1b70d5a1702f8e881af4787f4afd11837ef8767c63b07b3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2OGU3Zjk2MjU1MTJjM2E1NzQ5NDIzZDA2MDc0ODRhZjQxODc4NThmZDhkNjhhMTcwMWQ1ZWI1ODA3MWU4ZTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFkMjVmMDBjOGVlOTdlZDMzZGJiMTNiMzNiYmRjOWEyNjhjMzhkODZmMjlkYTQyYTg1YjBkN2U0NGE0YTYxNzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFiY2ZkYmJhNTAwZmIyZWEyMTlhZTdiNDQ0NmUzZTU1NTA4YWViMWQ3ZjdjYmM4MGYxNTUyNjMzMzk5ZGY1MWMifQ"
         }
       ]
     },
@@ -143,22 +151,68 @@
       "statement": "An AppStateSyncFailed event is reported as an Error only when at least one collection failed fatally; retryable or skipped collections are not shown.",
       "evidence": [
         {
-          "resource": "repo://src/whatsapp.rs#L989-L992",
+          "resource": "repo://src/whatsapp.rs#L1205-L1208",
           "version": "repo-lines-v1:sha256:af6676593e9e90c7ac6a4b2b64625d687ad77fa0265e7f46bb6b8dbef4446d86:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzFiZTM3ZmI4YTA3YjI0YzY1ZmVjNGQ3MGI2NWNhNDAzNTU5ODQwOGE3NDhhYjg2YWY4YTY4YWJlMGJjMTNhYSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYTAwZGIwNDBmMjkwOWJmOTc4ZmYwYTk1NmE1NWU4M2M2NTM0MThkZTZiYjY4ZjgwNDFkYTk3NmY4ZDNkMjI3MCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYjQ1MTJjNzhmN2UyZTJiODdhNDZhYThlZWUxYTg3YjIwZjZjMWE0Y2MwZmNkNDQ4ZDhmZjJmMWJlOGYxMGQ4YSJ9"
         },
         {
-          "resource": "repo://src/whatsapp.rs#L437-L446",
+          "resource": "repo://src/whatsapp.rs#L612-L621",
           "version": "repo-lines-v1:sha256:55006adc8131456b9811de9f46520c07874aab146e6d5deb0b359a138bc063e8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljNjAwZjllYTA2YzVjZTAxYjEzODExOGM5YjdhZDVjNTUxYzBhMmNjZDkxZTJmNWQxMWI4OTNiMWU5YzdjN2IiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MjJjNDJjZDE5YzZkMDNkODA4YzQ1MjcyMDU1MjgyNWI3ZWExYzViNDkxYjIxODUxMWNhYzc0MTVjZjlkOWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlhNDQ2MGQwYzUyNjkwYzM3YTQyOTI4MTc3NjkzNmQ4ZDA3YTJhYjg3MDEyYTRkMDY0MWNiMTQ4ZjkzOThjOGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ3YTNlYmViMDcyOTcwMjY0MTFmZWVhZWViOTNkODBhMDZjOGQzNmE0OWE0NzgyNjhiZDFjNjJkMWRkOTk2YTkifQ"
         },
         {
-          "resource": "repo://src/whatsapp.rs#L803-L810",
+          "resource": "repo://src/whatsapp.rs#L978-L985",
           "version": "repo-lines-v1:sha256:e6e70ed71f30e7f2d2201053fec0c50f7a478e2081980f40b73ee55fd11ccbfe:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiN2ZmYzA3OTdhYmQ1NjAwYmU5MWY4MjMyNWZmZjhlMjllMDVmNDY3MGZiOTQ5YzdmODZkZTRlZmJhOTQ5ZmIyOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzYxNmY0NTJmYTU5OWI0YjM5MWIwMmVjYjI1OTNjMzk3ODVhMDFjZDZiZTIwMmM0OTQxMjk2NGViYWQ3YzFmYyJ9"
         }
       ]
+    },
+    {
+      "id": "claim_3cfa1130ed5b45e181ff70a32682a79e",
+      "statement": "WaHandle holds the running Session in an Arc<Mutex<Option<Session>>> shared by all clones together with the session database path and the WaEvent sender, and client operations fail with 'WhatsApp session not running' while the slot is empty.",
+      "evidence": [
+        {
+          "resource": "repo://src/whatsapp.rs#L160-L180",
+          "version": "repo-lines-v1:sha256:0f8cd1675d28ef481cfcb8fdf5554a3529190b4edb526cd8f974d32643f56909:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdhYTg4OWY5NzI4NTVlZGJiMWEyNzExOGE1NDY1ZTc2MjhlM2NjZWYzMDg2OTU5YThjNGZlOTViMDJmNDM1ZWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVkYjk4OGU0ZDZiYzFkOGViNDU4NmY1NWY3MDBlNGVjY2EzYjhkYzliZDM2YzJjMjgyNDQ2MDhkN2M1NWEwMjAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjViODNiNGUwM2Y4ZmFkZmM1OWI5NGZjZjhmNGUyYTI2ZjNlNTdmMjI0NzczODU0NjVmNjAzNjBhODQwNDdiNTMifQ"
+        },
+        {
+          "resource": "repo://src/whatsapp.rs#L200-L219",
+          "version": "repo-lines-v1:sha256:5de6b5901fa8a014bd2fffe970ba820db963f438b9ec7bf051f62c957198ecd2:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwYjNlNTU4ZDczYzgzNWI1NWVkY2U4NWUxNTMzYjM1ODczM2U1OWU4ZGNmYTJmZTM3N2FmNDBjZTcyZjY4OTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ4Njc5ODk5ZGJiYzc0NzgxOGMzNWI3ZTNiMjY2ZWYwNTQyMWJmNTJkYzBmNmU4OWI4N2VhZjcyZjAzMzQ0NGEifQ"
+        },
+        {
+          "resource": "repo://src/whatsapp.rs#L85-L86",
+          "version": "repo-lines-v1:sha256:ee851fc74e7caf112f9b070991f9371e3bbc3d48aad2bfcd6b2c38abde32e4d4:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjE5NGEwYTk4MmFhMjdkODcxNzRlZGY2MzkzNDUyYmE3NTNmYmU1NTBiNDc3Zjc3M2FlNTFhMmI4Y2YxNGU5NSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTk2ZTRkZmU0ZWMxZjgxODE2NWRkZmU0M2RjOTU5YWNhM2UwMDA3MWM2ZGJjMmEwOWUyNTM3YTE5YTZjNjA0MyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjE2YTZmNGMyZGQ0ODZiMTg0MjZhZTM0ZjIyNzlhZWJiMzg5NjQ5MzcxMTRiZmZmNzU2Zjg0YjBjZGZkYzY3OCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNjE0YjMxNTYwYmE5YmZmZDJjODQ2OTVhZjE5MjM5MjBkYjBiODhhYjVjNDA1NmUyNmVlZDljNzVhMjNlZGIwZCJ9"
+        }
+      ]
+    },
+    {
+      "id": "claim_e16610e399df485b809df40d9aa68966",
+      "statement": "WaHandle::relink takes the current session out of the slot, stops it (logout, wait up to 10 seconds for the run loop, abort the translator), resets the session database files and starts a new unpaired client whose QR codes arrive as WaEvent::Qr; on error the slot stays empty.",
+      "evidence": [
+        {
+          "resource": "repo://src/whatsapp.rs#L182-L198",
+          "version": "repo-lines-v1:sha256:bc94058fad104fda8ef2d9889e6539ac65f19dd28226e1edec14d240905e4aa2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwMzUyMWI1YmRmYWViNzk2MzY5ZTExMzEwODIwMjIwZTNhMTI1MGUwY2E5NzIyMGEzYTI1OWFkMjQxNTcwZGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg3NzllYzNlN2JjNmZkMzdlNDYxMTdhODY2ZDA2YWIzZTVlNGVlYmE0ODJjYmViYzkzZjhjZGRjZjYyOTM5ZTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM4YzBjZDA2MzU4OWY4OWUzMTY0ZTQ3MjczYzg2ZDQzYjBjNzkwZTc2OThhMDFmNTU1NGQ1ZTUxODkzMmRiYjYifQ"
+        },
+        {
+          "resource": "repo://src/whatsapp.rs#L221-L237",
+          "version": "repo-lines-v1:sha256:63e137b22805f72a6e35c74bf1f9296c30d5dd1c696101d7b7db2f3dedb3b664:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY4YjU3MmE2ZGZjZGNjZDc1YmE0ZDBkZDZiYmEzNjUwY2UwNDFkZDkxYzRjNjE1YTBmMDJhNDllMjNlNTA2OWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIyNTdmMTI1NGQ0NjJmZjgyYTFmYTFmZjYyNzFlNzIzNTM1NTFkMDZlNjMxMDQ2OWYxMGZmZWUxZTY2ZmVlMjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OTU0Y2ZjYmI3ZmVlMDA0ZTg0M2I4ZDdmZjAxNTA3ZTlmOGU5YTYzNWI1MjZlOWQ1ODRmNzY1MDA4YjRiZDYifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_8358782b995d486dba455c1f98c6614e",
+      "statement": "reset_session_files deletes session.db and its -wal/-shm side files; when the database cannot be deleted (still open on Windows) it empties every table except SQLite internals and __diesel_schema_migrations in one transaction with a 5 second busy timeout.",
+      "evidence": [
+        {
+          "resource": "repo://src/whatsapp.rs#L314-L367",
+          "version": "repo-lines-v1:sha256:dae8e9fd58393c76159603a1143da95f40e9d77d62eaa8e9531737a4a58b8edb:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgyNTI5M2Y3MjNjNGMzMjZmNDJlMDFmMzc1Nzk1ZTJjMjE4NzQxNGEyNjI5NDEzOTEwODc3Mjc3MzdlMWMzMDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYwNjNjNGQxYWY4Y2Q4OWJiMGJkZDc2NTdmZmNiZjY4MDZhNjEwZDc5ZDZjOGQ0YzMwN2MyNDFkZjM5MDA1MjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI1MjJlMWI3MWMwMDczNGUwMWUwYTJhN2ZjYjU1ODdjYWEwN2RkYzRlZjRjMTViYTEyNGFhN2M1ZWRlZTVjYzUifQ"
+        },
+        {
+          "resource": "repo://src/whatsapp.rs#L88-L102",
+          "version": "repo-lines-v1:sha256:ee68380c7344a72f8bb87cef4651b5340e1832b4eaed61c614d88b8964dc7950:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE0YmIxNWU3MmU0YTFmMzBmNDU2OThjMDA4MDc2ODZjYWVjZTEzNTI1Yzk3YmU2ZTc5ZWZkYTlmMDQyMGNkZmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZkMDcxMDczNGRhYWVkNTFlMTIwNmE1ZDU0NzMxMGQwOTg2Nzg2MTAyYzA3YzM0NjRiYzEyMzY5ODJkZjFjY2MiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFlMDAzODBjNDQ0YjdjNmE5YTgyZTAxMDJjY2IwNzI0MWVlYjhhNTU1NGE2MTkxOTFhNzk2MDJhNGFhMDc4MGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjIxOTJmNjU0MjE1OGExOGY0MDRlMjg1ZmI5NGM2N2VjYmFmMjg0NzUxOGM5ZjQ3Y2IzZDA3MjVmOWYwNTk0NTUifQ"
+        }
+      ]
     }
   ],
   "verification": {
     "by": "openwiki/0.7.2",
-    "at": "2026-10-21T21:49:12.291Z"
+    "at": "2026-10-23T21:26:24.156Z"
   }
 }

+ 54 - 12
openwiki/.claims/operations/configuration-and-data.json

@@ -1,14 +1,14 @@
 {
   "schemaVersion": 1,
-  "pageVersion": "sha256:895dd056591483fa81fad9cbc781504961253ccdbdaf42c8ed0f92c3685c50cd",
+  "pageVersion": "sha256:4424aa91ddf23d979235806fa52b23f096e57c324e97fc229b803823db189ab9",
   "claims": [
     {
       "id": "claim_0519d797500c4fba86d6c64ac1e60df4",
-      "statement": "Configuration is read only from files in the data directory (config.toml, SOUL.md and vision.md) and no environment variables are used.",
+      "statement": "Configuration is read only from files in the data directory (config.toml, SOUL.md and VISION.md) and no environment variables are used.",
       "evidence": [
         {
           "resource": "repo://src/config.rs#L1-L25",
-          "version": "repo-lines-v1:sha256:4cfa961e2b8156a4d104f9c982c82d3d21b8707225ae8bec3ddc7e273ba1ed45:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI0MGE0NTBjZTI3ZmY3MjYwYzM4M2U4NDk4MjhjNzY1NTVlZTk1ZTllM2JmMTJmOWYxY2IxY2Q2OGZlZGFkMDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMyZTJkOGM5Y2M3MmUzODMyNGJkYzhmODkyZmY3NDdiNTVjMmNkNWRkMjhiYWFhMTdkNjE0NWMwMWI5Mjg0NWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcxMzE1MjZlMmQ3ZWFkMDgwNmJkNzJlYjRjMGJlMjE1NjI4ZWZiMGMzMjc3OTM0NjVkYjM1NmQ2NTQ4NDAzN2YifQ"
+          "version": "repo-lines-v1:sha256:3e4cf760dbcc82e6ee4f5b88a2c765c266de46001249e1c954503d63a7351276:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI0MGE0NTBjZTI3ZmY3MjYwYzM4M2U4NDk4MjhjNzY1NTVlZTk1ZTllM2JmMTJmOWYxY2IxY2Q2OGZlZGFkMDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE0Y2JhZGQ0ZTk3ZjA4YzA5MmE3ZmRkODNiNDM4NjUwNjFlNDBmZDlhMjExZjZlZDhkYjQzYTkxNTRlMTI4MTciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcxMzE1MjZlMmQ3ZWFkMDgwNmJkNzJlYjRjMGJlMjE1NjI4ZWZiMGMzMjc3OTM0NjVkYjM1NmQ2NTQ4NDAzN2YifQ"
         },
         {
           "resource": "repo://src/config.rs#L265-L281",
@@ -35,7 +35,7 @@
       "statement": "session.db, history.db and the optional ai_requests.log live in the data directory next to config.toml.",
       "evidence": [
         {
-          "resource": "repo://src/config.rs#L317-L339",
+          "resource": "repo://src/config.rs#L325-L347",
           "version": "repo-lines-v1:sha256:f895d312efc12c901e18e3c47999e82cde54de595ed326f94dc9c80be206fd75:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjY4MjQ0NGNiOTU1NTQxNjJjOGIzNjYxMjg0YTJhNGMzMDIwZmQxODU3OTYxMDU2ZGE1MjMxM2Y2YjgzYjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIxNTA5ZGYyZDNjMWJjODQxODQ2NDlhOTg3NzAwZTU4ZGVmNTJkZGQ1YzBkZTVlZWIwZjY0YTc1YmQ5MzBlYzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE1MjE2MDRiMzVmYzQwYWYwMGNiYTRlZWEyZmQ1ZTJjM2FmMTg2M2IzOTExODVkN2JmNGJiMDllNzJkMGM4YjMifQ"
         },
         {
@@ -117,11 +117,11 @@
       "statement": "The AI request log omits the API key and redacts image data URLs but records the full request body and outcome, appending without truncation.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L261-L280",
+          "resource": "repo://src/ai.rs#L266-L285",
           "version": "repo-lines-v1:sha256:80e196adb6578d005aa9692b9e78d664b43c74a606ded3b99a348545c412b065:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUxNzY0ZmUzOGM2MTVjODM3MzM2M2ZhM2U2OTVhNTQ3Y2M0MGM5MjhmZjIwYjE0ZTI3NGRjZjQxNDI5NjI5MjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEyYTMyMmRjMWM3OTk2OTEzNTU1MGI0YzRkMzYyMThiNTQ4ZDY2NjE4OWRmNDFkMWM1ODIyYjU1MmE3OTljNjMifQ"
         },
         {
-          "resource": "repo://src/ai.rs#L473-L566",
+          "resource": "repo://src/ai.rs#L478-L571",
           "version": "repo-lines-v1:sha256:6449a0d503ec713ff2c8159e6629639a1ce13aacf31d4a8c178d630e10e727f4:eyJzZWxlY3RlZExpbmVDb3VudCI6OTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNhZmRkOGE1NjBlNzQ4ZmFmNGRlZDhhYjJmOWU4NmUyMTU5NDA1YTAxMGUwYzE4YjI2M2NhNDE4MzAxMzFjZTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRkNmI1NWM2ODQyOGJkOWU4ZDZiNGU0Y2YwYmJmYmI4Mjc4MWEzMjU4NjEwYWU5ZTAxMjI3NzA0NjM1MGQwN2IiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjZTgwZDQ1MzU3ZWY2NWZiNjUzODgzZWEzOTU1NDMyMDMzN2Q0YjA3MzcxNzQ1ZWE0MGFkYzRmMGFjNTU0YTcifQ"
         }
       ]
@@ -139,7 +139,7 @@
           "version": "repo-lines-v1:sha256:6d490312b71acd64e2e0d7d854caad2acb8ba41147fcb3bcf92195ba26fc850e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM3NGJkYjgxZDEzY2MxOTQwZWE0Y2EzNTQ4NzZiODhiYmEyOWYxMmIzNTIzZTE1M2VlZDZiOGFlNWM4YjllYTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEzMjJlODc4N2I1NGUwYzg5MmMxMzExNmFmOTM5ZjY5YTZkM2VjNzE2ZTMzMWY3N2JkN2E3OTFmYzAwMjFhOWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzNDgzNDhhOTA1MjQyNTQzZWZkZWU4MmY4NzVkZTE5NTc1OWNkNDA5Mjg0N2QwZWViMjc3ZmFkODNkNzc1MWYifQ"
         },
         {
-          "resource": "repo://src/config.rs#L504-L528",
+          "resource": "repo://src/config.rs#L512-L536",
           "version": "repo-lines-v1:sha256:37db7f6e1b478b12a699a96825e0e7d8506be31e81194c64ea6f58027479b43f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE3NmFiMWQ5ZjdkNjNlNzQ4YjE1ZWQ4NzQzYjFjOGE2MWQxZmE4ODNmYzQ5NjkwMDM1NzUxMDk3N2UzMmJkMjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFjZjZmZDFkMjdkMzRmN2Q2N2RhMWQ5MDI2M2I0MzllZjk3MjNmOTRmYTY5YTJmZDI5NzZlYmE2ZDQ3MzAzNDAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwMGRiMDQwZjI5MDliZjk3OGZmMGE5NTZhNTVlODNjNjUzNDE4ZGU2YmI2OGY4MDQxZGE5NzZmOGQzZDIyNzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg5M2QyZGYwZGQzNzE4YmQ2M2NiMGFkYjE3NjllNWMxOWJmYzA4YzY4ZDM5NjEwYjU5NGM3NDM0MTE4YTQwMGQifQ"
         }
       ]
@@ -156,11 +156,11 @@
     },
     {
       "id": "claim_311d5db128b543a8b8bb49446e518e12",
-      "statement": "SOUL.md and vision.md are read as written with only a leading UTF-8 BOM removed; a missing or blank file yields no prompt, while an unreadable or non-UTF-8 file is an error naming the path.",
+      "statement": "SOUL.md and VISION.md are read as written with only a leading UTF-8 BOM removed; a missing or blank file yields no prompt, while an unreadable or non-UTF-8 file is an error naming the path.",
       "evidence": [
         {
           "resource": "repo://src/config.rs#L212-L223",
-          "version": "repo-lines-v1:sha256:9bb1147f77fa228eae840306e4371637700927c4c2a2aa01a36e028141fc5351:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc1ODlmMTZlMTM4ZjMwMTM1ZjE3ZGNkNWQ0OTVhOGM0NzhkOTE4MGI3NzA4YmNmZjBlYzdlN2FjNDA5YTQ4YjIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJjNjc1NTVkMWRiNmUxYTQwOTI2MzU5ZmVhYTMyODBlNDcyNTdmMGQ4NzQxN2Y4ZmI1MzBiMWM4ZjhmNmZkYzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJkM2E4MDAwMjZjNmY2MDFiYmM4MDdlYWI0YmI1Nzk1ZTY4MjEzNGYxMWU3MTRmNzRkMWVlMzQzOTUxNjE4NDYifQ"
+          "version": "repo-lines-v1:sha256:935a8d5c1ea2b970e49dfefd61104d95d4f7f9ab66af3adddb7b055acc5c5da0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjliZGJjNjVlNGE0YTc1ZjNmMzk1NmNmOTMxYzg4MTdmYWNlM2M4MzRjMjc3YjdjMDIwNTRlZGEzZmViZDZmYTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJjNjc1NTVkMWRiNmUxYTQwOTI2MzU5ZmVhYTMyODBlNDcyNTdmMGQ4NzQxN2Y4ZmI1MzBiMWM4ZjhmNmZkYzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJkM2E4MDAwMjZjNmY2MDFiYmM4MDdlYWI0YmI1Nzk1ZTY4MjEzNGYxMWU3MTRmNzRkMWVlMzQzOTUxNjE4NDYifQ"
         },
         {
           "resource": "repo://src/config.rs#L265-L281",
@@ -184,17 +184,59 @@
     },
     {
       "id": "claim_ff1c72aeb4f74a44acd82adc2ad022e7",
-      "statement": ".gitignore ignores any SOUL.md and *.log files in addition to the root-level config.toml.",
+      "statement": ".gitignore ignores any SOUL.md, any VISION.md and *.log files in addition to the root-level config.toml.",
       "evidence": [
         {
           "resource": "repo://.gitignore",
-          "version": "repo-file-v1:sha256:04dd19185e097f660d8472fbe810a075adcf70f7e81924e24649f934db1b4861"
+          "version": "repo-file-v1:sha256:ba7bfe210a58bf17e003336196f1ec63839c4c70e7504591a96396f1b9492b42"
+        }
+      ]
+    },
+    {
+      "id": "claim_a3b32d0c58cf4b34a6102c89674c5adc",
+      "statement": "config.toml, SOUL.md and VISION.md are polled every 2 seconds by modification time; any change, including a file being created or deleted, reloads the configuration and sends it, or the load error, to the application as AppEvent::ConfigReloaded.",
+      "evidence": [
+        {
+          "resource": "repo://src/config.rs#L317-L323",
+          "version": "repo-lines-v1:sha256:ffe0647e13679fb34451804a50c541dc7dbd58c8758cacebf62063952f220b8e:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjY4NmY2YmZhODVjMWJjMjM5OWFjMTAzYmM4ZmIwMmQ0NWZmYTU4ZTA0ZDI4OWIwNzkwNTg3ZWU4NDMxZWI4NyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjE1MDlkZjJkM2MxYmM4NDE4NDY0OWE5ODc3MDBlNThkZWY1MmRkZDVjMGRlNWVlYjBmNjRhNzViZDkzMGVjMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTZjMzk0MWE5YjYxMDI1Yjg1ZWU5ZWEzNzYzOTUyYTViMTM5NmNkMmY2ZjczMTkyZGQ3NDYwNDc5OTlhYjY0MSJ9"
+        },
+        {
+          "resource": "repo://src/config_watch.rs#L15-L52",
+          "version": "repo-lines-v1:sha256:1501e5e6ec52e197ae84506acd57d2b315d6a1dd434c842b88dc4a4b6d2f1bd3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3NGMwOGQzMzg5NGUyMWQyYzBmNTMyMDkwYmQwNjFjNDNjMzg1ZDU1MzkwMmI0MDkwZDkxZjJjZDY3M2VjMWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE1Y2VkZDkyMzY4ZGI1MDMwYjUxMjQ3ODcwODlmY2FmZTI5YzhmOGQ4MjExZDA0MTE4ZmM4MTViYWM1MDBjOWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ"
+        },
+        {
+          "resource": "repo://src/main.rs#L60-L60",
+          "version": "repo-lines-v1:sha256:4c448a0646f36833c45781a40616440b41ad3d485b8faf7089d78b8a85c580d6:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNGM0NDhhMDY0NmYzNjgzM2M0NTc4MWE0MDYxNjQ0MGI0MWFkM2Q0ODViOGZhZjcwODlkNzhiOGE4NWM1ODBkNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNGM0NDhhMDY0NmYzNjgzM2M0NTc4MWE0MDYxNjQ0MGI0MWFkM2Q0ODViOGZhZjcwODlkNzhiOGE4NWM1ODBkNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTJiNWYyOWYxMjhmYzYwODc1ZmE5YTYxMGI2MWExMTQ5ZjMzYmJhMDc2ZmU0NzdkZGU0NDA1OTUxNzdjYjAyYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZGQ4NmY4ZmE5MDAwN2QyYmY5ZDk4ZTE3NzNkMDg5ZWE4M2IzMWI4NzMwZjViMWIxZmRjYmViZmM5ZTZiZTMxYSJ9"
+        }
+      ]
+    },
+    {
+      "id": "claim_42627e2c70a146238f813e3ab930aead",
+      "statement": "A successful reload replaces the application configuration and rebuilds the AI client, while a failed reload keeps the current configuration and shows 'Configurazione non ricaricata: <error>'; the web panel port is bound only at startup.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L383-L400",
+          "version": "repo-lines-v1:sha256:11076b2b498b6d19b7a0629886f2cdb37229105049de848490f4e1d9faead710:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVhYTEzNjY2OGNiMzc5NzgyYTUwMTY4ZDNlOWM4Njg4MmM5YTBhZTAxOGQwNTZiNTEzZWU2ODA5OWYxNjc3YzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVmMjAxZDg2ZGM3NzY0YWFmOTA5MGNhNDRkODcxNDM3Y2JiODk4ZGE5ZWJlMGU0ZDhlNjVlNDZiZDJkZGVkYjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyNjE2ZDk0ZDJmZDg4NWViMDZhMGFmZjVkYjQ1YWUyOTdiMWQ5NzViYzU0MjRlZGIyYWUwZmE2NzZkMzE0ZTYifQ"
+        },
+        {
+          "resource": "repo://src/main.rs#L95-L104",
+          "version": "repo-lines-v1:sha256:54c6448bd45241fd4b90479b1a91e2fd528e7a38f4ac5d543eda728fbf3756d5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwZjA3MmY1NzA4NTRiMGM0ODg3YmRlZDU4YzU2NWRiZGExOTNlYmY3NGIwODZmMjYyNTFmZjZhOTZiMmJiYWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRmYWI3MTBkZGY3ODU5ZTU3NTc2NGE5MTNhMDAyYzc0NTg1ZmZhNzAwMTUxMGNiODgxMWRhODNkMmNmNzRiMzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI1OTAxZTAwNGRkYTg1ZmUwNjg0OGVkYjhkMWIzM2Q1NTNmYWM4ZjkyMzZlZTFlMjlkZTNmNGUzMWRmNTM4NTkifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_bdba51befb1b44ceb365b2343af0c697",
+      "statement": "The release profile only strips symbols, without LTO or a single codegen unit.",
+      "evidence": [
+        {
+          "resource": "repo://Cargo.toml#L31-L33",
+          "version": "repo-lines-v1:sha256:86778fe21f60c6caca6fe8e2791f86fe7e8df46a35c86acdbdc03cda812213b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTYxYjk5MGFkOGY0Mjg0YzM1NGNkOGY5YjZiMWIwZDAxNTY1MDJhYmIwOGFiMjlhYjUwODI0OTRkN2EwNzYwYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMGY4ZmNhNmE4OTMwYzJhZGY0N2NkOWQ0ZTNmN2FmNDRmMTZhMTA1ZmQ5OGM0YzZlMDg3YThkZjFhN2M4ZTVlOSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNWRhOGZhNGQ5YTg5NzFmZGMyMzI1Zjg0ZDlmYzI3ODUwYTIyMDA4MTA5NDQ4MzE3NjRkYjNiYjllNWI4NmViYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzVlMDU5OGEzNzQyYzljOWFlMWYwN2RjMWU1OWYwNDc2MzBjOTBhNDBiYzdkOTE4ZTk1YzAxMTdjOTdhNWQ5YSJ9"
         }
       ]
     }
   ],
   "verification": {
     "by": "openwiki/0.7.2",
-    "at": "2026-10-21T21:49:12.291Z"
+    "at": "2026-10-23T21:26:24.156Z"
   }
 }

+ 31 - 13
openwiki/.claims/quickstart.json

@@ -1,6 +1,6 @@
 {
   "schemaVersion": 1,
-  "pageVersion": "sha256:0904a154437de3ef07d0a913900c796b3fc862d2fcd254438da8ab3c23dcf515",
+  "pageVersion": "sha256:3763c54c3a6a1ddee93531d89a303f3b8c99e547a730b01dfb8e4c97d5211efc",
   "claims": [
     {
       "id": "claim_6b1c328bf7f7452992ea8eeef561edd4",
@@ -38,11 +38,11 @@
     },
     {
       "id": "claim_f1a67c370b204d5484f31751987d20d9",
-      "statement": "Unless --version or -V is given, main loads the configuration, opens the history, starts the WhatsApp client, the optional web panel and the input thread, and runs the TUI loop.",
+      "statement": "Unless --version or -V is given, main loads the configuration, opens the history, starts the WhatsApp client, the optional web panel, the configuration watcher and the input thread, and runs the TUI loop.",
       "evidence": [
         {
-          "resource": "repo://src/main.rs#L36-L64",
-          "version": "repo-lines-v1:sha256:2f5e5e305de372b540e8f2ab32af7faeb163b55a1cea3b4f31696097c921a6a7:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExZDA2ZTA0OGNkMjcyNzM4ZDgzN2IwYzBlYzZhYWIwYjAzMTQ1N2U0NzVmZDQ5YWJiY2JhZmM1ZjczYzAyMGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk3YjIxYjgxYWE2YTcxZmY5NGYxNmE2NGVhMThkMjlkODhiYTE5YzkyYjM2MjUzMDhlNmU4NDM2MTkyNzM0MmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAxZmI2ZDM1MThiYTRjMzUwOWRiMWM5MTAyMTk2NjQwMzI4ZjgzM2M3MDJmMzgwNmNmZjFlZGI5MzU4Y2Q5YTgifQ"
+          "resource": "repo://src/main.rs#L42-L71",
+          "version": "repo-lines-v1:sha256:1f8efd5e596ab7a7d1da0bfd9bc62005e6be9c40accb54842253ec733e4abfe3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExZDA2ZTA0OGNkMjcyNzM4ZDgzN2IwYzBlYzZhYWIwYjAzMTQ1N2U0NzVmZDQ5YWJiY2JhZmM1ZjczYzAyMGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE5NjlkZGFlYmE5MjRhNThlYTY2OGUyMDQzMDFiZWE5ZThjMzZjNjg5NTU3NTc3NmYzZDkzZWNiZmJlNzdjMzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAxZmI2ZDM1MThiYTRjMzUwOWRiMWM5MTAyMTk2NjQwMzI4ZjgzM2M3MDJmMzgwNmNmZjFlZGI5MzU4Y2Q5YTgifQ"
         }
       ]
     },
@@ -61,7 +61,7 @@
       "statement": "The AI autoreply can be enabled for private chats and groups but not for broadcasts or newsletters.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1403-L1416",
+          "resource": "repo://src/app.rs#L1544-L1557",
           "version": "repo-lines-v1:sha256:76ea2311dd543d33bf1c52b4cee8997184cb67643a8ba3b2f715abf2fea34667:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0NDI0MGFiOGNkMTM1NDE2ZTQ5MjMyYTEzMGIzYzlkYmI2NWZmYzg0OGNjYzY3OWJhYTQxYWQzNjY4ZTA3MjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg5NGUwY2FkODFjNTBiOWY4MWE5ODI2ZjA1YzUyZjY0MGE0ZGM3NzdiYWQyNGM2MGEwMjQ5NzhlZWJmMWRjMGMifQ"
         },
         {
@@ -72,35 +72,53 @@
     },
     {
       "id": "claim_f9fef3eb56c84ffba6d3f150690d3647",
-      "statement": "Commands use function keys: F2 switches chats/contacts, F3 toggles the open chat's autoreply, F4 toggles the global autoreply and F10 quits.",
+      "statement": "Commands use function keys: F2 switches chats/contacts, F3 toggles the autoreply of the selected or open chat depending on focus, F4 toggles the global autoreply, F8 twice relinks WhatsApp and F10 quits.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L23-L30",
-          "version": "repo-lines-v1:sha256:1b77b4b729c472be09a45c5473cf8594fc6554a18cc8f686ece6245ff2aa26ec:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmQxOTM5NWQwMDUxZDZhMDgzMGQyZDk5ZmE1OThkNTg3YTI5ZmQ1MDQ5OTA0ZDU5Y2I1MjZiZDdjNzg4MGIwZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWY2MjM3MDg5YjkxZDEwN2I2NjQ1Nzk3M2E4YmEyYTcxZDg3MDQ0ZWE5MWU5MThjZGUyMDVmODZhY2Q0NmM5MyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWVmZGQwMDhhMTYzNjZhM2YxN2I2NjUzYjI3NzIxOGRjNDY1NGIwYWNhMTkwYjViYWJkNzA2YjIzYjEwNDA3NyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZjM1MGFjYmZhYzdlNDEzZmFkNWYxMzk0NTQwYWE3MWM3YWY3ZWZmMDA4MDliYmMxYTlhNzQxZGRhNDFiOGYwZCJ9"
+          "resource": "repo://src/app.rs#L1101-L1116",
+          "version": "repo-lines-v1:sha256:f3a02114e65f3b57e1e9609cdb5227848e7d4a8d0d2d3abc6b38c40f5286461f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjMWZhZjI0NjQzNTZlZjY0NzAyNjI4YzM0MGYyOGNhZDljMjY3OGYxMTViNTAxNDZkMGMyNTBmNzhmZDk2NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkzNzNhMTgxNzI2YzQ3ZjdjNzlmOTI3ZDI2NzhhYzQ3NjYzY2VhMzI1OGYxOGEwNTM0ZTg2NDVlYmFlOTlkMGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc1YjdhNGU5MWJmNTc3ZDQwNjIyM2IzMjg2ZTFhYmI0NGRkNzUwYmViNmU1MzIwYTMyMGZlZDJkOTBjOWQ3ZDQifQ"
         },
         {
-          "resource": "repo://src/app.rs#L774-L804",
-          "version": "repo-lines-v1:sha256:37edcb8b1691dbaa467f7d59b80cdc3781376ce6ce1fc1783f167c52ae5ce169:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmODVjMjY1ZjBmMjNkOTA0NDcwMjYxNWZiM2NkMzQ2ODMxYTFiZWNiOTRlYzNkODYyNDYxYzFkNWEwMGQzNDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5OTRiMGIxZDNhNDA1MzRjYTFiZTVmNDdiNTRlNWY3ZDg3ZTZlMGMxMWE3ZjU5ZjA0NGU2ZGJmNTkyYzNlZDYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk2ZWU5ZDllODg2ODFmN2JmM2M4ZmZiZjEzZDgyODk2ODM4MGY2Y2E0M2Y1ZGE4ODI5MmU1MjY3ZDRjMDhlOWYifQ"
+          "resource": "repo://src/app.rs#L23-L32",
+          "version": "repo-lines-v1:sha256:683c16567f4074fbe88d85ef8581c16739d5ca0531826b0fb49c5df95ac18f20:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJkMTkzOTVkMDA1MWQ2YTA4MzBkMmQ5OWZhNTk4ZDU4N2EyOWZkNTA0OTkwNGQ1OWNiNTI2YmQ3Yzc4ODBiMGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFmNjIzNzA4OWI5MWQxMDdiNjY0NTc5NzNhOGJhMmE3MWQ4NzA0NGVhOTFlOTE4Y2RlMjA1Zjg2YWNkNDZjOTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFlZmRkMDA4YTE2MzY2YTNmMTdiNjY1M2IyNzcyMThkYzQ2NTRiMGFjYTE5MGI1YmFiZDcwNmIyM2IxMDQwNzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI2ZmI3YjljNTEzODFiZmNlNmIwYzU4ZTY0ZmZhMjhjODMyNjU5NTkyOWQ1MDNlYTUwYjBlMDRiNTk0N2QwNTMifQ"
+        },
+        {
+          "resource": "repo://src/app.rs#L884-L929",
+          "version": "repo-lines-v1:sha256:6e737f1163f7fc1e0f873a9756a9a2c32d247edaf022b24c9d62e4f5d3ecdc71:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZGRmNjgzMjdhYTA4ZWZjNDYxNGFmZDIwOTkzZTFkNzZiZjUxNmQ3NzRjM2QyMmIzOTUyOTEwNmNkOTBhZmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk2ZWU5ZDllODg2ODFmN2JmM2M4ZmZiZjEzZDgyODk2ODM4MGY2Y2E0M2Y1ZGE4ODI5MmU1MjY3ZDRjMDhlOWYifQ"
         }
       ]
     },
     {
       "id": "claim_463678f8d2c94b51b6b05219b319a405",
-      "statement": "The system prompt is read from SOUL.md in the data directory rather than from config.toml, and the optional vision.md replaces the built-in image description request.",
+      "statement": "The system prompt is read from SOUL.md in the data directory rather than from config.toml, and the optional VISION.md replaces the built-in image description request.",
       "evidence": [
         {
           "resource": "repo://src/config.rs#L19-L25",
-          "version": "repo-lines-v1:sha256:17242e32826a755038c29fdc065c9833b26b2b878da0847d6ff0dee1f741c6ac:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjhhODQwNDU2NTU5OWJhNzUzM2Q2NWYzYTBjODNmMzE3NzRlM2MxYjg2N2EzNzA2ZjViOTg3OWNkNTg4YTMyYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYzJlMmQ4YzljYzcyZTM4MzI0YmRjOGY4OTJmZjc0N2I1NWMyY2Q1ZGQyOGJhYWExN2Q2MTQ1YzAxYjkyODQ1YyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDg2ZGU3NmMzYjVlNGIwN2Q2MmNkZTNjNDY1ZDEwN2QzNmE4NDEyMjE2ZThlMWFhMjZjMjE2ZmFmMWE4ZDY5OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzEzMTUyNmUyZDdlYWQwODA2YmQ3MmViNGMwYmUyMTU2MjhlZmIwYzMyNzc5MzQ2NWRiMzU2ZDY1NDg0MDM3ZiJ9"
+          "version": "repo-lines-v1:sha256:755a1cd95dc7a5dbe595fb5421d48c62b8a21f7908ec1d68ac66efa32eaabe47:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjhhODQwNDU2NTU5OWJhNzUzM2Q2NWYzYTBjODNmMzE3NzRlM2MxYjg2N2EzNzA2ZjViOTg3OWNkNTg4YTMyYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYTRjYmFkZDRlOTdmMDhjMDkyYTdmZGQ4M2I0Mzg2NTA2MWU0MGZkOWEyMTFmNmVkOGRiNDNhOTE1NGUxMjgxNyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDg2ZGU3NmMzYjVlNGIwN2Q2MmNkZTNjNDY1ZDEwN2QzNmE4NDEyMjE2ZThlMWFhMjZjMjE2ZmFmMWE4ZDY5OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzEzMTUyNmUyZDdlYWQwODA2YmQ3MmViNGMwYmUyMTU2MjhlZmIwYzMyNzc5MzQ2NWRiMzU2ZDY1NDg0MDM3ZiJ9"
         },
         {
           "resource": "repo://src/config.rs#L265-L281",
           "version": "repo-lines-v1:sha256:4ade9876bd8e1653384dcebde47d413e1aab36995b53055ab5441d82e8b28290:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ4MDc5NGE2MDA2MzNhZTY5ZGFlMWYzOThjYTk3ZTc5Yzg3NDc5ZDFjNjE3MDg0ZTRkMTgyODZmMTlkODFjMTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgzZDI4NDFkZmUxZTBkZDIxMmQwYzE1ZDAxZTVhYzkyMTBmNTgwMGI4YmRmZTk5M2FkZDAwYmQ5NGYwMTRkNmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU2YzkxMzJjZTZhZmY4ZmJjMmFmMzRiY2JmYWE1ODVhN2RmMWUxODViYjc5OTFlY2I5YzgxYTUxODg3NWUyOGUifQ"
         }
       ]
+    },
+    {
+      "id": "claim_f6722827fd664f689bb69598e3c124cb",
+      "statement": "reqwest is built with rustls-no-provider and rustls with the ring provider, and the release profile only strips symbols.",
+      "evidence": [
+        {
+          "resource": "repo://Cargo.toml#L13-L16",
+          "version": "repo-lines-v1:sha256:ffa1110b9463e2e882dd6ee955610921a6626c1ac714795c354f305349c2de28:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDc1NjE4MjA2Mjk3MDFhYTMxMTUxMTA3NmU1MmU0ZTY4MmNmNmQzMzJhZTgzNTViOWY3NWI1ZmE4ZmNiNjZhNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzMyMGRlNDU2MmQ5YTlkYjc2YTA4YjdhNGMyYjdkZmRjNWQxZDM4YjU3NWUwYzkwMzQ3ZTM2YmRmMjQ1ZWMyMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2E4ODdjMTgwNzVjMTBjMWQwMGNiMTFmYjRjNmE3YWM2MzIyYzJmZjAwMjVmOWZlZWM4M2Y1MDQ5YzE3ZjJmZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOGUyOWE1OWE4ZjIxODhlOTZkOTQwZmI0MzIyNmUzMjY0Njk3NWM5MGU0NTRkOGNjMjU5OTgwNTZhNTU2ZDc0YiJ9"
+        },
+        {
+          "resource": "repo://Cargo.toml#L31-L33",
+          "version": "repo-lines-v1:sha256:86778fe21f60c6caca6fe8e2791f86fe7e8df46a35c86acdbdc03cda812213b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTYxYjk5MGFkOGY0Mjg0YzM1NGNkOGY5YjZiMWIwZDAxNTY1MDJhYmIwOGFiMjlhYjUwODI0OTRkN2EwNzYwYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMGY4ZmNhNmE4OTMwYzJhZGY0N2NkOWQ0ZTNmN2FmNDRmMTZhMTA1ZmQ5OGM0YzZlMDg3YThkZjFhN2M4ZTVlOSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNWRhOGZhNGQ5YTg5NzFmZGMyMzI1Zjg0ZDlmYzI3ODUwYTIyMDA4MTA5NDQ4MzE3NjRkYjNiYjllNWI4NmViYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzVlMDU5OGEzNzQyYzljOWFlMWYwN2RjMWU1OWYwNDc2MzBjOTBhNDBiYzdkOTE4ZTk1YzAxMTdjOTdhNWQ5YSJ9"
+        }
+      ]
     }
   ],
   "verification": {
     "by": "openwiki/0.7.2",
-    "at": "2026-10-21T21:49:12.291Z"
+    "at": "2026-10-23T21:26:24.156Z"
   }
 }

+ 49 - 25
openwiki/.claims/testing/unit-tests.json

@@ -1,17 +1,17 @@
 {
   "schemaVersion": 1,
-  "pageVersion": "sha256:10bcb258ed8fc94585fa952d923b1602f7daafeeed0fd6693ff6c44f40ffd393",
+  "pageVersion": "sha256:0f5f5df1184eb187831e0b8e3f9e8a2516c314386ce7360f1c4915c9450c587f",
   "claims": [
     {
       "id": "claim_2e232262c6d940c7ba52a945b96928f6",
       "statement": "Each source module except main.rs has an in-file cfg(test) test module.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L576-L577",
+          "resource": "repo://src/ai.rs#L581-L582",
           "version": "repo-lines-v1:sha256:03c5b4b27fb001def5489d5e0264e20f2538b8214c3df8ceda3354aaee53c2c8:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjVjZjVmYzIzZjJhNmQyM2MzZWE1MWU3MmY1ZTg5NmM3NTU1ZmJhYTA1Y2Q1YjFlYTE4MmJmOTYxOGE4ZTgxYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWUwYWYzZmQ0YTRjM2ExZDQ3YzdmNDcwZWQwZDg2ZDkwZDlkZTIxMGE1ZWIxNDU1ZmJkOTQ0ODJhNDc2NWI3MiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZWRjODMxMjQ5NjYxNGM2YzllMTE0NTc0YTc5ZDkxODU3MDM0YmJlM2IzMDk5MmYyNzYwZjcxNjY0ZjAwNWM2OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTM0OTA4YTE2NjZlZThkZWFiMmIyYTc2NjdjODQ0OGZiYjRkNDZlMzJhYjZjNTY4YmI3MjMyMGU1ZWJmMDlmMyJ9"
         },
         {
-          "resource": "repo://src/app.rs#L1443-L1444",
+          "resource": "repo://src/app.rs#L1596-L1597",
           "version": "repo-lines-v1:sha256:03c5b4b27fb001def5489d5e0264e20f2538b8214c3df8ceda3354aaee53c2c8:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjVjZjVmYzIzZjJhNmQyM2MzZWE1MWU3MmY1ZTg5NmM3NTU1ZmJhYTA1Y2Q1YjFlYTE4MmJmOTYxOGE4ZTgxYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWUwYWYzZmQ0YTRjM2ExZDQ3YzdmNDcwZWQwZDg2ZDkwZDlkZTIxMGE1ZWIxNDU1ZmJkOTQ0ODJhNDc2NWI3MiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjY5NGY2NjJmYWIyMzFlOGQ4ZGIwOTRiNTU3OTgxNWYwOTlkYTQwZGU3ZThiOTY1YWUwNDU2YWRjMDQ5YjM4NiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOGJlOTk2NWNlNGEzMmY5N2Y5ZjMzYzU1MzhjZWY5ZDE0Njc4YjE0NGQ4ODg1ZDY2Mjk4YTZkZTJjODg3NTZlMSJ9"
         },
         {
@@ -19,7 +19,7 @@
           "version": "repo-lines-v1:sha256:03c5b4b27fb001def5489d5e0264e20f2538b8214c3df8ceda3354aaee53c2c8:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjVjZjVmYzIzZjJhNmQyM2MzZWE1MWU3MmY1ZTg5NmM3NTU1ZmJhYTA1Y2Q1YjFlYTE4MmJmOTYxOGE4ZTgxYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWUwYWYzZmQ0YTRjM2ExZDQ3YzdmNDcwZWQwZDg2ZDkwZDlkZTIxMGE1ZWIxNDU1ZmJkOTQ0ODJhNDc2NWI3MiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTMyZjlmMDdlZDQ0ZTc0ZjliN2Y1ZGEzNTY1NjIxZjExNGRiNzdlZGY1NDc2NzZjNjZiNzRjMWMwNzQzN2MyMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiN2M0NzcyZjY4MTNiYmFmYmNhYjU4MWZlN2JiZmIwOTI4NDRlMGZhOWU3MjQwNmMxNzNjNmYyNTMxMjgyMTZmMSJ9"
         },
         {
-          "resource": "repo://src/config.rs#L342-L343",
+          "resource": "repo://src/config.rs#L350-L351",
           "version": "repo-lines-v1:sha256:03c5b4b27fb001def5489d5e0264e20f2538b8214c3df8ceda3354aaee53c2c8:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjVjZjVmYzIzZjJhNmQyM2MzZWE1MWU3MmY1ZTg5NmM3NTU1ZmJhYTA1Y2Q1YjFlYTE4MmJmOTYxOGE4ZTgxYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWUwYWYzZmQ0YTRjM2ExZDQ3YzdmNDcwZWQwZDg2ZDkwZDlkZTIxMGE1ZWIxNDU1ZmJkOTQ0ODJhNDc2NWI3MiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNGEwYTUzOWYxODQ2ODA0NzgzMDRlNjEzNTEzNGQyM2FhMjE2ZmZkZTBmMTYwMzJmYTZkNzYyOGQ0MzIxNzFhNSJ9"
         },
         {
@@ -27,11 +27,11 @@
           "version": "repo-lines-v1:sha256:03c5b4b27fb001def5489d5e0264e20f2538b8214c3df8ceda3354aaee53c2c8:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjVjZjVmYzIzZjJhNmQyM2MzZWE1MWU3MmY1ZTg5NmM3NTU1ZmJhYTA1Y2Q1YjFlYTE4MmJmOTYxOGE4ZTgxYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWUwYWYzZmQ0YTRjM2ExZDQ3YzdmNDcwZWQwZDg2ZDkwZDlkZTIxMGE1ZWIxNDU1ZmJkOTQ0ODJhNDc2NWI3MiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjA2M2M0ZDFhZjhjZDg5YmIwYmRkNzY1N2ZmY2JmNjgwNmE2MTBkNzlkNmM4ZDRjMzA3YzI0MWRmMzkwMDUyMiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMjgzMTBhMmUzZTliNGU0MTRjY2FiZmY2ZTdkMTc0YjgwMzVlNmZjZTRmODVmMTdjZTA4MTNmOWZlZTkzOTYxZiJ9"
         },
         {
-          "resource": "repo://src/ui.rs#L575-L576",
+          "resource": "repo://src/ui.rs#L579-L580",
           "version": "repo-lines-v1:sha256:03c5b4b27fb001def5489d5e0264e20f2538b8214c3df8ceda3354aaee53c2c8:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjVjZjVmYzIzZjJhNmQyM2MzZWE1MWU3MmY1ZTg5NmM3NTU1ZmJhYTA1Y2Q1YjFlYTE4MmJmOTYxOGE4ZTgxYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWUwYWYzZmQ0YTRjM2ExZDQ3YzdmNDcwZWQwZDg2ZDkwZDlkZTIxMGE1ZWIxNDU1ZmJkOTQ0ODJhNDc2NWI3MiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzViMjE2ODczZmZmNmEwNTc3MDU5NWM1OWNhZDNlNmE5NzM1MmJiNDE3ZDYwYzc2MmVhNDM4MWYxOTkwMjg3OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDVlYjIxMDg3NDU0NjZhOTVjZjg5YjVhZjdmMDZhMDA2NGU0OGRiMDVkMmMyOWU5Y2YzZDZkMGQxNmY3MjY5ZiJ9"
         },
         {
-          "resource": "repo://src/whatsapp.rs#L965-L966",
+          "resource": "repo://src/whatsapp.rs#L1140-L1141",
           "version": "repo-lines-v1:sha256:03c5b4b27fb001def5489d5e0264e20f2538b8214c3df8ceda3354aaee53c2c8:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjVjZjVmYzIzZjJhNmQyM2MzZWE1MWU3MmY1ZTg5NmM3NTU1ZmJhYTA1Y2Q1YjFlYTE4MmJmOTYxOGE4ZTgxYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWUwYWYzZmQ0YTRjM2ExZDQ3YzdmNDcwZWQwZDg2ZDkwZDlkZTIxMGE1ZWIxNDU1ZmJkOTQ0ODJhNDc2NWI3MiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDZkMTNkYTUzZGRmNTRlMDlhOWZiMzc3ODc0MTRhOTYxNDZkNDcxYzVlYmM2Y2UyNmNmOWEzODRmMTg3NzhmMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMWU1MWM0MjBiN2E3NDU0MmZlNGYwMjY4Yzk0YTYwZjRmYjAwZTdhODI0NmFhNTlkNzQ0NTkzYmY3OGUyN2M5MCJ9"
         }
       ]
@@ -42,7 +42,7 @@
       "evidence": [
         {
           "resource": "repo://Cargo.toml#L1-L37",
-          "version": "repo-lines-v1:sha256:61a45133c26cb38839e1ab763b6ecd5f7aeaf15aa3a58fef3840a2fc44b8b6a8:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhhM2NkNWE4MWIzZjlhNjIxYWE0OTNkOTBjNDVmNDJhYjU3MWQ0ZTQyYjhhZTVhZmYzNTFjYjBhMDJkMDZkODIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMzOGZmZWFhZDAxNWIxMmI4ZDdlZTAwMzJmNzIxM2M3NzgzMjhjMjNkMGQxNGVmM2EzYmM2MWRiMTEwZDBhNWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ"
+          "version": "repo-lines-v1:sha256:dd72e4014f3b1ca768fa49fb9e6a3322cb1f62fa8d64a4c46aca67928c3e31ba:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhhM2NkNWE4MWIzZjlhNjIxYWE0OTNkOTBjNDVmNDJhYjU3MWQ0ZTQyYjhhZTVhZmYzNTFjYjBhMDJkMDZkODIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMzOGZmZWFhZDAxNWIxMmI4ZDdlZTAwMzJmNzIxM2M3NzgzMjhjMjNkMGQxNGVmM2EzYmM2MWRiMTEwZDBhNWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ"
         }
       ]
     },
@@ -71,7 +71,7 @@
       "statement": "ui tests cover the footer key help, the version label, sender labels, clock time, QR text, wrapping, visible lines, input tail, messages and list titles and selection clamping.",
       "evidence": [
         {
-          "resource": "repo://src/ui.rs#L575-L752",
+          "resource": "repo://src/ui.rs#L579-L756",
           "version": "repo-lines-v1:sha256:e85913975a63028c40bc84fd3c1b842d226f277fb56a1588907f49fff7f23119:eyJzZWxlY3RlZExpbmVDb3VudCI6MTc4LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyNWNmNWZjMjNmMmE2ZDIzYzNlYTUxZTcyZjVlODk2Yzc1NTVmYmFhMDVjZDViMWVhMTgyYmY5NjE4YThlODFiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIwNmE2YTY2NDFiOGNiOGMwMjgxMWEzMzBhMmYyYTMyMzRkOTk5Zjg4YWJlYWNhYzEyYThlYTU1N2ZjZWQ4MzQzIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0"
         }
       ]
@@ -92,14 +92,14 @@
     },
     {
       "id": "claim_1814788a665c474fbe9623328521c079",
-      "statement": "The suite contains 205 test functions: autoreply 55, config 37, ai 27, whatsapp 22, app 20, history 18, ui 15, web 9, main 2.",
+      "statement": "The suite contains 209 test functions: autoreply 55, config 37, ai 27, whatsapp 23 (one of them an ignored live test), app 20, history 18, ui 15, web 11, main 2, config_watch 1.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L576-L905",
+          "resource": "repo://src/ai.rs#L581-L910",
           "version": "repo-lines-v1:sha256:d19f1348b5c8956f0781aa1895f12dd84dfe7f18a54ac9b446ef380fc38b9548:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyNWNmNWZjMjNmMmE2ZDIzYzNlYTUxZTcyZjVlODk2Yzc1NTVmYmFhMDVjZDViMWVhMTgyYmY5NjE4YThlODFiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlZGM4MzEyNDk2NjE0YzZjOWUxMTQ1NzRhNzlkOTE4NTcwMzRiYmUzYjMwOTkyZjI3NjBmNzE2NjRmMDA1YzY5IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0"
         },
         {
-          "resource": "repo://src/app.rs#L1443-L1646",
+          "resource": "repo://src/app.rs#L1596-L1799",
           "version": "repo-lines-v1:sha256:49775da3ba12a328474dedc2a4e2a0d82d1fb6d37c8adda1940d1fc34c97ec42:eyJzZWxlY3RlZExpbmVDb3VudCI6MjA0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyNWNmNWZjMjNmMmE2ZDIzYzNlYTUxZTcyZjVlODk2Yzc1NTVmYmFhMDVjZDViMWVhMTgyYmY5NjE4YThlODFiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJmNjk0ZjY2MmZhYjIzMWU4ZDhkYjA5NGI1NTc5ODE1ZjA5OWRhNDBkZTdlOGI5NjVhZTA0NTZhZGMwNDliMzg2IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0"
         },
         {
@@ -107,28 +107,32 @@
           "version": "repo-lines-v1:sha256:73d6683e98d1a479be5b3c5c82533ad41e429042edcbbc8f6c7d722a1eca2628:eyJzZWxlY3RlZExpbmVDb3VudCI6NTg5LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyNWNmNWZjMjNmMmE2ZDIzYzNlYTUxZTcyZjVlODk2Yzc1NTVmYmFhMDVjZDViMWVhMTgyYmY5NjE4YThlODFiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI5MzJmOWYwN2VkNDRlNzRmOWI3ZjVkYTM1NjU2MjFmMTE0ZGI3N2VkZjU0NzY3NmM2NmI3NGMxYzA3NDM3YzIxIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0"
         },
         {
-          "resource": "repo://src/config.rs#L342-L691",
+          "resource": "repo://src/config.rs#L350-L699",
           "version": "repo-lines-v1:sha256:f633f3663fa11b33dd8d6391a60b3b839aaf69dfaa341e10d37f025c069d49b3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyNWNmNWZjMjNmMmE2ZDIzYzNlYTUxZTcyZjVlODk2Yzc1NTVmYmFhMDVjZDViMWVhMTgyYmY5NjE4YThlODFiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIxOWU3NjM1NWNmMTE5OTQ4MGMzZjk5ODM2YjE0ODZjODQxZDg5MWZhYWQ5MDRjNjA4ZTA5MzAyMmQzNzU3YWVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0"
         },
+        {
+          "resource": "repo://src/config_watch.rs#L54-L74",
+          "version": "repo-lines-v1:sha256:e1c5a3b6eb06c5bf32c7c814c75a97ed0aedf8c45c474dcd004bb5f4c4023d58:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1Y2Y1ZmMyM2YyYTZkMjNjM2VhNTFlNzJmNWU4OTZjNzU1NWZiYWEwNWNkNWIxZWExODJiZjk2MThhOGU4MWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ4ODU5MjEwZWJjYjEyY2Y2OGM2MTYyMjJmYzU2MGMxZjY4ZTJmYTMxZjYzMDQ5M2RmODA5NTg4ODdkNTdhZjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ"
+        },
         {
           "resource": "repo://src/history.rs#L543-L852",
           "version": "repo-lines-v1:sha256:a4d3c1973b9e386233ba1511ab6e3adcf322e06bb30f97f8fb8f6a96258e6360:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyNWNmNWZjMjNmMmE2ZDIzYzNlYTUxZTcyZjVlODk2Yzc1NTVmYmFhMDVjZDViMWVhMTgyYmY5NjE4YThlODFiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI2MDYzYzRkMWFmOGNkODliYjBiZGQ3NjU3ZmZjYmY2ODA2YTYxMGQ3OWQ2YzhkNGMzMDdjMjQxZGYzOTAwNTIyIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0"
         },
         {
-          "resource": "repo://src/main.rs#L186-L208",
+          "resource": "repo://src/main.rs#L207-L229",
           "version": "repo-lines-v1:sha256:6f0ef80029cd8b3a3ed5570438cff5846f3f70c6e0fe4866184c8210aac3873f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1Y2Y1ZmMyM2YyYTZkMjNjM2VhNTFlNzJmNWU4OTZjNzU1NWZiYWEwNWNkNWIxZWExODJiZjk2MThhOGU4MWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ"
         },
         {
-          "resource": "repo://src/ui.rs#L575-L752",
+          "resource": "repo://src/ui.rs#L579-L756",
           "version": "repo-lines-v1:sha256:e85913975a63028c40bc84fd3c1b842d226f277fb56a1588907f49fff7f23119:eyJzZWxlY3RlZExpbmVDb3VudCI6MTc4LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyNWNmNWZjMjNmMmE2ZDIzYzNlYTUxZTcyZjVlODk2Yzc1NTVmYmFhMDVjZDViMWVhMTgyYmY5NjE4YThlODFiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIwNmE2YTY2NDFiOGNiOGMwMjgxMWEzMzBhMmYyYTMyMzRkOTk5Zjg4YWJlYWNhYzEyYThlYTU1N2ZjZWQ4MzQzIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0"
         },
         {
-          "resource": "repo://src/web.rs#L366-L445",
-          "version": "repo-lines-v1:sha256:932c4dcc02d7a1f510bc2e787ba8755c60639a9c77352797a663dc6ac0e063f3:eyJzZWxlY3RlZExpbmVDb3VudCI6ODAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1Y2Y1ZmMyM2YyYTZkMjNjM2VhNTFlNzJmNWU4OTZjNzU1NWZiYWEwNWNkNWIxZWExODJiZjk2MThhOGU4MWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmZDM2OGIzODI2OTRjMDYxZWMzMDkyOTYzNjNkYTBlYWNkNzI3NDlmZTQzYmFiNTM4ZWMyMjlhMmM5MDE0NTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ"
+          "resource": "repo://src/web.rs#L411-L503",
+          "version": "repo-lines-v1:sha256:c5f71abad037e96401b479b47443f71e3ace10e2d54fca94fb9318d2f14bbd6b:eyJzZWxlY3RlZExpbmVDb3VudCI6OTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1Y2Y1ZmMyM2YyYTZkMjNjM2VhNTFlNzJmNWU4OTZjNzU1NWZiYWEwNWNkNWIxZWExODJiZjk2MThhOGU4MWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmZDM2OGIzODI2OTRjMDYxZWMzMDkyOTYzNjNkYTBlYWNkNzI3NDlmZTQzYmFiNTM4ZWMyMjlhMmM5MDE0NTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ"
         },
         {
-          "resource": "repo://src/whatsapp.rs#L965-L1199",
-          "version": "repo-lines-v1:sha256:c10f061195e8e976464dc5e22a7640a0b1e09f1c92b8731f435f83832ed65415:eyJzZWxlY3RlZExpbmVDb3VudCI6MjM1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyNWNmNWZjMjNmMmE2ZDIzYzNlYTUxZTcyZjVlODk2Yzc1NTVmYmFhMDVjZDViMWVhMTgyYmY5NjE4YThlODFiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJkNmQxM2RhNTNkZGY1NGUwOWE5ZmIzNzc4NzQxNGE5NjE0NmQ0NzFjNWViYzZjZTI2Y2Y5YTM4NGYxODc3OGYxIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0"
+          "resource": "repo://src/whatsapp.rs#L1140-L1415",
+          "version": "repo-lines-v1:sha256:2057c5d8d4b99b0ca63b7bbd08e92640033c0d0ce471759da73c510b4d3af30a:eyJzZWxlY3RlZExpbmVDb3VudCI6Mjc2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyNWNmNWZjMjNmMmE2ZDIzYzNlYTUxZTcyZjVlODk2Yzc1NTVmYmFhMDVjZDViMWVhMTgyYmY5NjE4YThlODFiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJkNmQxM2RhNTNkZGY1NGUwOWE5ZmIzNzc4NzQxNGE5NjE0NmQ0NzFjNWViYzZjZTI2Y2Y5YTM4NGYxODc3OGYxIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0"
         }
       ]
     },
@@ -137,18 +141,18 @@
       "statement": "AI tests check log records and append-only writes, using a per-process temporary file for the append test.",
       "evidence": [
         {
-          "resource": "repo://src/ai.rs#L820-L868",
+          "resource": "repo://src/ai.rs#L825-L873",
           "version": "repo-lines-v1:sha256:28e9f0691f3d1a68739be49bd3325b406b41e5a83c60e4c0c671eed81f91a958:eyJzZWxlY3RlZExpbmVDb3VudCI6NDksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIwOTRhNzlkYTZkYmY5M2I2MzljZGM3ZTA3OGUxYWI3YzJkZmI5YWE2MzE1YjMyN2YzOWM1NjA0NWU1MjZkNWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjczZDVlZjE2ZDc2NzNhODk3ZWNhNGZmY2E5YzQ2NjExNmY2NGMxMzQxMGU4YjljMGM2YmJmMWNmNTAyMjY5OWQifQ"
         }
       ]
     },
     {
       "id": "claim_51223006b05b4b7f8d976e63537b9e22",
-      "statement": "web tests cover query stripping, route resolution, the X-Wh-Request header check, which routes require it, and request body trimming, rejection and size limit.",
+      "statement": "web tests cover query stripping, route resolution including /api/relink, the X-Wh-Request header check, which routes require it (relink included), qr_svg producing an SVG document, and request body trimming, rejection and size limit.",
       "evidence": [
         {
-          "resource": "repo://src/web.rs#L366-L445",
-          "version": "repo-lines-v1:sha256:932c4dcc02d7a1f510bc2e787ba8755c60639a9c77352797a663dc6ac0e063f3:eyJzZWxlY3RlZExpbmVDb3VudCI6ODAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1Y2Y1ZmMyM2YyYTZkMjNjM2VhNTFlNzJmNWU4OTZjNzU1NWZiYWEwNWNkNWIxZWExODJiZjk2MThhOGU4MWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmZDM2OGIzODI2OTRjMDYxZWMzMDkyOTYzNjNkYTBlYWNkNzI3NDlmZTQzYmFiNTM4ZWMyMjlhMmM5MDE0NTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ"
+          "resource": "repo://src/web.rs#L411-L503",
+          "version": "repo-lines-v1:sha256:c5f71abad037e96401b479b47443f71e3ace10e2d54fca94fb9318d2f14bbd6b:eyJzZWxlY3RlZExpbmVDb3VudCI6OTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1Y2Y1ZmMyM2YyYTZkMjNjM2VhNTFlNzJmNWU4OTZjNzU1NWZiYWEwNWNkNWIxZWExODJiZjk2MThhOGU4MWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmZDM2OGIzODI2OTRjMDYxZWMzMDkyOTYzNjNkYTBlYWNkNzI3NDlmZTQzYmFiNTM4ZWMyMjlhMmM5MDE0NTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ"
         }
       ]
     },
@@ -157,18 +161,38 @@
       "statement": "Config prompt-file tests write per-process scratch files under the target directory and remove them after reading.",
       "evidence": [
         {
-          "resource": "repo://src/config.rs#L349-L375",
+          "resource": "repo://src/config.rs#L357-L383",
           "version": "repo-lines-v1:sha256:074ff1586edc8fcdfee555ac451b2810d5deb73073328d3f7f989ee02aca2fb4:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmY2Y5NzI1NmRkZTlkNzYwNGJkZmZmYTMyZjVjOTI5ZGM1ZjIzN2UxNmZhNThhMWEzMmNiMTQyMWY1ZTY4NmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNhYWZmODY5NzZkYWEyYzYwYjNlZmU4MDE2MTQxZDI5ODM3N2NhNTI2OGQ0ZWJlNTk1ZWVhZDVkYzQyMzJiNmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU1NWQ1YWZiMzJlOWU4YmJlMjI3ZGY0NzAyMGQwNzU4YzcyZjc1Yjk0ZTBjOTVlODg2MGQyOTFlNmQ2MWJhMWMifQ"
         },
         {
-          "resource": "repo://src/config.rs#L418-L454",
+          "resource": "repo://src/config.rs#L426-L462",
           "version": "repo-lines-v1:sha256:1860a1e1d35b88e371e7056a3261ef07ddfe79442e512b5a65c493d79e7c6a47:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEzZWE5YzkzNGFmY2Y5Yzc5NDE0NjE0MTZhODY4ODAxNjNjYzc5NGZkZjQ4ZDk3NWVjNTAxZDE5MWJhY2UxMDkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMzMDUyMTUxNzFiNzNmMmE3MTliYTZkYzhmZTYzMGM1ZGUyNDExN2JmYzIwNTU3NmZjNjhmODY1NDg1YzQ0YzUifQ"
         }
       ]
+    },
+    {
+      "id": "claim_a4ea3352b58446fcaa58231192fe72ee",
+      "statement": "The whatsapp test relink_deletes_the_session_and_offers_a_new_qr is ignored by default because it connects to the WhatsApp servers; it relinks a real client and asserts that the second QR carries different device keys.",
+      "evidence": [
+        {
+          "resource": "repo://src/whatsapp.rs#L1143-L1183",
+          "version": "repo-lines-v1:sha256:041308c138b61aed5f622ca006de95e62d877c7ece53ef7e68db634046e5e877:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQxMzYwZTc4OTM3Mjc0OTA3NjlhYzM4ZTYxMjRhNzZhMGIyOGZkZGJiZjFlYTM1ZDJiOTk4MDYzMjNiMWZjODUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNjZWY2YTcxMDEyOTdmNmI5ZGNmZDFhOTE1YjE4Y2QyY2EzZTcyMmY5NWI2YzA2YWY5ZDYyNmZmYWNmZTFiYTYifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_fa490ff057bc4c08b292c1b86b903c63",
+      "statement": "The config_watch test checks that modification-time snapshots are None for a missing file and change when the file is created and deleted, using a per-process scratch file under target.",
+      "evidence": [
+        {
+          "resource": "repo://src/config_watch.rs#L54-L73",
+          "version": "repo-lines-v1:sha256:da199b4f8ef9b9aac55eada5da5b2f75daebee1df240a1bc0d2f3ba4aed098bd:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1Y2Y1ZmMyM2YyYTZkMjNjM2VhNTFlNzJmNWU4OTZjNzU1NWZiYWEwNWNkNWIxZWExODJiZjk2MThhOGU4MWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ4ODU5MjEwZWJjYjEyY2Y2OGM2MTYyMjJmYzU2MGMxZjY4ZTJmYTMxZjYzMDQ5M2RmODA5NTg4ODdkNTdhZjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ"
+        }
+      ]
     }
   ],
   "verification": {
     "by": "openwiki/0.7.2",
-    "at": "2026-10-21T21:49:12.291Z"
+    "at": "2026-10-23T21:26:24.156Z"
   }
 }

+ 45 - 31
openwiki/.claims/workflows/ai-autoreply.json

@@ -1,6 +1,6 @@
 {
   "schemaVersion": 1,
-  "pageVersion": "sha256:2d4f2f85140ac84b4be12c4f25bec9245efc316b27132e2568facabe948aff2b",
+  "pageVersion": "sha256:976c69745a1473cf4fe63a8e29ad6081b01d9823cb1d12071204bb9bcd039b6f",
   "claims": [
     {
       "id": "claim_4abb79458e064f78a8d565a7069cb356",
@@ -14,15 +14,15 @@
     },
     {
       "id": "claim_bd1031863e03434ea4649d5866821b34",
-      "statement": "Enabling a chat's autoreply requires a configured AI and a private chat or group, while disabling is always allowed, and the flag is persisted.",
+      "statement": "Enabling a chat's autoreply requires a configured AI and a private chat or group, while disabling is always allowed, and the flag is persisted; F3 targets the selected list row with list focus and the open chat with input focus.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1403-L1416",
-          "version": "repo-lines-v1:sha256:76ea2311dd543d33bf1c52b4cee8997184cb67643a8ba3b2f715abf2fea34667:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0NDI0MGFiOGNkMTM1NDE2ZTQ5MjMyYTEzMGIzYzlkYmI2NWZmYzg0OGNjYzY3OWJhYTQxYWQzNjY4ZTA3MjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ0MDNkOWZmNDhiN2ZiNmQ3MjNkZTVjOWIzMTg5MzEzZTI0NjcwYTFhOTYyYWFlMWU2MTg0YTI5ZWZjNzFjNWQifQ"
+          "resource": "repo://src/app.rs#L1101-L1137",
+          "version": "repo-lines-v1:sha256:0186ad31b1302456cd660841125a254c3e2f46e2074372b97855046cd705d5de:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjMWZhZjI0NjQzNTZlZjY0NzAyNjI4YzM0MGYyOGNhZDljMjY3OGYxMTViNTAxNDZkMGMyNTBmNzhmZDk2NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkzNzNhMTgxNzI2YzQ3ZjdjNzlmOTI3ZDI2NzhhYzQ3NjYzY2VhMzI1OGYxOGEwNTM0ZTg2NDVlYmFlOTlkMGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzZWNmNGIzNzRlN2M0ODFlZjUzODQ1Y2Y1YjlmNDI5MDk3NmQxYTlmNDlkOWY5NWJjNjIxNWEyY2VhMDYyOTAifQ"
         },
         {
-          "resource": "repo://src/app.rs#L976-L1007",
-          "version": "repo-lines-v1:sha256:c4358a68adc4872cb86ae5a6cf9f5e527660022add8d3e68770933ca4f7e6ea6:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEzYTUwNTFjOTU0MGU0MGQ2ZDM4ZTgzMWIzNTIxZWM3MWRiN2M5N2ZjN2IwYzRiZWQ1ZDlmYzY1YWVjMjBkNjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkzNzNhMTgxNzI2YzQ3ZjdjNzlmOTI3ZDI2NzhhYzQ3NjYzY2VhMzI1OGYxOGEwNTM0ZTg2NDVlYmFlOTlkMGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzZWNmNGIzNzRlN2M0ODFlZjUzODQ1Y2Y1YjlmNDI5MDk3NmQxYTlmNDlkOWY5NWJjNjIxNWEyY2VhMDYyOTAifQ"
+          "resource": "repo://src/app.rs#L1544-L1557",
+          "version": "repo-lines-v1:sha256:76ea2311dd543d33bf1c52b4cee8997184cb67643a8ba3b2f715abf2fea34667:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0NDI0MGFiOGNkMTM1NDE2ZTQ5MjMyYTEzMGIzYzlkYmI2NWZmYzg0OGNjYzY3OWJhYTQxYWQzNjY4ZTA3MjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhkYTViNjcyY2ViZDYwMjY1YTM5NmE4NTAxZmMwMmEyYzg2YTZkNWJkYzMyNzRlMGQyZDA5ZDYwMTJhY2Q1ZTgifQ"
         }
       ]
     },
@@ -41,15 +41,15 @@
       "statement": "Only newly saved messages reach maybe_autoreply, which requires an AI client and system prompt and treats the chat as enabled when the global or per-chat autoreply is on.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L333-L366",
+          "resource": "repo://src/app.rs#L450-L483",
           "version": "repo-lines-v1:sha256:6c4f5e0458f8089356df9af12ebae1223140c0e16a7db40e5cd5c048fe12b1a0:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk2ZjNjMTFjYTY5OWY4OTA5MTUwMjAyYzhkZTVlMjdlZjAwNTIyOGRmZmNjMzAzOTlmNDg4OTdmYzdhOTVlMTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdmYzAyNDRhNGJkOTI4YWRmMDE3YjljOWQ0ODIxYjU4NjgwZGRhMmI2YTBhZDEyYWU2MWVhNDBlOWZkMjM3OGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI1YjUzYzQyYTJjZjU2YmRlZWIxOWJhYTZhZjdhYTQzZmRkNjFmN2VhNTQzOGVjOWRmYTAwYjc0Nzg3NWM1ZGQifQ"
         },
         {
-          "resource": "repo://src/app.rs#L521-L527",
+          "resource": "repo://src/app.rs#L638-L644",
           "version": "repo-lines-v1:sha256:9190cc2d950eb45e132eae01c1ae01b5fb4af82470bb42fae8b4e0227a315710:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmMzZGZhMjZjNzdiNTNkZDcyZWZmZGI1MmEyNWFlNGY5YzMwNTdiZTk2NWI0MzRlMTMyOWIwMTM2MGZmNGViNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTYyZWU1N2U0MWE4YzIwYWUwMTY0ZGYyNjE0MzlkY2M0OTlkMzUyMDgzNDE1MTUxNmQwNDdlOGRlYWU4YTQ2NiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTEyYzE5ODI4NjExZDJhZjdmZWFmNjQyN2ZmNzE0MjFiN2JhMTU4MzM5OWY0YWU5ODBiMzJkNThlNDUzYTJiYSJ9"
         },
         {
-          "resource": "repo://src/app.rs#L628-L634",
+          "resource": "repo://src/app.rs#L745-L751",
           "version": "repo-lines-v1:sha256:8954f8190e201652fd68eb454a728e5e4173257eddebb901b8a0b8d4c773564e:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODA1ZTgyYjdjMjlkNzVkZDI3YmZhYTZiYjkxMzZiOTMwMjQyYWEzYjJkNmM5NjI4ZWVmMmY3ZjIxNzdlMzhhNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjRmNGQ3NzU2OTA1OWI4NjMyNGU2YTVmNGY2YWU4YWI4NjM4YmUzNTY5YjM5MjRlZDJkOWJiZjY0MzczMjUyNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYTZlYTYzN2IyZDc3YWVjZjY1Y2EyOWFjNTgzOGQ4NmRmNGEwODM4NTdmMDQwM2U5OWM2NWU3NzQyMzI5NDMwYyJ9"
         }
       ]
@@ -87,15 +87,15 @@
       "statement": "A scheduled chat stays pending from schedule_reply until finish_reply, which runs after every AI outcome, so messages arriving during the wait or the generation schedule no additional reply.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L267-L283",
-          "version": "repo-lines-v1:sha256:30440aa53c9dd61b844f344f5fb331a44d386119170b337bf66d0dbcc172b2f0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVkOGIyMTkxMzczNzkzN2Y5NWE1M2YzYzg2ODM3Y2JhZmY3MWVmZDM0MjhlMDM3NGVkNGUxZGUzODg3Mzg1YzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwOTM2MDA2NDY1Y2FkNmM5ZDNiZjBlYjFkNWUxZDUzYTEyMDdhYjdiZTBhMmU0YjA3NDhjZjJhNGRiNTExMTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyNjE2ZDk0ZDJmZDg4NWViMDZhMGFmZjVkYjQ1YWUyOTdiMWQ5NzViYzU0MjRlZGIyYWUwZmE2NzZkMzE0ZTYifQ"
+          "resource": "repo://src/app.rs#L293-L317",
+          "version": "repo-lines-v1:sha256:bce7d6f44230304cb06893520f9fcf066f4371503113d1fed37a26d6019c2cbc:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVkOGIyMTkxMzczNzkzN2Y5NWE1M2YzYzg2ODM3Y2JhZmY3MWVmZDM0MjhlMDM3NGVkNGUxZGUzODg3Mzg1YzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwOTM2MDA2NDY1Y2FkNmM5ZDNiZjBlYjFkNWUxZDUzYTEyMDdhYjdiZTBhMmU0YjA3NDhjZjJhNGRiNTExMTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVlYjYyZTI2M2M3OTNlYjM0MWRiYjMxMDMxMTFkNWNjYzM5NmFhNjZiZGFjNGU0MmUwNDgzMTJiMzNkNjEzOTIifQ"
         },
         {
-          "resource": "repo://src/app.rs#L368-L376",
+          "resource": "repo://src/app.rs#L485-L493",
           "version": "repo-lines-v1:sha256:df493befb56dd10251865172577fc5c0bae10fe73f070b409398d5734657fd5f:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjVhYmM1NWJiNzAyNjM3YTU5MGU3ZjU3ZjVlNGQ1ZDI3MTc5NmYzNmY4YWQxNzM5ODI3NWZlZThlOWY3ODM4NCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTk5YmRlZmVlYWNhNjM0MGUwMjkwZGM4NmU4ODljODlkNWUxODlhYTViYTQ0YjU4NWIzYjcwNzM0ZjRhNjJkOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMGZkOTUxZjMzMzEyYjBkYjE5OTA3M2NkMjM0MDgxYzczMzI0MjFmNjBmNGM2ZDgwMDdiZTZiMzVjMzA4NjE0ZSJ9"
         },
         {
-          "resource": "repo://src/app.rs#L499-L519",
+          "resource": "repo://src/app.rs#L616-L636",
           "version": "repo-lines-v1:sha256:9c95e2b6201e18825840eb6e7889d5b1dd8fdc2f20ba7da3471f9d4641456107:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ1NDc2YjZjNjU4MzI4NGViNzUyMTA1Mzg3NGFjZmU3NDY1MGQ0NTJhOTRhMjYxYzg4YTFhYzBjYTYzNWYwY2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNmMGY4ZWU3NGMyYmNhMjZlNTlmY2NmYjUzZmQyYWI2OGJjNzIwZTY2MGZiZjM3M2IwZjFhNTY4ZmIyNjBjOTUifQ"
         }
       ]
@@ -123,11 +123,11 @@
       "statement": "Before a reply, images in the context without a stored or in-progress description, at most the 5 most recent, are downloaded and described in parallel by the vision model; the reply starts after their ImagesDescribed event.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1231-L1292",
+          "resource": "repo://src/app.rs#L1372-L1433",
           "version": "repo-lines-v1:sha256:3fb067a4234c53059cbc431004a05bb2d3c9fba17d27286a1860ffc3bbc41547:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBmNDNmYzY5OWMxYzQyODRjMjI1MWJmMDdiZDk5ZDcyNjQzMGU4YzhhYWE2YzM2YTQyM2ZkZTQyYzc2Mzk2YmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRmYWI3MTBkZGY3ODU5ZTU3NTc2NGE5MTNhMDAyYzc0NTg1ZmZhNzAwMTUxMGNiODgxMWRhODNkMmNmNzRiMzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU0MDAyNGFmZmIxODk1NGEyZjYxODAzYjk5ODBiOGY1NmVkNTlmZjJjN2FiODAyZDU5NDI2NGZiMDFkNmMyOGUifQ"
         },
         {
-          "resource": "repo://src/app.rs#L430-L463",
+          "resource": "repo://src/app.rs#L547-L580",
           "version": "repo-lines-v1:sha256:9715aab5bcb9a42cb7f22b16e2ae0f531098ff6d6be6ca1ed6da8c95353afde8:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRmODNjYmFiZjFkYzg0MDU0ZTM0OWEyOWI5M2QzODU0NTQ3NGZjYjkzMDU0ZjNkOTYzMDg4MWQxODNjZWI0MzciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI4ZTNjM2Y4MmI0ZjZmMzU0Mzg1NWNmMjZkNjljZmI0M2NjN2RmMTgxYzE5N2NmNDI0YmJhZjM0ODExZDA5M2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzZjUxYTZkNTgwMzYzOTIyNGZmZWQwOTU0MTEyNTZhNzhlMzA2NTU5NDZjZWY1ZWFmMzlmZjJkNDExMGM1MjIifQ"
         }
       ]
@@ -137,7 +137,7 @@
       "statement": "AI text replies are discarded if both the global and per-chat autoreply were disabled meanwhile or the owner wrote by hand after the answered context, otherwise sent and stored with is_ai true.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L703-L765",
+          "resource": "repo://src/app.rs#L820-L882",
           "version": "repo-lines-v1:sha256:dc263d71dc225c968c33c780874494b82440bfd214546483d0aef9fd6a02bf46:eyJzZWxlY3RlZExpbmVDb3VudCI6NjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBkNzQ1Mzc3ODY0MzI0MmZhZDE3MGZhNjcxNTUxYTJiYjI4ODM4Y2M2NmQ5ZjMwZWNkODhjZTNmNjFhYjRlMGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYzYjQ0YTU2ODZkYjM0MjNlMDVmOGMxOWMwYmMyNzlmMDA1NTE4NDJmMzgwZjIxMTM1ZTkzMTZhY2IwNmQ5ODEifQ"
         }
       ]
@@ -147,11 +147,11 @@
       "statement": "The reply is sent reply_delay_seconds after the first unanswered message of a chat: a timer task emits ReplyDue after that delay.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1199-L1209",
+          "resource": "repo://src/app.rs#L1340-L1350",
           "version": "repo-lines-v1:sha256:be3fc24d7db22727c3dddd8c5116b8fd64a13c8a473f99818b61f3bb6ac76204:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxMmIwOGNlNWY0N2RmY2RjMTQ1MTA4ZDFkNmQ2NzhiYTllNDhjZjBmMGY5YzdiYTBhNzMxYTkzMzFjYjc2ZDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRjNjg0ZTk4OWVmNzk4ODJiNmIyOTMwNDdiMTJiNDg4YjkwNjlhYmUzMzc2ZTVhNTNhOGEyZTI5YjY4ODcyMTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA1ZDJlYmFjZThlMTE0NjAzZTYyZDc1YjVmNmRjODFjMzJiMzgzMmI4YjBiMGUzZDVhZDY0OTMyMDQ4YTZkMzgifQ"
         },
         {
-          "resource": "repo://src/app.rs#L368-L376",
+          "resource": "repo://src/app.rs#L485-L493",
           "version": "repo-lines-v1:sha256:df493befb56dd10251865172577fc5c0bae10fe73f070b409398d5734657fd5f:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjVhYmM1NWJiNzAyNjM3YTU5MGU3ZjU3ZjVlNGQ1ZDI3MTc5NmYzNmY4YWQxNzM5ODI3NWZlZThlOWY3ODM4NCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTk5YmRlZmVlYWNhNjM0MGUwMjkwZGM4NmU4ODljODlkNWUxODlhYTViYTQ0YjU4NWIzYjcwNzM0ZjRhNjJkOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMGZkOTUxZjMzMzEyYjBkYjE5OTA3M2NkMjM0MDgxYzczMzI0MjFmNjBmNGM2ZDgwMDdiZTZiMzVjMzA4NjE0ZSJ9"
         },
         {
@@ -165,11 +165,11 @@
       "statement": "When the reply is due it is dropped if the autoreply was disabled, the AI is missing, the chat is empty or the latest message was written by hand by the owner outside the self-chat.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L417-L451",
+          "resource": "repo://src/app.rs#L534-L568",
           "version": "repo-lines-v1:sha256:a63345aecce224550feeba0b579240e2e6f55ea65600d2512c4b1e8291f1ed06:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhhMGZkOGQ4YmVjYTA3ZDNkNGZkZDViOGFiMWI4NmQ1Y2MwNGVlYzdkNzY3ZTc1Y2Y3ODgxMDk2OGI2N2Q3NDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU3ODQ1MTBjNTZmNWE5YTNiMmY0ZTdkNmZjMDFjNGM4NTEyOWJjYzUzM2U1YTZjZTA2MTYyY2FiMmIwNGY4YzIifQ"
         },
         {
-          "resource": "repo://src/app.rs#L529-L547",
+          "resource": "repo://src/app.rs#L646-L664",
           "version": "repo-lines-v1:sha256:45691eccde8d968061d2835ed9d342c7665536f57d65eba18d6818bf6cdbaccc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRlNjMzYzk0ZmI0NTZhMDkxMTliNmQyOWYyZGMzNjQ2MTI5ZTVlY2M4MDYwMTdlNzVkYzk0MTM3N2RjZDQxNjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJkYTA0OWEwZDljNDFjMmNjZGQ4OWY2MmQ2Zjc1OTA0ODBjNjQyZWI3Mzc5NTcwM2Q0MTQ1Y2I5MzQ2Y2VhZjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNhMzhmYjI3YWI5N2YyMTc1ZGQ0MDc1NDhmMTcyMGUxNzMwYzY4YjA5ZGI1M2MzZTZiZTQ4OGQyYjMzMzMxZmYifQ"
         },
         {
@@ -183,7 +183,7 @@
       "statement": "On the first OfflineSyncCompleted per process, catch_up prepares one reply without delay for each enabled non-group chat (all chats when the global autoreply is on) whose last message is from the contact, older than app start and within catch_up_seconds, including its offline images, and reports the count in the status.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L567-L626",
+          "resource": "repo://src/app.rs#L684-L743",
           "version": "repo-lines-v1:sha256:662ddb2a45a183036d34d982acfd7438be6f423e58df87505555df7b3c0a5c12:eyJzZWxlY3RlZExpbmVDb3VudCI6NjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM5ZmJjMjYxZDk1MDVkNTBjZTU1MWY4ZDI4OTQ2MTZjMWM2MGY3NTlhZTAwYTE1OWY4Yzg3OGVmMWJjM2Y2ZWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg0ZTkyMmFmNjJhMTJlOTYxYjczZWM0ZGMyMzNjMTk2MDhjNzRmNDU1MzExNzUxOWQzZWU3MjViZDZkNWNkNmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc4OTU5NGE5NzIzN2Y2NjVkMmU0Y2RhMTIyZmY1NTIyNzJjMjVmZTFhMTI0NmZjMGNkNjllMDQ4MjE2ZTNlYWMifQ"
         },
         {
@@ -197,7 +197,7 @@
       "statement": "The AI context is loaded with messages_since from local midnight of yesterday, capped at CONTEXT_MESSAGES.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L636-L643",
+          "resource": "repo://src/app.rs#L753-L760",
           "version": "repo-lines-v1:sha256:7c77ded5ac6d33c39416f329b08cebb428d4745fd351bf74af8942d0b7a7ca02:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTUxNGM3ODU1Zjc2MTlkMWNmNzRmYjkxNzViOTNiMDc1ZTAwMGFjNTA0MjFlMWRmMWQ0Nzc4NWQ3NDkxYTdmOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWJhZTkzZGRjNWYwMDFlOWRhMTI5YmY0ODA5MzQ3MGI1ZWQ4ZjNjNzdjYzA2MjQ2YjJjZTkxODdmMTIzNGI0ZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNmZlMTlkZGI5ZjIxNTk1NWQzNzM5NjBlODk1MTI1ZGE0ZmYwYTYwNTAxMGY1ZmVmYWExYTI2ZDE0NWQ4NmNkMiJ9"
         },
         {
@@ -221,7 +221,7 @@
       "statement": "The global autoreply is persisted separately from the per-chat flags and toggling it does not change any per-chat flag.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1052-L1069",
+          "resource": "repo://src/app.rs#L1186-L1203",
           "version": "repo-lines-v1:sha256:05cde78eab7927a8b5c33ce971f85c0e210ce60936eada88f8c9481585c1519e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhMjY2MjVlMDNiMmE0MDg1NDg0YjQ2NjUzOTllOWRmOWFhZDQzYmVkNGQxNWFkODEwODMzMGIyMDhjNmM5ZTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU2ZWQ1MmZlYjhjZTliMjNiYmNiYTkwMGFkYjk0MzE3MjA4ODNhZTk0ODY4NmQyNjhlNmRkMjFlNjljYTU3YjkifQ"
         },
         {
@@ -235,11 +235,11 @@
       "statement": "After a reply, if a newer message that passes should_schedule arrived after the answered context, finish_reply schedules one new reply keeping the images received meanwhile.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1307-L1314",
+          "resource": "repo://src/app.rs#L1448-L1455",
           "version": "repo-lines-v1:sha256:b961fc471f97ce4ba30ba2d05c7240d1d8e6038ccae49f417602f58eb534920b:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzNhMDkyMjQ3MTNmYjQ0MDIzMjJlMGFjODEwMWI3OTI4ZWQwNThlZTExZTU5NmZkYjMwM2NjMGRhOGJiZTc3YyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTVlMTI4NTMzNjllMzNkZDlkMmUyZDQxNjFlYmJhNWNjYWMxMDkxYzIwMzg1ZTViMzNlMjkwMGIyNjdlOTVlYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDlhNTk5ZGY5ZDUwOWVhMzBiNDMzZjkzYjY4ZjE4ZGQ1ODY2ZDVmMGJjMGMzMGEyMTU1OWFkN2U4MWM1ZmMwMCJ9"
         },
         {
-          "resource": "repo://src/app.rs#L499-L519",
+          "resource": "repo://src/app.rs#L616-L636",
           "version": "repo-lines-v1:sha256:9c95e2b6201e18825840eb6e7889d5b1dd8fdc2f20ba7da3471f9d4641456107:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ1NDc2YjZjNjU4MzI4NGViNzUyMTA1Mzg3NGFjZmU3NDY1MGQ0NTJhOTRhMjYxYzg4YTFhYzBjYTYzNWYwY2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNmMGY4ZWU3NGMyYmNhMjZlNTlmY2NmYjUzZmQyYWI2OGJjNzIwZTY2MGZiZjM3M2IwZjFhNTY4ZmIyNjBjOTUifQ"
         }
       ]
@@ -263,7 +263,7 @@
       "statement": "Mentions of the account owner's number or LID in message texts are replaced with @[te] before the turns are built, without matching longer numbers.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L654-L673",
+          "resource": "repo://src/app.rs#L771-L790",
           "version": "repo-lines-v1:sha256:0a937f62450210fb1b4a45c2eb392c8533cab8fc5e7f2ca88caec3ba64669dbb:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkMjA2ZWQ4NGMzNGNhMWE0ZmNmZmQ1NmFlOTBiNDgyZDIyNGM2OWQ0ZmRlMDY4MmI2MTllODNlMDRlNWJhYjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEyZTA5MzVlNDEzNWQ5MWEyZGFlMDUxOGE3ZTY5NjQ0MmIxYmM5ZGYxOThiNDA1ZmVmMGQzMjUwY2VmOWU1ZWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQwMjI2ZGQ4YmZkY2JkMDhlNjUxMjUzOGY4MmU5YWNiOTEyNzg2NTgwNDEwMmQ2YjRlMzQ4NmRmMjU4YjMxNmYifQ"
         },
         {
@@ -281,11 +281,11 @@
       "statement": "Image descriptions are stored only when their message still exists; failed descriptions leave those images undescribed and set a status with the failure count and first error.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1316-L1324",
+          "resource": "repo://src/app.rs#L1457-L1465",
           "version": "repo-lines-v1:sha256:a271b225aba65e2765d3d7b1ab0f8a02114cdf60cf3a5298066b80ba40c06576:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZmI5YzZkZjEyNGI2NDgxODMyZDk5NTgyMjRmNzFjYjRjZDJjM2Y3YTU3Njc1OWQ0OGM5NjBkZDNmZTE4OTliZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjk2YWY2OWU3OGEwNDBlNTAxNjM2YjMwNDhkNGVjNTQ2Yzc0OTU1ZTEyN2IyY2M5OTU4MDFiZDI2YWM2NjZkZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTViMzliZDc5NmMyYjVjYThiYjhjMWEzNjUzODk5NzY5MGE0OTE5ZDY5NzVlNGMxMGZiMWE1ZGE3MGU1Mjc3OSJ9"
         },
         {
-          "resource": "repo://src/app.rs#L465-L497",
+          "resource": "repo://src/app.rs#L582-L614",
           "version": "repo-lines-v1:sha256:49bdb09f74d399910bca1c218286bf995bae8e8457efe83cb97034814f6d67d5:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJkOGRiMTAzNzg3MWI3NGU1OTVmYTZhZThkNjAwNTE4NDIyNDNkNzJkODZkNDM2OGUyZGU1OWI2MTdkYWM2NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJhMGM2MzVlMzlkMTFmNTgyOGE1Mzk1MzhkYjE0ZTdlNzU1YWFiMzlhZDNhNzljYmM0ZTFmZTQ4ZDgyZGZmYzIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBhN2YyNDA4YTExOTJiY2VmZTI0ODJhNTg2MGM1MTVhMmQ4NGIxMzQzZTE1ZDdmMGVmNGRhMTA0YzY1ZTJiM2EifQ"
         }
       ]
@@ -295,18 +295,32 @@
       "statement": "Each image download makes up to 3 attempts, each abandoned after 60 seconds, waiting 2 seconds times the attempt number between attempts.",
       "evidence": [
         {
-          "resource": "repo://src/app.rs#L1336-L1362",
+          "resource": "repo://src/app.rs#L1477-L1503",
           "version": "repo-lines-v1:sha256:503c0157ef6bb7cec106cb2a0c2a270703d688580b0dcdc4e710dc7b9dc1fc08:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZmMjVmNjQxZDQ5ZGRkYTllYmMyNjgyOGViZDJmNzM2OWFkNDBmMjBmODRhNDAwZDFkYjliYzE1YTNlMmQyNTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAwOTlhMTBhNjE5YjUxNmM0MDM5NjFmZmQ4NzhlYWJiMDM2NGRjYjEzYjcxNWFlODI5OTg2ZjhkY2VjMzA1ZGIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY5OTNmMTFjZmFmODg0N2M5ZTgxNmQ0NDhiMWE0MzZiNjlhMjk1NjQzZmVmMGRlNDliYTMyY2FkYmFjNTIxM2UifQ"
         },
         {
-          "resource": "repo://src/app.rs#L64-L72",
+          "resource": "repo://src/app.rs#L66-L74",
           "version": "repo-lines-v1:sha256:165e39ca6d47b5010cdaa8900029d4f8be1acafd1768902f613c6b9232914b22:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmYzYmQxMmI3NWRiNDMzNWFmZjQwOGNhYzM3YjU4YjIxZDEyYWZlZTZlODgyYWM0ODlkYmNlN2JlMWJjOWYzNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg4NjhmM2IxNzUxODE3YTdlZTgwYTMwNTU1MzhhNDBmNmU4ZTlhMmE3N2QwZTZmNDYwMGIwOTAwOTkyN2I2YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiODQyYTI1MTg4ZDEyNDhhNDk1MmZjN2E4MGI2OWM5ZThhNzE1NjJmYjhiOGFiNTQ4MmU5MGRmYjQwZDUwOWUwOSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzhkMTA1ZjI4ZjVmMWMwMzFmYTE3MjY3NDE4MDgxN2NkZWUxZDJlODBiZmRiNWM4MzUzOWY3YTFhNzY5MGUwYSJ9"
         }
       ]
+    },
+    {
+      "id": "claim_688b76332f0f4c86bb98d3bde215ffa2",
+      "statement": "When the configuration files change, the reloaded configuration replaces the current one and the AI client is rebuilt, while replies already running keep the previous client.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L383-L400",
+          "version": "repo-lines-v1:sha256:11076b2b498b6d19b7a0629886f2cdb37229105049de848490f4e1d9faead710:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVhYTEzNjY2OGNiMzc5NzgyYTUwMTY4ZDNlOWM4Njg4MmM5YTBhZTAxOGQwNTZiNTEzZWU2ODA5OWYxNjc3YzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVmMjAxZDg2ZGM3NzY0YWFmOTA5MGNhNDRkODcxNDM3Y2JiODk4ZGE5ZWJlMGU0ZDhlNjVlNDZiZDJkZGVkYjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyNjE2ZDk0ZDJmZDg4NWViMDZhMGFmZjVkYjQ1YWUyOTdiMWQ5NzViYzU0MjRlZGIyYWUwZmE2NzZkMzE0ZTYifQ"
+        },
+        {
+          "resource": "repo://src/config_watch.rs#L15-L40",
+          "version": "repo-lines-v1:sha256:39a3d9ca14f3c3010ef0a2fadf3e6a8ecf8749be831215006c0808392cb62813:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3NGMwOGQzMzg5NGUyMWQyYzBmNTMyMDkwYmQwNjFjNDNjMzg1ZDU1MzkwMmI0MDkwZDkxZjJjZDY3M2VjMWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE1Y2VkZDkyMzY4ZGI1MDMwYjUxMjQ3ODcwODlmY2FmZTI5YzhmOGQ4MjExZDA0MTE4ZmM4MTViYWM1MDBjOWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI4MTU5NjA0YjAyZjI1MGMwMzAwZTljNDQ5ODlmMzMyYjA4N2RjN2UxODAxZGI5Y2IyMmM2ZWM5YzdmMjUyZjIifQ"
+        }
+      ]
     }
   ],
   "verification": {
     "by": "openwiki/0.7.2",
-    "at": "2026-10-21T21:49:12.291Z"
+    "at": "2026-10-23T21:26:24.156Z"
   }
 }

+ 112 - 0
openwiki/.claims/workflows/relink-and-pairing.json

@@ -0,0 +1,112 @@
+{
+  "schemaVersion": 1,
+  "pageVersion": "sha256:2e5dcb382a443a271be7c245304c8aad46d09a5722939de151a90fe1c5e0c86a",
+  "claims": [
+    {
+      "id": "claim_5f458a6b32c4452a9d25edf481e1f07e",
+      "statement": "F8 is handled on every screen: the first press asks for confirmation and the second starts the relink, while any other key cancels the pending request and an Esc used to cancel is consumed.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L356-L381",
+          "version": "repo-lines-v1:sha256:b64194da9c7bce682333ce6e12c758fef9076e9a0af5e51f088b07063965a086:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI2OTEwMzllOGFhMzZiZmZmZmI3OTMyMjRhMjA3MWIwYmRlZmE0Mzg3OGNlYmE0MDJiMmY1NDlhODczYjNjMDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM4MDg4M2RjYzQ1NWNiNGUwODk3NDkxNzMwZmVhYmQzOWM3MTRhYTRmYTUzZTc4MzA1YThkN2ViMWQyZjYxZmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBlZjIyYTA2NDVkNDAwY2EyNWVmYzI2Mjg2ZTdjMDZmOWUzZmZhZmQxY2UwMzVjMmZjOTkyYWZhNGI2NGIxY2EifQ"
+        },
+        {
+          "resource": "repo://src/app.rs#L884-L903",
+          "version": "repo-lines-v1:sha256:dd7415d7eb098a51dfb451afc950bc0918baaa5c2b9d01f64bd1354a87ccdb76:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZGRmNjgzMjdhYTA4ZWZjNDYxNGFmZDIwOTkzZTFkNzZiZjUxNmQ3NzRjM2QyMmIzOTUyOTEwNmNkOTBhZmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE2NTVlZGVmNDZhYzg4YTI3NGFkODVmNWQ2NmRhYTgyYTZkZDg3ZGY4MWQwOWNiMTIxNzVjNDFkNWY5YWY1MTgifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_6074752bd7964af7bc44d6897ef2c067",
+      "statement": "The web command Relink calls start_relink directly, without the TUI double-press confirmation; the browser page asks for confirmation before posting /api/relink with the X-Wh-Request header.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L1144-L1151",
+          "version": "repo-lines-v1:sha256:6a5fafc027a4dad94444764670c73f86302a4de5f80873b8c2731496881881a1:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOGY4NmU3NjA1MmFiZTAzYjI3OGY0NjA0MzNmOTJhNjFlOWE2ZmM2YTA2NzA0Mjg4NGQ3MjJjNmVjNmMwNWEwMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTAzYWM0MTkxYWMxNTg3OGMxODk3Zjc5N2MwNTcwYzkzNmQ5ZTM4NDgyODgxOTNkMWM2ZDhhN2MzMDBkNTA0MyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTc3ZDk1Mzg3NWIzYjM4OTk2YjhkOTM5Mzk5M2YyZTUxOGY1MGMzMzY1NjgzZDMxNGFjNmE5NjNhMjliOTBmMSJ9"
+        },
+        {
+          "resource": "repo://src/web.rs#L166-L174",
+          "version": "repo-lines-v1:sha256:81e9004c1f6afdff65111c92f71cf2a8b9e2fd1f87d5d54d60e8446e7b1dd361:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODU5OWJkMWM5YjdhMTQ5ZjczYzQzMjQ2MjljMzczMzFmOGNlZTM1NDVhN2IzYWUwNjk1ZWM0NmFlZjBiZmJjNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGRhMDBiODU3YTc3N2IyNzQwZmQxZjg0MWIxZWQ0MzcwNmYwOTc5YWEyNzE5Zjk1YzJjOTJjMGNkYWEzOWJkYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNGNmODQ4NzQ5MjczZDdkMWJlZDQ1ZTg2ZTNmMTU3ZmY3NTE0MjIyMTFkMWE3YzFlM2RhZDIzYWNkODNiZWRmNCJ9"
+        },
+        {
+          "resource": "repo://src/web_page.html#L703-L718",
+          "version": "repo-lines-v1:sha256:edcf716f5c77bc9ba443f65a19535e9c1b254b9949a238743590df637a35157e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVhZjIyY2YwNzMyNTkyYzI0NzE0MWNiZTYxZWZhMTEwMTI2MTJmNWEzMTIyYjlhZjliYmFmNTJmNTcyNGQxOTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc2NTNiYWEyY2QxMjM0ZDlmZTgzOGNiZTJhN2EzODc1YTcyMWMxOTRmNGRjMjZiZTgwYjZlOTU5MGY2YzE3MmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4MjJhZjlkZGY3ZTI1ZjQxYTFmZjU1NDA2MDljN2FhNGNhZmZjODBkMjhjM2VjNjkxY2ExY2RmOTMxMzQ0MDMifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_61c1cf660cdb4769a895097a3dc5d711",
+      "statement": "start_relink ignores requests while a relink is running; otherwise it shows the connecting screen, clears the QR, resets own_jids_known and spawns a task that runs WaHandle::relink and reports AppEvent::Relinked.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L320-L341",
+          "version": "repo-lines-v1:sha256:562a22627a48012be000f4fe8e09f389e6e78bb3e5f7b9930922352ad69710bc:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIzZDgxYmQ3NmJjMThmZDc2YTBlMzE3M2FiZTI5Yzc5ZDZiMzYzZGUxMjhkZDEwOGI5ZDg4M2Q2OWVlNGJlNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM0YjVjYjQyNzgzMDViZTExYTkyOWY2MjYzOWFkYjMzMzI3NjczYTdjZmVmZDgxNTBjOGNhMjcwMmJiZjE5N2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI5ZWM4YTdlN2ExNmVjZmZhNzMzMzI3ODJjM2M4YzRlNjNkYjczODcxNzQ5YjVlMjhkMWNlMmYyOTZmZmYyYmMifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_a64da236b7ef44d391eb09c8a0b7c999",
+      "statement": "WaHandle::relink stops the old client (logout, wait up to 10 seconds, abort the translator), deletes session.db and its side files or empties its tables when the file is locked, and starts a new unpaired client that sends its events to the same channel.",
+      "evidence": [
+        {
+          "resource": "repo://src/whatsapp.rs#L182-L237",
+          "version": "repo-lines-v1:sha256:18f2dfb379587e6702f10be30e023ea43187acf5f2e74eee0646e34c148de96c:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwMzUyMWI1YmRmYWViNzk2MzY5ZTExMzEwODIwMjIwZTNhMTI1MGUwY2E5NzIyMGEzYTI1OWFkMjQxNTcwZGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg3NzllYzNlN2JjNmZkMzdlNDYxMTdhODY2ZDA2YWIzZTVlNGVlYmE0ODJjYmViYzkzZjhjZGRjZjYyOTM5ZTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OTU0Y2ZjYmI3ZmVlMDA0ZTg0M2I4ZDdmZjAxNTA3ZTlmOGU5YTYzNWI1MjZlOWQ1ODRmNzY1MDA4YjRiZDYifQ"
+        },
+        {
+          "resource": "repo://src/whatsapp.rs#L314-L367",
+          "version": "repo-lines-v1:sha256:dae8e9fd58393c76159603a1143da95f40e9d77d62eaa8e9531737a4a58b8edb:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgyNTI5M2Y3MjNjNGMzMjZmNDJlMDFmMzc1Nzk1ZTJjMjE4NzQxNGEyNjI5NDEzOTEwODc3Mjc3MzdlMWMzMDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYwNjNjNGQxYWY4Y2Q4OWJiMGJkZDc2NTdmZmNiZjY4MDZhNjEwZDc5ZDZjOGQ0YzMwN2MyNDFkZjM5MDA1MjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI1MjJlMWI3MWMwMDczNGUwMWUwYTJhN2ZjYjU1ODdjYWEwN2RkYzRlZjRjMTViYTEyNGFhN2M1ZWRlZTVjYzUifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_f53cf16e91a448c69a18c9991038dad7",
+      "statement": "While no session is running during or after a failed relink, sends, image downloads and own JID lookups fail or return nothing because WaHandle::client reports 'WhatsApp session not running'.",
+      "evidence": [
+        {
+          "resource": "repo://src/whatsapp.rs#L200-L210",
+          "version": "repo-lines-v1:sha256:8ed098e34276e2c4b9ff74e786b647375c2d873d70640b609b0ae4bca9505394:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwYjNlNTU4ZDczYzgzNWI1NWVkY2U4NWUxNTMzYjM1ODczM2U1OWU4ZGNmYTJmZTM3N2FmNDBjZTcyZjY4OTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE3M2Y4MjFiY2U5NjYxNDdhMDEyMmRjYjVhZGExZmVjZjgxMWIyZmNiNjIwZDUwZGFiY2U2MTg0OTUzNjVhNWUifQ"
+        },
+        {
+          "resource": "repo://src/whatsapp.rs#L279-L295",
+          "version": "repo-lines-v1:sha256:2716d1991bf1de21e1b70d5a1702f8e881af4787f4afd11837ef8767c63b07b3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2OGU3Zjk2MjU1MTJjM2E1NzQ5NDIzZDA2MDc0ODRhZjQxODc4NThmZDhkNjhhMTcwMWQ1ZWI1ODA3MWU4ZTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFkMjVmMDBjOGVlOTdlZDMzZGJiMTNiMzNiYmRjOWEyNjhjMzhkODZmMjlkYTQyYTg1YjBkN2U0NGE0YTYxNzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFiY2ZkYmJhNTAwZmIyZWEyMTlhZTdiNDQ0NmUzZTU1NTA4YWViMWQ3ZjdjYmM4MGYxNTUyNjMzMzk5ZGY1MWMifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_68f154e0c2c9462eb3e3328666058891",
+      "statement": "A relink resets only the WhatsApp session database; the history database with chats, messages and autoreply flags is not touched.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L320-L354",
+          "version": "repo-lines-v1:sha256:f986a6b8380806257f348dc108fa11b5fbf0d7fca2d638d33cdedb965da8a9b6:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIzZDgxYmQ3NmJjMThmZDc2YTBlMzE3M2FiZTI5Yzc5ZDZiMzYzZGUxMjhkZDEwOGI5ZDg4M2Q2OWVlNGJlNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM0YjVjYjQyNzgzMDViZTExYTkyOWY2MjYzOWFkYjMzMzI3NjczYTdjZmVmZDgxNTBjOGNhMjcwMmJiZjE5N2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU1ZTg0YjQyYzc5NDMxZTNkOWI4Mjk4MGQwN2Y5NWQxMTBiYjQ1ZGUxZTI0NjlkYjcxMDJlNzJmODRiM2FjOWIifQ"
+        },
+        {
+          "resource": "repo://src/whatsapp.rs#L221-L237",
+          "version": "repo-lines-v1:sha256:63e137b22805f72a6e35c74bf1f9296c30d5dd1c696101d7b7db2f3dedb3b664:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY4YjU3MmE2ZGZjZGNjZDc1YmE0ZDBkZDZiYmEzNjUwY2UwNDFkZDkxYzRjNjE1YTBmMDJhNDllMjNlNTA2OWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIyNTdmMTI1NGQ0NjJmZjgyYTFmYTFmZjYyNzFlNzIzNTM1NTFkMDZlNjMxMDQ2OWYxMGZmZWUxZTY2ZmVlMjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OTU0Y2ZjYmI3ZmVlMDA0ZTg0M2I4ZDdmZjAxNTA3ZTlmOGU5YTYzNWI1MjZlOWQ1ODRmNzY1MDA4YjRiZDYifQ"
+        }
+      ]
+    },
+    {
+      "id": "claim_26a79653b21a4f10992d81769d99eb47",
+      "statement": "New pairing QR payloads switch the app to the Pairing screen and are exposed both to the TUI and, rendered as SVG, to the web panel state.",
+      "evidence": [
+        {
+          "resource": "repo://src/app.rs#L1171-L1184",
+          "version": "repo-lines-v1:sha256:643e53e7eee86239879c37d679254899bc317cdbf3cd55b8314e04ba1657043c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIzN2M0MzFhZmQ5NGMyNzU0NzJkNjliODNlMGM0MmRmMjU0NmQ2MDQ3NmM4ODg3Yzk3MDRmZWM2Y2U0MDkyMDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRkZDFkZTJkMmJiNWE5ODAxZDE4MTAyYTQwYzlmOTc4NjRkNjNkYmEzMzkzMzdiZDVlYTU3Y2FiOGU4YTYwYzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJlODM0YjUzNzA2ODgzYmQzMmMxMmViNmQ0OGEyMmFjZjlkNTIyYzIzOWJlNTE5NDZmNzA5YjIxZDNkNjIwMzIifQ"
+        },
+        {
+          "resource": "repo://src/app.rs#L406-L411",
+          "version": "repo-lines-v1:sha256:b1ff41a223bdf9bbf36663f8088fa2379ee28283923bdb2f0cbbc4f5adbdcaee:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNmQ4Y2YwMmQ4NTY5ZjVhNDdmNGI2ZjRmNzkzMGY3OTMxNzMzZWY0YTg0MzA4M2ZiZmMzMDM2Y2IzYmIyNzkwZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTUyMmM0MmNkMTljNmQwM2Q4MDhjNDUyNzIwNTUyODI1YjdlYTFjNWI0OTFiMjE4NTExY2FjNzQxNWNmOWQ5YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYzk0OTJlYzlhMzZlOGQ4NTQ2MTUyYjgyMmQ1N2ZkMjAwOTg5MTUwMzBhMjBkOWJkYTFkNWZhZDQ0ZjhmMWI1ZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTYxODU0MTVhYzdlMzU2MTg4MWU4NTViMTEwN2Y3NDYzNjIwZTI5NjM3MWI0NWU4ODMwNGNhMjYxODY0MDYxZCJ9"
+        },
+        {
+          "resource": "repo://src/web.rs#L364-L379",
+          "version": "repo-lines-v1:sha256:e6d58dec7c8a45140e76290e175a7b2f5a9b3c59f60b3e244064813974a3ed1d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMxYjQ2MDBlYjQ4M2JkNTEzNjA1ZTE0ODVkNmRjNmNjMDA3NjJkNzRiODNjNzg4OTQ2MGQzOWNkMGRjMzQ1YTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ0NGIyYWRlMGYzNzRmOGZjZGYxNGM5MTQxYzNkNTMxYjcxMzgwN2ZkZTc0NzI1NTZiOWJlNDI1MjE5NzM4OWYifQ"
+        }
+      ]
+    }
+  ],
+  "verification": {
+    "by": "openwiki/0.7.2",
+    "at": "2026-10-23T21:26:24.156Z"
+  }
+}

+ 2 - 2
openwiki/.last-update.json

@@ -1,7 +1,7 @@
 {
-  "updatedAt": "2026-10-21T21:59:27.228Z",
+  "updatedAt": "2026-10-23T21:42:31.279Z",
   "command": "update",
-  "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
+  "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
   "model": "host-agent/claude",
   "status": "complete",
   "language": "en"

+ 50 - 43
openwiki/.page-manifest.json

@@ -2,81 +2,88 @@
   "schemaVersion": 1,
   "pages": {
     "/openwiki/architecture/app-state.md": {
-      "pageVersion": "sha256:a11d9385ed0fa92efef8cc9b1d8eb7a4c392af8f751c1b660a60d6044eac8893",
+      "pageVersion": "sha256:5a08c085203eed7c6af86a5ee968fd9f61a42c7dec64787f196a9cfc4a404452",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
     },
     "/openwiki/architecture/runtime-and-event-loop.md": {
-      "pageVersion": "sha256:3abe82512bfb153e658472ff7afa50967adbaa76f4efa6bb2af3215c6b83795a",
+      "pageVersion": "sha256:e2f63ba48d127d1aaa5a3dc9ea68ed4b0137c88abb763235e4ed0f305b984766",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
     },
     "/openwiki/architecture/terminal-ui.md": {
-      "pageVersion": "sha256:d47df0cc7d426e68c11679d15aeb6042a2f2f7ca975321194f44268297b7bc05",
+      "pageVersion": "sha256:63e03e3e582fd585379805751a335bfe7b208a8a64451a79c8c177698950d3a0",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
     },
     "/openwiki/concepts/history-store.md": {
+      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
+      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9",
       "pageVersion": "sha256:fb61743878eebfa7a936223b03e0c9a3d4247468218b42035889a35e1283bc0a",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26"
     },
     "/openwiki/integrations/ai-client.md": {
-      "pageVersion": "sha256:e71947f4ffd79153e75af279f7eade4504e8b9a729d276a1f1938a45a094f7a0",
+      "pageVersion": "sha256:fddcffbcc714b9d61c2a530a57c5b40dbc6dd4279ee7a363ee96b0d7a44fb7bb",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
     },
     "/openwiki/integrations/web-control-panel.md": {
-      "pageVersion": "sha256:3ffa0ef4024e9b6d6363e8a29f878dc08440b9bdb206eb8cbc0e7a43f37d70ed",
+      "pageVersion": "sha256:5fefadb20f2b8cf87afcc027db804249288fd112025b45dd9e740fc8bca85d7e",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
     },
     "/openwiki/integrations/whatsapp-client.md": {
-      "pageVersion": "sha256:6a3c8a9d9be1d3bc11179381bdb384623d59cb3e2c026f4f3d62b3bf3a5f84da",
+      "pageVersion": "sha256:18ed8fdf1d8ab4689b600aa550246fd3d6b6b26340ea7a345c775d128f40bc56",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
     },
     "/openwiki/operations/configuration-and-data.md": {
-      "pageVersion": "sha256:895dd056591483fa81fad9cbc781504961253ccdbdaf42c8ed0f92c3685c50cd",
+      "pageVersion": "sha256:4424aa91ddf23d979235806fa52b23f096e57c324e97fc229b803823db189ab9",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
     },
     "/openwiki/quickstart.md": {
-      "pageVersion": "sha256:0904a154437de3ef07d0a913900c796b3fc862d2fcd254438da8ab3c23dcf515",
+      "pageVersion": "sha256:3763c54c3a6a1ddee93531d89a303f3b8c99e547a730b01dfb8e4c97d5211efc",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
     },
     "/openwiki/testing/unit-tests.md": {
-      "pageVersion": "sha256:10bcb258ed8fc94585fa952d923b1602f7daafeeed0fd6693ff6c44f40ffd393",
+      "pageVersion": "sha256:0f5f5df1184eb187831e0b8e3f9e8a2516c314386ce7360f1c4915c9450c587f",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
     },
     "/openwiki/workflows/ai-autoreply.md": {
-      "pageVersion": "sha256:2d4f2f85140ac84b4be12c4f25bec9245efc316b27132e2568facabe948aff2b",
+      "pageVersion": "sha256:976c69745a1473cf4fe63a8e29ad6081b01d9823cb1d12071204bb9bcd039b6f",
       "completedBy": "claude-code",
-      "completedRunId": "0da681ea-cfc0-4af9-a2fe-1b34cb0fad26",
-      "gitHead": "e147fe64421138ee5b6068d78986d551fd7e6be7",
-      "sourceFingerprint": "sha256:c91de01aa07805e76fe5ac531adad9e790a95e925f32e6ef21aa6b743d82f3e9"
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
+    },
+    "/openwiki/workflows/relink-and-pairing.md": {
+      "pageVersion": "sha256:2e5dcb382a443a271be7c245304c8aad46d09a5722939de151a90fe1c5e0c86a",
+      "completedBy": "claude-code",
+      "completedRunId": "a6d2d910-a412-4673-9738-ac2cf39deacc",
+      "gitHead": "5c2fe04925ebbc998a25ef3427b9228fe16c419c",
+      "sourceFingerprint": "sha256:c24f8ad189dd06410a92d4dfd7b616ebadbe9a4f3c5a1177586200d86d308d05"
     }
   }
 }

+ 132 - 77
openwiki/architecture/app-state.md

@@ -1,15 +1,15 @@
 ---
 type: architecture
 title: Application state and event handling
-description: How the App struct in src/app.rs holds UI state, routes AppEvent values (keys, WhatsApp, AI replies, image descriptions, reply timers, web panel), handles function keys, the list filter and chat deletion, toggles global and per-chat autoreply, and runs background tasks.
-tags: [app, state, events, keybindings, tokio, web-panel]
+description: How the App struct in src/app.rs holds UI state, routes AppEvent values (keys, WhatsApp, AI replies, image descriptions, reply timers, web panel, configuration reloads, relink outcomes), handles function keys (including F8 relink), the list filter and chat deletion, toggles global and per-chat autoreply, and runs background tasks.
+tags: [app, state, events, keybindings, tokio, web-panel, relink, config-reload]
 verified:
   - by: openwiki/0.7.2
-    at: 2026-10-21T21:49:12.291Z
+    at: 2026-10-23T21:26:24.156Z
 sources:
   - id: openwiki-source-9fec9d1af408501c9c455161
     resource: repo://src/app.rs
-generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" }
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
 ---
 
 # Application state and event handling
@@ -23,7 +23,7 @@ and [web panel](../integrations/web-control-panel.md) activity.
 
 ## Events
 
-`AppEvent` (`src/app.rs:90-121`) is the single message type of the main channel:
+`AppEvent` (`src/app.rs:106-141`) is the single message type of the main channel:
 
 | Variant | Producer | Meaning |
 |---|---|---|
@@ -35,15 +35,17 @@ and [web panel](../integrations/web-control-panel.md) activity.
 | `ReplyDue { chat_jid }` | reply timer task | The reply delay of a chat has elapsed. |
 | `Sent { chat_jid, text, is_ai, result }` | send background task | Outcome of a text send. |
 | `Web(WebRequest)` | `wh-web` thread | A command from the [web control panel](../integrations/web-control-panel.md) with a reply channel. |
+| `ConfigReloaded(Result<Box<Config>, String>)` | configuration watcher task | A configuration file changed: the reloaded `Config` or the load error. |
+| `Relinked(Result<(), String>)` | relink task | Outcome of a relink: the new unpaired session started, or the error. |
 
-`handle_event` (`src/app.rs:267-283`) dispatches each variant; after an
+`handle_event` (`src/app.rs:293-317`) dispatches each variant; after an
 `AiReply` it always calls `finish_reply` to end the chat's pending state. Only
 fatal history (SQLite) errors propagate as `Err`; every other failure is turned
 into a `status` text shown in the footer.
 
 ## State
 
-`App` (`src/app.rs:169-215`) exposes UI fields read by the renderer: `screen`,
+`App` (`src/app.rs:188-239`) exposes UI fields read by the renderer: `screen`,
 `qr_code`, `focus`, `list_mode`, `chats`, `contacts`, `selected`, `filter`,
 `open_chat`, `open_chat_name`, `open_chat_autoreply`, `global_autoreply`,
 `messages`, `input`, `status` and `should_quit`. Private fields hold the
@@ -59,36 +61,50 @@ bookkeeping:
 | `answered_up_to` | per chat, the id of the last message in the context of the reply in progress |
 | `own_jids_known`, `catch_up_done` | one-shot flags for own JIDs and the startup catch-up |
 | `pending_delete` | the chat awaiting a deletion confirmation |
+| `pending_relink` | a first F8 press awaits the second one that confirms the relink |
+| `relinking` | a relink is running; further relink requests are ignored until it ends |
 
-- `Screen` (`src/app.rs:131-137`): `Connecting` → `Pairing` (QR shown) → `Main`.
-- `Focus` (`src/app.rs:139-144`): `List` or `Input`.
-- `ListMode` (`src/app.rs:146-151`): `Chats` or `Contacts`.
+- `Screen` (`src/app.rs:151-157`): `Connecting` → `Pairing` (QR shown) → `Main`.
+- `Focus` (`src/app.rs:159-164`): `List` or `Input`.
+- `ListMode` (`src/app.rs:166-171`): `Chats` or `Contacts`.
 
-`App::new` (`src/app.rs:222-260`) builds the AI client with `build_ai`, loads
+`App::new` (`src/app.rs:246-286`) builds the AI client with `build_ai`, loads
 chats, contacts and the persisted global autoreply flag from history, starts on
-`Connecting` with list focus in chat mode and an empty filter, and creates the
-`ReplyGate` with the current time, so that backlog messages are never
-auto-answered live.
+`Connecting` with list focus in chat mode, an empty filter and no pending
+deletion or relink, and creates the `ReplyGate` with the current time, so that
+backlog messages are never auto-answered live.
 
-`build_ai` (`src/app.rs:1393-1401`) returns no client when the system prompt
+`build_ai` (`src/app.rs:1534-1542`) returns no client when the system prompt
 (`SOUL.md`, see [Configuration](../operations/configuration-and-data.md)) is
 missing — status `AI non configurata: imposta openai_api_key in config.toml e
 il prompt in SOUL.md` — and reports a client build error with the
 `AI non disponibile` prefix.
 
+## Configuration reload
+
+The [configuration watcher](../operations/configuration-and-data.md#live-reload)
+sends `ConfigReloaded` when `config.toml`, `SOUL.md` or `VISION.md` change.
+`apply_reloaded_config` (`src/app.rs:388-400`) keeps the current configuration
+and shows `Configurazione non ricaricata: <error>` when the load failed;
+otherwise it replaces `config`, rebuilds the AI client with `build_ai` (replies
+already running keep the client they were started with) and shows
+`Configurazione ricaricata`, or the `build_ai` status when the AI is not
+configured or unavailable. Later replies therefore use the new prompt, model,
+delays and retries without a restart.
+
 ## WhatsApp events
 
-`handle_wa_event` (`src/app.rs:289-312`):
+`handle_wa_event` (`src/app.rs:406-429`):
 
 - `Qr` → switch to `Pairing` and store the QR payload.
-- `Connected` → switch to `Main`, status `Connesso`, refresh own JIDs.
+- `Connected` → switch to `Main`, clear the QR, status `Connesso`, refresh own JIDs.
 - `Disconnected` / `Error` → update `status` only.
 - `LoggedOut` → back to `Connecting`, status `Sessione scollegata`.
 - `OfflineSyncCompleted` → `on_offline_sync_completed`, the startup catch-up
   (see below).
 - `Contact` → `on_contact`: upsert the contact, reload contacts and chats and
-  refresh the open chat name (`src/app.rs:318-326`).
-- `Message { message, image }` → `on_message` (`src/app.rs:333-342`).
+  refresh the open chat name (`src/app.rs:435-443`).
+- `Message { message, image }` → `on_message` (`src/app.rs:450-459`).
 
 `on_message` saves the message and stops immediately when
 `History::save_message` reports a duplicate, so history sync replays never
@@ -96,50 +112,74 @@ trigger UI reloads or autoreplies twice. New messages reload the chat list, the
 open conversation and then go through `maybe_autoreply` (see
 [AI autoreply](../workflows/ai-autoreply.md)).
 
-Own JIDs are fetched by `refresh_own_jids` (`src/app.rs:680-687`); the list is
-empty while the device is not paired, so the refresh is retried later
-(`own_jids_known` stays false until it succeeds).
+Own JIDs are fetched by `refresh_own_jids` (`src/app.rs:797-804`); the list is
+empty while the device is not paired (or no session is running during a
+relink), so the refresh is retried later (`own_jids_known` stays false until it
+succeeds).
+
+## Relink
+
+F8 (twice) and the web panel's `Relink` command replace the WhatsApp session
+with a new unpaired one; the full flow is in
+[WhatsApp relink and pairing](../workflows/relink-and-pairing.md). In `App`:
+
+- `request_relink` (`src/app.rs:361-369`) cancels a pending chat deletion; the
+  first F8 sets `pending_relink` and shows
+  `Cancellare la sessione WhatsApp e rifare l'abbinamento? F8 conferma, Esc annulla`,
+  the second calls `start_relink`.
+- `cancel_pending_relink` (`src/app.rs:375-381`) clears the flag on any other
+  key and shows `Nuovo abbinamento annullato`.
+- `start_relink` (`src/app.rs:326-341`) is a no-op while `relinking`;
+  otherwise it switches to `Connecting`, clears the QR, shows
+  `Cancellazione della sessione in corso...`, resets `own_jids_known` (the new
+  pairing may be another account) and spawns a task that runs
+  `WaHandle::relink` and sends `Relinked`. The web command calls it directly,
+  without the two-press confirmation (the browser asks instead).
+- `finish_relink` (`src/app.rs:348-354`) clears `relinking` and shows
+  `Sessione cancellata: scansiona il nuovo QR` or
+  `Nuovo abbinamento fallito: <error>`; the screen then follows the new
+  session's `Qr` / `Connected` events.
 
 ## Autoreply orchestration
 
 A chat is "enabled" when the global autoreply is on **or** its per-chat flag
-is on (`autoreply_enabled`, `src/app.rs:525-527`). `ai_setup`
-(`src/app.rs:632-634`) returns nothing, and so stops every path, when the AI
+is on (`autoreply_enabled`, `src/app.rs:642-644`). `ai_setup`
+(`src/app.rs:749-751`) returns nothing, and so stops every path, when the AI
 client or the system prompt is missing. The full workflow is in
 [AI autoreply](../workflows/ai-autoreply.md); the state machine in `App` is:
 
-- **Schedule** — `maybe_autoreply` (`src/app.rs:351-366`) calls
+- **Schedule** — `maybe_autoreply` (`src/app.rs:468-483`) calls
   `schedule_reply` when `ReplyGate::should_schedule` allows it: the chat is
-  marked pending and `spawn_reply_timer` (`src/app.rs:1204-1209`) emits
+  marked pending and `spawn_reply_timer` (`src/app.rs:1345-1350`) emits
   `ReplyDue` after `reply_delay_seconds`. A chat already pending gets no second
   timer, so messages arriving meanwhile are answered together. An image sent by
   an allowed sender in an enabled chat is kept by `keep_image`
-  (`src/app.rs:384-398`): in `pending_images` for a pending chat, in
+  (`src/app.rs:501-515`): in `pending_images` for a pending chat, in
   `backlog_images` for an offline image before the catch-up, or described at
   once without reply (`describe_late_image`) after it.
-- **Reply due** — `handle_reply_due` (`src/app.rs:422-428`) takes the kept
+- **Reply due** — `handle_reply_due` (`src/app.rs:539-545`) takes the kept
   images and, if the chat is still pending, calls `prepare_reply`
-  (`src/app.rs:439-451`). `reply_context` (`src/app.rs:535-547`) returns
+  (`src/app.rs:556-568`). `reply_context` (`src/app.rs:652-664`) returns
   nothing when the autoreply was disabled, the AI is missing, the chat is
   empty or its last message was written by hand by the owner; then the
   pending state ends. Otherwise images still lacking a description go to
   `spawn_descriptions` (reply follows on `ImagesDescribed`), or `start_reply`
   runs at once.
-- **Descriptions** — `handle_images_described` (`src/app.rs:474-497`) stores
+- **Descriptions** — `handle_images_described` (`src/app.rs:591-614`) stores
   each description whose message still exists with
   `History::save_image_description`, sets one status for failures
   (`Descrizione immagine fallita (N): <first error>`) and, when `then_reply`
   holds and the chat is still pending, starts the reply.
-- **Start** — `start_reply` (`src/app.rs:654-673`) records the last context
+- **Start** — `start_reply` (`src/app.rs:771-790`) records the last context
   message in `answered_up_to`, marks own mentions, reads the chat name and the
   stored image descriptions, builds the turns and calls `spawn_ai_request`.
-- **Finish** — `finish_reply` (`src/app.rs:505-519`) clears the pending state
+- **Finish** — `finish_reply` (`src/app.rs:622-636`) clears the pending state
   and `answered_up_to`; if a newer message that passes `should_schedule`
   arrived after the answered one, it schedules a new reply (keeping its
   images), otherwise it drops the kept images.
-- **Startup catch-up** — `on_offline_sync_completed` (`src/app.rs:571-577`)
+- **Startup catch-up** — `on_offline_sync_completed` (`src/app.rs:688-694`)
   runs `catch_up` once per process (`catch_up_done`). `catch_up`
-  (`src/app.rs:587-607`) asks `catch_up_chats` (`src/app.rs:614-626`) for the
+  (`src/app.rs:704-724`) asks `catch_up_chats` (`src/app.rs:731-743`) for the
   enabled chats (all chats when the global autoreply is on) whose latest
   stored message passes `ReplyGate::catch_up_due` within `catch_up_seconds`,
   marks each pending and calls `prepare_reply` with its offline images (no
@@ -148,13 +188,15 @@ client or the system prompt is missing. The full workflow is in
 ## Key bindings
 
 Commands use function keys (`src/app.rs:23-32`); `handle_key`
-(`src/app.rs:774-804`) dispatches them:
+(`src/app.rs:884-929`) dispatches them:
 
 | Key | Where | Action |
 |---|---|---|
 | F10 | any screen | Quit (`should_quit = true`). |
+| F8 | any screen | First press asks to delete the WhatsApp session and pair again; second press confirms (see [Relink](#relink)). |
+| Esc | after a first F8 | Cancels the pending relink (the key is consumed). |
 | F2 | main screen, any focus | Switch chat list / contact list; selection resets to 0, filter kept. |
-| F3 | main screen, any focus | Toggle autoreply for the open chat. |
+| F3 | main screen | Toggle autoreply for the focused chat: the selected row with list focus, the open chat with input focus. |
 | F4 | main screen, any focus | Toggle the global autoreply ("AI tutte"). |
 | Tab | main screen | Switch focus between list and input. |
 | Up / Down | list focus | Move selection by one row, clamped to the filtered list. |
@@ -168,109 +210,122 @@ Commands use function keys (`src/app.rs:23-32`); `handle_key`
 | Esc | input focus | Return focus to the list. |
 | Enter | input focus | Send the trimmed input to the open chat. |
 
-Non-main screens ignore everything except F10. Ctrl chords are swallowed
-(they cancel a pending deletion and do nothing else) so they never type into
-the input or the filter; `is_command` (`src/app.rs:1434-1441`) treats Ctrl as
-a command only when Alt is not held, because Windows reports AltGr as
-Ctrl+Alt (needed for `@`, `#`, `€` on the Italian layout). Switching list
-mode with F2 also makes the event loop clear the terminal (see
+F10 and F8 are checked first, on every screen. Any other key cancels a
+pending relink; an Esc that does so is consumed, every other key then
+continues normally. Non-main screens ignore everything else. Ctrl chords are
+swallowed (they cancel a pending deletion and do nothing else) so they never
+type into the input or the filter; `is_command` (`src/app.rs:1592-1594`)
+treats Ctrl as a command only when Alt is not held, because Windows reports
+AltGr as Ctrl+Alt (needed for `@`, `#`, `€` on the Italian layout). Switching
+list mode with F2 also makes the event loop repaint the whole terminal (see
 [Runtime and event loop](runtime-and-event-loop.md)).
 
 ### List filter
 
 Typing while the list has focus edits `filter` (`handle_list_key`,
-`edit_filter`, `src/app.rs:812-848`). `visible_chats` / `visible_contacts`
-(`src/app.rs:923-933`) keep the entries whose name contains the filter,
-case-insensitively (`matches_filter`, `src/app.rs:1148-1150`); selection,
-opening and deletion all work on the filtered list.
+`edit_filter`, `src/app.rs:937-973`). `visible_chats` / `visible_contacts`
+(`src/app.rs:1048-1058`) keep the entries whose name contains the filter,
+case-insensitively (`matches_filter`, `src/app.rs:1289-1291`); selection,
+opening, F3 and deletion all work on the filtered list.
 
 ### Local chat deletion
 
-`request_delete` (`src/app.rs:867-884`) works only in chat mode: the first
+`request_delete` (`src/app.rs:992-1009`) works only in chat mode: the first
 Delete stores the selected chat in `pending_delete` and shows
 `Eliminare la chat <name>? Canc conferma, Esc annulla`; a second Delete on
-the same chat calls `confirm_delete` (`src/app.rs:891-906`), which removes
+the same chat calls `confirm_delete` (`src/app.rs:1016-1031`), which removes
 the chat from the history (messages, image descriptions and row; no WhatsApp
 call), drops its pending reply, kept images and answered snapshot, closes it
-if open and shows `Chat <name> eliminata`. Any other key (Tab, F2, F3, F4,
+if open and shows `Chat <name> eliminata`. Any other key (Tab, F2, F3, F4, F8,
 list keys, Ctrl chords) cancels the pending deletion with
-`Eliminazione annullata` (`cancel_pending_delete`, `src/app.rs:854-860`).
+`Eliminazione annullata` (`cancel_pending_delete`, `src/app.rs:979-985`).
 
 ### Opening and toggling
 
-`open_selected` (`src/app.rs:1075-1091`) loads display name, per-chat
-autoreply flag and the last `MESSAGES_SHOWN` (200) messages of the selected
-visible entry, then moves focus to the input. The `reload_*` helpers
-(`src/app.rs:1108-1133`) keep `selected` inside the active filtered list.
-
-`toggle_autoreply` (F3, `src/app.rs:981-986`) delegates to
-`toggle_chat_autoreply` (`src/app.rs:994-1007`), which always allows
-disabling. Enabling goes through `can_enable_autoreply`
-(`src/app.rs:1408-1416`), which first requires an AI client and then a chat
+`selected_jid` (`src/app.rs:1221-1232`) returns the JID on the selected row of
+the active filtered list (chat or contact), or nothing when it is empty.
+`open_selected` (`src/app.rs:1205-1219`) uses it to load display name,
+per-chat autoreply flag and the last `MESSAGES_SHOWN` (200) messages, then
+moves focus to the input. The `reload_*` helpers (`src/app.rs:1249-1274`) keep
+`selected` inside the active filtered list.
+
+`toggle_autoreply` (F3, `src/app.rs:1101-1116`) picks the target from the
+focus — `selected_jid` with list focus, `open_chat` with input focus — and
+delegates to `toggle_chat_autoreply` (`src/app.rs:1124-1137`), which always
+allows disabling and updates `open_chat_autoreply` when the target is the open
+chat. Enabling goes through `can_enable_autoreply`
+(`src/app.rs:1549-1557`), which first requires an AI client and then a chat
 accepted by `supports_autoreply` (private chats and groups; broadcasts,
 status and newsletters are refused with
 `Autoreply disponibile solo nelle chat private e nei gruppi`). The flag is
 persisted with `History::set_autoreply` and the status shows
 `Autoreply ON|OFF per <name>`.
 
-`toggle_global_autoreply` (F4, `src/app.rs:1058-1069`) refuses to enable
+`toggle_global_autoreply` (F4, `src/app.rs:1192-1203`) refuses to enable
 without an AI client (`STATUS_AI_NOT_CONFIGURED`), persists the flag with
 `History::set_global_autoreply`, updates `global_autoreply` and shows
 `Autoreply globale ON|OFF`. Disabling is always allowed.
 
 ## Web panel requests
 
-`handle_web_request` (`src/app.rs:1014-1025`) applies a `WebCommand` and then
+`handle_web_request` (`src/app.rs:1139-1156`) applies a `WebCommand` and then
 always sends back a `WebState` snapshot on the request's oneshot channel:
 
 - `State` → nothing, just the snapshot;
 - `ToggleGlobal` → `toggle_global_autoreply` (same rules as F4);
-- `ToggleChat(jid)` → `toggle_web_chat` (`src/app.rs:1032-1038`): if the JID is
+- `ToggleChat(jid)` → `toggle_web_chat` (`src/app.rs:1163-1169`): if the JID is
   not among the panel's chats the status becomes `Chat non trovata`,
   otherwise `toggle_chat_autoreply` (same rules as F3);
+- `Relink` → `start_relink` (no second confirmation; ignored while a relink runs);
 - `Quit` → `should_quit = true`.
 
-`web_state` (`src/app.rs:1044-1050`) returns the global flag, the status line
-and `web_chats` (`src/app.rs:1422-1432`): the TUI chat list in the same order,
-restricted to chats that support autoreply. See
+`web_state` (`src/app.rs:1171-1184`) returns the global flag, the status line,
+`web_chats` (`src/app.rs:1575-1585`) — the TUI chat list in the same order,
+restricted to chats that support autoreply — the current screen as
+`connecting` / `pairing` / `main` (`web_screen`, `src/app.rs:1563-1569`) and,
+while a QR payload is stored, the QR code rendered as SVG by `web::qr_svg`. See
 [Web control panel](../integrations/web-control-panel.md).
 
 ## Sending and background tasks
 
 All network work runs in tokio tasks that report back through the event
 channel; `send_event` ignores a closed channel because that only happens on
-shutdown (`src/app.rs:1194-1197`).
+shutdown (`src/app.rs:1335-1338`).
 
-- `submit_input` (`src/app.rs:955-963`) ignores blank input or no open chat,
+- `submit_input` (`src/app.rs:1080-1088`) ignores blank input or no open chat,
   clears the input and calls `spawn_send` with `is_ai: false`.
-- `spawn_send` (`src/app.rs:1377-1386`) calls `WaHandle::send_text` and emits
-  `Sent`.
-- `handle_sent` (`src/app.rs:741-765`) stores a successful send as a
+- `spawn_send` (`src/app.rs:1518-1527`) calls `WaHandle::send_text` and emits
+  `Sent`; during a relink no session is running, so sends fail with a send
+  error.
+- `handle_sent` (`src/app.rs:858-882`) stores a successful send as a
   `from_me` message (with `is_ai` preserved) and reloads the views; a failure
   sets `Invio fallito: …`.
-- `spawn_ai_request` (`src/app.rs:1218-1229`) runs the text completion in one
+- `spawn_ai_request` (`src/app.rs:1359-1370`) runs the text completion in one
   task and waits for it in another, so `AiReply` is always emitted — even when
   the request task panics — and the chat never stays pending.
-- `spawn_image_descriptions` (`src/app.rs:1240-1263`) runs one task per image
+- `spawn_image_descriptions` (`src/app.rs:1381-1404`) runs one task per image
   in parallel (download with up to 3 attempts and a 60 s timeout each, then
   `AiClient::describe_image`) and a collector task that emits a single
   `ImagesDescribed`; a failed image does not stop the others and the bytes
   stay in memory.
-- `handle_ai_reply` (`src/app.rs:695-701`) shows
+- `handle_ai_reply` (`src/app.rs:812-818`) shows
   `AI: nessuna risposta necessaria` for `Reply::NoReply`, `Errore AI: …` for
   an error, and passes text to `send_ai_reply_if_enabled`
-  (`src/app.rs:709-722`), which sends it only if the chat is still enabled and
+  (`src/app.rs:826-839`), which sends it only if the chat is still enabled and
   the owner did not answer by hand meanwhile (`owner_answered_meanwhile`);
   otherwise it shows `Risposta AI scartata: autoreply disattivato` or
   `Risposta AI scartata: hai già risposto`.
+- The relink task (see [Relink](#relink)) runs `WaHandle::relink` and emits
+  `Relinked`.
 
 ## Tests
 
-Unit tests at `src/app.rs:1443` onward cover `can_enable_autoreply` (private,
+Unit tests at `src/app.rs:1596` onward cover `can_enable_autoreply` (private,
 self-chat LID, groups allowed; broadcasts/newsletters and missing AI refused),
 `web_chats` filtering and order, `vision_applies`,
 `select_images_to_describe` (last five, skip known and out-of-context),
 `arrived_after`, `manual_reply_after`, `description_failure_status`,
 `matches_filter`, selection clamping/stepping and page steps, the toggle
-helpers, `is_command` and the fixed function-key codes (F2, F3, F4, F10). See
-[Unit tests](../testing/unit-tests.md).
+helpers, `is_command` and the fixed function-key codes (F2, F3, F4, F10; F8 is
+not part of that check). Relink, configuration reload and F3 targeting have no
+`App`-level tests. See [Unit tests](../testing/unit-tests.md).

+ 3 - 3
openwiki/architecture/index.md

@@ -1,5 +1,5 @@
 # Files
 
-- [Application state and event handling](app-state.md) - How the App struct in src/app.rs holds UI state, routes AppEvent values (keys, WhatsApp, AI replies, image descriptions, reply timers, web panel), handles function keys, the list filter and chat deletion, toggles global and per-chat autoreply, and runs background tasks.
-- [Runtime and event loop](runtime-and-event-loop.md) - Process startup in src/main.rs (including the --version flag and the build version embedded by build.rs), the tokio runtime, the two event channels, the input thread, the WhatsApp forwarder, the optional web panel thread and the draw/recv TUI loop.
-- [Terminal UI rendering](terminal-ui.md) - How src/ui.rs draws the connecting, QR pairing and main screens with ratatui, including the filtered list with global autoreply marker, message formatting, wrapping, the input box and the footer with key help and build version.
+- [Application state and event handling](app-state.md) - How the App struct in src/app.rs holds UI state, routes AppEvent values (keys, WhatsApp, AI replies, image descriptions, reply timers, web panel, configuration reloads, relink outcomes), handles function keys (including F8 relink), the list filter and chat deletion, toggles global and per-chat autoreply, and runs background tasks.
+- [Runtime and event loop](runtime-and-event-loop.md) - Process startup in src/main.rs (including the --version flag and the build version embedded by build.rs), the tokio runtime, the two event channels, the input thread, the WhatsApp forwarder, the optional web panel thread, the configuration watcher task, the draw/recv TUI loop and how the screen is repainted and wiped on exit.
+- [Terminal UI rendering](terminal-ui.md) - How src/ui.rs draws the connecting, QR pairing and main screens with ratatui, including the F8/F10 key help on the connecting and pairing screens, the filtered list with global autoreply marker, message formatting, wrapping, the input box and the footer with key help and build version.

+ 61 - 39
openwiki/architecture/runtime-and-event-loop.md

@@ -1,17 +1,19 @@
 ---
 type: architecture
 title: Runtime and event loop
-description: Process startup in src/main.rs (including the --version flag and the build version embedded by build.rs), the tokio runtime, the two event channels, the input thread, the WhatsApp forwarder, the optional web panel thread and the draw/recv TUI loop.
-tags: [runtime, tokio, event-loop, ratatui, crossterm, web-panel, version]
-verified:
-  - by: openwiki/0.7.2
-    at: 2026-10-21T21:49:12.291Z
+description: Process startup in src/main.rs (including the --version flag and the build version embedded by build.rs), the tokio runtime, the two event channels, the input thread, the WhatsApp forwarder, the optional web panel thread, the configuration watcher task, the draw/recv TUI loop and how the screen is repainted and wiped on exit.
+tags: [runtime, tokio, event-loop, ratatui, crossterm, web-panel, version, config-reload]
 sources:
   - id: openwiki-source-36f054f1f38f2197a6af5bca
     resource: repo://build.rs
+  - id: openwiki-source-d6b2055bbb49804b8a855fd2
+    resource: repo://src/config_watch.rs
   - id: openwiki-source-b55a21a31ede1b56cd31a6a6
     resource: repo://src/main.rs
-generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" }
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
+verified:
+  - by: openwiki/0.7.2
+    at: 2026-10-23T21:26:24.156Z
 ---
 
 # Runtime and event loop
@@ -27,19 +29,19 @@ result as the `WH_GIT_HASH` environment variable (`unknown` when git is
 missing, fails or prints nothing); it asks cargo to rebuild when `.git/HEAD`,
 `.git/refs` or `.git/packed-refs` change (`build.rs:14-33`). `main.rs`
 combines it into `BUILD_VERSION` = `<CARGO_PKG_VERSION> (<hash>)`
-(`src/main.rs:24-25`), which the [terminal UI](terminal-ui.md) shows at the
+(`src/main.rs:30-31`), which the [terminal UI](terminal-ui.md) shows at the
 bottom right.
 
 ## Startup sequence
 
-`main` (`src/main.rs:36-64`) runs on the multi-threaded tokio runtime
+`main` (`src/main.rs:35-71`) runs on the multi-threaded tokio runtime
 (`#[tokio::main]`). If any command-line argument is `--version` or `-V`
-(`wants_version`, `src/main.rs:66-72`) it only prints `wh <BUILD_VERSION>`
+(`wants_version`, `src/main.rs:73-79`) it only prints `wh <BUILD_VERSION>`
 and exits, without reading the configuration or touching the terminal.
 Otherwise it:
 
 1. Loads the configuration with `Config::load` (`config.toml`, `SOUL.md`,
-   `vision.md`) and creates the data directory (`ensure_data_dir`). See
+   `VISION.md`) and creates the data directory (`ensure_data_dir`). See
    [Configuration and data](../operations/configuration-and-data.md).
 2. Opens the SQLite [history store](../concepts/history-store.md) at
    `history_db_path()`.
@@ -49,71 +51,91 @@ Otherwise it:
    `whatsapp::start(session_db_path, wa_tx)`.
 5. Spawns the WhatsApp forwarder.
 6. Starts the optional [web control panel](../integrations/web-control-panel.md)
-   with `start_web_panel` (`src/main.rs:88-97`): nothing happens when
+   with `start_web_panel` (`src/main.rs:95-104`): nothing happens when
    `web_port` is unset, otherwise `web::start(port, app_tx)` binds the port.
-7. Builds `App` from `AppDeps { config, history, wa, events }`.
-8. Spawns the input thread.
-9. Runs the TUI until quit.
+7. Spawns the configuration watcher with
+   `config_watch::spawn(config.source_files(), app_tx)` (`src/main.rs:60`).
+8. Builds `App` from `AppDeps { config, history, wa, events }`.
+9. Spawns the input thread.
+10. Runs the TUI until quit.
 
 Any failure before the TUI starts aborts with an `anyhow` context such as
 `cannot load configuration`, `cannot open history database`,
 `cannot start WhatsApp client` or `cannot start web server on port <port>`
-(for example when the port is already in use).
+(for example when the port is already in use). The web panel is started once,
+so a changed `web_port` only applies after a restart.
 
 ## Event sources
 
 ```
- crossterm (blocking)     whatsapp-rust bot      tiny_http (blocking)    tokio tasks in App
-   "wh-input" thread       Translator task         "wh-web" thread       (AI, image description,
-        |                        |                       |                send, reply timers)
-        | Key/Redraw             | WaEvent               | Web(WebRequest)      |
-        v                        v                       v                      | AiReply / Sent /
-   app_tx  <------  WhatsApp forwarder (Wa(..))  <-------+                      | ImagesDescribed /
-        ^                                                                       | ReplyDue
-        +-----------------------------------------------------------------------+
+ crossterm (blocking)   whatsapp-rust bot   tiny_http (blocking)  config_watch task   tokio tasks in App
+   "wh-input" thread     Translator task     "wh-web" thread      (2 s polling)       (AI, image description,
+        |                      |                   |                   |               send, reply timers, relink)
+        | Key/Redraw           | WaEvent           | Web(WebRequest)   | ConfigReloaded       |
+        v                      v                   v                   v                      | AiReply / Sent /
+   app_tx  <----  WhatsApp forwarder (Wa(..))  <---+-------------------+                      | ImagesDescribed /
+        ^                                                                                     | ReplyDue / Relinked
+        +-------------------------------------------------------------------------------------+
         |
      app_rx  -->  event_loop: draw ui::render -> recv -> app.handle_event
 ```
 
-- **WhatsApp forwarder** (`src/main.rs:74-86`): a tokio task that wraps each
-  `WaEvent` in `AppEvent::Wa` and stops when either channel side closes.
+- **WhatsApp forwarder** (`src/main.rs:81-93`): a tokio task that wraps each
+  `WaEvent` in `AppEvent::Wa` and stops when either channel side closes. The
+  `WaEvent` channel outlives a relink: the new session started by
+  `WaHandle::relink` sends into the same sender, so the forwarder keeps
+  working.
 - **Web thread** (`wh-web`, see [Web control panel](../integrations/web-control-panel.md)):
   a blocking HTTP server on its own OS thread; each API request is sent as
   `AppEvent::Web` and the thread waits on a oneshot channel for the state
   snapshot that `App` sends back after handling it.
-- **Input thread** (`src/main.rs:99-134`): a named OS thread (`wh-input`)
+- **Configuration watcher** (`src/config_watch.rs:24-40`): a tokio task that
+  every 2 seconds reads the modification time of `config.toml`, `SOUL.md` and
+  `VISION.md`; when any of them differs from the last snapshot (including a
+  file appearing or disappearing) it runs `Config::load` and sends
+  `AppEvent::ConfigReloaded` with the new configuration or the error text. It
+  ends when the receiver is dropped. See
+  [Configuration and data](../operations/configuration-and-data.md#live-reload).
+- **Input thread** (`src/main.rs:106-141`): a named OS thread (`wh-input`)
   because `crossterm::event::read` blocks. On a read error it sends
   `WaEvent::Error("terminal input error: …")` and stops instead of spinning.
-- **Event mapping** (`src/main.rs:136-147`): only `KeyEventKind::Press` keys
+- **Event mapping** (`src/main.rs:143-154`): only `KeyEventKind::Press` keys
   become `AppEvent::Key` (Windows also emits release and repeat events);
   `Resize` becomes `AppEvent::Redraw`; everything else is dropped.
 - **Background tasks** spawned by `App` (AI completions, image downloads and
-  descriptions, sends, reply-delay timers) hold a clone of the sender and
-  report with `AiReply` / `ImagesDescribed` / `Sent` / `ReplyDue`.
+  descriptions, sends, reply-delay timers, relink) hold a clone of the sender
+  and report with `AiReply` / `ImagesDescribed` / `Sent` / `ReplyDue` /
+  `Relinked`.
 
 ## TUI loop
 
-`run_tui` (`src/main.rs:149-158`) calls `ratatui::init`, runs the loop and
-always calls `ratatui::restore`, so raw mode and the alternate screen are
-left even on errors.
+`run_tui` (`src/main.rs:156-166`) calls `ratatui::init`, runs the loop, then
+wipes the screen with `clear_screen` (`src/main.rs:168-175`: clear all and
+move the cursor to the top-left, with no cursor-position query) and always
+calls `ratatui::restore`, so raw mode and the alternate screen are left even
+on errors and terminals without an alternate screen are not left showing the
+UI. The loop error wins over a clear error (`result.and(cleared)`).
 
-`event_loop` (`src/main.rs:160-184`) repeats while `!app.should_quit`:
+`event_loop` (`src/main.rs:177-205`) repeats while `!app.should_quit`:
 draw a frame with [`ui::render`](terminal-ui.md), await the next event, and
 pass it to `app.handle_event`. The loop also ends cleanly when the event
 channel closes. Draw errors and fatal `handle_event` errors end the process.
 `should_quit` is set by F10 or by a quit request from the web panel.
 
 Before dispatching, the loop records `list_mode_before = app.list_mode`; if
-the event changed the list mode (F2 switches chats/contacts), it calls
-`terminal.clear()` to force a full repaint, so wide glyphs from the previous
-list cannot leave stale cells behind.
+the event changed the list mode (F2 switches chats/contacts), it reads the
+terminal size and calls `terminal.resize` with it, which clears the
+fullscreen viewport and forces a full repaint so wide glyphs from the
+previous list cannot leave stale cells behind. `Terminal::clear` is avoided
+because it queries the cursor position, which times out on some terminals.
 
 Because rendering happens only after an event, the screen is redrawn on key
-presses, resizes, WhatsApp events, web panel requests and background task
-results — there is no timer tick.
+presses, resizes, WhatsApp events, web panel requests, configuration reloads
+and background task results — there is no timer tick.
 
 ## Tests
 
 `src/main.rs` tests check that only `--version` / `-V` request the version
 and that `BUILD_VERSION` starts with the package version and names a commit
-in parentheses. See [Unit tests](../testing/unit-tests.md).
+in parentheses. `src/config_watch.rs` tests that modification-time snapshots
+track a file's creation and deletion. See [Unit tests](../testing/unit-tests.md).

+ 41 - 34
openwiki/architecture/terminal-ui.md

@@ -1,15 +1,15 @@
 ---
 type: architecture
 title: Terminal UI rendering
-description: How src/ui.rs draws the connecting, QR pairing and main screens with ratatui, including the filtered list with global autoreply marker, message formatting, wrapping, the input box and the footer with key help and build version.
+description: How src/ui.rs draws the connecting, QR pairing and main screens with ratatui, including the F8/F10 key help on the connecting and pairing screens, the filtered list with global autoreply marker, message formatting, wrapping, the input box and the footer with key help and build version.
 tags: [ui, ratatui, tui, qr-code, rendering]
 sources:
   - id: openwiki-source-6bddea306b9b87f180f1fc02
     resource: repo://src/ui.rs
-generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" }
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
 verified:
   - by: openwiki/0.7.2
-    at: 2026-10-21T21:49:12.291Z
+    at: 2026-10-23T21:26:24.156Z
 ---
 
 # Terminal UI rendering
@@ -18,39 +18,46 @@ verified:
 [application state](app-state.md) and fills a ratatui frame. It never mutates
 `App`; the [event loop](runtime-and-event-loop.md) calls it once per event.
 All user-facing strings are Italian constants at the top of the file
-(`src/ui.rs:21-99`).
+(`src/ui.rs:21-103`).
 
 ## Screens
 
-`render` (`src/ui.rs:107-114`) dispatches on `app.screen`:
+`render` (`src/ui.rs:105-116`) dispatches on `app.screen`:
 
 | Screen | Function | Content |
 |---|---|---|
-| `Connecting` | `render_connecting` | "Connessione a WhatsApp in corso..." plus the status line, centered |
-| `Pairing` | `render_pairing` | Title, QR code, pairing steps ("WhatsApp > Impostazioni > Dispositivi collegati > Collega un dispositivo"), status |
+| `Connecting` | `render_connecting` | "Connessione a WhatsApp in corso..." plus the status line and the dimmed key help `F8: nuovo QR   F10: esci`, centered |
+| `Pairing` | `render_pairing` | Title, QR code, pairing steps ("WhatsApp > Impostazioni > Dispositivi collegati > Collega un dispositivo"), status, dimmed key help `F8: nuovo QR   F10: esci` |
 | `Main` | `render_main` | List pane, messages pane, input pane, footer |
 
-`render_centered` (`src/ui.rs:152-165`) centers a block of lines vertically
-and horizontally.
+`render_centered` (`src/ui.rs:152-169`) centers a block of lines vertically
+and horizontally. The key help on the two pre-connection screens
+(`TEXT_SCREEN_KEYS`, `src/ui.rs:80-83`) advertises the only keys that work
+there: F8 to delete the session and pair again (see
+[WhatsApp relink and pairing](../workflows/relink-and-pairing.md)) and F10 to
+quit. During a relink the app shows the `Connecting` screen with the status
+`Cancellazione della sessione in corso...` until the new QR arrives.
 
 ### QR code
 
 The QR payload comes from the [WhatsApp client](../integrations/whatsapp-client.md)
-(`WaEvent::Qr`). `qr_to_text` (`src/ui.rs:192-201`) encodes it with the
+(`WaEvent::Qr`). `qr_to_text` (`src/ui.rs:191-205`) encodes it with the
 `qrcode` crate using `Dense1x2` half-block characters and a quiet zone;
 colors are inverted (dark modules drawn as empty cells) so the code scans on a
-dark terminal background.
+dark terminal background. The [web control panel](../integrations/web-control-panel.md)
+renders the same payload separately as SVG.
 
-`qr_block_lines` (`src/ui.rs:171-185`) replaces the QR with a single line when:
+`qr_block_lines` (`src/ui.rs:171-189`) replaces the QR with a single line when:
 
 - no code has arrived yet: "In attesa del codice QR...";
 - encoding fails: "Errore generazione QR: <error>";
-- the code does not fit the terminal (rows minus `PAIRING_TEXT_ROWS = 5`, or
-  columns): "Terminale troppo piccolo per il QR: ingrandisci la finestra".
+- the code does not fit the terminal (rows minus `PAIRING_TEXT_ROWS = 6` —
+  title, two blanks, steps, status and key help — or columns):
+  "Terminale troppo piccolo per il QR: ingrandisci la finestra".
 
 ## Main screen layout
 
-`render_main` (`src/ui.rs:207-222`):
+`render_main` (`src/ui.rs:207-226`):
 
 ```
 +---------------------------------+--------------------------------------+
@@ -62,47 +69,47 @@ dark terminal background.
 |                                 | Messaggio                             |
 |                                 | > typed text_                         |
 +---------------------------------+--------------------------------------+
- <status>   Tab:focus  Enter:apri/invia  F2:chat/contatti  Canc:elimina  F3:AI chat  F4:AI tutte  F10:esci      v0.1.0 (e147fe6)
+ <status>   Tab:focus  Enter:apri/invia  F2:chat/contatti  Canc:elimina  F3:AI chat  F4:AI tutte  F8:nuovo QR  F10:esci      v0.1.0 (5c2fe04)
 ```
 
 (The list title is a single line: base title, then the global marker, then
 the search text.)
 
-- **List pane** (30% width, `src/ui.rs:242-261`): the filtered chats or
+- **List pane** (30% width, `src/ui.rs:242-265`): the filtered chats or
   contacts (`App::visible_chats` / `visible_contacts`) depending on
-  `list_mode`. The title (`list_title`, `src/ui.rs:268-277`) is
+  `list_mode`. The title (`list_title`, `src/ui.rs:267-281`) is
   "Chat [F2: Contatti]" / "Contatti [F2: Chat]", followed — in chat mode
   only — by `●AI tutte` when the global autoreply is on, and by
   `Cerca: <filter>` when a filter is typed. The selection is reversed with a
   `"> "` marker and clamped by `selected_index`. Chats whose per-chat
   autoreply is enabled show a green ` ●AI` marker (`chat_item`,
-  `src/ui.rs:291-298`).
-- **Messages pane** (`src/ui.rs:312-348`): title is
+  `src/ui.rs:291-302`). With list focus, F3 toggles the selected row.
+- **Messages pane** (`src/ui.rs:312-352`): title is
   ` <name> ── AI: ON|OFF ` or " Nessuna chat aperta "; `AI: ON` is shown when
   the open chat's flag **or** the global autoreply is on. All messages are
   wrapped to the inner width and only the last rows that fit are kept, so the
   newest messages are always visible (no scrolling).
-- **Input pane** (3 rows, `src/ui.rs:494-506`): shows `"> " + input`; when
+- **Input pane** (3 rows, `src/ui.rs:494-510`): shows `"> " + input`; when
   the text is wider than the box only the tail is shown (`tail_fitting`), and
   the terminal cursor is placed after it while the input has focus.
-- **Footer** (1 row, `src/ui.rs:530-573`): split into a left area with the
+- **Footer** (1 row, `src/ui.rs:530-577`): split into a left area with the
   bold status text followed by the key help
-  `Tab:focus  Enter:apri/invia  F2:chat/contatti  Canc:elimina  F3:AI chat  F4:AI tutte  F10:esci`,
-  and a right area, exactly as wide as needed, with the dimmed build version
-  `v<version> (<git hash>) ` (`version_label`, from `BUILD_VERSION`, see
-  [Runtime and event loop](runtime-and-event-loop.md)). The help is dimmed,
-  except `F4:AI tutte`, which is drawn green while the global autoreply is on
-  (`key_help_spans`).
+  `Tab:focus  Enter:apri/invia  F2:chat/contatti  Canc:elimina  F3:AI chat  F4:AI tutte  F8:nuovo QR  F10:esci`
+  (`src/ui.rs:53-59`), and a right area, exactly as wide as needed, with the
+  dimmed build version `v<version> (<git hash>) ` (`version_label`, from
+  `BUILD_VERSION`, see [Runtime and event loop](runtime-and-event-loop.md)).
+  The help is dimmed, except `F4:AI tutte`, which is drawn green while the
+  global autoreply is on (`key_help_spans`).
 
 The focused pane (list or input) has a yellow border (`pane_block`,
-`src/ui.rs:228-236`).
+`src/ui.rs:228-240`).
 
 ## Message formatting
 
 `format_message_line` (`src/ui.rs:379-399`) renders a
 [stored message](../concepts/history-store.md) as `HH:MM <sender>: <text>` in
 the local time zone; out-of-range timestamps show `--:--`
-(`format_clock_time`, `src/ui.rs:401-410`).
+(`format_clock_time`, `src/ui.rs:401-413`).
 
 `sender_label` (`src/ui.rs:416-433`) picks:
 
@@ -111,20 +118,20 @@ the local time zone; out-of-range timestamps show `--:--`
 2. otherwise the trimmed, non-blank sender name;
 3. otherwise the user part of the sender JID (text before `@`).
 
-Colors (`message_style`, `src/ui.rs:367-373`): AI messages cyan, own messages
+Colors (`message_style`, `src/ui.rs:367-377`): AI messages cyan, own messages
 green, incoming messages default.
 
 ## Wrapping and widths
 
 Widths are measured in terminal cells through ratatui's `Line::width`, so
-wide characters (CJK, emoji) count as two cells (`src/ui.rs:443-455`).
-`wrap_text` (`src/ui.rs:461-488`) splits on `\n`, wraps each paragraph by
+wide characters (CJK, emoji) count as two cells (`src/ui.rs:443-459`).
+`wrap_text` (`src/ui.rs:461-492`) splits on `\n`, wraps each paragraph by
 characters (not words), drops control characters and always emits at least
 one row per paragraph.
 
 ## Tests
 
-Unit tests in `src/ui.rs:575` onward cover the footer key help (F4
+Unit tests in `src/ui.rs:579` onward cover the footer key help (F4
 highlighted only when the global autoreply is on), the version label, sender
 labels and fallbacks, clock
 formatting in a fixed time zone, QR text generation and its error/missing

+ 1 - 1
openwiki/index.md

@@ -4,7 +4,7 @@ okf_version: "0.2"
 
 # Files
 
-- [Quickstart](quickstart.md) - What the wh WhatsApp terminal client with per-chat and global AI autoreply (system prompt in SOUL.md, reply delay, image descriptions) and optional web control panel does, how to build and run it, and where to find each topic in this wiki.
+- [Quickstart](quickstart.md) - What the wh WhatsApp terminal client with per-chat and global AI autoreply (system prompt in SOUL.md, reply delay, image descriptions), live configuration reload, re-pairing with F8 and optional web control panel does, how to build and run it, and where to find each topic in this wiki.
 
 # Directories
 

+ 38 - 27
openwiki/integrations/ai-client.md

@@ -1,17 +1,19 @@
 ---
 type: integration
 title: AI chat-completions client
-description: The OpenAI-compatible HTTP client in src/ai.rs — text completions offering the no_reply tool, image descriptions with the vision model (request text from vision.md), image validation, timeouts, retries with linear backoff, error handling, the optional request log and reply parsing.
+description: The OpenAI-compatible HTTP client in src/ai.rs — text completions offering the no_reply tool, image descriptions with the vision model (request text from VISION.md), image validation, timeouts, retries with linear backoff, error handling, the optional request log and reply parsing.
 tags: [ai, openai, reqwest, vision, http, retry, tools]
-verified:
-  - by: openwiki/0.7.2
-    at: 2026-10-21T21:49:12.291Z
 sources:
+  - id: openwiki-source-651d1fb6c9e49916a916ab51
+    resource: repo://Cargo.toml
   - id: openwiki-source-1b05507d7085e8e99a9b64f1
     resource: repo://src/ai.rs
   - id: openwiki-source-2a737474d86fc75cc9d9694f
     resource: repo://src/config.rs
-generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" }
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
+verified:
+  - by: openwiki/0.7.2
+    at: 2026-10-23T21:26:24.156Z
 ---
 
 # AI chat-completions client
@@ -42,23 +44,32 @@ API key and the image bytes (`src/ai.rs:124-160`).
 
 ## Construction
 
-`AiClient::new(&Config)` (`src/ai.rs:169-197`):
+`AiClient::new(&Config)` (`src/ai.rs:170-202`):
 
 - requires `openai_api_key`; otherwise fails with
   `openai_api_key mancante in config.toml`;
+- installs the `ring` rustls crypto provider as the process-wide default
+  (`rustls::crypto::ring::default_provider().install_default()`), ignoring
+  the error that only means a provider is already installed: `reqwest` is
+  built with `rustls-no-provider`, so TLS needs a provider installed first
+  (`Cargo.toml:13-16`); `ring` is already compiled for `whatsapp-rust`, and
+  this avoids the large `aws-lc-sys` C build;
 - builds the HTTP client with a 60 s request timeout
   (`REQUEST_TIMEOUT_SECONDS`), applied to each attempt;
 - builds the endpoint as `openai_base_url` (trailing `/` trimmed) +
   `/chat/completions`;
 - copies `openai_model`, `openai_vision_model` and `ai_retries`;
-- takes the image description request from `vision.md`
+- takes the image description request from `VISION.md`
   (`Config::image_description_prompt`), falling back to the built-in
   `DEFAULT_IMAGE_DESCRIPTION_REQUEST` (`src/ai.rs:22-23`), an Italian request
   for an objective, concise description of subjects, actions, readable text
   and context;
 - sets the log path to `Config::ai_log_path()` only when `ai_log` is true.
 
-No network traffic happens until a request is made. Defaults and config keys
+No network traffic happens until a request is made. Because the client is
+rebuilt whenever the configuration is reloaded (see
+[Application state](../architecture/app-state.md#configuration-reload)), the
+provider installation runs again and is a no-op after the first time. Defaults and config keys
 are described in
 [Configuration and data](../operations/configuration-and-data.md)
 (default base URL `https://ollama.com/v1`, default model
@@ -66,7 +77,7 @@ are described in
 
 ## Text completions and the `no_reply` tool
 
-`complete(&turns)` (`src/ai.rs:199-208`) sends
+`complete(&turns)` (`src/ai.rs:204-213`) sends
 
 ```json
 { "model": "<model>",
@@ -77,7 +88,7 @@ are described in
 ```
 
 built by `build_request_body`, `build_tools` and `build_messages`
-(`src/ai.rs:343-379`). The only tool offered is the parameterless `no_reply`
+(`src/ai.rs:348-384`). The only tool offered is the parameterless `no_reply`
 function; its description is neutral ("does not send any message in the
 chat: call it instead of writing a reply") and never says *when* to use it —
 that decision belongs to the system prompt in `SOUL.md`
@@ -86,40 +97,40 @@ means "send nothing".
 
 ## Image descriptions
 
-`has_vision()` (`src/ai.rs:210-216`) is true when a vision model is
-configured. `describe_image(&image)` (`src/ai.rs:218-236`) asks the vision
+`has_vision()` (`src/ai.rs:215-221`) is true when a vision model is
+configured. `describe_image(&image)` (`src/ai.rs:223-241`) asks the vision
 model for a description of **one** image, independently of any
 conversation:
 
 1. fails with `modello vision non configurato` without a vision model;
-2. validates the image (`validate_image`, `src/ai.rs:408-427`): empty →
+2. validates the image (`validate_image`, `src/ai.rs:413-432`): empty →
    `immagine vuota`, larger than `MAX_IMAGE_BYTES` (5 MiB) →
    `immagine troppo grande: N byte` (errors mention the size only, never the
    data);
 3. sends a single user message `[{"type":"text", <description request>},
    {"type":"image_url", ...}]` to the vision model, **without tools**
-   (`build_description_body`, `src/ai.rs:381-392`);
+   (`build_description_body`, `src/ai.rs:386-397`);
 4. returns the description text; a `NoReply` outcome is turned into the
    `risposta del servizio AI vuota` error.
 
 The image is embedded as a `data:<mime>;base64,<...>` URL
-(`build_image_part`, `src/ai.rs:394-406`). Validation errors happen before any
+(`build_image_part`, `src/ai.rs:399-411`). Validation errors happen before any
 request and are not retried. The caller stores the description in the
 [history store](../concepts/history-store.md), so each image is described
 once and later replies see only the text.
 
 ## Retries
 
-Both request kinds go through `send_with_retry` (`src/ai.rs:238-259`),
+Both request kinds go through `send_with_retry` (`src/ai.rs:243-264`),
 which performs up to `1 + ai_retries` attempts with the same body:
 
 - after a failed attempt it retries only while attempts remain and
-  `is_retryable` (`src/ai.rs:317-332`) accepts the error: HTTP 429 and 5xx
+  `is_retryable` (`src/ai.rs:322-337`) accepts the error: HTTP 429 and 5xx
   responses and every non-status failure (transport errors, timeouts, body
   read errors, invalid JSON, missing or empty reply) are retried; any other
   4xx response (e.g. 401, 400) fails immediately;
 - before retry number *n* it sleeps `retry_delay(n)` = 2 s × n
-  (`src/ai.rs:334-341`), i.e. 2 s, 4 s, … (linear backoff);
+  (`src/ai.rs:339-346`), i.e. 2 s, 4 s, … (linear backoff);
 - the returned error is the one of the last attempt.
 
 `ai_retries = 0` disables retrying. Each attempt is a separate HTTP request
@@ -127,8 +138,8 @@ and, when enabled, a separate log record.
 
 ## Sending and error handling
 
-`send_body` (`src/ai.rs:261-280`) delegates to `request_reply`
-(`src/ai.rs:282-314`), which posts JSON with bearer auth and returns
+`send_body` (`src/ai.rs:266-285`) delegates to `request_reply`
+(`src/ai.rs:287-319`), which posts JSON with bearer auth and returns
 Italian `anyhow` errors:
 
 | Failure | Message |
@@ -139,7 +150,7 @@ Italian `anyhow` errors:
 | Invalid JSON | `risposta del servizio AI non valida: <body>` |
 
 Response bodies in errors are truncated to 300 characters on a character
-boundary (`truncate_chars`, `src/ai.rs:568-574`); the API key is never
+boundary (`truncate_chars`, `src/ai.rs:573-579`); the API key is never
 included.
 
 ## Optional request log
@@ -147,24 +158,24 @@ included.
 When `ai_log = true` in `config.toml` (default `false`), `send_body` appends
 one record per attempt to `ai_requests.log` in the data directory
 (`Config::ai_log_path`, `src/config.rs:333-339`) after the attempt completes.
-`format_log_record` (`src/ai.rs:521-547`) writes a header
+`format_log_record` (`src/ai.rs:526-552`) writes a header
 `===== <local timestamp> | <endpoint> | modello: <model> =====`, the
 pretty-printed request body and the outcome labelled `RISPOSTA` or `ERRORE`;
 a `NoReply` outcome is logged as `RISPOSTA` with the text
 `(tool no_reply: nessun messaggio inviato)`.
 
 - Image data URLs are replaced by `data:<mime>;base64,<omessi N byte>`
-  (`redact_images` / `redact_data_url`, `src/ai.rs:473-519`); text parts and
+  (`redact_images` / `redact_data_url`, `src/ai.rs:478-524`); text parts and
   remote URLs are kept.
 - The API key is never written (it is only in the bearer header, not in the
   body).
 - The file is opened in append mode and created if missing
-  (`append_to_file`, `src/ai.rs:556-566`); write failures are ignored so
+  (`append_to_file`, `src/ai.rs:561-571`); write failures are ignored so
   logging never makes a reply fail and never writes over the TUI.
 
 ## Reply parsing
 
-`parse_reply` (`src/ai.rs:429-477`) looks at `choices[0]`:
+`parse_reply` (`src/ai.rs:434-482`) looks at `choices[0]`:
 
 - if any of its `message.tool_calls` is a function call named `no_reply`
   (`calls_no_reply`), the result is `Reply::NoReply`, even when the message
@@ -177,8 +188,8 @@ a `NoReply` outcome is logged as `RISPOSTA` with the text
 
 ## Tests
 
-`src/ai.rs:576` onward covers the description body (request text plus image,
-no tools), the `vision.md` request and its default, missing vision model,
+`src/ai.rs:581` onward covers the description body (request text plus image,
+no tools), the `VISION.md` request and its default, missing vision model,
 oversize/empty images (without leaking data), the exact size limit,
 `has_vision`, `Debug` redaction, request body shape including `tools`, the
 `no_reply` tool definition, reply parsing (trimmed text, `no_reply` with or

+ 3 - 3
openwiki/integrations/index.md

@@ -1,5 +1,5 @@
 # Files
 
-- [AI chat-completions client](ai-client.md) - The OpenAI-compatible HTTP client in src/ai.rs — text completions offering the no_reply tool, image descriptions with the vision model (request text from vision.md), image validation, timeouts, retries with linear backoff, error handling, the optional request log and reply parsing.
-- [Web control panel](web-control-panel.md) - The optional HTTP control panel in src/web.rs and src/web_page.html, enabled by web_port — routes, the custom-header guard on POST, the request/reply bridge to App through AppEvent::Web, the quit flow, the browser page and its security caveats.
-- [WhatsApp client integration](whatsapp-client.md) - How src/whatsapp.rs wraps whatsapp-rust — session startup, translation of raw events into WaEvent, JID canonicalization, contact names, history sync, message rendering, image download and sending.
+- [AI chat-completions client](ai-client.md) - The OpenAI-compatible HTTP client in src/ai.rs — text completions offering the no_reply tool, image descriptions with the vision model (request text from VISION.md), image validation, timeouts, retries with linear backoff, error handling, the optional request log and reply parsing.
+- [Web control panel](web-control-panel.md) - The optional HTTP control panel in src/web.rs and src/web_page.html, enabled by web_port — routes (including POST /api/relink), the custom-header guard on POST, the request/reply bridge to App through AppEvent::Web, the state with screen and pairing QR as SVG, the quit and relink flows, the browser page with its pairing card and its security caveats.
+- [WhatsApp client integration](whatsapp-client.md) - How src/whatsapp.rs wraps whatsapp-rust — the replaceable session behind WaHandle, session startup and relink (logout, session database reset, new unpaired client), translation of raw events into WaEvent, JID canonicalization, contact names, history sync, message rendering, image download and sending.

+ 79 - 43
openwiki/integrations/web-control-panel.md

@@ -1,8 +1,8 @@
 ---
 type: integration
 title: Web control panel
-description: The optional HTTP control panel in src/web.rs and src/web_page.html, enabled by web_port — routes, the custom-header guard on POST, the request/reply bridge to App through AppEvent::Web, the quit flow, the browser page and its security caveats.
-tags: [web, http, tiny_http, control-panel, security, autoreply]
+description: The optional HTTP control panel in src/web.rs and src/web_page.html, enabled by web_port — routes (including POST /api/relink), the custom-header guard on POST, the request/reply bridge to App through AppEvent::Web, the state with screen and pairing QR as SVG, the quit and relink flows, the browser page with its pairing card and its security caveats.
+tags: [web, http, tiny_http, control-panel, security, autoreply, qr-code, relink]
 sources:
   - id: openwiki-source-9fec9d1af408501c9c455161
     resource: repo://src/app.rs
@@ -12,36 +12,41 @@ sources:
     resource: repo://src/web_page.html
   - id: openwiki-source-7b75006986d8b0337adc6ca0
     resource: repo://src/web.rs
-generated: { by: "claude-code", at: "2026-10-20T20:27:06.515Z" }
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
 verified:
   - by: openwiki/0.7.2
-    at: 2026-10-21T21:49:12.291Z
+    at: 2026-10-23T21:26:24.156Z
 ---
 
 # Web control panel
 
 `src/web.rs` is an optional, minimal HTTP server that lets a browser (for
-example a phone on the same network) see the chats and toggle the global and
-per-chat AI autoreply, or quit the bot. It is off unless `web_port` is set in
-`config.toml` (see [Configuration and data](../operations/configuration-and-data.md)).
+example a phone on the same network) see the chats, toggle the global and
+per-chat AI autoreply, pair WhatsApp by scanning the QR code shown in the
+page, request a new pairing, or quit the bot. It is off unless `web_port` is
+set in `config.toml` (see [Configuration and data](../operations/configuration-and-data.md)).
 It has **no authentication** and listens on **all interfaces** (`0.0.0.0`,
-`src/web.rs:1-2`, `src/web.rs:16-17`).
+`src/web.rs:1-2`, `src/web.rs:16-19`).
 
 ## Startup and threading
 
 `main` calls `start_web_panel`, which calls `web::start(port, app_tx)` only
 when `web_port` is set ([Runtime and event loop](../architecture/runtime-and-event-loop.md)).
-`start` (`src/web.rs:165-173`) binds a `tiny_http::Server` on
+`start` (`src/web.rs:185-199`) binds a `tiny_http::Server` on
 `0.0.0.0:<port>` and spawns a detached OS thread named `wh-web`. A bind
-failure aborts startup with `cannot start web server on port <port>`.
+failure aborts startup with `cannot start web server on port <port>`. The
+port is read once: unlike the other settings, a changed `web_port` is not
+applied by the configuration live reload and needs a restart.
 
-`serve` (`src/web.rs:179-186`) handles requests **one at a time** on that
+`serve` (`src/web.rs:201-212`) handles requests **one at a time** on that
 thread and stops after a request once the application channel is closed.
-`tiny_http` is the only extra dependency (`Cargo.toml:24-25`).
+`tiny_http` is the only extra dependency for the server; the `qrcode` crate
+(already used by the TUI) is built with its `svg` feature for the pairing
+QR (`Cargo.toml`).
 
 ## Routes
 
-`resolve_route` (`src/web.rs:325-334`) matches method and path; the query
+`resolve_route` (`src/web.rs:348-362`) matches method and path; the query
 string is ignored (`path_without_query`):
 
 | Method | Path | Effect | Response |
@@ -50,35 +55,46 @@ string is ignored (`path_without_query`):
 | GET | `/api/state` | `WebCommand::State` | 200 JSON state |
 | POST | `/api/global` | `WebCommand::ToggleGlobal` | 200 JSON state |
 | POST | `/api/chat` | `WebCommand::ToggleChat(<body>)`; body = chat JID as plain text | 200 JSON state, 400 on bad body |
+| POST | `/api/relink` | `WebCommand::Relink` | 200 JSON state |
 | POST | `/api/quit` | `WebCommand::Quit` | 200 `{"status":"Bot in chiusura"}` |
 | other | other | — | 404 `non trovato` |
 
-The JSON state is `WebState` (`src/web.rs:92-112`):
+The JSON state is `WebState` (`src/web.rs:123-136`):
 
 ```json
 { "global_autoreply": false,
   "chats": [ { "jid": "...", "name": "...", "autoreply": true } ],
-  "status": "<TUI status line>" }
+  "status": "<TUI status line>",
+  "screen": "connecting | pairing | main",
+  "qr_svg": "<svg ...>...</svg>" }
 ```
 
+`screen` mirrors the TUI screen (`SCREEN_CONNECTING`, `SCREEN_PAIRING`,
+`SCREEN_MAIN`, `src/web.rs:34-39`). `qr_svg` is `null` unless the app holds
+a pairing QR payload; then `qr_svg` (`src/web.rs:364-379`) renders it as an
+SVG document with 6-pixel black modules on a white quiet zone. A payload
+that cannot be encoded yields `null` (the TUI pairing screen shows the
+encoding error).
+
 ### Request checks
 
-- **Custom header on POST** — every POST route must carry the header
-  `X-Wh-Request: 1` (name case-insensitive, value exact); otherwise the
-  server answers 403 `richiesta non autorizzata` and forwards nothing
-  (`handle_request`, `has_request_header`, `src/web.rs:193-205`,
-  `src/web.rs:341-345`). A cross-site page cannot add a custom header without
-  a CORS preflight, which the server never approves, so this blocks simple
-  cross-site form/fetch requests (CSRF). It is **not** authentication: anyone
-  who can reach the port can send the header.
+- **Custom header on POST** — every POST route (global, chat, relink, quit)
+  must carry the header `X-Wh-Request: 1` (name case-insensitive, value
+  exact); otherwise the server answers 403 `richiesta non autorizzata` and
+  forwards nothing (`requires_request_header`, `handle_request`,
+  `has_request_header`, `src/web.rs:166-174`, `src/web.rs:214-231`,
+  `src/web.rs:381-390`). A cross-site page cannot add a custom header
+  without a CORS preflight, which the server never approves, so this blocks
+  simple cross-site form/fetch requests (CSRF). It is **not**
+  authentication: anyone who can reach the port can send the header.
 - **Body limit** — `/api/chat` reads at most 1024 bytes
   (`MAX_BODY_BYTES`); an oversized, empty (after trimming), unreadable or
   non-UTF-8 body yields 400 `corpo della richiesta non valido`
-  (`read_limited_body`, `src/web.rs:352-364`).
+  (`read_limited_body`, `src/web.rs:392-409`).
 
 ## Bridge to the application
 
-The server never touches application state. `forward` (`src/web.rs:252-261`)
+The server never touches application state. `forward` (`src/web.rs:275-288`)
 sends `AppEvent::Web(WebRequest { command, reply })` with a tokio `oneshot`
 sender and blocks the web thread (`blocking_recv`) until `App` answers.
 `App::handle_web_request` runs inside the normal event loop, so web commands
@@ -91,6 +107,10 @@ are serialised with keys and WhatsApp events
 - `ToggleChat(jid)` is accepted only for JIDs present in the panel's chat
   list — otherwise only the status becomes `Chat non trovata` — and then
   follows the same rules as F3 (AI configured, private chat or group);
+- `Relink` starts the session deletion and re-pairing at once (the browser
+  asks for confirmation instead of the TUI's double F8); it is ignored while
+  a relink is already running. See
+  [WhatsApp relink and pairing](../workflows/relink-and-pairing.md);
 - the reply is always the state after the command; refusals are visible
   only through `status`.
 
@@ -104,7 +124,7 @@ If the event channel is closed or the reply is dropped, the server answers
 
 ### Quit
 
-`handle_quit` (`src/web.rs:234-246`) first writes the 200 response, then
+`handle_quit` (`src/web.rs:256-273`) first writes the 200 response, then
 sends `WebCommand::Quit` without waiting for a reply, because the process
 exits as soon as `App` sets `should_quit` and the event loop ends. The TUI
 terminal is restored as on F10.
@@ -112,40 +132,56 @@ terminal is restored as on F10.
 ## Browser page
 
 `src/web_page.html` is a self-contained page (inline CSS and JS, Italian
-UI) with a global "AI tutte" switch, one switch per chat, a quit button and
-a status bar:
-
-- it polls `GET /api/state` every 5 s while the tab is visible and refreshes
-  when it becomes visible again (`src/web_page.html:330`,
-  `src/web_page.html:612-635`);
+UI) with a global "AI tutte" switch, a "Nuovo QR" button, a "Chiudi bot"
+button, a pairing card, one switch per chat and a status bar:
+
+- it polls `GET /api/state` every 5 s while the tab is visible — every 2 s
+  while `screen` is not `main`, so a rotated QR shows up quickly
+  (`adjustPolling`) — and refreshes when it becomes visible again
+  (`src/web_page.html:382-383`, `src/web_page.html:619-628`,
+  `src/web_page.html:739-763`);
+- the pairing card (`renderPairing`, `src/web_page.html:600-617`) is hidden
+  on `main`; otherwise it shows "Connessione a WhatsApp in corso...", "In
+  attesa del codice QR..." or, with a QR, "Collega WhatsApp: scansiona il
+  codice QR", the QR image (the SVG as a `data:` URL, replaced only when it
+  changes) and the WhatsApp linking steps;
 - every POST carries `X-Wh-Request: 1`; the chat toggle sends the JID as a
-  `text/plain` body (`buildHeaders`, `src/web_page.html:377-382`);
+  `text/plain` body (`buildHeaders`, `src/web_page.html:441-450`);
 - while a toggle is in flight its switch is disabled and keeps the user's
   position; polling is paused, and responses are applied in request order
   through a ticket counter so a stale poll cannot overwrite a newer toggle
-  result (`src/web_page.html:534-577`);
+  result (`src/web_page.html:643-700`);
 - names are inserted with `textContent` only;
+- "Nuovo QR" (`relinkBot`, `src/web_page.html:703-718`) asks for
+  confirmation (the device will be unlinked from the phone), posts
+  `/api/relink`, applies the returned state (the pairing card appears) and
+  keeps the button disabled while the request runs;
 - quit asks for confirmation (`Chiudere il bot?`) and then shows a
   full-screen "Bot chiuso" overlay and stops polling; a network error after
   a quit request is treated as the bot having exited
-  (`src/web_page.html:417-439`, `src/web_page.html:597-610`).
+  (`src/web_page.html:485-510`, `src/web_page.html:720-737`).
 
 ## Security and operations
 
 - No authentication and no TLS: anyone who can reach `<host>:<web_port>` can
-  read chat names and JIDs, toggle autoreplies and stop the bot. Enable it
-  only on a trusted network, or restrict the port with the host firewall.
+  read chat names and JIDs, toggle autoreplies, stop the bot, unlink the
+  WhatsApp device with `/api/relink` and, while pairing, read the QR code
+  and link their own phone. Enable it only on a trusted network, or restrict
+  the port with the host firewall.
 - The server binds to every interface; there is no setting for the bind
   address.
 - `web_port = 0` is rejected at startup; values above 65535 or negative are
   rejected by TOML parsing.
 - Requests are served sequentially and each API request waits for the event
-  loop, so a slow request delays the others.
+  loop, so a slow request delays the others. A relink request returns as
+  soon as the relink task is spawned, not when it completes.
 
 ## Tests
 
-`src/web.rs:366-445` covers query stripping, route resolution (known routes,
-wrong method or path), the custom header check (any case accepted, missing
-or wrong value rejected), which routes require the header, and body reading
-(trimming, empty/invalid UTF-8 rejection, the 1024-byte limit). `web_chats`
-filtering is tested in `src/app.rs`. See [Unit tests](../testing/unit-tests.md).
+`src/web.rs:411-503` covers query stripping, route resolution (known routes
+including `/api/relink`, wrong method or path, `GET /api/relink` → not
+found), the custom header check (any case accepted, missing or wrong value
+rejected), which routes require the header (relink included), `qr_svg`
+producing an SVG document, and body reading (trimming, empty/invalid UTF-8
+rejection, the 1024-byte limit). `web_chats` filtering is tested in
+`src/app.rs`. See [Unit tests](../testing/unit-tests.md).

+ 92 - 41
openwiki/integrations/whatsapp-client.md

@@ -1,17 +1,17 @@
 ---
 type: integration
 title: WhatsApp client integration
-description: How src/whatsapp.rs wraps whatsapp-rust — session startup, translation of raw events into WaEvent, JID canonicalization, contact names, history sync, message rendering, image download and sending.
-tags: [whatsapp, whatsapp-rust, events, jid, history-sync]
+description: How src/whatsapp.rs wraps whatsapp-rust — the replaceable session behind WaHandle, session startup and relink (logout, session database reset, new unpaired client), translation of raw events into WaEvent, JID canonicalization, contact names, history sync, message rendering, image download and sending.
+tags: [whatsapp, whatsapp-rust, events, jid, history-sync, relink, session]
 sources:
   - id: openwiki-source-651d1fb6c9e49916a916ab51
     resource: repo://Cargo.toml
   - id: openwiki-source-7ea5eac45e5030178a6ea05b
     resource: repo://src/whatsapp.rs
-generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" }
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
 verified:
   - by: openwiki/0.7.2
-    at: 2026-10-21T21:49:12.291Z
+    at: 2026-10-23T21:26:24.156Z
 ---
 
 # WhatsApp client integration
@@ -24,25 +24,44 @@ verified:
 - a stream of plain `WaEvent` values sent to the
   [event loop](../architecture/runtime-and-event-loop.md);
 - a cloneable `WaHandle` used by [`App`](../architecture/app-state.md) to
-  send text, download images and query its own JIDs.
+  send text, download images, query its own JIDs and replace the session
+  with a new unpaired one (`relink`).
 
 `rusqlite` is pinned so that it shares `libsqlite3-sys` with the `diesel`
-backend of `whatsapp-rust-sqlite-storage` (`Cargo.toml:14-15`).
+backend of `whatsapp-rust-sqlite-storage` (`Cargo.toml:17-18`); the module
+also uses it directly to empty the session database during a relink.
+
+## Session and handle
+
+`WaHandle` (`src/whatsapp.rs:160-170`) holds the running `Session` in an
+`Arc<Mutex<Option<Session>>>`, the `session.db` path and the `WaEvent`
+sender shared by every session. Clones share the same slot, so a relink
+started from one clone is seen by all. A `Session` (`src/whatsapp.rs:172-180`)
+is one client (`Arc<Client>`), the abort handle of its translator task and a
+`watch` receiver that becomes `true` when the client run loop stops.
+
+Every client operation goes through `WaHandle::client`
+(`src/whatsapp.rs:200-210`), which fails with
+`WhatsApp session not running` while the slot is empty — during a relink or
+after a failed one. The lock recovers from poisoning (`lock_session`).
 
 ## Startup
 
-`start(session_db, &events)` (`src/whatsapp.rs:208-241`):
+`start(session_db, events)` (`src/whatsapp.rs:297-312`) calls
+`open_session` and wraps the result in a new `WaHandle`. `open_session`
+(`src/whatsapp.rs:369-416`):
 
 1. requires a UTF-8 session path and creates its parent directory;
 2. opens the session store with `SqliteStore::new` (`session.db`, see
    [Configuration and data](../operations/configuration-and-data.md));
 3. builds a `Bot` with that backend and an `EventForwarder` handler;
-4. spawns the `Translator` task and the bot;
-5. when the bot task ends, emits `Disconnected("client stopped")`.
+4. spawns the `Translator` task (keeping its abort handle) and the bot;
+5. when the bot task ends, emits `Disconnected("client stopped")` and marks
+   the session stopped on its `watch` channel.
 
-`EventForwarder` (`src/whatsapp.rs:244-265`) only pushes raw events, in
+`EventForwarder` (`src/whatsapp.rs:417-439`) only pushes raw events, in
 order, into an internal unbounded channel; its interest set is
-`SUBSCRIBED_KINDS` (`src/whatsapp.rs:51-70`), which includes
+`SUBSCRIBED_KINDS` (`src/whatsapp.rs:54-74`), which includes
 `OfflineSyncCompleted` and `AppStateSyncFailed`. All translation happens in
 `Translator`, which also owns a name cache.
 
@@ -50,13 +69,38 @@ order, into an internal unbounded channel; its interest set is
 whatsapp-rust Bot --raw events--> EventForwarder --chan--> Translator --WaEvent--> App
 ```
 
+## Relink
+
+`WaHandle::relink` (`src/whatsapp.rs:221-237`) replaces the session with a
+new, unpaired one so a new QR code is offered (end-to-end flow:
+[WhatsApp relink and pairing](../workflows/relink-and-pairing.md)):
+
+1. takes the current session out of the slot (the slot stays empty) and
+   calls `Session::stop` (`src/whatsapp.rs:182-198`): `logout` (deregisters
+   the device on the phone when connected, best effort, and disconnects),
+   waits up to 10 s (`STOP_TIMEOUT_SECONDS`) for the run loop to stop, then
+   aborts the translator task;
+2. `reset_session_files` (`src/whatsapp.rs:314-329`) deletes `session.db`
+   and its `-wal` / `-shm` side files (missing files are fine). When the
+   deletion fails — on Windows the library keeps the database open until the
+   process exits — `clear_session_tables` (`src/whatsapp.rs:349-367`)
+   instead deletes every row of every table except SQLite internals and the
+   `__diesel_schema_migrations` record, in one transaction with a 5 s busy
+   timeout. The stopped client writes nothing more afterwards;
+3. `open_session` starts a new client on the empty database and stores it in
+   the slot. Its pairing QR codes arrive as `WaEvent::Qr` on the same events
+   channel.
+
+Any error in steps 2–3 is returned and leaves the slot empty until the next
+successful relink (sends then fail with `WhatsApp session not running`).
+
 ## WaEvent
 
-`WaEvent` (`src/whatsapp.rs:81-108`):
+`WaEvent` (`src/whatsapp.rs:104-130`):
 
 | Variant | Meaning |
 |---|---|
-| `Qr(String)` | Pairing QR payload to render ([Terminal UI](../architecture/terminal-ui.md)) |
+| `Qr(String)` | Pairing QR payload to render ([Terminal UI](../architecture/terminal-ui.md), [web panel](web-control-panel.md)) |
 | `Connected` | Session is online |
 | `Disconnected(String)` | Connection lost, with reason |
 | `LoggedOut` | Device unlinked; pairing must restart |
@@ -65,19 +109,20 @@ whatsapp-rust Bot --raw events--> EventForwarder --chan--> Translator --WaEvent-
 | `Contact(Contact)` | A new or better contact name |
 | `Error(String)` | Non-fatal problem shown in the status line |
 
-`Translator::handle_event` (`src/whatsapp.rs:413-459`) maps raw events to
+`Translator::handle_event` (`src/whatsapp.rs:585-634`) maps raw events to
 these variants (`Event::OfflineSyncCompleted` becomes
 `WaEvent::OfflineSyncCompleted`). Failures become `Error` texts such as
-`pairing QR codes exhausted; restart to pair again`, `pairing failed: ...`,
+`pairing QR codes exhausted; request a new QR to pair again` (F8 or the web
+panel's "Nuovo QR"), `pairing failed: ...`,
 `WhatsApp rejected this client version (client outdated)`,
 `account temporarily banned: <code> (expires in Ns)`,
 `connection failure: <reason> (<message>)` (`describe_connect_failure`,
-`src/whatsapp.rs:796-801`), `stream error: ...` and
+`src/whatsapp.rs:971-976`), `stream error: ...` and
 `could not decrypt a message from ...`; `StreamReplaced` becomes
 `Disconnected("stream replaced by another session")`.
 
 `AppStateSyncFailed` is reported only when at least one collection failed
-**fatally** (`is_fatal_sync_failure`, `src/whatsapp.rs:808-810`), as
+**fatally** (`is_fatal_sync_failure`, `src/whatsapp.rs:983-985`), as
 `app state sync incomplete (fatal: [...], retryable: [...])`; retryable or
 skipped collections are retried by the library on a forced reconnect and are
 not shown.
@@ -85,23 +130,24 @@ not shown.
 ## JIDs
 
 - JIDs are stored as `user@server` without device suffix
-  (`jid_to_string` uses `to_non_ad_string`, `src/whatsapp.rs:825-827`).
-- `canonical_jid` (`src/whatsapp.rs:366-386`) prefers the phone-number (PN)
+  (`jid_to_string` uses `to_non_ad_string`, `src/whatsapp.rs:1000-1002`).
+- `canonical_jid` (`src/whatsapp.rs:541-556`) prefers the phone-number (PN)
   form: non-LID JIDs are kept; a LID uses the alternate PN JID from the
   message, else the store's LID→PN mapping; on lookup failure the LID is
   kept and `LID lookup failed for ...` is reported.
-- `WaHandle::own_jids` (`src/whatsapp.rs:191-197`) returns this account's PN
-  and LID; it is empty before pairing.
+- `WaHandle::own_jids` (`src/whatsapp.rs:279-295`) returns this account's PN
+  and LID; it is empty before pairing completes and while no session is
+  running.
 
 ## Live messages
 
-`handle_live_message` (`src/whatsapp.rs:475-521`):
+`handle_live_message` (`src/whatsapp.rs:650-696`):
 
 - skips `status@broadcast` and messages with no renderable content;
 - keeps group JIDs as the chat; for 1:1 chats canonicalizes using
   `recipient_alt` for own messages and `sender_alt` otherwise;
 - records the sender push name as a contact name (`push_name_of` drops own
-  and empty names, `src/whatsapp.rs:817-820`);
+  and empty names, `src/whatsapp.rs:992-995`);
 - attaches an `ImageRef` only when the rendered text is an image text;
 - emits `StoredMessage` with the WhatsApp timestamp and `is_ai = false`.
 
@@ -112,10 +158,10 @@ rendered, and the [AI autoreply](../workflows/ai-autoreply.md) only rewrites
 ## Contact names
 
 Names come from two sources ranked `Push < Saved` (`NameSource`,
-`src/whatsapp.rs:267-275`): push names from messages and `PushNameUpdate`,
+`src/whatsapp.rs:442-450`): push names from messages and `PushNameUpdate`,
 saved names from `ContactUpdate` (full name, else first name,
-`src/whatsapp.rs:526-540`) and from history sync. `note_name`
-(`src/whatsapp.rs:329-351`) ignores empty names, never lets a push name
+`src/whatsapp.rs:701-715`) and from history sync. `note_name`
+(`src/whatsapp.rs:499-522`) ignores empty names, never lets a push name
 override a saved one, skips unchanged names and otherwise emits
 `WaEvent::Contact`. The [history store](../concepts/history-store.md)
 persists them.
@@ -123,9 +169,9 @@ persists them.
 ## History sync
 
 Each `HistorySync` chunk is processed in its own task
-(`handle_history`, `src/whatsapp.rs:555-575`). Decoding (inflate + protobuf)
-runs in `spawn_blocking` via `extract_history` (`src/whatsapp.rs:704-745`).
-`emit_history` (`src/whatsapp.rs:581-648`) then emits push names, inline
+(`handle_history`, `src/whatsapp.rs:730-750`). Decoding (inflate + protobuf)
+runs in `spawn_blocking` via `extract_history` (`src/whatsapp.rs:879-920`).
+`emit_history` (`src/whatsapp.rs:756-822`) then emits push names, inline
 address-book contacts, conversation names and every renderable message.
 History messages never carry an image reference (so their images are never
 described); the sender is the
@@ -135,36 +181,41 @@ Undecodable conversations are reported as
 
 ## Message rendering
 
-`describe_message` (`src/whatsapp.rs:836-848`) turns a protobuf message into
+`describe_message` (`src/whatsapp.rs:1011-1023`) turns a protobuf message into
 text, in this order:
 
 1. plain or extended text;
 2. image → `[immagine]` or `[immagine] <caption>`; audio (including voice
    notes) → `[messaggio vocale]` (`describe_image_or_audio`,
-   `src/whatsapp.rs:856-868`);
+   `src/whatsapp.rs:1031-1043`);
 3. caption of other media;
-4. `describe_other` (`src/whatsapp.rs:876-894`): protocol, reaction,
+4. `describe_other` (`src/whatsapp.rs:1051-1069`): protocol, reaction,
    encrypted reaction, poll vote and sender-key distribution messages are
    silent (`None`, not stored); video, document, sticker and PTV →
    `[media]`; anything else → `[messaggio non supportato]`.
 
 Helpers used by the [AI autoreply](../workflows/ai-autoreply.md)
-(`src/whatsapp.rs:915-963`): `is_placeholder` (only `[media]` and
+(`src/whatsapp.rs:1090-1138`): `is_placeholder` (only `[media]` and
 `[messaggio non supportato]`), `is_voice_note`, `is_image` and
 `image_caption`.
 
 ## Sending and downloading
 
-- `WaHandle::send_text(chat_jid, text)` (`src/whatsapp.rs:150-163`) rejects
-  empty text, parses the JID and returns the new message id.
-- `WaHandle::download_image(&ImageRef)` (`src/whatsapp.rs:170-183`) downloads
+- `WaHandle::send_text(chat_jid, text)` (`src/whatsapp.rs:239-257`) rejects
+  empty text, parses the JID and returns the new message id; it fails while
+  no session is running.
+- `WaHandle::download_image(&ImageRef)` (`src/whatsapp.rs:259-277`) downloads
   and decrypts the image into memory (`DownloadedImage`), falling back to
   `image/jpeg` when the MIME type is blank. Images are never written to disk,
-  and `ImageRef`'s `Debug` omits key material (`src/whatsapp.rs:109-128`).
+  and `ImageRef`'s `Debug` omits key material (`src/whatsapp.rs:132-151`).
 
 ## Tests
 
-`src/whatsapp.rs:965+` covers `is_fatal_sync_failure`, text and extended text, captions and placeholders, image
-and voice rendering, image references, placeholder helpers, silent
-reactions/protocol messages, JID device-suffix removal and push-name
-filtering. See [Unit tests](../testing/unit-tests.md).
+`src/whatsapp.rs:1140+` covers `is_fatal_sync_failure`, text and extended
+text, captions and placeholders, image and voice rendering, image
+references, placeholder helpers, silent reactions/protocol messages, JID
+device-suffix removal and push-name filtering. An `#[ignore]`d live test,
+`relink_deletes_the_session_and_offers_a_new_qr`
+(`src/whatsapp.rs:1163-1183`), connects to the WhatsApp servers, waits for a
+QR, relinks and checks that the second QR carries new device keys; run it
+with `cargo test -- --ignored`. See [Unit tests](../testing/unit-tests.md).

+ 87 - 30
openwiki/operations/configuration-and-data.md

@@ -1,30 +1,40 @@
 ---
 type: operations
 title: Configuration and data files
-description: How src/config.rs loads config.toml, the system prompt from SOUL.md and the optional image description request from vision.md, the supported keys and defaults (reply_delay_seconds, catch_up_seconds, ai_retries, web_port), the template files, where the data directory and databases live, the build version, and operational and security caveats.
-tags: [configuration, toml, data-directory, sqlite, security, soul-md]
-verified:
-  - by: openwiki/0.7.2
-    at: 2026-10-21T21:49:12.291Z
+description: How src/config.rs loads config.toml, the system prompt from SOUL.md and the optional image description request from VISION.md, how src/config_watch.rs reloads them live every 2 seconds, the supported keys and defaults (reply_delay_seconds, catch_up_seconds, ai_retries, web_port), the template files, where the data directory and databases live, the build version and release profile, and operational and security caveats.
+tags: [configuration, toml, data-directory, sqlite, security, soul-md, live-reload]
 sources:
   - id: openwiki-source-ea70eb6c045047448e446296
     resource: repo://.gitignore
+  - id: openwiki-source-651d1fb6c9e49916a916ab51
+    resource: repo://Cargo.toml
   - id: openwiki-source-0d5b861992295d2e1663735c
     resource: repo://config.toml.global.template
   - id: openwiki-source-1b05507d7085e8e99a9b64f1
     resource: repo://src/ai.rs
+  - id: openwiki-source-9fec9d1af408501c9c455161
+    resource: repo://src/app.rs
+  - id: openwiki-source-d6b2055bbb49804b8a855fd2
+    resource: repo://src/config_watch.rs
   - id: openwiki-source-2a737474d86fc75cc9d9694f
     resource: repo://src/config.rs
-generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" }
+  - id: openwiki-source-b55a21a31ede1b56cd31a6a6
+    resource: repo://src/main.rs
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
+verified:
+  - by: openwiki/0.7.2
+    at: 2026-10-23T21:26:24.156Z
 ---
 
 # Configuration and data files
 
-All runtime settings come from files in the data directory, loaded once at
-startup by `Config::load` (`src/config.rs:265-281`): `config.toml` for the
-settings, `SOUL.md` for the system prompt and the optional `vision.md` for
-the image description request. No environment variable is ever read
-(`src/config.rs:1-25`). Editing any of them requires restarting `wh`.
+All runtime settings come from files in the data directory, loaded by
+`Config::load` (`src/config.rs:265-281`): `config.toml` for the settings,
+`SOUL.md` for the system prompt and the optional `VISION.md` for the image
+description request. No environment variable is ever read
+(`src/config.rs:1-25`). The files are loaded at startup and reloaded
+automatically when they change (see [Live reload](#live-reload)); only
+`web_port` needs a restart.
 
 ## Data directory
 
@@ -38,10 +48,10 @@ there). It cannot be changed from the config file. At startup
 |---|---|---|
 | `config.toml` | Optional settings | `CONFIG_FILE`, `src/config.rs:54` |
 | `SOUL.md` | System prompt: the responder's identity and rules; required for the AI | `SOUL_FILE`, `src/config.rs:56` |
-| `vision.md` | Optional request sent with each image to obtain its description | `VISION_FILE`, `src/config.rs:58` |
-| `session.db` | WhatsApp session store used by `whatsapp-rust` ([WhatsApp client](../integrations/whatsapp-client.md)) | `session_db_path`, `src/config.rs:317-323` |
-| `history.db` | Local messages, chats, contacts, per-chat autoreply flags, the global autoreply setting and image descriptions ([History store](../concepts/history-store.md)) | `history_db_path`, `src/config.rs:325-331` |
-| `ai_requests.log` | Optional AI request log, written only when `ai_log = true` ([AI client](../integrations/ai-client.md)) | `ai_log_path`, `src/config.rs:333-339` |
+| `VISION.md` | Optional request sent with each image to obtain its description | `VISION_FILE`, `src/config.rs:58` |
+| `session.db` | WhatsApp session store used by `whatsapp-rust` ([WhatsApp client](../integrations/whatsapp-client.md)) | `session_db_path`, `src/config.rs:325-331` |
+| `history.db` | Local messages, chats, contacts, per-chat autoreply flags, the global autoreply setting and image descriptions ([History store](../concepts/history-store.md)) | `history_db_path`, `src/config.rs:333-339` |
+| `ai_requests.log` | Optional AI request log, written only when `ai_log = true` ([AI client](../integrations/ai-client.md)) | `ai_log_path`, `src/config.rs:341-347` |
 
 The two databases are separate by design: `session.db` belongs to the
 WhatsApp library (linked-device session and keys, schema owned by
@@ -82,7 +92,7 @@ its default and a comment (with `web_port` commented out) and points to
 `SOUL.md` for the prompt; copy it to the data directory as `config.toml` and
 fill in `openai_api_key`.
 
-## Prompt files: `SOUL.md` and `vision.md`
+## Prompt files: `SOUL.md` and `VISION.md`
 
 `read_prompt_file` (`src/config.rs:212-223`) reads both files:
 
@@ -103,9 +113,12 @@ factual context to the prompt (chat kind and name, who writes the `user` and
 behaviour — including when to call the `no_reply` tool and stay silent —
 belongs in `SOUL.md`.
 
-`vision.md` becomes `Config::image_description_prompt`; when it is missing or
+`VISION.md` becomes `Config::image_description_prompt`; when it is missing or
 blank the [AI client](../integrations/ai-client.md) uses its built-in Italian
-description request.
+description request. The vision model receives only this request and the
+image, never the conversation, so tone and reply style belong in `SOUL.md`.
+The file was previously named `vision.md`: on Linux, where names are
+case-sensitive, an old lowercase file is no longer read and must be renamed.
 
 ## Loading rules for `config.toml`
 
@@ -126,9 +139,38 @@ description request.
   `web_port in config.toml must be between 1 and 65535`.
 
 Startup aborts with `cannot load configuration` on any of these errors
-([Runtime](../architecture/runtime-and-event-loop.md)).
-
-## Build version
+([Runtime](../architecture/runtime-and-event-loop.md)); during a live reload
+the same errors only keep the current configuration.
+
+## Live reload
+
+`main` starts `config_watch::spawn(config.source_files(), app_tx)`.
+`Config::source_files` (`src/config.rs:317-323`) lists `config.toml`,
+`SOUL.md` and `VISION.md` inside the data directory. The watcher
+(`src/config_watch.rs:15-52`) polls their modification times every 2 seconds
+(`POLL_INTERVAL_SECONDS`) instead of using file notifications, so it needs
+no extra dependency and behaves the same on Windows and Linux. A file whose
+metadata cannot be read counts as `None`, so creating or deleting a file is
+also a change. On any change it runs `Config::load` again and sends
+`AppEvent::ConfigReloaded` with the new `Config` or the error text; the task
+ends when the application receiver is dropped.
+
+[`App`](../architecture/app-state.md#configuration-reload) then:
+
+- on success replaces its `Config`, rebuilds the AI client (replies already
+  in flight keep the old one) and shows `Configurazione ricaricata`, or
+  `AI non configurata: ...` / `AI non disponibile: ...` when the new files
+  leave the AI unusable;
+- on error keeps the current configuration and shows
+  `Configurazione non ricaricata: <error>`.
+
+New values of the AI keys, `SOUL.md`, `VISION.md`, `ai_log`,
+`reply_delay_seconds`, `catch_up_seconds` and `ai_retries` therefore apply
+to the next replies without restarting. `web_port` does not: the web panel is
+bound once at startup. Saving a file in several steps can trigger more than
+one reload; each one reloads the full set of files.
+
+## Build version and profile
 
 `wh --version` (or `-V`) prints `wh <version> (<short git commit>)` and
 exits without reading any file; the same text is shown at the bottom right
@@ -136,6 +178,14 @@ of the main screen. The commit hash is embedded at build time by `build.rs`
 and is `unknown` when built without git (see
 [Runtime and event loop](../architecture/runtime-and-event-loop.md)).
 
+The release profile only strips symbols (`Cargo.toml:31-33`); LTO and a
+single codegen unit are no longer used, which keeps release rebuilds fast.
+TLS for the AI client uses `ring` only: `reqwest` is built with
+`rustls-no-provider` and the `ring` provider is installed when the
+[AI client](../integrations/ai-client.md) is built, so the large `aws-lc-sys`
+C build is not needed, which matters for the Linux musl cross-compile with
+`cargo-zigbuild` described in `README.md`.
+
 ## Security and operational notes
 
 - The API key is never printed: `Config`'s `Debug` shows `***` or `<unset>`
@@ -148,16 +198,22 @@ and is `unknown` when built without git (see
   Enable `ai_log` only for diagnosis and delete the file afterwards.
 - `web_port` exposes an unauthenticated HTTP panel on every network
   interface: anyone who can reach the port can see chat names, toggle
-  autoreplies and quit the bot. Set it only on a trusted network or
+  autoreplies, quit the bot, unlink the WhatsApp device and, while pairing,
+  scan the QR code. Set it only on a trusted network or
   firewall the port. A port already in use aborts startup with
   `cannot start web server on port <port>`.
-- Keep `config.toml` and `SOUL.md` in the data directory, outside the
-  repository. `.gitignore` ignores a root-level `/config.toml`, `.env`,
-  any `SOUL.md` and `*.log` files (`.gitignore:4-18`), as well as SQLite
+- Keep `config.toml`, `SOUL.md` and `VISION.md` in the data directory,
+  outside the repository; the two prompt files are personal. `.gitignore`
+  ignores a root-level `/config.toml`, `.env`, any `SOUL.md`, any
+  `VISION.md` and `*.log` files (`.gitignore:4-19`), as well as SQLite
   journal/WAL/SHM files; note that the `*.db` pattern is commented out
   (`.gitignore:7`), so databases copied into the repo would not be ignored.
-- `session.db` holds the linked-device credentials: deleting it forces a new
-  QR pairing (copying it to another machine reuses the linked session).
+- `session.db` holds the linked-device credentials. Copying it to another
+  machine reuses the linked session, but never run both copies at the same
+  time. To pair again without restarting, press F8 twice or use the web
+  panel's "Nuovo QR": the device is logged out and `session.db` is deleted
+  or emptied in place (see
+  [WhatsApp relink and pairing](../workflows/relink-and-pairing.md)).
   `history.db` can be deleted to reset local history, image descriptions and
   the per-chat and global autoreply flags; it is recreated on start.
 - Images downloaded for description are kept in memory only and never
@@ -166,7 +222,7 @@ and is `unknown` when built without git (see
 
 ## Tests
 
-`src/config.rs:342+` covers defaults, full files, the ignored legacy
+`src/config.rs:350+` covers defaults, full files, the ignored legacy
 `system_prompt` key, blank values, trimming, base URL normalisation and
 errors, `Debug` masking, vision model, DB and AI log paths, `ai_log`,
 `reply_delay_seconds` (default, custom values including `0`, negatives
@@ -174,5 +230,6 @@ rejected, legacy cooldown keys ignored), catch-up and `ai_retries` defaults
 and negatives, `web_port` (unset, valid, `0` and out-of-range rejected),
 unknown keys, BOM/CRLF, non-leaking parse errors, missing, existing, invalid
 and unreadable files, and `SOUL.md` reading (missing, blank, kept as written
-without BOM, non-UTF-8 error naming the path). See
-[Unit tests](../testing/unit-tests.md).
+without BOM, non-UTF-8 error naming the path). `src/config_watch.rs` checks
+that modification-time snapshots change when a watched file is created and
+deleted. See [Unit tests](../testing/unit-tests.md).

+ 1 - 1
openwiki/operations/index.md

@@ -1,3 +1,3 @@
 # Files
 
-- [Configuration and data files](configuration-and-data.md) - How src/config.rs loads config.toml, the system prompt from SOUL.md and the optional image description request from vision.md, the supported keys and defaults (reply_delay_seconds, catch_up_seconds, ai_retries, web_port), the template files, where the data directory and databases live, the build version, and operational and security caveats.
+- [Configuration and data files](configuration-and-data.md) - How src/config.rs loads config.toml, the system prompt from SOUL.md and the optional image description request from VISION.md, how src/config_watch.rs reloads them live every 2 seconds, the supported keys and defaults (reply_delay_seconds, catch_up_seconds, ai_retries, web_port), the template files, where the data directory and databases live, the build version and release profile, and operational and security caveats.

+ 38 - 26
openwiki/quickstart.md

@@ -1,8 +1,8 @@
 ---
 type: overview
 title: Quickstart
-description: What the wh WhatsApp terminal client with per-chat and global AI autoreply (system prompt in SOUL.md, reply delay, image descriptions) and optional web control panel does, how to build and run it, and where to find each topic in this wiki.
-tags: [overview, quickstart, rust, whatsapp, tui, ai, web-panel]
+description: What the wh WhatsApp terminal client with per-chat and global AI autoreply (system prompt in SOUL.md, reply delay, image descriptions), live configuration reload, re-pairing with F8 and optional web control panel does, how to build and run it, and where to find each topic in this wiki.
+tags: [overview, quickstart, rust, whatsapp, tui, ai, web-panel, pairing]
 sources:
   - id: openwiki-source-651d1fb6c9e49916a916ab51
     resource: repo://Cargo.toml
@@ -14,10 +14,10 @@ sources:
     resource: repo://src/config.rs
   - id: openwiki-source-b55a21a31ede1b56cd31a6a6
     resource: repo://src/main.rs
-generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" }
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
 verified:
   - by: openwiki/0.7.2
-    at: 2026-10-21T21:49:12.291Z
+    at: 2026-10-23T21:26:24.156Z
 ---
 
 # Quickstart
@@ -29,7 +29,8 @@ SQLite and can answer incoming messages automatically with an
 OpenAI-compatible model — chat by chat or for all chats at once — in private
 chats and in groups (broadcasts, status and newsletters excluded). An
 optional [web control panel](integrations/web-control-panel.md) lets a
-browser toggle the autoreplies and stop the bot.
+browser toggle the autoreplies, pair WhatsApp from the QR shown in the page,
+request a new pairing and stop the bot.
 
 ## Build and run
 
@@ -41,23 +42,30 @@ cargo test
 
 The crate uses edition 2024. `whatsapp-rust` is built with
 `default-features = false` because its default `simd` feature needs nightly
-(`Cargo.toml:8-10`). The release profile strips symbols and uses LTO with a
-single codegen unit (`Cargo.toml:27-33`). `[lints.rust] linker_messages =
-"allow"` (`Cargo.toml:35-37`) silences the informational MSVC linker line
-printed because the binary exports symbols.
+(`Cargo.toml:8-10`). TLS uses `ring` only: `reqwest` is built with
+`rustls-no-provider` and the `ring` provider is installed when the AI client
+is built, avoiding the large `aws-lc-sys` C build (`Cargo.toml:13-16`); this
+keeps the Linux musl cross-compile with `cargo-zigbuild` (see `README.md`)
+fast. The release profile only strips symbols, without LTO
+(`Cargo.toml:31-33`). `[lints.rust] linker_messages = "allow"`
+(`Cargo.toml:35-37`) silences the informational MSVC linker line printed
+because the binary exports symbols.
 
 `wh --version` (or `-V`) prints `wh <version> (<git commit>)` and exits; the
 same version is shown at the bottom right of the main screen
-(`src/main.rs:24-41`, `build.rs`).
+(`src/main.rs:30-48`, `build.rs`).
 
 On first start the app shows a QR code: scan it from WhatsApp → *Linked
 devices*. The session is kept in `session.db`, so later starts connect
-directly. Main keys: `Tab` switches list/input, `Enter` opens a chat or
-sends, typing in the list filters it, `PgUp`/`PgDn`/`Home`/`End` move
-through the list, `F2` chats/contacts, `F3` toggles
-autoreply for the open chat, `F4` toggles the global autoreply ("AI tutte"),
-`Canc` twice deletes the selected chat locally, `F10` quits
-([key bindings](architecture/app-state.md#key-bindings)).
+directly; `F8` twice (on any screen) or the web panel's "Nuovo QR" unlinks
+the device, empties `session.db` and shows a new QR without restarting
+([WhatsApp relink and pairing](workflows/relink-and-pairing.md)). Main keys:
+`Tab` switches list/input, `Enter` opens a chat or sends, typing in the list
+filters it, `PgUp`/`PgDn`/`Home`/`End` move through the list, `F2`
+chats/contacts, `F3` toggles autoreply for the selected chat (list focus) or
+the open chat (input focus), `F4` toggles the global autoreply ("AI tutte"),
+`Canc` twice deletes the selected chat locally, `F10` quits and clears the
+screen ([key bindings](architecture/app-state.md#key-bindings)).
 
 ## Configuration in short
 
@@ -67,8 +75,11 @@ Settings live in `config.toml` inside the data directory
 (`src/config.rs:46-58`, `src/config.rs:160-168`); the repository's
 `config.toml.global.template` lists every key. The system prompt is not a
 key: it is the file `SOUL.md` in the same folder (start from
-`soul.md.template`), and the optional `vision.md` replaces the built-in
-request used to describe images. The two databases are separate by design:
+`soul.md.template`), and the optional `VISION.md` replaces the built-in
+request used to describe images. The three files are checked every 2
+seconds and reloaded when they change, without restarting; only `web_port`
+needs a restart
+([live reload](operations/configuration-and-data.md#live-reload)). The two databases are separate by design:
 `session.db` is the WhatsApp library's session and key store, `history.db`
 holds the app's chats, messages, autoreply flags and image descriptions.
 Without `openai_api_key` and a non-blank `SOUL.md` the app still runs as a
@@ -94,21 +105,22 @@ by `SOUL.md`. Details: [AI auto-reply workflow](workflows/ai-autoreply.md).
 
 `main` prints the version and exits for `--version`; otherwise it loads the
 config, opens the history, starts the WhatsApp client, the optional web
-panel and an input thread, then runs the draw/receive loop until quit
-(`src/main.rs:36-64`, see
+panel, the configuration watcher and an input thread, then runs the
+draw/receive loop until quit (`src/main.rs:35-71`, see
 [Runtime and event loop](architecture/runtime-and-event-loop.md)).
 
 ## Where to look
 
 | Task | Page | Source |
 |---|---|---|
-| Startup, version flag, channels, threads, main loop | [Runtime and event loop](architecture/runtime-and-event-loop.md) | `src/main.rs`, `build.rs` |
-| Events, state, keys, filter, chat deletion, reply orchestration, background tasks | [Application state](architecture/app-state.md) | `src/app.rs` |
+| Startup, version flag, channels, threads, config watcher, main loop | [Runtime and event loop](architecture/runtime-and-event-loop.md) | `src/main.rs`, `src/config_watch.rs`, `build.rs` |
+| Events, state, keys, filter, chat deletion, config reload, relink, reply orchestration, background tasks | [Application state](architecture/app-state.md) | `src/app.rs` |
 | Screens, rendering, message formatting, footer version | [Terminal UI](architecture/terminal-ui.md) | `src/ui.rs` |
-| WhatsApp session, events, JIDs, media | [WhatsApp client](integrations/whatsapp-client.md) | `src/whatsapp.rs` |
+| WhatsApp session, relink, events, JIDs, media | [WhatsApp client](integrations/whatsapp-client.md) | `src/whatsapp.rs` |
 | AI HTTP requests, `no_reply` tool, image descriptions, retries, errors | [AI client](integrations/ai-client.md) | `src/ai.rs` |
-| Web control panel: routes, security, browser page | [Web control panel](integrations/web-control-panel.md) | `src/web.rs`, `src/web_page.html` |
+| Web control panel: routes, security, pairing QR, browser page | [Web control panel](integrations/web-control-panel.md) | `src/web.rs`, `src/web_page.html` |
 | When and how the AI replies (global/per-chat, groups, reply delay, owner-answered drop, images, catch-up, context) | [AI auto-reply workflow](workflows/ai-autoreply.md) | `src/autoreply.rs`, `src/app.rs` |
+| Pairing, F8 / "Nuovo QR" relink, session reset, new QR in TUI and web | [WhatsApp relink and pairing](workflows/relink-and-pairing.md) | `src/app.rs`, `src/whatsapp.rs`, `src/web.rs` |
 | Local SQLite messages, chats, contacts, settings, image descriptions | [History store](concepts/history-store.md) | `src/history.rs` |
-| Config keys, `SOUL.md` / `vision.md`, data dir, security notes | [Configuration and data](operations/configuration-and-data.md) | `src/config.rs` |
-| Test suites and how to run them | [Unit tests](testing/unit-tests.md) | `src/*.rs` |
+| Config keys, `SOUL.md` / `VISION.md`, live reload, data dir, build profile, security notes | [Configuration and data](operations/configuration-and-data.md) | `src/config.rs`, `src/config_watch.rs` |
+| Test suites, the ignored live relink test and how to run them | [Unit tests](testing/unit-tests.md) | `src/*.rs` |

+ 1 - 1
openwiki/testing/index.md

@@ -1,3 +1,3 @@
 # Files
 
-- [Unit tests](unit-tests.md) - Overview of the in-module unit test suites in src/*.rs (including the web panel and the version flag), what each one covers, how they isolate files, and how to run them.
+- [Unit tests](unit-tests.md) - Overview of the in-module unit test suites in src/*.rs (including the web panel, the version flag and the configuration watcher), the ignored live relink test, what each one covers, how they isolate files, and how to run them.

+ 39 - 20
openwiki/testing/unit-tests.md

@@ -1,11 +1,8 @@
 ---
 type: testing
 title: Unit tests
-description: Overview of the in-module unit test suites in src/*.rs (including the web panel and the version flag), what each one covers, how they isolate files, and how to run them.
+description: Overview of the in-module unit test suites in src/*.rs (including the web panel, the version flag and the configuration watcher), the ignored live relink test, what each one covers, how they isolate files, and how to run them.
 tags: [testing, cargo, unit-tests]
-verified:
-  - by: openwiki/0.7.2
-    at: 2026-10-21T21:49:12.291Z
 sources:
   - id: openwiki-source-651d1fb6c9e49916a916ab51
     resource: repo://Cargo.toml
@@ -15,6 +12,8 @@ sources:
     resource: repo://src/app.rs
   - id: openwiki-source-043e6252badc57a1ee6bfb48
     resource: repo://src/autoreply.rs
+  - id: openwiki-source-d6b2055bbb49804b8a855fd2
+    resource: repo://src/config_watch.rs
   - id: openwiki-source-2a737474d86fc75cc9d9694f
     resource: repo://src/config.rs
   - id: openwiki-source-ce0c4ea8cd3b15431fa691ce
@@ -27,15 +26,19 @@ sources:
     resource: repo://src/web.rs
   - id: openwiki-source-7ea5eac45e5030178a6ea05b
     resource: repo://src/whatsapp.rs
-generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" }
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
+verified:
+  - by: openwiki/0.7.2
+    at: 2026-10-23T21:26:24.156Z
 ---
 
 # Unit tests
 
 All tests are plain Rust unit tests in `#[cfg(test)] mod tests` blocks at the
-bottom of each source file. There are no integration tests, no
-`[dev-dependencies]` (`Cargo.toml` only has `[dependencies]`) and no test
-touches the network, binds a port or uses a real WhatsApp session.
+bottom of each source file. There are no integration tests and no
+`[dev-dependencies]` (`Cargo.toml` only has `[dependencies]`). Apart from one
+`#[ignore]`d live test (see below), no test touches the network, binds a port
+or uses a real WhatsApp session.
 
 ## Running
 
@@ -43,23 +46,36 @@ touches the network, binds a port or uses a real WhatsApp session.
 cargo test                        # whole suite
 cargo test autoreply::            # one module
 cargo test should_schedule        # by name filter
+cargo test -- --ignored           # only the ignored live test (needs network)
 ```
 
+The live test `whatsapp::tests::relink_deletes_the_session_and_offers_a_new_qr`
+(`src/whatsapp.rs:1163-1183`) is skipped by a plain `cargo test`. It starts a
+real client on a per-process `target/wh-relink-test-<pid>.db`, waits up to
+60 s for a pairing QR from the WhatsApp servers, calls `WaHandle::relink`,
+waits for the next QR and asserts that the device keys in the two QR
+payloads differ (a new device). It finally stops the session and empties the
+test database, because on Windows the file stays open until the process
+exits. See [WhatsApp relink and pairing](../workflows/relink-and-pairing.md).
+
 ## Suites
 
 | Module | Tests | Location | Covers |
 |---|---|---|---|
 | `autoreply` | 55 | `src/autoreply.rs:505+` | private/group/self-chat detection, `supports_autoreply` (groups yes, broadcasts and newsletters no), `ReplyGate::should_schedule` rules (disabled, own messages, self-chat, backlog, blank/placeholder text, voice notes and images trigger, pending chat), pending tracking and `take_pending`, `answered_by_owner`, `catch_up_due` rules, `build_turns` (system turn naming private chat, group or self-chat, roles, `is_ai` in the self-chat, context cap, blank texts skipped, voice notes, stored image descriptions vs. unseen images, assistant turns untouched, group sender prefixes), `context_start`, `mark_own_mentions` |
-| `config` | 37 | `src/config.rs:342+` | defaults, full files, ignored legacy `system_prompt` key, blank values, trimming, base URL normalisation, `Debug` masking, DB and AI log paths, `ai_log`, `reply_delay_seconds` (default, custom and `0`, negatives rejected, legacy cooldown keys ignored), catch-up and `ai_retries` defaults/custom values/negative rejection, `web_port` (unset, valid, `0` and out of range rejected), BOM/CRLF, non-leaking parse errors, missing/invalid/unreadable files, `SOUL.md` reading (missing, blank, as written without BOM, non-UTF-8 error naming the path) |
-| `ai` | 27 | `src/ai.rs:576+` | text request body with the `no_reply` tool, image description body (no tools) and its `vision.md`/default request, image validation limits, `describe_image` errors, `Debug` redaction, reply parsing (`no_reply` with or without text, other tool calls ignored, missing choices/content), endpoint construction, AI log records (no key, images redacted, `no_reply` record), logging only when enabled, append-only log writes, retry policy (`is_retryable`, `StatusError` message, linear `retry_delay`), truncation |
-| `whatsapp` | 22 | `src/whatsapp.rs:965+` | fatal-only app state sync reporting, message rendering and placeholders, captions, image references, voice/image detection, silent messages, JID device suffix, push-name filtering |
-| `app` | 20 | `src/app.rs:1443+` | autoreply enable rules (private, self-chat LID, groups; broadcasts, newsletters and missing AI refused), `web_chats` filtering and order, vision applicability, `select_images_to_describe` (last five in arrival order, known and out-of-context images skipped, nothing without vision), `arrived_after`, `manual_reply_after`, `description_failure_status`, list filter matching, selection, page steps and toggle helpers, AltGr handling, fixed function-key codes |
+| `config` | 37 | `src/config.rs:350+` | defaults, full files, ignored legacy `system_prompt` key, blank values, trimming, base URL normalisation, `Debug` masking, DB and AI log paths, `ai_log`, `reply_delay_seconds` (default, custom and `0`, negatives rejected, legacy cooldown keys ignored), catch-up and `ai_retries` defaults/custom values/negative rejection, `web_port` (unset, valid, `0` and out of range rejected), BOM/CRLF, non-leaking parse errors, missing/invalid/unreadable files, `SOUL.md` reading (missing, blank, as written without BOM, non-UTF-8 error naming the path) |
+| `ai` | 27 | `src/ai.rs:581+` | text request body with the `no_reply` tool, image description body (no tools) and its `VISION.md`/default request, image validation limits, `describe_image` errors, `Debug` redaction, reply parsing (`no_reply` with or without text, other tool calls ignored, missing choices/content), endpoint construction, AI log records (no key, images redacted, `no_reply` record), logging only when enabled, append-only log writes, retry policy (`is_retryable`, `StatusError` message, linear `retry_delay`), truncation |
+| `whatsapp` | 22 + 1 ignored | `src/whatsapp.rs:1140+` | ignored live relink test (new QR with new device keys), fatal-only app state sync reporting, message rendering and placeholders, captions, image references, voice/image detection, silent messages, JID device suffix, push-name filtering |
+| `app` | 20 | `src/app.rs:1596+` | autoreply enable rules (private, self-chat LID, groups; broadcasts, newsletters and missing AI refused), `web_chats` filtering and order, vision applicability, `select_images_to_describe` (last five in arrival order, known and out-of-context images skipped, nothing without vision), `arrived_after`, `manual_reply_after`, `description_failure_status`, list filter matching, selection, page steps and toggle helpers, AltGr handling, fixed function-key codes |
 | `history` | 18 | `src/history.rs:543+` | dedupe by message id, ordering and limits, `messages_since` (boundary, limit keeps most recent, ascending order), chat name fallbacks, per-chat and global autoreply flags, `message_exists`, image descriptions (per chat, replaced, deleted with the chat), `delete_chat`, contacts, on-disk open |
-| `ui` | 15 | `src/ui.rs:575+` | footer key help (F4 highlight), version label, sender labels, clock time, QR text, wrapping, visible lines, input tail, messages title, list title (global marker, filter), selection clamp |
-| `web` | 9 | `src/web.rs:366+` | query stripping, route resolution, `X-Wh-Request` header check (any case, missing/wrong value), which routes require it, request body trimming, empty/invalid UTF-8 rejection and the 1024-byte limit |
-| `main` | 2 | `src/main.rs:186+` | only `--version` / `-V` request the version; `BUILD_VERSION` starts with the package version and names a commit |
+| `ui` | 15 | `src/ui.rs:579+` | footer key help (F4 highlight), version label, sender labels, clock time, QR text, wrapping, visible lines, input tail, messages title, list title (global marker, filter), selection clamp |
+| `web` | 11 | `src/web.rs:411+` | query stripping, route resolution (including `POST /api/relink`, and `GET /api/relink` not found), `X-Wh-Request` header check (any case, missing/wrong value), which routes require it (relink included), `qr_svg` output is an SVG document, request body trimming, empty/invalid UTF-8 rejection and the 1024-byte limit |
+| `main` | 2 | `src/main.rs:207+` | only `--version` / `-V` request the version; `BUILD_VERSION` starts with the package version and names a commit |
+| `config_watch` | 1 | `src/config_watch.rs:54+` | modification-time snapshots are `None` for a missing file and change when the file is created and deleted |
 
-Total: 205 `#[test]` functions.
+Total: 209 test functions — 208 `#[test]` run by `cargo test` plus the
+ignored `#[tokio::test]` live relink test. Relink, configuration reload and
+F3 targeting have no tests at the `App` level.
 
 ## Test isolation
 
@@ -68,12 +84,15 @@ Total: 205 `#[test]` functions.
   `std::env::temp_dir()` in a per-process directory (`src/history.rs:837-852`).
 - AI tests check request bodies, parsing, log records and the retry policy
   without sending requests; the append test writes a per-process file under
-  `std::env::temp_dir()` and removes it (`src/ai.rs:856-868`).
+  `std::env::temp_dir()` and removes it (`src/ai.rs:861-873`).
 - Config tests that read `SOUL.md`-style prompt files write per-process
   scratch files under the `target` directory and remove them
-  (`scratch_path`, `src/config.rs:349-375`).
-- Web tests exercise the pure routing and parsing helpers; the HTTP server
-  itself is never started.
+  (`scratch_path`, `src/config.rs:357-383`). The `config_watch` test does the
+  same with a per-process file under `target` (`src/config_watch.rs:54-73`).
+- Web tests exercise the pure routing, parsing and QR rendering helpers; the
+  HTTP server itself is never started.
+- The live relink test writes its session database under `target` and is
+  ignored by default because it needs the WhatsApp servers.
 
 Pages with module details: [AI autoreply](../workflows/ai-autoreply.md),
 [Configuration](../operations/configuration-and-data.md),

+ 52 - 36
openwiki/workflows/ai-autoreply.md

@@ -1,21 +1,23 @@
 ---
 type: workflow
 title: AI auto-reply workflow
-description: End-to-end trace of the AI autoreply — enabling it per chat (F3) or globally (F4 / web panel) for private chats and groups, the ReplyGate rules, the reply delay that batches messages into one reply, the drop when the owner answered by hand, image descriptions stored once and reused, the startup catch-up, building the AI context from SOUL.md, the no_reply tool, retries and sending the reply.
+description: End-to-end trace of the AI autoreply — enabling it per chat (F3 on the selected or open chat) or globally (F4 / web panel) for private chats and groups, the ReplyGate rules, the reply delay that batches messages into one reply, the drop when the owner answered by hand, image descriptions stored once and reused, the startup catch-up, building the AI context from SOUL.md, the no_reply tool, retries and sending the reply.
 tags: [ai, autoreply, workflow, vision, reply-gate, reply-delay, catch-up, image-description]
-verified:
-  - by: openwiki/0.7.2
-    at: 2026-10-21T21:49:12.291Z
 sources:
   - id: openwiki-source-9fec9d1af408501c9c455161
     resource: repo://src/app.rs
   - id: openwiki-source-043e6252badc57a1ee6bfb48
     resource: repo://src/autoreply.rs
+  - id: openwiki-source-d6b2055bbb49804b8a855fd2
+    resource: repo://src/config_watch.rs
   - id: openwiki-source-2a737474d86fc75cc9d9694f
     resource: repo://src/config.rs
   - id: openwiki-source-ce0c4ea8cd3b15431fa691ce
     resource: repo://src/history.rs
-generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" }
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
+verified:
+  - by: openwiki/0.7.2
+    at: 2026-10-23T21:26:24.156Z
 ---
 
 # AI auto-reply workflow
@@ -51,21 +53,23 @@ WaEvent::OfflineSyncCompleted (first time only)
 ## 1. Enabling
 
 There are two independent switches, and a chat is **enabled** when either is
-on (`autoreply_enabled`, `src/app.rs:521-527`):
+on (`autoreply_enabled`, `src/app.rs:638-644`):
 
-- **Per chat** — F3 toggles the flag of the open chat (`toggle_autoreply` /
-  `toggle_chat_autoreply`, `src/app.rs:976-1007`); the
+- **Per chat** — F3 toggles the flag of the focused chat: the selected row
+  of the (filtered) list when the list has focus, the open chat when the
+  input has focus (`toggle_autoreply` / `toggle_chat_autoreply`,
+  `src/app.rs:1101-1137`), so a chat can be enabled without opening it; the
   [web control panel](../integrations/web-control-panel.md) can toggle any
   listed chat the same way. Turning it off is always allowed; turning it on
   requires a configured AI (API key in `config.toml` and a non-blank
   `SOUL.md`, see [Configuration](../operations/configuration-and-data.md))
   and a chat that supports autoreply (`can_enable_autoreply`,
-  `src/app.rs:1403-1416`), otherwise the status shows
+  `src/app.rs:1544-1557`), otherwise the status shows
   `Autoreply disponibile solo nelle chat private e nei gruppi`. The flag is
   persisted in the `chats` table of the
   [history store](../concepts/history-store.md) (default off).
 - **Global ("AI tutte")** — F4 or the web panel toggles
-  `global_autoreply` (`toggle_global_autoreply`, `src/app.rs:1052-1069`);
+  `global_autoreply` (`toggle_global_autoreply`, `src/app.rs:1186-1203`);
   enabling requires an AI client. It is persisted in the `settings` table,
   loaded at startup and does not change any per-chat flag, so turning it off
   restores the per-chat selection. The TUI marks it in the list title and
@@ -75,6 +79,17 @@ The global switch does not bypass the chat-type rule: `meets_content_rules`
 still requires a supported chat, so broadcasts, status and newsletters are
 never answered.
 
+The AI setup itself follows the configuration files live: when
+`config.toml`, `SOUL.md` or `VISION.md` change, the configuration is
+reloaded and the AI client rebuilt within about 2 seconds
+(`apply_reloaded_config`, `src/app.rs:383-400`, see
+[Configuration](../operations/configuration-and-data.md#live-reload)).
+Replies already running keep the previous client; later schedules, contexts
+and requests use the new prompt, models, `reply_delay_seconds`,
+`catch_up_seconds` and `ai_retries`. If the new files leave the AI
+unconfigured, every autoreply path stops as described below, but the
+persisted flags are kept.
+
 `supports_autoreply` (`src/autoreply.rs:99-107`) accepts private chats and
 groups:
 
@@ -87,10 +102,10 @@ groups:
 ## 2. Trigger and ReplyGate rules
 
 Only messages that `History::save_message` reports as new reach
-`maybe_autoreply` (`on_message`, `src/app.rs:333-342`; `maybe_autoreply`,
-`src/app.rs:344-366`), so duplicates and history-sync re-deliveries of
+`maybe_autoreply` (`on_message`, `src/app.rs:450-459`; `maybe_autoreply`,
+`src/app.rs:461-483`), so duplicates and history-sync re-deliveries of
 stored messages never trigger. `maybe_autoreply` needs an AI client and a
-system prompt (`ai_setup`, `src/app.rs:628-634`), refreshes the own JIDs if
+system prompt (`ai_setup`, `src/app.rs:745-751`), refreshes the own JIDs if
 unknown and computes `enabled` (global or per chat).
 
 `ReplyGate::should_schedule(message, enabled)` (`src/autoreply.rs:182-191`)
@@ -114,19 +129,19 @@ chats; mentions and quotes no longer change anything.
 
 ## 3. Reply delay and batching
 
-When `should_schedule` passes, `schedule_reply` (`src/app.rs:368-376`) marks
+When `should_schedule` passes, `schedule_reply` (`src/app.rs:485-493`) marks
 the chat pending in the gate (`mark_pending`, `src/autoreply.rs:202-210`)
-and `spawn_reply_timer` (`src/app.rs:1199-1209`) emits
+and `spawn_reply_timer` (`src/app.rs:1340-1350`) emits
 `AppEvent::ReplyDue { chat_jid }` after `reply_delay_seconds` (default 30;
 `0` answers at once). The chat stays pending until `finish_reply`, so every
 message arriving during the wait — and while the AI is generating — gets no
 timer of its own: the reply is built from the context loaded when it starts,
 so it covers everything received during the wait.
 
-`handle_reply_due` (`src/app.rs:417-428`) takes the images kept for the chat
+`handle_reply_due` (`src/app.rs:534-545`) takes the images kept for the chat
 and, if the chat is still pending (a local deletion removes the pending
-mark), calls `prepare_reply` (`src/app.rs:430-451`). Its `reply_context`
-(`src/app.rs:529-547`) drops the reply — and `finish_reply` ends the pending
+mark), calls `prepare_reply` (`src/app.rs:547-568`). Its `reply_context`
+(`src/app.rs:646-664`) drops the reply — and `finish_reply` ends the pending
 state — when:
 
 - the autoreply was turned off (both switches) during the wait;
@@ -141,10 +156,10 @@ Otherwise it describes the new images first (see §5) or calls
 
 ### After the reply
 
-`AiReply` is always followed by `finish_reply` (`src/app.rs:499-519`), which
+`AiReply` is always followed by `finish_reply` (`src/app.rs:616-636`), which
 clears the pending state and the `answered_up_to` snapshot. If a reply was
 started and the latest stored message is a different one
-(`arrived_after`, `src/app.rs:1307-1314`) that itself passes
+(`arrived_after`, `src/app.rs:1448-1455`) that itself passes
 `should_schedule`, a **new** reply is scheduled (with a fresh delay, keeping
 the images received meanwhile); otherwise the kept images are dropped. So
 messages that arrive while a reply is being generated are answered once,
@@ -156,10 +171,10 @@ Messages received while the app was off are delivered on connection and are
 older than `started_at`, so the live rules treat them as backlog. When
 WhatsApp signals that the offline queue has been delivered
 (`WaEvent::OfflineSyncCompleted`), `on_offline_sync_completed`
-(`src/app.rs:567-577`) runs `catch_up` once per process (`catch_up_done`;
+(`src/app.rs:684-694`) runs `catch_up` once per process (`catch_up_done`;
 later reconnections skip it).
 
-`catch_up` / `catch_up_chats` (`src/app.rs:579-626`) look at every enabled
+`catch_up` / `catch_up_chats` (`src/app.rs:696-743`) look at every enabled
 chat — all chats when the global autoreply is on, otherwise those with the
 per-chat flag — and take its last stored message.
 `ReplyGate::catch_up_due` (`src/autoreply.rs:267-290`) accepts it only when:
@@ -176,28 +191,29 @@ the reply delay, together with the images of its offline messages
 (`backlog_images`); offline images of other chats are dropped. When at least
 one reply went ahead the status shows `Risposte di recupero: N`. Offline
 images that arrive after the catch-up already ran are described on their own,
-without a reply (`describe_late_image`, `src/app.rs:400-415`).
+without a reply (`describe_late_image`, `src/app.rs:517-532`).
 
 ## 5. Image descriptions
 
 Images are never sent to the text model. Each image sent by an allowed
-sender in an enabled chat is kept by `keep_image` (`src/app.rs:378-398`) and,
+sender in an enabled chat is kept by `keep_image` (`src/app.rs:495-515`) and,
 just before the reply that covers it, described **once** by the vision model
-(`AiClient::describe_image`, request text from `vision.md` or the built-in
-one, see [AI client](../integrations/ai-client.md)). The description is
+(`AiClient::describe_image`, request text from `VISION.md` or the built-in
+one, see [AI client](../integrations/ai-client.md)). The vision model sees
+only that request and the image, never the conversation. The description is
 stored in the `image_descriptions` table and reused in every later context.
 
-- `select_images_to_describe` (`src/app.rs:1265-1292`) keeps only the images
+- `select_images_to_describe` (`src/app.rs:1406-1433`) keeps only the images
   whose message is in the reply context and that have neither a stored
   description nor one in progress (`describing`), and at most the **5 most
   recent** (`MAX_IMAGES_PER_REPLY`); older images of the same batch are
   dropped for good, to cap the cost of albums. Without `openai_vision_model`
   nothing is described (`vision_applies`).
-- `spawn_image_descriptions` (`src/app.rs:1231-1263`) describes all selected
+- `spawn_image_descriptions` (`src/app.rs:1372-1404`) describes all selected
   images in parallel. Each download (`download_image_with_retry`,
-  `src/app.rs:1336-1362`) makes up to 3 attempts, each abandoned after 60 s,
+  `src/app.rs:1477-1503`) makes up to 3 attempts, each abandoned after 60 s,
   waiting 2 s × attempt between them; the bytes stay in memory.
-- `handle_images_described` (`src/app.rs:465-497`) stores each description
+- `handle_images_described` (`src/app.rs:582-614`) stores each description
   whose message still exists (`History::message_exists`), so a chat deleted
   meanwhile gets no orphan rows. Failed images stay without description, the
   others are unaffected, and the status shows
@@ -206,7 +222,7 @@ stored in the `image_descriptions` table and reused in every later context.
 
 ## 6. Building the context
 
-`start_reply` (`src/app.rs:645-673`) loads the context with
+`start_reply` (`src/app.rs:762-790`) loads the context with
 `History::messages_since(chat_jid, context_start(now), CONTEXT_MESSAGES)`:
 
 - `context_start` (`src/autoreply.rs:459-468`) is local midnight of
@@ -251,7 +267,7 @@ descriptions:
 
 ## 7. Request, `no_reply` and retries
 
-`spawn_ai_request` (`src/app.rs:1211-1229`) calls `AiClient::complete`, which
+`spawn_ai_request` (`src/app.rs:1352-1370`) calls `AiClient::complete`, which
 offers the model a single parameterless tool, `no_reply`. When the model
 calls it, the outcome is `Reply::NoReply`: nothing is sent and the status
 shows `AI: nessuna risposta necessaria`. The app never says when to use it —
@@ -270,18 +286,18 @@ pending. When `ai_log = true`, every attempt is also appended to
 ## 8. Sending
 
 A text reply is handled by `send_ai_reply_if_enabled`
-(`src/app.rs:703-722`), which re-checks two things:
+(`src/app.rs:820-839`), which re-checks two things:
 
 - `autoreply_enabled`: if both the global and the per-chat switch are off by
   then, the reply is discarded with status
   `Risposta AI scartata: autoreply disattivato`;
-- `owner_answered_meanwhile` (`src/app.rs:724-735`): if a hand-written owner
+- `owner_answered_meanwhile` (`src/app.rs:841-852`): if a hand-written owner
   message follows the last message of the reply context
   (`manual_reply_after`), the reply is discarded with
   `Risposta AI scartata: hai già risposto`.
 
 Otherwise the text is sent with `spawn_send(..., is_ai = true)`; on success
-`handle_sent` (`src/app.rs:737-765`) stores it as an own message with
+`handle_sent` (`src/app.rs:854-882`) stores it as an own message with
 `is_ai = true`, which keeps it from re-triggering the self-chat and from
 counting as a manual answer. Errors appear in the status line
 (`Errore AI: ...`, `Invio fallito: ...`).
@@ -291,6 +307,6 @@ counting as a manual answer. Errors appear in the status line
 The gate rules, pending tracking, catch-up, mention marking and turn building
 are covered by `src/autoreply.rs:505+`; enable rules, `web_chats`, image
 selection, `arrived_after`, `manual_reply_after` and the description failure
-status by `src/app.rs:1443+`; image descriptions and the global flag storage
+status by `src/app.rs:1596+`; image descriptions and the global flag storage
 by `src/history.rs`; the `no_reply` tool, descriptions and the retry policy by
 `src/ai.rs`. See [Unit tests](../testing/unit-tests.md).

+ 2 - 1
openwiki/workflows/index.md

@@ -1,3 +1,4 @@
 # Files
 
-- [AI auto-reply workflow](ai-autoreply.md) - End-to-end trace of the AI autoreply — enabling it per chat (F3) or globally (F4 / web panel) for private chats and groups, the ReplyGate rules, the reply delay that batches messages into one reply, the drop when the owner answered by hand, image descriptions stored once and reused, the startup catch-up, building the AI context from SOUL.md, the no_reply tool, retries and sending the reply.
+- [AI auto-reply workflow](ai-autoreply.md) - End-to-end trace of the AI autoreply — enabling it per chat (F3 on the selected or open chat) or globally (F4 / web panel) for private chats and groups, the ReplyGate rules, the reply delay that batches messages into one reply, the drop when the owner answered by hand, image descriptions stored once and reused, the startup catch-up, building the AI context from SOUL.md, the no_reply tool, retries and sending the reply.
+- [WhatsApp relink and pairing](relink-and-pairing.md) - End-to-end trace of pairing wh as a WhatsApp linked device and of re-pairing without restarting — F8 twice in the TUI or "Nuovo QR" in the web panel, App::start_relink, WaHandle::relink (logout, stop, session.db deletion or in-place reset, new unpaired client), the new QR on the TUI pairing screen and in the web pairing card, failure behaviour and tests.

+ 184 - 0
openwiki/workflows/relink-and-pairing.md

@@ -0,0 +1,184 @@
+---
+type: workflow
+title: WhatsApp relink and pairing
+description: End-to-end trace of pairing wh as a WhatsApp linked device and of re-pairing without restarting — F8 twice in the TUI or "Nuovo QR" in the web panel, App::start_relink, WaHandle::relink (logout, stop, session.db deletion or in-place reset, new unpaired client), the new QR on the TUI pairing screen and in the web pairing card, failure behaviour and tests.
+tags: [whatsapp, pairing, relink, qr-code, session, web-panel, tui]
+verified:
+  - by: openwiki/0.7.2
+    at: 2026-10-23T21:26:24.156Z
+sources:
+  - id: openwiki-source-9fec9d1af408501c9c455161
+    resource: repo://src/app.rs
+  - id: openwiki-source-ac7a3eaa396cc3aa88c00eef
+    resource: repo://src/web_page.html
+  - id: openwiki-source-7b75006986d8b0337adc6ca0
+    resource: repo://src/web.rs
+  - id: openwiki-source-7ea5eac45e5030178a6ea05b
+    resource: repo://src/whatsapp.rs
+generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
+---
+
+# WhatsApp relink and pairing
+
+`wh` is a WhatsApp *linked device*: it pairs by QR code and keeps the device
+credentials in `session.db` (see
+[Configuration and data](../operations/configuration-and-data.md)). Pairing
+happens on first start, and can be redone at any time without restarting the
+process — to link another account, or after the QR codes expired — with
+**F8 pressed twice** in the TUI or the **"Nuovo QR"** button of the
+[web control panel](../integrations/web-control-panel.md).
+
+## Flow
+
+```
+TUI: F8 -> confirm prompt -> F8          Web: "Nuovo QR" -> confirm() -> POST /api/relink
+            (Esc / other key cancels)                         (X-Wh-Request: 1)
+                     \                                         /
+                      v                                       v
+             App::request_relink                   App::handle_web_request(Relink)
+                      \                                       /
+                       +--------> App::start_relink <--------+
+                                   (ignored while relinking)
+                                   screen = Connecting, qr cleared,
+                                   status "Cancellazione della sessione in corso...",
+                                   own_jids_known = false
+                                          |
+                                   tokio task: WaHandle::relink
+                                     1. take session out of the slot
+                                     2. Session::stop: logout, wait <= 10 s, abort translator
+                                     3. reset_session_files: delete session.db (+ -wal, -shm)
+                                          or, if locked, empty its tables in place
+                                     4. open_session: new unpaired client on the same events channel
+                                          |
+                                   AppEvent::Relinked(result) -> App::finish_relink (status)
+                                          |
+                     new client -> WaEvent::Qr -> Screen::Pairing, qr_code stored
+                                          |                                   \
+                     TUI pairing screen (half-block QR)        GET /api/state: screen "pairing",
+                                          |                    qr_svg -> web pairing card
+                     phone scans -> WaEvent::Connected -> Screen::Main, own JIDs refreshed
+```
+
+## 1. Requesting a relink
+
+**TUI.** F8 works on every screen (connecting, pairing and main), so a stuck
+or expired pairing can be restarted from the pairing screen itself, whose key
+help reads `F8: nuovo QR   F10: esci` ([Terminal UI](../architecture/terminal-ui.md)).
+`handle_key` checks F8 right after F10 (`src/app.rs:884-903`).
+`request_relink` (`src/app.rs:356-369`) cancels a pending chat deletion; the
+first press sets `pending_relink` and shows
+`Cancellare la sessione WhatsApp e rifare l'abbinamento? F8 conferma, Esc annulla`;
+the second press calls `start_relink`. Any other key in between clears the
+flag through `cancel_pending_relink` (`src/app.rs:371-381`) with the status
+`Nuovo abbinamento annullato`; an Esc used that way does nothing else, other
+keys are then handled normally.
+
+**Web panel.** "Nuovo QR" (`relinkBot`, `src/web_page.html:703-718`) asks
+`Cancellare la sessione WhatsApp e rifare l'abbinamento? Il dispositivo verrà
+scollegato dal telefono.` and posts `/api/relink` with the mandatory
+`X-Wh-Request: 1` header (`src/web.rs:166-174`, `src/web.rs:238-254`).
+`App::handle_web_request` maps `WebCommand::Relink` straight to
+`start_relink` — the browser dialog replaces the double F8
+([Application state](../architecture/app-state.md#web-panel-requests)).
+
+## 2. Starting it in `App`
+
+`start_relink` (`src/app.rs:320-341`) clears `pending_relink` and returns at
+once if `relinking` is already set, so repeated requests during a relink are
+ignored. Otherwise it sets `relinking`, switches to `Screen::Connecting`,
+drops the stored QR, shows `Cancellazione della sessione in corso...` and
+resets `own_jids_known`, because the next pairing may be a different account
+and the [autoreply gate](ai-autoreply.md) must learn its JIDs again. It then
+spawns a tokio task that awaits `WaHandle::relink` and sends
+`AppEvent::Relinked` with `Ok(())` or the error text.
+
+The web request returns the state snapshot taken right after
+`start_relink`, i.e. `screen: "connecting"` and no QR; the page shows its
+pairing card with "Connessione a WhatsApp in corso..." and starts polling
+every 2 s.
+
+## 3. Replacing the session
+
+`WaHandle::relink` (`src/whatsapp.rs:221-237`, see
+[WhatsApp client](../integrations/whatsapp-client.md#relink)):
+
+1. **Take** the current `Session` out of the shared slot. From now until
+   step 4 succeeds every client operation fails with
+   `WhatsApp session not running`: manual and AI sends end with
+   `Invio fallito: ...`, image downloads fail, `own_jids` is empty.
+2. **Stop** it (`Session::stop`, `src/whatsapp.rs:182-198`): `logout`
+   deregisters the device on the phone when connected (best effort) and
+   disconnects; then it waits up to `STOP_TIMEOUT_SECONDS` (10 s) for the
+   client run loop to report it stopped, and aborts the translator task,
+   which holds a reference to the client.
+3. **Reset** the session database (`reset_session_files`,
+   `src/whatsapp.rs:314-329`): delete `session.db` and its `-wal` / `-shm`
+   side files. If the deletion fails — on Windows `whatsapp-rust` keeps the
+   SQLite file open until the process exits — `clear_session_tables`
+   (`src/whatsapp.rs:349-367`) opens it with `rusqlite` (5 s busy timeout)
+   and, in one transaction, deletes every row of every table except SQLite
+   internals and `__diesel_schema_migrations`, so the schema stays valid for
+   the next client. If both fail the error reads
+   `cannot delete (<io error>) nor clear session database <path>`.
+4. **Start** a new client on the empty database with `open_session`
+   (`src/whatsapp.rs:369-416`) and put it in the slot. It sends its events
+   to the same `WaEvent` channel as the old one, so the forwarder and `App`
+   need no rewiring.
+
+Only `session.db` is reset. `history.db` — chats, messages, contacts,
+autoreply flags and image descriptions — is untouched, so chats of the
+previously linked account stay visible until deleted locally.
+
+## 4. Outcome and new QR
+
+`finish_relink` (`src/app.rs:343-354`) clears `relinking` and shows
+`Sessione cancellata: scansiona il nuovo QR` or
+`Nuovo abbinamento fallito: <error>`. After a failure the handle has no
+session: the app stays on the connecting screen and the user can press F8
+twice again (or restart).
+
+The new client emits `WaEvent::Qr` payloads; `handle_wa_event` switches to
+`Screen::Pairing` and stores the payload (`src/app.rs:406-411`). It is shown
+in two places:
+
+- the TUI pairing screen draws it with half-block characters
+  (`qr_to_text`), or a "terminal too small" message
+  ([Terminal UI](../architecture/terminal-ui.md#qr-code));
+- `App::web_state` renders it to SVG with `web::qr_svg`
+  (`src/web.rs:364-379`) and returns it as `qr_svg` with
+  `screen: "pairing"`; the page's pairing card shows it as an image and the
+  WhatsApp linking steps, polling every 2 s so rotated codes appear quickly
+  (`src/web_page.html:596-628`).
+
+When the library runs out of QR codes the status shows
+`pairing QR codes exhausted; request a new QR to pair again` — another
+relink starts a fresh sequence. Scanning the code produces
+`WaEvent::Connected`: the app switches to `Screen::Main`, clears the QR,
+shows `Connesso` and fetches the new account's JIDs; the web page hides the
+pairing card and returns to 5 s polling.
+
+## Failure and edge cases
+
+- A relink requested while another runs is ignored (`relinking`).
+- If the old client does not stop within 10 s the relink continues anyway;
+  on a still-open database the in-place reset is used.
+- The startup catch-up runs once per process (`catch_up_done`), so it is not
+  repeated after a relink.
+- The web panel is unauthenticated: anyone reaching the port can unlink the
+  device and, while pairing, scan the QR to link their own phone (see
+  [Web control panel](../integrations/web-control-panel.md#security-and-operations)).
+- Never run two copies of `wh` with the same `session.db`
+  (`README.md`).
+
+## Tests
+
+- `web.rs`: `POST /api/relink` resolves to the relink route, requires the
+  `X-Wh-Request` header, and `GET /api/relink` is not found; `qr_svg`
+  produces an SVG document (`src/web.rs:422-442`).
+- `whatsapp.rs`: the ignored live test
+  `relink_deletes_the_session_and_offers_a_new_qr`
+  (`src/whatsapp.rs:1163-1183`) starts a real client, waits for a QR,
+  relinks, waits for the next QR and checks that the device keys in the two
+  payloads differ; run it with `cargo test -- --ignored` (network needed).
+- The `App` side (F8 confirmation, `start_relink`, `finish_relink`) and the
+  page script have no automated tests. See [Unit tests](../testing/unit-tests.md).