Skip to content

Fix reply bubble misidentification and add reply announcement with left-arrow navigation#75

Merged
keyang556 merged 5 commits into
mainfrom
claude/elegant-goldberg-2e8468
May 7, 2026
Merged

Fix reply bubble misidentification and add reply announcement with left-arrow navigation#75
keyang556 merged 5 commits into
mainfrom
claude/elegant-goldberg-2e8468

Conversation

@keyang556
Copy link
Copy Markdown
Owner

@keyang556 keyang556 commented May 7, 2026

Summary

  • Bug fix: After caching chat with NVDA+Windows+U, focusing a reply bubble no longer reads the quoted preview text — it now correctly identifies the actual reply sender and content. The root cause was that the quoted text (longer) scored higher on content overlap than the actual reply; fixed by detecting when OCR contains two distinct cached sender names and restricting candidates to the actual reply sender.
  • Feature: Reply messages are now announced as Sender 回覆 OriginalSender Content Time in a single utterance (previously time was split into a separate announcement).
  • Feature: Pressing Left Arrow on a reply bubble speaks the original quoted message: OriginalSender OriginalContent. The original is found by scanning backward in the cached chat history from the reply's position.

Test plan

  • Run pytest tests/test_chat_cache.py --ignore=tests/test_message_reader_parser.py — all 126 tests pass
  • In LINE Desktop, press NVDA+Windows+U to cache a chat containing reply bubbles
  • Navigate to a reply bubble: NVDA should announce Sender 回覆 OriginalSender Content HH:MM in one utterance
  • Press Left Arrow on a reply bubble: NVDA should speak OriginalSender OriginalContent
  • Navigate to a non-reply bubble: normal announcement, no "回覆" prefix

🤖 Generated with Claude Code

Greptile Summary

此 PR 修正了回覆氣泡被誤識的問題,並新增「回覆訊息」播報格式以及左鍵朗讀被引用原文的功能。核心邏輯透過行首錨定的正規表達式偵測 OCR 中的兩個發送者姓名,從而區分「回覆者」與「被回覆者」,並搭配向上掃描快取以找到被引用的原始訊息。

  • Bug 修正_detectReplyNames() 現在使用行首錨定正規表達式,防止姓名出現在句子中間或作為長姓名的子字串時誤觸回覆篩選器。
  • 功能新增:回覆訊息現在以「發送者 回覆 被回覆者 內容 時間」的格式一次播報;按下左鍵可朗讀被引用的原始訊息,第一次觸發後 _lastReplyInfo 會被清除,確保後續左鍵正常穿透。
  • 測試:新增 10 個針對回覆偵測邏輯的單元測試,涵蓋關鍵邊界情況。

Confidence Score: 5/5

此 PR 可安全合併,邏輯設計周全,邊界條件均有對應測試覆蓋。

回覆偵測邏輯使用行首錨定正規表達式,正確排除了姓名出現在句子中間或作為長姓名子字串的情況;左鍵攔截在播報後立即清除 _lastReplyInfo,不會重複觸發;lookupMessage 在函式起始即重設 _lastReplyInfo,確保非回覆訊息不會繼承舊狀態;10 個新增單元測試涵蓋所有關鍵邊界情況。

所有檔案皆無需特別關注。

Important Files Changed

Filename Overview
addon/appModules/_chatCache.py 新增 _detectReplyNames、_findQuotedOriginal、getLastReplyInfo、clearLastReplyInfo 四個函式,以及全域狀態 _lastReplyInfo;lookupMessage 整合回覆篩選邏輯,邏輯正確,邊界條件處理完善。
addon/appModules/line.py _copyAndReadMessage 新增回覆格式播報;script_navigateAndTrack 新增左鍵攔截邏輯,正確在播報後呼叫 clearLastReplyInfo() 防止重複觸發。
tests/test_chat_cache.py 新增 10 個回覆偵測相關測試,涵蓋正常回覆、自清除、僅向上搜尋、行首錨定防誤觸、引號前綴符號相容性及子字串姓名防誤判,覆蓋率良好。

Sequence Diagram

sequenceDiagram
    participant User
    participant script_navigateAndTrack
    participant _copyAndReadMessage
    participant _chatCache

    User->>script_navigateAndTrack: 按下任意導覽鍵
    alt "keyName == leftArrow 且有回覆資訊"
        script_navigateAndTrack->>_chatCache: getLastReplyInfo()
        _chatCache-->>script_navigateAndTrack: replyInfo (含 originalContent)
        script_navigateAndTrack->>User: ui.message(OriginalSender OriginalContent)
        script_navigateAndTrack->>_chatCache: clearLastReplyInfo()
        Note over script_navigateAndTrack: return(不穿透按鍵)
    else 其他鍵 或無回覆資訊
        script_navigateAndTrack->>User: gesture.send() + 排程 UIA 焦點查詢
    end

    User->>_copyAndReadMessage: 焦點移到訊息氣泡
    _copyAndReadMessage->>_chatCache: lookupMessage(ocrText)
    _chatCache->>_chatCache: _detectReplyNames(ocrText)
    alt 偵測到兩個姓名(回覆氣泡)
        _chatCache->>_chatCache: 篩選候選訊息為 replySender
        _chatCache->>_chatCache: _findQuotedOriginal(bestIdx, quotedSender, ocrText)
        _chatCache->>_chatCache: 設定 _lastReplyInfo
        _chatCache-->>_copyAndReadMessage: (formattedText, idx)
        _copyAndReadMessage->>_chatCache: getLastReplyInfo()
        _chatCache-->>_copyAndReadMessage: replyInfo
        _copyAndReadMessage->>User: ui.message(發送者 回覆 被回覆者 內容 時間)
    else 普通訊息
        _chatCache->>_chatCache: "_lastReplyInfo = None"
        _chatCache-->>_copyAndReadMessage: (formattedText, idx)
        _copyAndReadMessage->>User: ui.message(cachedText)
    end
Loading

Reviews (4): Last reviewed commit: "Fix reply detection regression: handle q..." | Re-trigger Greptile

keyang556 and others added 2 commits May 7, 2026 17:32
…ft-arrow navigation

- Replace _detectReplySender with _detectReplyNames returning (replySender, quotedSender)
  to correctly identify reply bubbles containing two distinct cached sender names
- Restrict candidate scoring to actual reply sender, preventing quoted preview text
  from winning on content overlap and causing wrong message to be announced
- Add _findQuotedOriginal to locate the original quoted message by scanning backward
  from the reply's cache index
- Add getLastReplyInfo() module getter so line.py can read reply context after lookup
- Announce reply messages as "Sender 回覆 OriginalSender Content Time" in one utterance
- Intercept Left Arrow in script_navigateAndTrack to speak the original quoted message
- Add 6 new tests covering reply detection, original lookup, and edge cases

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment thread addon/appModules/_chatCache.py
Comment thread addon/appModules/_chatCache.py
- Use line-anchored regex (^name\s*$) instead of plain str.find() in
  _detectReplyNames, fixing two cases:
  P1: a name mentioned mid-sentence (e.g. 'Thanks Bob') no longer
  triggers the reply filter, since it won't occupy an entire line.
  P2: a short name that is a substring of a longer name (e.g. '王昱'
  inside '王昱涵') no longer matches the line belonging to the longer
  name, preventing false sender identification.
- Update existing reply-bubble test OCR strings to use proper format
  where the quoted sender name is on its own line (as real LINE OCR does).
- Add two new regression tests covering the P1 and P2 cases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment thread addon/appModules/line.py
keyang556 and others added 2 commits May 7, 2026 17:50
Without this, pressing Left Arrow a second time on the same reply
bubble would repeat the announcement indefinitely since focus never
moves and lookupMessage() is never called again.  Add
clearLastReplyInfo() to _chatCache and call it immediately after the
ui.message() announcement in the left-arrow intercept path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Real LINE OCR for reply bubbles renders a quote-indicator glyph
(commonly OCR'd as '0 ') before the quoted user name, so the quoted
name does NOT occupy a standalone line.  The previous regex
'^name\\s*\$' was too strict and caused a regression where reply
bubbles were no longer detected, falling back to the original bug
of reading the long quoted preview instead of the actual reply.

New regex: '^[^CJK/Latin-letters]*name(?![CJK/Latin-letters])'
- Allows non-letter prefix (icons, digits, punctuation, whitespace)
  so '0 王昱涵' still matches.
- Anchored to line start with letter-class prefix exclusion, so
  mid-sentence mentions like '感謝Bob' still cannot match (because
  '感謝' are CJK letters and won't be consumed by the prefix class).
- Negative lookahead blocks substring matches, so '王昱' cannot
  match a line containing '王昱涵'.

Add regression test using the exact OCR string from the user's log.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@keyang556 keyang556 merged commit 6170f65 into main May 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant