From 5e53a91f8d500467c191faaf90ffc35e5e05a607 Mon Sep 17 00:00:00 2001 From: Jer Miller Date: Sat, 18 Jul 2026 21:35:02 -0600 Subject: [PATCH] fix(convey): share drawer line formatting Extract Drawer.formatLine so hand-authored drawer markup can use the same escaped numeric emphasis as Drawer.render output. This keeps settings aligned with the shared drawer standard without copying the line-emphasis regex. --- solstone/apps/body/tests/test_body_app.py | 3 ++- solstone/convey/static/drawer.js | 15 ++++++++++----- tests/test_convey_drawer.py | 6 +++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/solstone/apps/body/tests/test_body_app.py b/solstone/apps/body/tests/test_body_app.py index 83a30100a..347323900 100644 --- a/solstone/apps/body/tests/test_body_app.py +++ b/solstone/apps/body/tests/test_body_app.py @@ -590,7 +590,8 @@ def test_drawer_render_emphasizes_digits_after_escaping_under_node(): node = _node_or_skip() source = Path("solstone/convey/static/drawer.js").read_text(encoding="utf-8") functions = "\n".join( - _function_source(source, name) for name in ("escapeHtml", "render") + _function_source(source, name) + for name in ("escapeHtml", "formatLine", "render") ) script = "\n".join( [ diff --git a/solstone/convey/static/drawer.js b/solstone/convey/static/drawer.js index 8a0957e32..c8b9b41b2 100644 --- a/solstone/convey/static/drawer.js +++ b/solstone/convey/static/drawer.js @@ -17,15 +17,20 @@ })[char]); } + function formatLine(line) { + const value = String(line ?? '').trim(); + return value + ? escapeHtml(value).replace(/&#?\w+;|\d[\d,.:]*(?:\s?(?:am|pm|s))?/g, (m) => (m.startsWith('&') ? m : `${m}`)) + : ''; + } + function render(options) { const config = options || {}; const id = String(config.id ?? '').trim(); const idAttr = id ? ` data-drawer-id="${escapeHtml(id)}"` : ''; const openAttr = config.open ? ' open' : ''; - const line = String(config.line ?? '').trim(); - const lineHtml = line - ? `${escapeHtml(line).replace(/&#?\w+;|\d[\d,.:]*(?:\s?(?:am|pm|s))?/g, (m) => (m.startsWith('&') ? m : `${m}`))}` - : ''; + const line = formatLine(config.line); + const lineHtml = line ? `${line}` : ''; const chipText = String(config.chipText ?? '').trim(); const chipTone = config.chipTone === 'warn' || config.chipTone === 'danger' ? ` drawer-chip--${config.chipTone}` @@ -65,5 +70,5 @@ return result; } - window.Drawer = Object.freeze({ render, preserveOpen }); + window.Drawer = Object.freeze({ render, preserveOpen, formatLine }); })(); diff --git a/tests/test_convey_drawer.py b/tests/test_convey_drawer.py index 137da3613..d597806e3 100644 --- a/tests/test_convey_drawer.py +++ b/tests/test_convey_drawer.py @@ -59,7 +59,9 @@ def test_drawer_js_contract_and_constraints(): assert "(function () {" in source assert "'use strict';" in source - assert "window.Drawer = Object.freeze({ render, preserveOpen });" in source + assert ( + "window.Drawer = Object.freeze({ render, preserveOpen, formatLine });" in source + ) assert "Storage" not in source assert "localStorage" not in source assert "sessionStorage" not in source @@ -285,6 +287,8 @@ const directDrawer = window.Drawer.render({ line: "median statement length 1.5s — needs 2s", bodyHtml: "", }); +assert(Object.keys(window.Drawer).join("|") === "render|preserveOpen|formatLine", "drawer exports formatLine"); +assert(window.Drawer.formatLine("2 files created · <5>").includes("2 files created · <5>"), "formatLine escapes and emphasizes digits"); assert(lineHtml(directDrawer).includes("1.5s"), "drawer emphasizes compact seconds"); const tooFewPayload = payload(); -- 2.51.2