mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-22 18:30:28 +00:00
c73f2ff387
Closes #1442 (server-side _LOGIN_LOCALE missing ja/pt/ko) Closes #1443 (promote _isImeEnter helper to 6 other Safari Enter guards) Closes #1446 (glued-bold-heading lift for LLM thinking-block output) Closes #1447 (markdown heading visual hierarchy in chat messages) All four issues were filed by the Opus pre-release advisor on the v0.50.264 batch or by Cygnus via Discord (relayed by @AvidFuturist, May 1 2026). They share a common shape — narrow, well-scoped, independent of each other, all adding regression tests. == #1442: _LOGIN_LOCALE parity (api/routes.py + static/i18n.js) == Added entries for ja/pt/ko to the server-side _LOGIN_LOCALE dict that renders the localized login page BEFORE the JS i18n bundle loads. With v0.50.264 shipping Japanese as the 8th built-in locale, ja/pt/ko users were seeing the English login page even with their language preference set. While auditing static/i18n.js for English leakage, also fixed: - ko: 10 user-facing login/sign-out/password keys still in English - es: 3 sign-out/auth-disabled keys still in English Tests: tests/test_login_locale_parity.py (20 tests) — pins both invariants: (a) every locale in i18n.js LOCALES has a matching _LOGIN_LOCALE entry (b) every locale's login-flow keys (13 of them) are translated, not English == #1443: window._isImeEnter promotion == PR #1441 fixed the Safari IME-composition Enter race in the chat composer (`#msg`) by widening the guard from `e.isComposing` to a `_isImeEnter(e)` helper that combines three signals (isComposing || keyCode===229 || _imeComposing flag). Six other Enter-input handlers were left on the original narrow guard and would still drop IME composition Enters on Safari for Japanese/Chinese/Korean users. Promoted the helper to `window._isImeEnter` (defined in static/boot.js) and replaced the `e.isComposing` guards at all six sites: - static/sessions.js: session rename, project create, project rename - static/ui.js: app dialog (confirm/prompt), message edit, workspace rename The state-free part of the helper (`isComposing || keyCode===229`) handles Safari's race for any focused input without needing per-input composition listeners — only `#msg` keeps the local `_imeComposing` flag. Tests: - tests/test_issue1443_ime_helper_promotion.py (9 tests) — pins each site + verifies no raw `e.isComposing` Enter-guards remain in sessions.js/ui.js - tests/test_ime_composition.py — alternation regex extended to accept the windowed helper form (loosen-test-on-shape-change pattern from v0.50.264 reflection notes) == #1446: glued-bold-heading lift (static/ui.js renderMd + Python mirror) == LLMs in thinking/reasoning mode emit "section headers" glued to the end of the previous paragraph with no whitespace: Para 1 text.**Heading to Para 2** Para 2 text.**Heading to Para 3** The renderer correctly produces inline `<strong>` per CommonMark, but it looks like trailing emphasis on the body text rather than a section break. Cygnus reported this as "Markdown feedback 2 of 3." Added a single regex pre-pass in renderMd(): s.replace(/([.!?])\*\*([^*\n]{1,80})\*\*\n\n/g, '$1\n\n**$2**\n\n') Constraints chosen to avoid false positives: - Trigger only on `[.!?]` IMMEDIATELY before `**` (no space) — almost always an LLM-glued heading, not intentional emphasis - Inner text ≤80 chars, no `*` or newline (single-line only) - Trailing `\n\n` required — preserves "this is **important** to know." mid-paragraph emphasis untouched - Position: after rawPreStash restore, before fence_stash restore — fenced code blocks stay protected (their content is `\x00P` / `\x00F` tokens when the lift runs) Mirrored in tests/test_sprint16.py render_md() so both stay in sync. Tests: tests/test_issue1446_glued_heading_lift.py (17 tests, 5 of which drive the actual ui.js renderMd via node) — covers all 3 trigger forms (.!?), all 4 preserve-emphasis cases the issue spec'd, fenced/inline code protection, chained glued headings, source-level position pin, regex shape pin. == #1447: markdown heading visual hierarchy (static/style.css) == Pre-fix sizes in `.msg-body`: h1 18px, h2 16px, h3 14px (= body), h4 13px, h5 12px, h6 11px So h3 was indistinguishable from body and h4/h5/h6 were SMALLER than body. Cygnus's report: "Markdown feedback 3 of 3 — Headings seem to be missing across the board in Hermes. They're there, but all plaintext." New sizes: h1 24px (border-bottom) h2 20px (border-bottom) h3 17px h4 15px h5 14px (uppercase, tracked) h6 13px (uppercase, tracked, muted) All headings now `font-weight:700` + `color:var(--strong)` for stronger ink. h5/h6 use uppercase + letter-spacing for "label-style" affordance instead of being smaller-than-body. Synced .preview-md (file preview pane) to match exactly so a markdown file preview and a chat message render identically. Added missing h4/h5/h6 rules to .preview-md (it only had h1-h3 before). Updated data-font-size="small"/"large" h1-h6 overrides to scale proportionally with the new defaults. Hierarchy preserved at all three font-size settings. Tests: tests/test_issue1447_heading_hierarchy.py (9 tests) — pins the size hierarchy, the bottom borders on h1/h2, the uppercase affordance on h5/h6, the .preview-md sync, and the small/large override scaling. == Verification == pytest tests/ -q → 3748 passed (+56 new) bash ~/WebUI/scripts/run-browser-tests.sh → 20 + 11 PASS bash ~/WebUI/scripts/webui_qa_agent.sh 8789 → 23/23 PASS Visual confirmation in browser at port 8789: - Heading hierarchy clearly visible at all 6 levels - Glued-bold lift produces separate paragraphs as designed - window._isImeEnter accessible from any module after boot.js - Login page renders ja/pt/ko strings correctly (curl -s /login)
77 lines
2.9 KiB
Python
77 lines
2.9 KiB
Python
import pathlib
|
|
import re
|
|
|
|
|
|
REPO_ROOT = pathlib.Path(__file__).parent.parent.resolve()
|
|
BOOT_JS = (REPO_ROOT / "static" / "boot.js").read_text(encoding="utf-8")
|
|
UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8")
|
|
SESSIONS_JS = (REPO_ROOT / "static" / "sessions.js").read_text(encoding="utf-8")
|
|
|
|
|
|
def _ime_guarded_enter_pattern(event_var_pattern, require_no_shift=False):
|
|
"""Accept the IME guard in any of three shapes that have been used in the codebase:
|
|
|
|
1. Original `e.isComposing` (pre-#1441)
|
|
2. Module-local `_isImeEnter(e)` (PR #1441 — chat composer in boot.js)
|
|
3. Window-exposed `window._isImeEnter(e)` (issue #1443 — promoted to ui.js,
|
|
sessions.js so all 6 Enter-input sites use the same Safari-aware guard).
|
|
"""
|
|
no_shift = rf"\s*&&\s*!\s*{event_var_pattern}\.shiftKey" if require_no_shift else ""
|
|
# Either: if(e.isComposing) ... | if(_isImeEnter(e)) ... | if(window._isImeEnter&&window._isImeEnter(e)) ...
|
|
guard = (
|
|
rf"if\s*\(\s*"
|
|
rf"(?:{event_var_pattern}\.isComposing"
|
|
rf"|_isImeEnter\(\s*{event_var_pattern}\s*\)"
|
|
rf"|window\._isImeEnter\s*&&\s*window\._isImeEnter\s*\(\s*{event_var_pattern}\s*\))"
|
|
rf"\s*\)\s*"
|
|
)
|
|
return (
|
|
rf"if\s*\(\s*{event_var_pattern}\.key\s*===\s*'Enter'{no_shift}\s*\)\s*\{{\s*"
|
|
+ guard +
|
|
rf"(?:\{{\s*return\s*;?\s*\}}|return\s*;?)"
|
|
)
|
|
|
|
|
|
def test_boot_chat_enter_send_respects_ime_composition():
|
|
assert re.search(
|
|
_ime_guarded_enter_pattern("e"),
|
|
BOOT_JS,
|
|
re.DOTALL,
|
|
), "Chat composer Enter handler must ignore IME composition Enter in static/boot.js"
|
|
assert re.search(
|
|
_ime_guarded_enter_pattern("e", require_no_shift=True),
|
|
BOOT_JS,
|
|
re.DOTALL,
|
|
), "Command dropdown Enter handler must ignore IME composition Enter in static/boot.js"
|
|
|
|
|
|
def test_ui_enter_submit_paths_respect_ime_composition():
|
|
assert re.search(
|
|
rf"document\.addEventListener\('keydown',e=>\{{[\s\S]*?{_ime_guarded_enter_pattern('e')}",
|
|
UI_JS,
|
|
re.DOTALL,
|
|
), \
|
|
"App dialog Enter handler must ignore IME composition Enter in static/ui.js"
|
|
assert re.search(
|
|
_ime_guarded_enter_pattern("e", require_no_shift=True),
|
|
UI_JS,
|
|
re.DOTALL,
|
|
), \
|
|
"Message edit Enter-to-save handler must ignore IME composition Enter in static/ui.js"
|
|
assert re.search(
|
|
rf"inp\.onkeydown=\(e2\)=>\{{\s*{_ime_guarded_enter_pattern('e2')}",
|
|
UI_JS,
|
|
re.DOTALL,
|
|
), \
|
|
"Workspace rename Enter handler must ignore IME composition Enter in static/ui.js"
|
|
|
|
|
|
def test_sessions_enter_submit_paths_respect_ime_composition():
|
|
matches = re.findall(
|
|
_ime_guarded_enter_pattern(r"e2?"),
|
|
SESSIONS_JS,
|
|
re.DOTALL,
|
|
)
|
|
assert len(matches) >= 3, \
|
|
"Session and project rename/create Enter handlers must ignore IME composition Enter in static/sessions.js"
|