diff --git a/apps/__init__.py b/apps/__init__.py index 48863b74c..75156a96a 100644 --- a/apps/__init__.py +++ b/apps/__init__.py @@ -28,6 +28,7 @@ app.json fields (all optional): "label": "Custom Label", # Display label (default: title-cased app name) "facets": {}, # Facet options: {"disabled": true} to hide facet bar "date_nav": true, # Show date navigation bar (default: false) + "app_bar": false, # Hide the universal chat bar on this app (default: true) "allow_future_dates": true # Allow future dates in month picker (default: false) } @@ -73,6 +74,9 @@ class App: # Date navigation (renders date nav below facet bar) date_nav: bool = False + # Hide the universal chat bar on this app + app_bar: bool = True + # Allow clicking future dates in month picker (for todos) allow_future_dates: bool = False @@ -167,6 +171,9 @@ class AppRegistry: # Date navigation date_nav = metadata.get("date_nav", False) + # Universal app bar + app_bar = metadata.get("app_bar", True) + # Allow future dates in month picker allow_future_dates = metadata.get("allow_future_dates", False) @@ -234,6 +241,7 @@ class AppRegistry: background_template=background_template, facets_config=facets_config, date_nav=date_nav, + app_bar=app_bar, allow_future_dates=allow_future_dates, ) diff --git a/apps/home/workspace.html b/apps/home/workspace.html index 39b5d51ad..dd08b2bbb 100644 --- a/apps/home/workspace.html +++ b/apps/home/workspace.html @@ -1399,7 +1399,7 @@ if (el.closest('[data-routine-click]') || el.hasAttribute('data-routine-click')) { fetch('/app/home/api/routines/seen', {method: 'POST'}); } - window.openConversation({ prompt: el.dataset.conversation }); + window.openConversation(el.dataset.conversation); } }); dashboard.addEventListener('keydown', function(e) { @@ -1423,7 +1423,7 @@ if (el.closest('[data-routine-click]') || el.hasAttribute('data-routine-click')) { fetch('/app/home/api/routines/seen', {method: 'POST'}); } - window.openConversation({ prompt: el.dataset.conversation }); + window.openConversation(el.dataset.conversation); } }); } diff --git a/convey/static/app.css b/convey/static/app.css index 48137f3ab..8f38e0406 100644 --- a/convey/static/app.css +++ b/convey/static/app.css @@ -107,6 +107,9 @@ body { margin-top: var(--facet-bar-height); padding-top: 8px; margin-left: var(--menu-bar-width-minimal); +} + +body.has-app-bar .workspace { margin-bottom: calc(var(--app-bar-height) + 80px); } @@ -117,6 +120,10 @@ body.has-date-nav .workspace { /* Reserve space when error log is visible */ body.has-error-log .workspace { + margin-bottom: 30vh; +} + +body.has-app-bar.has-error-log .workspace { margin-bottom: calc(var(--app-bar-height) + 80px + 30vh); } @@ -1385,7 +1392,7 @@ body.menu-all .menu-bar { filter: grayscale(0.4); } -/* App Bar (bottom) */ +/* chat bar + talent-view modal */ .app-bar { position: fixed; bottom: 16px; @@ -1397,9 +1404,11 @@ body.menu-all .menu-bar { margin-right: auto; z-index: var(--z-bars); min-height: var(--app-bar-height); - padding: 0; + padding: 10px 14px; display: flex; - flex-direction: column; + align-items: center; + gap: 12px; + flex-wrap: wrap; border-radius: 12px; border: 1px solid var(--facet-border, #e5e0db); box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.05), 0 2px 12px rgba(0, 0, 0, 0.08); @@ -1408,10 +1417,7 @@ body.menu-all .menu-bar { .app-bar::before { content: ''; position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + inset: 0; background: white; pointer-events: none; z-index: -2; @@ -1421,10 +1427,7 @@ body.menu-all .menu-bar { .app-bar::after { content: ''; position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + inset: 0; background: var(--facet-bg, #b06a1a1a); transition: background-color 0.3s ease; pointer-events: none; @@ -1432,132 +1435,54 @@ body.menu-all .menu-bar { border-radius: 12px; } -.app-bar:not(.app-bar--focused):hover { - cursor: pointer; -} - -.app-bar:not(.app-bar--focused) .chat-bar-input-row { - cursor: default; -} - -.app-bar:not(.app-bar--focused) .chat-bar-input { - border-color: transparent; -} - -/* Response panel (above input row) */ -.chat-bar-response-panel { - max-height: 0; +.chat-bar-status { + flex: 1 1 auto; + min-width: 0; overflow: hidden; - padding: 0 20px; - position: relative; - z-index: 1; - transition: max-height 0.2s ease; } -.app-bar--glance .chat-bar-response-panel { - max-height: 200px; -} - -.app-bar--glance .chat-bar-thinking { - margin-top: 12px; - margin-bottom: 8px; - opacity: 1; -} - -.app-bar--glance .chat-bar-response { - margin-top: 12px; - margin-bottom: 8px; - opacity: 1; -} - -.chat-bar-separator { - height: 1px; - background: var(--facet-border, #e5e0db); - margin: 12px -20px 0; +.chat-bar-status #chatBarStatusText, +.chat-bar-status > span { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: #888; + font-size: 13px; + line-height: 1.4; } -.chat-bar-dismiss { - position: absolute; - top: 12px; - right: 16px; - min-width: 44px; - min-height: 44px; - border: none; - background: transparent; - color: #999; - font-size: 16px; - font-weight: 300; - cursor: pointer; - border-radius: 4px; +.chat-bar-talents { display: flex; align-items: center; - justify-content: center; - z-index: 2; - opacity: 0; - pointer-events: none; - visibility: hidden; - transition: opacity 0.15s ease, visibility 0.15s ease; -} - -.chat-bar-dismiss.visible { - opacity: 1; - pointer-events: auto; - visibility: visible; -} - -.chat-bar-dismiss:hover { - background: rgba(0, 0, 0, 0.06); - color: #666; -} - -.chat-bar-dismiss:active { - background: rgba(0, 0, 0, 0.10); + gap: 4px; + max-width: calc(8 * 24px); + overflow: hidden; } -/* Input row */ -.chat-bar-input-row { +.chat-bar-form { display: flex; - flex-direction: row; align-items: center; - gap: 8px; - padding: 0 20px; - min-height: var(--app-bar-height); + gap: 6px; } -/* Chat Bar Elements */ .chat-bar-input { - flex: 1; resize: none; - border: 1px solid var(--facet-border, #e5e0db); - border-radius: 20px; - padding: 0.6em 1.25em; - font-family: inherit; - font-size: 15px; - line-height: 1.5; + overflow: hidden; max-height: 120px; - min-height: 38px; - background: transparent; - position: relative; - z-index: 1; - transition: border-color 0.2s ease; -} - -.chat-bar-input:hover { - border-color: #bbb; + min-width: 240px; + padding: 6px 8px; + border: 1px solid var(--facet-border, #e5e0db); + border-radius: 6px; + background: rgba(255, 255, 255, 0.92); + color: inherit; + font: inherit; + line-height: 1.4; } .chat-bar-input:focus { - outline: none; - border-color: var(--facet-color, #b06a1a); -} - -.chat-bar-input::placeholder { - color: #aaa; - transition: opacity 0.2s ease; -} - -.chat-bar-input:focus::placeholder { - opacity: 0.5; + outline: 2px solid var(--facet-color, #b06a1a); + outline-offset: 1px; } .chat-bar-input:disabled { @@ -1566,356 +1491,232 @@ body.menu-all .menu-bar { } .chat-bar-send { - width: 44px; - height: 44px; - border-radius: 50%; - border: none; - background: var(--facet-color, #b06a1a); - color: white; + padding: 6px 12px; + border: 1px solid var(--facet-border, #e5e0db); + border-radius: 6px; + background: rgba(255, 255, 255, 0.88); + color: inherit; + font: inherit; cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - position: relative; - z-index: 1; - flex-shrink: 0; - opacity: 0; - pointer-events: none; - visibility: hidden; - transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s ease; -} - -.chat-bar-send.visible { - opacity: 1; - pointer-events: auto; - visibility: visible; -} - -.chat-bar-send.visible:hover { - opacity: 0.85; -} - -.chat-bar-send.visible:active { - transform: scale(0.92); - opacity: 0.75; } -@media (max-width: 768px) { - .chat-bar-send { - width: 48px; - height: 48px; - } +.chat-bar-send:disabled { + opacity: 0.5; + cursor: not-allowed; } -.chat-bar-thinking { - display: flex; +.chat-bar-talent { + width: 20px; + height: 20px; + border: 1px solid var(--facet-border, #e5e0db); + border-radius: 50%; + background: transparent; + color: #888; + padding: 0; + display: inline-flex; align-items: center; - gap: 0.5em; - color: #999; - font-size: 0.9em; - position: relative; - z-index: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; - opacity: 0; - transition: opacity 0.15s ease 0.05s; -} - -.chat-bar-thinking > [aria-hidden="true"] { - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; - transition: opacity 0.15s ease; + justify-content: center; + cursor: pointer; + flex-shrink: 0; } -.chat-bar-thinking-dot { +.chat-bar-talent-dot { width: 8px; height: 8px; - background: var(--facet-color, #b06a1a); border-radius: 50%; - animation: chat-bar-pulse 1s ease-in-out infinite; -} - -@keyframes chat-bar-pulse { - 0%, 100% { opacity: 0.3; } - 50% { opacity: 1; } -} - -.thinking-elapsed { - opacity: 0; - font-variant-numeric: tabular-nums; - transition: opacity 0.3s ease; -} - -.chat-bar-response { - font-size: 14px; - line-height: 1.5; - color: #666; - max-height: 5em; - overflow: hidden; - padding-right: 28px; - position: relative; - z-index: 1; - -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%); - mask-image: linear-gradient(to bottom, black 60%, transparent 100%); - opacity: 0; - transition: opacity 0.2s ease 0.1s; -} - -.chat-bar-response--error { - color: #d97706; -} - -/* ======================================== - * Conversation Panel - * Expandable focus-mode conversation surface. - * The chat bar expands upward to 80% viewport. - * Backdrop blur excludes aruco corner-tags via z-index layering: - * backdrop at z-bars+49 < corner-tags at z-tags (400). - * ======================================== */ - -/* Backdrop — blurs convey content when panel is open */ -.conversation-backdrop { - position: fixed; - inset: 0; - z-index: calc(var(--z-bars) + 49); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); - background: rgba(0, 0, 0, 0.12); - opacity: 0; - pointer-events: none; - transition: opacity 0.3s ease; + background: currentColor; } -.conversation-backdrop.visible { - opacity: 1; - pointer-events: auto; - cursor: pointer; -} - -/* Panel expanded state */ -.app-bar.app-bar--focused { - height: 80vh; /* fallback for browsers without dvh */ - height: 80dvh; - bottom: 0; - border-radius: 12px 12px 0 0; - z-index: calc(var(--z-bars) + 50); - animation: panel-expand 0.3s cubic-bezier(0.4, 0, 0.2, 1); +.chat-bar-talent[data-status="active"] { + color: var(--facet-color, #b06a1a); } -/* Panel closing animation */ -.app-bar.app-bar--dismissing { - z-index: calc(var(--z-bars) + 50); - animation: panel-collapse 0.25s ease forwards; +.chat-bar-talent[data-status="active"] .chat-bar-talent-dot { + animation: chat-bar-talent-pulse 1.2s ease-in-out infinite; } -@keyframes panel-expand { - from { - height: var(--app-bar-height); - bottom: 16px; - border-radius: 12px; - } +.chat-bar-talent--overflow { + width: auto; + min-width: 28px; + height: 20px; + border-radius: 10px; + padding: 0 8px; + font-size: 11px; + font-weight: 600; + line-height: 1; } -@keyframes panel-collapse { - from { - height: 80vh; - height: 80dvh; - bottom: 0; - border-radius: 12px 12px 0 0; - } - to { - height: var(--app-bar-height); - bottom: 16px; - border-radius: 12px; - } +.chat-bar-talent[data-status="errored"] { + color: #b42318; } -@media (max-width: 768px) { - .app-bar.app-bar--focused { - left: 8px; - right: 8px; - max-width: none; - width: auto; - } +@keyframes chat-bar-talent-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } } -/* Conversation messages area */ -.conversation-messages { +.talent-view-modal[hidden] { display: none; - flex-direction: column; - gap: 12px; - overflow-y: auto; - padding: 16px; - flex: 1; - min-height: 0; - background: #fffcf9; - scrollbar-width: thin; - scrollbar-color: rgba(0, 0, 0, 0.15) transparent; } -.conversation-messages::-webkit-scrollbar { - width: 6px; +.talent-view-modal { + position: fixed; + inset: 0; + z-index: var(--z-modals); + display: flex; + align-items: center; + justify-content: center; } -.conversation-messages::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.15); - border-radius: 3px; +.talent-view-backdrop { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.6); } -.app-bar.app-bar--focused .conversation-messages { +.talent-view-panel { + position: relative; + width: min(760px, calc(100vw - 48px)); + max-height: calc(100vh - 80px); display: flex; + flex-direction: column; + border: 1px solid var(--facet-border, #e5e0db); + border-radius: 10px; + background: white; + overflow: hidden; } -/* Hide inline response panel in panel mode */ -.app-bar.app-bar--focused .chat-bar-response-panel { - display: none; -} - -/* Separator between messages and input (panel mode only) */ -.conversation-separator { - display: none; +.talent-view-header { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + border-bottom: 1px solid var(--facet-border, #e5e0db); } -.app-bar.app-bar--focused .conversation-separator { - display: block; - height: 1px; - background: var(--facet-border, #e5e0db); - flex-shrink: 0; +.talent-view-title { + flex: 1 1 auto; + min-width: 0; + margin: 0; + font-size: 15px; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -/* Message bubbles */ -.conversation-message { - max-width: 85%; - padding: 10px 16px; - border-radius: 16px; - font-size: 14px; - line-height: 1.5; - word-wrap: break-word; - white-space: pre-wrap; +.talent-view-status { + padding: 2px 8px; + border-radius: 999px; + font-size: 11px; + letter-spacing: 0.04em; + text-transform: uppercase; } -.conversation-message.user { - align-self: flex-end; - background: var(--facet-color, #b06a1a); - color: white; - border-bottom-right-radius: 4px; - font-weight: 500; +.talent-view-status[data-status="running"] { + background: rgba(176, 106, 26, 0.16); + color: var(--facet-color, #b06a1a); } -.conversation-message.agent { - align-self: flex-start; - background: #f5f0eb; - color: #1f2937; - border-bottom-left-radius: 4px; - white-space: normal; - word-break: break-word; - overflow-wrap: break-word; +.talent-view-status[data-status="completed"] { + background: rgba(15, 23, 42, 0.08); + color: #888; } -.conversation-message.agent p { - margin: 0 0 0.6em 0; +.talent-view-status[data-status="errored"] { + background: rgba(180, 35, 24, 0.16); + color: #b42318; } -.conversation-message.agent p:last-child { - margin-bottom: 0; +.talent-view-close { + background: transparent; + border: 0; + color: inherit; + font-size: 20px; + cursor: pointer; + padding: 0 4px; } -.conversation-message.agent ul, -.conversation-message.agent ol { - margin: 0.25em 0 0.5em 0; - padding-left: 1.5em; +.talent-view-timeline { + flex: 1 1 auto; + padding: 12px 16px; + overflow: auto; } -.conversation-message.agent li { - margin-bottom: 0.2em; +.talent-view-empty { + margin: 0; + color: #6b7280; } -.conversation-message.agent code { - background: rgba(0, 0, 0, 0.06); - padding: 0.15em 0.35em; - border-radius: 4px; - font-size: 0.9em; - font-family: 'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', monospace; +.talent-view-event { + margin-bottom: 12px; + font-size: 13px; } -.conversation-message.agent pre { - background: rgba(0, 0, 0, 0.06); - padding: 0.6em 0.8em; - border-radius: 6px; - overflow-x: auto; - margin: 0.4em 0; +.talent-view-event-header { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + margin-bottom: 8px; } -.conversation-message.agent pre code { - background: none; - padding: 0; - border-radius: 0; - font-size: 0.875em; - white-space: pre; +.talent-view-event-label { + font-weight: 600; } -.conversation-message.agent blockquote { - border-left: 3px solid rgba(0, 0, 0, 0.15); - margin: 0.4em 0; - padding: 0.2em 0 0.2em 0.8em; +.talent-view-event-time { color: #6b7280; + font-size: 12px; + flex-shrink: 0; } -.conversation-message.agent h1, -.conversation-message.agent h2, -.conversation-message.agent h3, -.conversation-message.agent h4, -.conversation-message.agent h5, -.conversation-message.agent h6 { - margin: 0.6em 0 0.3em 0; - font-weight: 600; - line-height: 1.3; +.talent-view-event--thinking, +.talent-view-thinking { + color: #888; + font-style: italic; } -.conversation-message.agent h1 { font-size: 1.2em; } -.conversation-message.agent h2 { font-size: 1.1em; } -.conversation-message.agent h3 { font-size: 1.08em; } - -.conversation-message.agent a { - color: #b06a1a; - text-decoration: underline; - text-decoration-color: rgba(176, 106, 26, 0.3); +.talent-view-info { + line-height: 1.5; } -.conversation-message.agent a:hover { - text-decoration-color: rgba(176, 106, 26, 0.7); +.talent-view-pre { + margin: 0; + padding: 10px 12px; + border: 1px solid rgba(0, 0, 0, 0.06); + border-radius: 8px; + background: rgba(15, 23, 42, 0.03); + font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + white-space: pre-wrap; + word-break: break-word; + overflow: auto; } -.conversation-message.agent hr { - border: none; - border-top: 1px solid rgba(0, 0, 0, 0.1); - margin: 0.5em 0; +.talent-view-event--tool summary, +.talent-view-event details > summary { + cursor: pointer; + font-weight: 600; } -.conversation-message.agent.thinking { - display: flex; - align-items: center; - gap: 0.5em; - color: #999; - font-size: 0.9em; - background: #f5f0eb; +.talent-view-event--error { + color: #b42318; } -.conversation-message.error { - align-self: flex-start; - background: rgba(217, 119, 6, 0.06); - color: #1f2937; - border-left: 4px solid #d97706; - border-bottom-left-radius: 4px; - font-weight: 500; -} +@media (max-width: 768px) { + .app-bar { + padding: 10px; + gap: 10px; + } -.conversation-message.error::before { - content: '\26A0\FE0F '; - color: #d97706; + .chat-bar-form { + min-width: 0; + width: 100%; + } + + .talent-view-modal { + padding: 12px; + } } /* Error log display */ @@ -2406,36 +2207,6 @@ button.close { cursor: pointer; } -/* Visually-hidden expand/collapse button - focusable but invisible */ -.chat-bar-expand { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - background: none; - font: inherit; - color: inherit; -} - -.chat-bar-expand:focus-visible { - position: static; - width: auto; - height: auto; - margin: 0; - overflow: visible; - clip: auto; - white-space: normal; - font-size: 14px; - padding: 4px 8px; - border-radius: 4px; - background: rgba(0, 0, 0, 0.05); -} - /* ======================================== * Reduced Motion * Respects prefers-reduced-motion user preference @@ -2453,9 +2224,8 @@ button.close { animation-iteration-count: 1 !important; } - .chat-bar-thinking-dot { + .chat-bar-talent[data-status="active"] .chat-bar-talent-dot { animation: none; - opacity: 1; } .status-indicator--connecting { @@ -2494,10 +2264,6 @@ button.close { transform: none; } - .chat-bar-send.visible:active { - transform: none; - } - .mp-day:active:not(.mp-other):not(.mp-empty) { transform: none; } @@ -2522,7 +2288,6 @@ button.close { #hamburger:focus-visible, .facet-bar .status-icon:focus-visible, .chat-bar-send:focus-visible, -.chat-bar-dismiss:focus-visible, .date-nav-arrow:focus-visible, .facet-create-submit:focus-visible, .facet-create-cancel:focus-visible, diff --git a/convey/static/websocket.js b/convey/static/websocket.js index c17710b01..6aae4fa18 100644 --- a/convey/static/websocket.js +++ b/convey/static/websocket.js @@ -214,7 +214,6 @@ // Built-in tract: navigate browser to a path and/or switch facet listeners['navigate'] = [function(msg) { - if (window._closeConversationPanel) window._closeConversationPanel(); if (msg.facet && !msg.path) { window.selectFacet && window.selectFacet(msg.facet); } else if (msg.path) { diff --git a/convey/templates/app.html b/convey/templates/app.html index 15076a7b5..b6036604e 100644 --- a/convey/templates/app.html +++ b/convey/templates/app.html @@ -34,7 +34,7 @@ {% endif %} {% set body_classes = [] %} -{% set _ = body_classes.append('has-app-bar') %} +{% if app_registry.apps[app].app_bar %}{% set _ = body_classes.append('has-app-bar') %}{% endif %} {% if app_registry.apps[app].date_nav_enabled() and day %}{% set _ = body_classes.append('has-date-nav') %}{% endif %} diff --git a/convey/templates/chat_bar.html b/convey/templates/chat_bar.html new file mode 100644 index 000000000..d309345bc --- /dev/null +++ b/convey/templates/chat_bar.html @@ -0,0 +1,16 @@ +
+
+ +
+
+
+ + +
+
diff --git a/convey/templates/conversation_panel.html b/convey/templates/conversation_panel.html deleted file mode 100644 index 53386db1f..000000000 --- a/convey/templates/conversation_panel.html +++ /dev/null @@ -1,32 +0,0 @@ - -
- - -
- -
- - -
- - - -
-
- - -
- - -
- -
- - -
-
-
diff --git a/tests/test_no_legacy_chat_imports.py b/tests/test_no_legacy_chat_imports.py index 923a2f194..3784c7f29 100644 --- a/tests/test_no_legacy_chat_imports.py +++ b/tests/test_no_legacy_chat_imports.py @@ -11,6 +11,23 @@ ALLOWED_UNIFIED_PATHS = { ROOT / "apps/sol/maint/006_rename_unified_triage_providers.py", ROOT / "tests/test_maint_006_rename_unified_triage_providers.py", } +FORBIDDEN_CHAT_LITERALS = { + "conversationBackdrop", + "conversationMessages", + "chatBarResponsePanel", + "chatBarThinking", + "chatBarResponse", + "chatBarDismiss", + "conversation-backdrop", + "conversation-messages", + "conversation-separator", + "solstone:conversationState", + "solstone:chatBarState", + "panelFocusTrapHandler", + "openPanel", + "closePanel", + "_closeConversationPanel", +} def _parts(*pieces: str) -> str: @@ -42,6 +59,18 @@ def _python_files() -> list[Path]: ] +def _text_scan_files() -> list[Path]: + blocked_parts = {"tests/fixtures", ".venv", "node_modules", "__pycache__"} + files: list[Path] = [] + for pattern in ("*.html", "*.js"): + for path in ROOT.rglob(pattern): + path_str = str(path) + if any(part in path_str for part in blocked_parts): + continue + files.append(path) + return files + + def _parse(path: Path) -> ast.Module | None: try: return ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) @@ -88,3 +117,18 @@ def test_no_live_unified_literals_outside_migration_paths(): violations.append(str(path)) assert violations == [] + + +def test_no_legacy_chat_dom_literals_in_templates_or_js(): + violations: list[str] = [] + this_file = Path(__file__).resolve() + + for path in _text_scan_files(): + if path.resolve() == this_file: + continue + content = path.read_text(encoding="utf-8") + for literal in FORBIDDEN_CHAT_LITERALS: + if literal in content: + violations.append(f"{path}: {literal}") + + assert violations == []