Files
hermes-webui/static
happy5318 6455aa9b2b fix(media): two-pass MEDIA: scan (pre-pass for wrapped, bare class without backtick exclusion) (#7708 round 2)
#7708 re-gate (9/22 23:47), both findings:

**Finding 1 (SILENT)**: A bare path that contains a backtick
(MEDIA:/tmp/report`final.png) is truncated at the first backtick,
so the server stops authorizing it. The round-1 fix traded one bug
for another: adding the backtick to the bare-token exclusion set
dropped the inline-code closing backtick (good) but truncated any
bare path with a backtick in the filename (bad — POSIX and Windows
both allow backticks).

**Finding 2 (SILENT)**: TTS now reads filename fragments aloud
(MEDIA:/tmp/report(1).png -> 'a file).png') because the
parenthesis-exclusion was added to a site that should not need it.
Inline code is already stripped at the top of the TTS flattener.

**Fix shape** (maintainer-suggested two-pass scan):

1. Pre-pass: `MEDIA:([^`\s]+)` -> MEDIA:$1 — strips the
   inline-code backticks off the wrapped form so the bare class
   below sees a plain MEDIA:path.
2. Bare class: MEDIA:([^\s\)\]]) (original, backtick
   removed from the exclusion set) — captures the full filename
   even when the path itself contains a backtick.
3. TTS: restore the original MEDIA:[^\s]+ (no paren/bracket
   exclusion either). Inline code is stripped at the top of the
   flattener so the closing backtick of an inline-code wrapper has
   already been eaten by the time the TTS regex scans the text.

Applied to all 7 capture sites:
- api/routes.py:20453 (_MEDIA_TOKEN_RE, findall site 20513)
- api/media_snapshots.py:507 (media_re, findall site 533)
- static/ui.js:7695 (linkifier, replace site)
- static/ui.js:9142 (TTS flattener — separate shape)
- static/messages.js:4848 (chunk finalize, exec)
- static/messages.js:4896 (combined walk, exec loop)
- static/messages.js:4922 (partial tail, exec)

**Tests** (24 total, all green):
- 11 source-shape guards: each JS/PY site must carry the bare-token
  class literal AND the wrapped-form pre-pass literal
- 4 behaviour tests against the live source: wrapped form strips
  closing backtick, bare form captures plain path, bare form with
  backtick in filename captures the full path (the new regression),
  trailing paren is still excluded
- 2 round-1 regression guards documenting the trade-off that drove
  the two-pass compose
- 1 composed two-pass test (pre-pass + bare class on 4 inputs)
- 2 node --check syntax guards
- 1 site-count guard: 5 JS + 2 PY = 7 capture sites, all guarded

**Verification**:
- pytest tests/test_issue7359_media_backtick_strip.py -v -> 24/24 pass
- python3 scripts/ruff_lint.py --diff origin/master -> 'no new
  violations on added/modified lines. OK.'
- node --check on the two modified JS files -> both OK
2026-09-23 12:03:33 +08:00
..