/
deka
/
buffer
Обзор
Документация
Войти
/
deka
/
buffer
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
dev
tests/wiki-clipboard-lab.test.mjs
1 110 строк
114 KB
Maksim Ratnikov
fix: в Excel уезжала одна таблица из десяти
10 авг 2026, 16:45
10 авг 2026, 16:45
698d06e
Код
Авторство
О чём код?
import assert from "node:assert/strict"; import { existsSync, readFileSync } from "node:fs"; const html = readFileSync(new URL("../lab/wiki-clipboard-lab.html", import.meta.url), "utf8"); const buildScript = readFileSync(new URL("../tools/build-lab.mjs", import.meta.url), "utf8"); const style = html.match(/<style>([\s\S]*?)<\/style>/)?.[1] ?? ""; const rootRule = style.match(/:root\s*\{[\s\S]*?\n \}/)?.[0] ?? ""; const escapeHtmlFunction = html.match(/function escapeHtml\([\s\S]*?\n }\n\n function cleanTableText/)[0]; const formatHtmlEditorFunction = html.match(/function formatHtmlEditor\([\s\S]*?\n }\n\n function loadClipboardPayload/)[0]; assert.match(html, /id="pasteZone"/, "paste zone exists"); assert.equal(existsSync(new URL("../prompts/goal-extraction.system.md", import.meta.url)), true, "goal extraction prompt lives under prompts/"); assert.equal(existsSync(new URL("../prompts/meeting-narrative.md", import.meta.url)), true, "meeting narrative prompt lives under prompts/"); assert.match(buildScript, /const MEETING_NARRATIVE_PROMPT_PATH = "prompts\/meeting-narrative\.md";/, "build reads meeting narrative prompt from prompts/"); assert.match(buildScript, /const GOAL_EXTRACTION_PROMPT_PATH = "prompts\/goal-extraction\.system\.md";/, "build reads goal extraction prompt from prompts/"); assert.doesNotMatch(buildScript, /const GOAL_EXTRACTION_PROMPT_PATH = "goal-extraction\.system\.md";/, "build no longer reads goal extraction prompt from repo root"); assert.equal((html.match(/<!doctype html>/g) || []).length, 1, "assembled lab has exactly one doctype"); assert.equal((html.match(/<html lang="ru">/g) || []).length, 1, "assembled lab has exactly one root html element"); assert.equal((html.match(/<nav class="tool-tabs"/g) || []).length, 1, "assembled lab has exactly one top-level tool tabs nav"); assert.match(html, /<link rel="icon"[^>]*fill='%23157a4b'[^>]*%3EWL%3C/, "favicon uses the accent-green WL brand mark"); assert.doesNotMatch(html, /<link rel="icon"[^>]*fill='%230b6bcb'/, "favicon no longer uses the old blue mark"); assert.match(html, /class="tool-tabs"/, "lab has top-level tool tabs"); assert.match(html, /<div class="brand-row">[\s\S]*<strong>Wiki Lab<\/strong>[\s\S]*<span>Пространство знаний<\/span>[\s\S]*<\/div>\s*<nav class="tool-tabs"/, "sidebar starts with the product identity"); assert.match(html, /<div class="nav-group-label">Создание<\/div>[\s\S]*<div class="nav-group-label">Конвертация<\/div>/, "sidebar navigation exposes the two tool groups"); assert.match(html, /\.tool-tabs\s*\{[\s\S]*padding:\s*18px 12px;[\s\S]*overflow-y:\s*auto/, "tool tabs form a scrollable vertical sidebar"); assert.match(html, /\.tool-tabs button\[aria-selected="true"\]::before\s*\{[\s\S]*background:\s*var\(--accent\)/, "active tool tab has a restrained accent marker"); assert.match(html, /data-tool-tab="clipboard"/, "clipboard tool tab exists"); assert.match(html, /data-tool-tab="excel"/, "wiki-to-excel tool tab exists"); assert.match(html, /data-tool-tab="layout"/, "layout-json tool tab exists"); assert.match(html, /data-tool-tab="markdown"/, "html-to-markdown tool tab exists"); const toolTabOrder = Array.from(html.matchAll(/data-tool-tab="([^"]+)"/g)).map((match) => match[1]); assert.deepEqual(toolTabOrder, ["meet-protocol", "knowledge-base", "goal-extraction", "layout", "board", "md-to-wiki", "markdown", "excel", "clipboard"], "tool tabs go creation, then visualization, then conversion"); assert.match(html, /data-tool-tab="board"/, "board tool tab exists"); assert.match(html, /<section id="boardTool" class="tool-view" hidden>/, "board panel is not the default visible panel"); assert.match(html, /id="copyBoardPayload"/, "board tab exposes the clipboard button"); assert.match(html, /function sceneToBoardPayload\(/, "board payload codec is inlined into the lab"); assert.match(html, /function boardLayoutToScene\(/, "board layout engine is inlined into the lab"); assert.match(html, /function boardLayoutToMarkdown\(/, "board to markdown converter is inlined into the lab"); assert.match(html, /id="boardToMarkdown"/, "board tab exposes the markdown button"); assert.match(html, /const BOARD_PROMPT_TEXT = /, "board prompt is inlined into the lab"); assert.match(html, /id="buildBoardPromptFromText"/, "board tab offers the prompt builder"); assert.match(html, /board-layout-v1/, "board prompt carries the schema name"); assert.match(html, /<option value="board">JSON to Board<\/option>/, "board tab is reachable from the mobile switcher"); assert.match(html, /board: document\.getElementById\("boardTool"\)/, "board panel is registered in el.toolViews"); assert.match(html, /"board":\s*\{\s*title: "JSON to Board"/, "board tab has a description for the info popover"); assert.match(html, /board: parseBoardIntoScene/, "Ctrl+Enter works on the board tab"); assert.match(html, /if \(html !== null\) \{\s*copyEvent\.clipboardData\.setData\("text\/html", html\)/, "clipboard writer can publish text/plain alone"); assert.match(html, /<div class="tab-group" data-tab-group="generate">\s*<div class="nav-group-label">Создание<\/div>\s*<button data-tool-tab="meet-protocol"[\s\S]*?<button data-tool-tab="knowledge-base"[\s\S]*?<button data-tool-tab="goal-extraction"/, "generation group is meeting, knowledge base and goal"); assert.match(html, /<div class="tab-group" data-tab-group="visualize">\s*<div class="nav-group-label">Визуализация<\/div>\s*<button data-tool-tab="layout"[^>]*><span>JSON to SberTrack<\/span>[\s\S]*?<button data-tool-tab="board"[^>]*><span>JSON to Board<\/span>/, "visualization group holds SberTrack and Board"); assert.match(html, /GROUP_TITLES = \{ generate: "Создание", visualize: "Визуализация", convert: "Конвертация" \}/, "the active-tool heading knows the visualization group"); assert.doesNotMatch(html, /<span>Layout JSON to Wiki<\/span>/, "the old tab name is gone"); assert.match(html, /<div class="tab-group" data-tab-group="convert">[\s\S]*?<button data-tool-tab="md-to-wiki"[^>]*><span>Markdown to HTML<\/span>[\s\S]*?<button data-tool-tab="markdown"[^>]*><span>HTML to Markdown<\/span>[\s\S]*?<button data-tool-tab="excel"/, "conversion group starts with Markdown to HTML, then HTML to Markdown"); assert.match(html, /<div class="tab-group" data-tab-group="convert">[\s\S]*?<button data-tool-tab="clipboard"[^>]*>[\s\S]*?<\/button>\s*<\/div>/, "conversion group ends with clipboard"); assert.match(html, /\.tab-group\s*\{[\s\S]*display:\s*flex/, ".tab-group is a flex container"); assert.match(html, /data-tool-tab="meet-protocol" aria-controls="meetProtocolTool" aria-selected="true"/, "meeting protocol tab is selected by default"); assert.match(html, /<section id="meetProtocolTool" class="tool-view">/, "meeting protocol panel is visible by default"); assert.match(html, /<section id="layoutTool" class="tool-view" hidden>/, "layout panel is not the default visible panel"); assert.match(html, /<section id="clipboardTool" class="tool-view" hidden>/, "clipboard panel is not the default visible panel"); assert.match(html, /<div class="app-topbar">[\s\S]*id="activeToolGroup"[\s\S]*id="activeToolTitle"[\s\S]*id="activeToolInfo"[^>]*data-active-tool-info/, "desktop and mobile share a synchronized active-tool heading"); assert.match(html, /<select id="mobileToolSelect"[\s\S]*<option value="meet-protocol">Протокол встречи<\/option>[\s\S]*<option value="knowledge-base">База знаний<\/option>[\s\S]*<option value="layout">JSON to SberTrack<\/option>[\s\S]*<option value="md-to-wiki">Markdown to HTML<\/option>[\s\S]*<option value="markdown">HTML to Markdown<\/option>/, "mobile tool switcher mirrors the prioritized desktop order"); assert.match(html, /el\.activeToolTitle\.textContent = description\?\.title/, "tab activation updates the shell title"); assert.match(html, /el\.mobileToolSelect\.value = name/, "tab activation keeps the mobile selector synchronized"); assert.match(html, /function initMobilePaneTabs\(\)[\s\S]*button\.dataset\.mobilePaneTarget = pane/, "two-pane tools get compact mobile input and output tabs"); assert.doesNotMatch(html, /upgradeExternalLauncherGroups/, "LLM launchers are laid out statically, not reflowed by JS on load"); assert.match(html, /<span class="llm-launchers">\s*<span class="external-group-label">открыть в:<\/span>(?:\s*<button class="btn-external [a-z-]+-llm-link"[\s\S]*?<\/button>){5}\s*<\/span>\s*<button id="clear[A-Za-z]+" class="btn-clear">Очистить<\/button>/, "LLM launchers form a static group in the action flow; Очистить is the last button"); assert.match(html, /\.toolbar \.btn-clear\s*\{[^}]*margin-left:\s*auto/, "Очистить is pinned to the far right on every toolbar"); assert.match(html, /<symbol id="ico-zai"[\s\S]*<symbol id="ico-deepseek"[\s\S]*<symbol id="ico-gigachat"[\s\S]*<symbol id="ico-kimi"[\s\S]*<symbol id="ico-qwen"/, "brand icon sprite defines all five LLM marks"); assert.match(html, /\.llm-ico\s*\{/, "llm-ico CSS rule exists"); assert.match(html, /\.llm-launchers\s*\{/, "llm-launchers CSS rule exists"); assert.match(html, /@media \(max-width: 900px\)[\s\S]*header\s*\{\s*display:\s*none;/, "mobile layout replaces the desktop sidebar before two-pane content becomes cramped"); assert.match(html, /@keyframes tool-view-enter\s*\{[\s\S]*opacity:\s*0;[\s\S]*transform:\s*translateY\(4px\)/, "tool changes use a restrained entrance transition"); assert.match(html, /@keyframes floating-surface-enter\s*\{/, "menus and popovers share a short floating-surface transition"); assert.match(html, /@media \(prefers-reduced-motion: reduce\)[\s\S]*animation-duration:\s*0\.01ms !important/, "motion respects the reduced-motion preference"); assert.doesNotMatch(html, /(?:linear|radial)-gradient\(/, "modern shell does not depend on decorative gradients"); assert.match(html, /data-tool-tab="meet-protocol"/, "meeting protocol tool tab exists"); assert.match(html, /id="clipboardTool"/, "clipboard tool panel exists"); assert.match(html, /id="meetProtocolTool"/, "meeting protocol tool panel exists"); assert.match(html, /id="excelTool"/, "wiki-to-excel tool panel exists"); assert.match(html, /id="excelPasteZone"/, "wiki-to-excel paste zone exists"); assert.match(html, /id="excelPasteZone"[\s\S]*contenteditable="true"/, "wiki-to-excel paste zone accepts direct paste"); assert.doesNotMatch(html, /paste-callout/, "paste target guidance does not use the rejected separate callout block"); assert.match(html, /\.paste-target\s*\{[\s\S]*position:\s*relative/, "paste target is an in-field overlay wrapper"); assert.match(html, /\.paste-target-prompt\s*\{[\s\S]*font-size:\s*24px;[\s\S]*font-weight:\s*400;[\s\S]*font-style:\s*italic;[\s\S]*color:\s*var\(--accent\)/, "paste target heading is medium, italic, non-bold, and green"); assert.match(html, /\.paste-target-prompt\s*\{[\s\S]*white-space:\s*nowrap/, "paste target heading stays on one line"); assert.match(html, /\.paste-target-helper\s*\{[\s\S]*font-size:\s*var\(--placeholder-font-size\)/, "paste target helper keeps normal helper text size"); assert.match(html, /\.paste-target:focus-within \.paste-target-overlay\s*\{\s*display:\s*none;/, "in-field paste guidance hides when the field is focused"); assert.match(html, /\.paste-target > textarea:not\(:placeholder-shown\) ~ \.paste-target-overlay,[\s\S]*\.paste-target > \.excel-paste-zone:not\(:empty\) ~ \.paste-target-overlay\s*\{\s*display:\s*none;/, "in-field paste guidance hides when the field has content"); for (const targetId of [ "layoutJsonInput", "meetTranscriptInput", "kbCurrentInput", "kbProtocolInput", "goalExtractionDataInput", "goalExtractionJsonInput", "excelPasteZone", "markdownHtmlInput", "mdToWikiInput", "pasteZone" ]) { assert.match(html, new RegExp(`<div class="paste-target">[\\s\\S]*?id="${targetId}"[\\s\\S]*?<div class="paste-target-overlay" aria-hidden="true">\\s*<div class="paste-target-prompt">Вставить сюда\\.\\.\\.<\\/div>\\s*<div class="paste-target-helper">[\\s\\S]*?<\\/div>\\s*<\\/div>`), `${targetId} has an in-field paste target overlay`); } assert.doesNotMatch(html, /id="useCurrentTable"/, "wiki-to-excel no longer has Use Current"); assert.match(html, /id="layoutTool"/, "layout-json tool panel exists"); assert.match(html, /id="markdownTool"/, "html-to-markdown tool panel exists"); assert.doesNotMatch(html, /id="excelInput"/, "wiki-to-excel raw input textarea is removed"); assert.match(html, /excelSourceHtml:\s*""/, "wiki-to-excel keeps raw source in state instead of a visible field"); assert.match(html, /id="excelOutput"/, "wiki-to-excel output exists"); assert.match(html, /<div class="prompt-output-shell" data-output-shell="excel">[\s\S]*<textarea id="excelOutput"[\s\S]*<div id="excelOutputEmpty" class="preview-empty prompt-output-empty" data-empty-for="excel-output">/, "Excel output has a blocked preview-style empty state"); assert.match(html, /<p class="preview-empty-hint">Сконвертируйте таблицу — здесь появится результат для Excel<\/p>/, "Excel output empty hint explains the result"); assert.match(html, /id="normalizeExcel"/, "wiki-to-excel normalize action exists"); assert.match(html, /<button id="normalizeExcel">Convert<\/button>/, "Excel Convert is a neutral button (auto-runs on paste)"); assert.match(html, /<button id="copyExcelOutput" class="btn-primary">Copy Excel<\/button>/, "Excel primary action is Copy Excel"); assert.match(html, /id="copyExcelOutput"/, "wiki-to-excel copy action exists"); assert.match(html, /excelOutputEmpty: document\.getElementById\("excelOutputEmpty"\)/, "excel output empty state is wired"); assert.match(html, /id="layoutJsonInput"/, "layout-json input exists"); assert.match(html, /id="layoutHtmlOutput"/, "layout wiki HTML output exists"); assert.match(html, /<div id="layoutPreview"[^>]*>\s*<div class="preview-empty" data-empty-for="layout">/, "#layoutPreview contains a .preview-empty child"); assert.match(html, /<p class="preview-empty-hint">Вставьте JSON слева — здесь появится preview<\/p>/, "Layout empty hint exists"); assert.match(html, /id="renderLayoutJson"/, "layout-json render action exists"); assert.match(html, /<button id="renderLayoutJson">Render<\/button>/, "Layout Render is a neutral button (auto-runs on paste)"); assert.match(html, /id="copyLayoutHtml"/, "layout wiki HTML copy action exists"); assert.match(html, /id="copyLayoutRich"/, "layout rich copy action exists"); assert.match(html, /<button id="copyLayoutRich" class="btn-primary">Copy Back<\/button>/, "Layout primary action is Copy Back"); assert.match(html, /id="markdownHtmlInput"/, "html-to-markdown input exists"); assert.match(html, /id="markdownOutput"/, "markdown output exists"); assert.match(html, /<div class="prompt-output-shell" data-output-shell="markdown">[\s\S]*<textarea id="markdownOutput"[\s\S]*<div id="markdownOutputEmpty" class="preview-empty prompt-output-empty" data-empty-for="markdown-output">/, "Markdown output has a blocked preview-style empty state"); assert.match(html, /<p class="preview-empty-hint">Сконвертируйте HTML — здесь появится Markdown<\/p>/, "Markdown output empty hint explains the result"); assert.match(html, /id="convertMarkdown"/, "html-to-markdown convert action exists"); assert.match(html, /<button id="convertMarkdown">Convert<\/button>/, "Markdown Convert is a neutral button (auto-runs on paste)"); assert.match(html, /id="copyMarkdown"/, "html-to-markdown copy action exists"); assert.match(html, /<button id="copyMarkdown" class="btn-primary">Copy Markdown<\/button>/, "Markdown primary action is Copy Markdown"); assert.match(html, /id="clearMarkdown"/, "html-to-markdown clear action exists"); assert.match(html, /markdownHtmlInput: document\.getElementById\("markdownHtmlInput"\)/, "html-to-markdown input is wired"); assert.match(html, /markdownOutput: document\.getElementById\("markdownOutput"\)/, "markdown output is wired"); assert.match(html, /markdownOutputEmpty: document\.getElementById\("markdownOutputEmpty"\)/, "markdown output empty state is wired"); assert.match(html, /function htmlToMarkdown/, "html-to-markdown converter exists"); assert.match(html, /function loadMarkdownClipboardPayload/, "html-to-markdown clipboard loader exists"); const convertMarkdownFn = html.match(/function convertHtmlToMarkdown\(\)\s*\{[\s\S]*?\n \}/); assert.ok(convertMarkdownFn, "convertHtmlToMarkdown block parses"); assert.match(convertMarkdownFn[0], /setPromptOutputEmpty\(el\.markdownOutput,\s*el\.markdownOutputEmpty\)/, "convertHtmlToMarkdown updates blocked output empty state"); const loadMarkdownFn = html.match(/function loadMarkdownClipboardPayload\(clipboardData\)\s*\{[\s\S]*?\n \}/); assert.ok(loadMarkdownFn, "loadMarkdownClipboardPayload block parses"); assert.match(loadMarkdownFn[0], /setPromptOutputEmpty\(el\.markdownOutput,\s*el\.markdownOutputEmpty\)/, "markdown clipboard paste updates blocked output empty state"); assert.match(html, /case "table":\s*return markdownTableFromElement/, "html-to-markdown converts tables through table helper"); assert.match(html, /case "a":\s*return markdownLinkFromElement/, "html-to-markdown converts links through link helper"); assert.match(html, /case "del":[\s\S]*return content \? `~~\$\{content\}~~`/, "html-to-markdown converts deleted text"); assert.match(html, /function markdownMediaFromElement/, "html-to-markdown preserves linked audio, video and iframe media"); assert.match(html, /function markdownDefinitionListFromElement/, "html-to-markdown converts definition lists"); assert.match(html, /function markdownDetailsFromElement/, "html-to-markdown converts details and summary content"); assert.match(html, /function markdownFigureFromElement/, "html-to-markdown converts figures and captions"); assert.match(html, /function markdownTableWithCaption/, "html-to-markdown preserves table captions"); assert.match(html, /node\.getAttribute\("start"\)/, "html-to-markdown respects ordered-list start values"); assert.match(html, /node\.hasAttribute\("reversed"\)/, "html-to-markdown respects reversed ordered lists"); assert.match(html, /child\.getAttribute\("type"\)\?\.toLowerCase\(\) === "checkbox"/, "html-to-markdown converts task-list checkboxes"); assert.match(html, /if \(isIgnoredMarkdownElement\(node\)\) \{\s*return "";\s*\}/, "html-to-markdown omits hidden and non-content elements"); assert.match(html, /el\.convertMarkdown\.addEventListener\("click", convertHtmlToMarkdown\)/, "html-to-markdown convert action is wired"); assert.match(html, /el\.copyMarkdown\.addEventListener\("click",\s*\(event\)\s*=>\s*copyMarkdownOutput\(event\)\)/, "html-to-markdown copy action is wired with button feedback"); assert.match(html, /el\.clearMarkdown\.addEventListener\("click"/, "html-to-markdown clear action is wired"); assert.match(html, /el\.markdownHtmlInput\.addEventListener\("paste"/, "html-to-markdown input handles rich paste"); assert.match(html, /function scheduleAfterPaste\(action\)/, "paste-triggered conversion helper exists"); assert.match(html, /el\.layoutJsonInput\.addEventListener\("paste"[\s\S]*scheduleAfterPaste\(renderLayoutJson\)/, "layout paste renders immediately after insertion"); assert.match(html, /el\.meetTranscriptInput\.addEventListener\("paste"[\s\S]*scheduleAfterPaste\(prepareMeetProtocolPrompt\)/, "meeting transcript paste prepares prompt immediately after insertion"); assert.doesNotMatch(html, /el\.excelInput\.addEventListener/, "wiki-to-excel no longer wires a hidden raw input paste handler"); assert.match(html, /el\.mdToWikiInput\.addEventListener\("paste"[\s\S]*scheduleAfterPaste\(convertMdToWiki\)/, "markdown-to-wiki paste renders immediately after insertion"); assert.match(html, /type === "text\/html" \|\| type === "text\/plain"/, "html-to-markdown paste handles separated html/plain payloads"); assert.match(html, /clipboardData\.getData\("text\/html"\)/, "html-to-markdown paste reads separated text/html"); assert.match(html, /clipboardData\.getData\("text\/plain"\)/, "html-to-markdown paste reads separated text/plain"); assert.match(html, /layoutHtmlOutput: document\.getElementById\("layoutHtmlOutput"\)/, "layout raw HTML output is wired"); assert.match(html, /<input id="layoutHtmlOutput" type="hidden"/, "layout raw HTML is stored without a visible code textarea"); assert.doesNotMatch(html, /<textarea id="layoutHtmlOutput"/, "layout raw HTML textarea is not visible"); assert.match(html, /id="editor"/, "editable output zone exists"); assert.match(html, /contenteditable="true"/, "editor is contenteditable"); assert.match(html, /addEventListener\("paste"/, "paste event is handled"); assert.match(html, /clipboardData\.types/, "clipboard types are inspected"); assert.match(html, /getData\("text\/html"\)/, "text/html is read from pasted clipboard data"); assert.match(html, /getData\("text\/plain"\)/, "text/plain is read from pasted clipboard data"); assert.match(html, /new ClipboardItem/, "rich clipboard copy is supported"); assert.match(html, /"text\/html"/, "copy payload includes text/html"); assert.match(html, /"text\/plain"/, "copy payload includes text/plain"); assert.match(html, /execCommand\("copy"\)/, "selection copy fallback is present"); assert.match(html, /class="panel-body raw-body"/, "raw panels use expandable bodies"); assert.match(html, /resize: both;/, "raw textareas can be resized"); assert.match(html, /function formatHtml/, "HTML formatter exists"); assert.match(html, /return String\(value \?\? ""\)/, "HTML escaping preserves text content"); assert.doesNotMatch(escapeHtmlFunction, /document\.createElement\("template"\)/, "HTML escaping does not use template innerHTML"); assert.match(html, /formatHtml\(html\)/, "pasted HTML is formatted before display"); assert.match(html, /formatHtml\(state\.currentHtml\)/, "edited HTML is formatted when synced"); assert.match(html, /<div class="panel editor-panel">[\s\S]*<h2>Редактор<\/h2>[\s\S]*id="editor"/, "editor panel has bounded layout class"); assert.match(html, /<section class="workspace">\s*<div class="panel editor-panel">/, "workspace editor panel is bounded directly"); assert.doesNotMatch(html, /<div class="panel editor-panel">\s*<div class="panel-title">\s*<h2>Форматы<\/h2>/, "formats panel is not using editor layout"); assert.match(html, /\.editor-panel\s*{[\s\S]*grid-template-rows: auto minmax\(0, 1fr\);/, "editor panel constrains editor height"); assert.match(html, /\.editor\s*{[\s\S]*min-height: 0;/, "visual editor can shrink inside bounded panel"); assert.match(html, /#htmlEditor\s*{[\s\S]*min-height: 0;/, "HTML editor can shrink inside bounded panel"); assert.match(html, /id="formatHtmlEditor"/, "HTML editor has an explicit format button"); assert.match(html, /formatHtmlEditor: document\.getElementById\("formatHtmlEditor"\)/, "HTML format button is wired"); assert.match(html, /function formatHtmlEditor/, "HTML editor format action exists"); assert.match(formatHtmlEditorFunction, /const html = wasHtmlMode \? el\.htmlEditor\.value : state\.currentHtml \|\| getCurrentHtml\(\)/, "HTML editor format action formats the active editor HTML"); assert.match(formatHtmlEditorFunction, /el\.htmlEditor\.value = formatHtml\(html\)/, "HTML editor format action writes formatted HTML"); assert.doesNotMatch(formatHtmlEditorFunction, /setMode\("html"\)/, "HTML format action does not discard active HTML edits"); assert.match(formatHtmlEditorFunction, /const wasHtmlMode = !el\.htmlEditor\.classList\.contains\("hidden"\)/, "HTML format action detects active HTML mode"); assert.match(html, /el\.formatHtmlEditor\.addEventListener\("click", formatHtmlEditor\)/, "HTML format button triggers formatting"); assert.match(html, /id="payloadDump"/, "combined payload textarea exists"); assert.match(html, /<textarea id="payloadDump" readonly/, "combined payload output is read-only"); assert.match(html, /payloadDump: document\.getElementById\("payloadDump"\)/, "combined payload output is wired"); assert.match(html, /function buildPayloadDump/, "combined payload is built explicitly"); assert.match(html, /mimeTypes: state\.types/, "combined payload includes MIME types"); assert.match(html, /original:[\s\S]*text\/html/, "combined payload includes original text/html"); assert.match(html, /original:[\s\S]*text\/html:formatted/, "combined payload includes formatted original text/html"); assert.match(html, /original:[\s\S]*text\/plain/, "combined payload includes original text/plain"); assert.match(html, /copyBack:[\s\S]*text\/html/, "combined payload includes copy-back text/html"); assert.match(html, /copyBack:[\s\S]*text\/plain/, "combined payload includes copy-back text/plain"); assert.match(html, /id="copyPayload"/, "combined payload can be copied for analysis"); // Сборка склеивает все модули в одну область видимости, поэтому две одноимённые // функции из разных файлов молча перекрывают друг друга: побеждает последняя, а // вызывающий код получает чужую реализацию без единой ошибки при сборке. const topLevelFunctionNames = Array.from(html.matchAll(/^ {4}(?:async )?function ([A-Za-z0-9_$]+)\(/gm)).map((match) => match[1]); const duplicateFunctionNames = topLevelFunctionNames.filter((name, index) => topLevelFunctionNames.indexOf(name) !== index); assert.deepEqual(duplicateFunctionNames, [], "no two assembled modules declare the same top-level function name"); assert.match(html, /function normalizeTableToRows/, "Excel table normalizer exists"); assert.match(html, /function normalizeTableToExcelPayload/, "Excel table rich payload normalizer exists"); assert.match(html, /function normalizeTablesToExcelPayload/, "Excel normalizer stacks every pasted table, not just the first"); assert.match(html, /function topLevelTables/, "Excel tab collects top-level tables so nested ones are not counted twice"); assert.match(html, /function cloneCellStyle/, "Excel table style preservation exists"); assert.match(html, /function activateToolTab/, "top-level tool tabs are switchable"); assert.match(html, /function renderLayoutJson/, "layout-json render action exists"); assert.match(html, /function setPreviewEmpty\(container, isEmpty\)/, "setPreviewEmpty toggle exists"); assert.match(html, /function renderIntoPreview\(container, html\)/, "renderIntoPreview helper exists"); assert.match(html, /function renderLayoutJson\(\)\s*\{[\s\S]*setPreviewEmpty\(el\.layoutPreview/, "renderLayoutJson toggles preview-empty"); assert.match(html, /function renderLayoutBlocks/, "browser layout renderer is implemented"); assert.match(html, /async function copyLayoutRich/, "layout rich copy function exists"); assert.match(html, /el\.copyLayoutRich\.addEventListener\("click",\s*\(event\)\s*=>\s*copyLayoutRich\(event\)\)/, "layout rich copy click is wired with button feedback"); assert.match(html, /function normalizeExcelInput/, "manual wiki-to-excel action exists"); assert.match(html, /function loadExcelClipboardPayload/, "wiki-to-excel clipboard paste loader exists"); const normalizeExcelFn = html.match(/function normalizeExcelInput\(\)\s*\{[\s\S]*?\n \}/); assert.ok(normalizeExcelFn, "normalizeExcelInput block parses"); assert.match(normalizeExcelFn[0], /setPromptOutputEmpty\(el\.excelOutput,\s*el\.excelOutputEmpty\)/, "normalizeExcelInput updates blocked output empty state"); assert.match(normalizeExcelFn[0], /normalizeTablesToExcelPayload\(tables\)/, "normalizeExcelInput converts every pasted table"); assert.doesNotMatch(html, /function tableFromHtml/, "the single-table lookup that silently dropped the rest is gone"); assert.match(html, /el\.excelPasteZone\.addEventListener\("paste"/, "wiki-to-excel paste zone handles paste"); assert.doesNotMatch(html, /el\.excelInput\.addEventListener\("paste"/, "wiki-to-excel raw input paste handler is gone"); assert.match(html, /clipboardData\.getData\("text\/html"\)/, "wiki-to-excel paste reads text/html"); assert.match(html, /function writeClipboardViaCopyEvent\(html, text\)/, "verbatim copy-event clipboard writer exists"); assert.match(html, /copyEvent\.clipboardData\.setData\("text\/html", html\)/, "copy-event writer sets rich text\/html payload"); assert.match(html, /copyEvent\.clipboardData\.setData\("text\/plain", text\)/, "copy-event writer sets TSV text\/plain payload"); const copyExcelOutputFn = html.match(/async function copyExcelOutput\([\s\S]*?\n \}/); assert.ok(copyExcelOutputFn, "copyExcelOutput block parses"); assert.match(copyExcelOutputFn[0], /if \(!writeClipboardViaCopyEvent\(html, tsv\)\)/, "Excel copy prefers the verbatim copy-event path"); assert.match(html, /if \(text !== null\) \{\s*copyEvent\.clipboardData\.setData\("text\/plain", text\);/, "copy-event writer can publish text/html alone"); const richCopyFn = html.match(/async function copyRich\([\s\S]*?\n \}/); assert.ok(richCopyFn, "copyRich block parses"); assert.match(richCopyFn[0], /if \(!writeClipboardViaCopyEvent\(html, plain\)\)/, "Clipboard round-trip copy keeps both flavours"); // The wiki editor picks text/plain whenever it is offered and rebuilds the page as // one tab-separated table, so the two wiki-targeted copies publish text/html alone. for (const fnName of ["copyLayoutRich", "copyMdToWikiRich"]) { const wikiCopyFn = html.match(new RegExp(`async function ${fnName}\\([\\s\\S]*?\\n \\}`)); assert.ok(wikiCopyFn, `${fnName} block parses`); assert.match(wikiCopyFn[0], /const verbatim = writeClipboardViaCopyEvent\(html, null\);/, `${fnName} publishes text/html alone via the copy event`); assert.doesNotMatch(wikiCopyFn[0], /"text\/plain"/, `${fnName} never puts text/plain in the clipboard`); assert.match(wikiCopyFn[0], /verbatim \? "[^"]*copy-event[^"]*" : "[^"]*async clipboard[^"]*"/, `${fnName} reports which clipboard path was used`); } assert.match(html, /new ClipboardItem\(\{\s*"text\/html": new Blob\(\[html\]/, "wiki-to-excel copy keeps async rich HTML fallback"); assert.match(html, /"text\/plain": new Blob\(\[tsv\]/, "wiki-to-excel copy keeps TSV fallback"); assert.match(html, /el\.normalizeExcel\.addEventListener\("click", normalizeExcelInput\)/, "manual wiki-to-excel action is wired"); assert.match(html, /el\.renderLayoutJson\.addEventListener\("click", renderLayoutJson\)/, "layout-json render action is wired"); assert.match(html, /el\.toolTabs\.forEach\(\(tab\) =>/, "top-level tool tabs are wired"); // App shell styles use semantic tokens rather than repeated literal colours. for (const token of [ "--bg", "--panel", "--panel-sunken", "--text", "--text-strong", "--muted", "--line", "--line-strong", "--accent", "--accent-strong", "--accent-soft", "--on-accent", "--ok", "--warn", "--warn-soft", "--bad", "--bad-soft", "--focus-ring", "--shadow", "--font-mono", "--font-sans", "--fs-xs", "--fs-sm", "--fs-base", "--fs-md", "--fs-lg", "--lh-tight", "--lh-base", "--ls-label", "--radius", "--panel-radius", "--btn-radius", "--gap-tight", "--gap", "--gap-loose", "--btn-h", "--header-h", "--statusbar-h" ]) { assert.match(rootRule, new RegExp(`${token}:`), `${token} is declared in :root`); } assert.match(rootRule, /--font-sans:\s*'IBM Plex Sans', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;/, "--font-sans prefers the self-hosted IBM Plex Sans"); assert.match(rootRule, /--font-mono:\s*'IBM Plex Mono', "SFMono-Regular", "Cascadia Mono", Consolas, "Liberation Mono", monospace;/, "--font-mono prefers the self-hosted IBM Plex Mono"); for (const [selector, expectedTokens] of [ ["button", ["--line", "--font-sans"]], ["button:hover", ["--line-strong", "--btn-bg-hover"]], [".btn-soft", ["--accent-soft", "--accent", "--accent-strong"]], [".btn-soft:hover", ["--accent-strong"]], [".btn-warning", ["--warn", "--warn-soft"]], [".btn-external", ["--muted", "--line-strong", "--font-sans"]], [".btn-external:hover", ["--accent-strong", "--accent"]], [".prompt-modal-backdrop", ["--overlay"]], [".prompt-modal-body", ["--panel-sunken", "--text-strong"]], [".brand", ["--accent"]], [".status-bar[data-state=\"ok\"]", ["--accent-soft", "--accent-strong", "--accent"]], [".status-bar[data-state=\"warn\"]", ["--warn-soft", "--warn"]], [".status-bar[data-state=\"bad\"]", ["--bad-soft", "--bad"]], [".toolbar", ["--line", "--panel-sunken"]], [".panel-title", ["--panel-sunken"]], [".popover", ["--font-sans", "--radius", "--shadow"]], [".popover-title", ["--font-sans", "--accent-strong"]], [".type-pill", ["--panel-sunken", "--line", "--muted", "--font-mono"]], [".type-pill.wiki", ["--accent-soft", "--accent", "--accent-strong"]], [".mini-label", ["--font-mono", "--ls-label"]], [".meta-grid", ["--font-mono"]], ["textarea::-webkit-scrollbar-thumb,\n .editor::-webkit-scrollbar-thumb,\n .layout-output::-webkit-scrollbar-thumb,\n .paste-zone::-webkit-scrollbar-thumb,\n .excel-paste-zone::-webkit-scrollbar-thumb,\n .prompt-modal-body::-webkit-scrollbar-thumb", ["--line-strong"]], [".paste-zone:focus,\n .excel-paste-zone:focus,\n textarea:focus", ["--accent", "--focus-ring"]], [".editor:focus", ["--focus-ring-inset"]], [".editor [data-type=\"info\"],\n .editor .node-info .container_15vtd", ["--wiki-info-border", "--wiki-info-bg", "--wiki-info-text"]], [".editor [data-type=\"status\"],\n .editor .node-status [class*=\"status-\"]", ["--line", "--panel-sunken", "--muted", "--font-mono"]], [".editor [data-type=\"uiButton\"] button,\n .editor .node-uiButton button", ["--btn-radius", "--line-strong", "--panel", "--text", "--font-sans"]], [".editor [data-type=\"uiButton\"][color=\"Foreground.forePositive\"] button", ["--accent", "--accent-soft", "--accent-strong"]], [".editor [data-type=\"uiButton\"][color=\"Foreground.foreAccent\"] button,\n .editor [data-type=\"uiButton\"][color=\"sky.sky500\"] button", ["--wiki-info-border", "--wiki-info-bg", "--wiki-info-text"]], [".editor [data-type=\"uiButton\"][color=\"Foreground.foreWarning\"] button", ["--wiki-warning-border", "--wiki-warning-bg", "--wiki-warning-text"]], [".editor [data-type=\"table\"],\n .editor .node-table", ["--wiki-table-border", "--wiki-table-bg"]], [".editor [data-type=\"table\"] th,\n .editor .node-table th", ["--wiki-table-head-bg", "--text-strong"]], [".editor [data-type=\"hint\"],\n .editor .hint_jjms4", ["--wiki-hint-bg", "--wiki-hint-text"]], [".editor [data-type=\"tabs\"],\n .editor .tabsWrapper_d6kqf", ["--wiki-chrome-border"]], [".editor .tabHeader_d6kqf", ["--wiki-chrome-bg", "--wiki-chrome-border"]], [".editor .tabLabel_1f49m", ["--wiki-chrome-border-soft"]], [".editor .tabTitle_z4u1j", ["--wiki-chrome-bg", "--wiki-chrome-text"]], [".layout-preview .preview-tab-bar", ["--wiki-chrome-bg", "--wiki-chrome-border"]], [".layout-preview .preview-tab-bar button", ["--wiki-chrome-border-soft", "--wiki-preview-tab-bg", "--wiki-preview-tab-text"]], [".layout-preview .preview-tab-bar button:hover", ["--wiki-preview-tab-hover-text"]], [".layout-preview .preview-tab-bar button[aria-selected=\"true\"]", ["--wiki-preview-tab-active-text", "--wiki-chrome-active-border"]], [".editor .expand,\n .editor [data-type=\"expand\"]", ["--wiki-chrome-border"]], [".editor .expand-summary", ["--wiki-chrome-bg", "--wiki-chrome-text"]] ]) { const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const rule = style.match(new RegExp(`${escapedSelector}\\s*\\{[\\s\\S]*?\\n \\}`))?.[0] ?? ""; assert.ok(rule, `${selector} CSS rule exists`); for (const token of expectedTokens) { assert.match(rule, new RegExp(`var\\(${token}\\)`), `${selector} uses ${token}`); } } const htmlEditorListener = html.match(/el\.htmlEditor\.addEventListener\("input", \(\) => \{[\s\S]*?\}\);/); assert.ok(htmlEditorListener, "htmlEditor input listener exists"); assert.match(htmlEditorListener[0], /state\.currentPlain\s*=/, "htmlEditor input updates state.currentPlain"); assert.match(htmlEditorListener[0], /el\.plainText\.value\s*=\s*state\.currentPlain/, "htmlEditor input mirrors plain into plainText textarea"); const getCurrentPlainTextFn = html.match(/function getCurrentPlainText\(\)\s*\{[\s\S]*?\n \}/); assert.ok(getCurrentPlainTextFn, "getCurrentPlainText exists"); assert.match(getCurrentPlainTextFn[0], /^\s*function getCurrentPlainText\(\)\s*\{\s*return el\.plainText\.value;\s*\}$/m, "getCurrentPlainText is a single-line plainText.value reader"); assert.doesNotMatch(getCurrentPlainTextFn[0], /state\.originalPlain/, "getCurrentPlainText no longer compares against originalPlain"); assert.match(html, /lastEditedSurface:\s*null/, "state declares lastEditedSurface"); const setModeFn = html.match(/function setMode\(mode\)\s*\{[\s\S]*?\n \}/); assert.ok(setModeFn, "setMode exists"); assert.match(setModeFn[0], /state\.lastEditedSurface\s*===\s*"html"/, "setMode preserves html surface when last edit was html"); assert.match(setModeFn[0], /state\.lastEditedSurface\s*===\s*"visual"/, "setMode preserves visual surface when last edit was visual"); assert.match(html, /el\.editor\.addEventListener\("input", \(\) => \{[\s\S]*?state\.lastEditedSurface\s*=\s*"visual"/, "visual editor input marks surface as visual"); assert.match(html, /el\.htmlEditor\.addEventListener\("input", \(\) => \{[\s\S]*?state\.lastEditedSurface\s*=\s*"html"/, "html editor input marks surface as html"); assert.match(html, /el\.plainText\.addEventListener\("input", \(\) => \{[\s\S]*?state\.lastEditedSurface\s*=\s*"plain"/, "plain text input marks surface as plain"); assert.match(html, /el\.applyRaw\.addEventListener\("click",[\s\S]*?state\.lastEditedSurface\s*=\s*"visual"/, "Apply HTML resets surface to visual"); assert.match(html, /el\.syncRaw\.addEventListener\("click",[\s\S]*?state\.lastEditedSurface\s*=\s*"html"/, "Sync From Editor resets surface to html"); assert.match(html, /function formatHtmlEditor\(\)\s*\{[\s\S]*?state\.lastEditedSurface\s*=\s*"html"/, "Format HTML resets surface to html"); const clearAllFn = html.match(/function clearAll\(\)\s*\{[\s\S]*?\n \}/); assert.ok(clearAllFn, "clearAll exists"); assert.match(clearAllFn[0], /el\.pasteZone\.innerHTML\s*=\s*""/, "clearAll resets paste zone"); assert.match(clearAllFn[0], /el\.originalHtml\.value\s*=\s*""/, "clearAll resets original HTML"); assert.match(clearAllFn[0], /el\.payloadDump\.value\s*=\s*""/, "clearAll resets payload dump"); assert.match(clearAllFn[0], /el\.plainText\.value\s*=\s*""/, "clearAll resets plain text"); assert.match(clearAllFn[0], /el\.editor\.innerHTML\s*=\s*""/, "clearAll resets editor"); assert.match(clearAllFn[0], /el\.htmlEditor\.value\s*=\s*""/, "clearAll resets html editor"); assert.doesNotMatch(clearAllFn[0], /el\.excelInput\.value/, "clearAll no longer touches Excel state"); assert.doesNotMatch(clearAllFn[0], /el\.layoutJsonInput\.value/, "clearAll no longer touches Layout state"); assert.doesNotMatch(clearAllFn[0], /el\.markdownHtmlInput\.value/, "clearAll no longer touches Markdown state"); assert.doesNotMatch(clearAllFn[0], /el\.mdToWikiInput\.value/, "clearAll no longer touches MD-to-Wiki state"); const statusBarRule = html.match(/\.status-bar \{[\s\S]*?\n \}/); assert.ok(statusBarRule, ".status-bar CSS rule exists"); assert.match(statusBarRule[0], /position:\s*sticky/, ".status-bar is sticky under the header"); assert.match(statusBarRule[0], /height:\s*var\(--statusbar-h\)/, ".status-bar uses the shared height token"); assert.match(statusBarRule[0], /font-family:\s*var\(--font-mono\)/, ".status-bar renders in mono"); for (const [selector, message] of [ ["button", "base buttons use sans, not mono"], [".tool-tabs button", "tool tabs use sans, not mono"], ["h2", "panel headings use sans, not mono"] ]) { const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const rule = style.match(new RegExp(`${escapedSelector}\\s*\\{[\\s\\S]*?\\n \\}`))?.[0] ?? ""; assert.ok(rule, `${selector} CSS rule exists`); assert.match(rule, /font-family:\s*var\(--font-sans\)/, message); assert.doesNotMatch(rule, /font-family:\s*var\(--font-mono\)/, message); } assert.match(html, /function flashCopySuccess\(btn, opts = \{\}\)/, "flashCopySuccess helper exists"); assert.match(html, /\.btn-flash-ok\s*\{[\s\S]*background:\s*var\(--accent\)/, ".btn-flash-ok CSS rule exists"); for (const handlerName of [ "copyRich", "copyPayload", "copyExcelOutput", "copyLayoutRich", "copyLayoutHtml", "copyMarkdownOutput", "copyMdToWikiRich", "saveMdToWikiHtml", "copyMeetProtocolOutput", "prepareKbPrompt" ]) { assert.match( html, new RegExp(`function\\s+(?:async\\s+)?${handlerName}\\([^)]*\\)\\s*\\{[\\s\\S]*?flashCopySuccess\\(`), `${handlerName} flashes the calling button on success` ); } assert.match(html, /async function copyMeetProtocolAndOpen\(url,\s*button\)\s*\{[\s\S]*flashCopySuccess\([\s\S]*?duration:\s*400/, "Meet Protocol Copy & Open uses a shorter 400ms flash before opening the URL"); assert.match(html, /async function copyKbPromptAndOpen\(url,\s*button\)\s*\{[\s\S]*flashCopySuccess\([\s\S]*?duration:\s*400/, "Knowledge Base Copy & Open uses a shorter 400ms flash before opening the URL"); const activateToolTabFn = html.match(/function activateToolTab\(name, \{ persist = true \} = \{\}\)\s*\{[\s\S]*?\n \}/); assert.ok(activateToolTabFn, "activateToolTab exists"); assert.match(activateToolTabFn[0], /persist\s*=\s*true/, "activateToolTab can skip persistence during init"); assert.match(activateToolTabFn[0], /localStorage\.setItem\(ACTIVE_TAB_STORAGE_KEY, name\)/, "activateToolTab persists active tab"); assert.match(html, /const DEFAULT_TOOL_TAB\s*=\s*"meet-protocol"/, "meeting protocol is the default tool tab"); assert.match(html, /localStorage\.getItem\(ACTIVE_TAB_STORAGE_KEY\)/, "init reads stored tab from the configured key"); assert.match(html, /activateToolTab\(initialTab, \{ persist: false \}\)/, "init activates a default tab without rewriting storage"); assert.match(html, /Object\.prototype\.hasOwnProperty\.call\(el\.toolViews, [a-zA-Z]+\)/, "init validates stored tab against known views"); assert.match(html, /document\.addEventListener\("keydown",/, "global keydown listener exists"); const keydownBlock = html.match(/document\.addEventListener\("keydown",[\s\S]*?\}\);/); assert.ok(keydownBlock, "keydown listener block parses"); assert.match(keydownBlock[0], /event\.ctrlKey\s*\|\|\s*event\.metaKey/, "keydown handler checks ctrl/meta modifier"); assert.match(keydownBlock[0], /event\.key\s*===\s*"Enter"/, "keydown handler reacts to Enter"); assert.match(keydownBlock[0], /clipboard:\s*copyRich/, "keydown handler maps clipboard tab to copyRich"); assert.match(keydownBlock[0], /excel:\s*normalizeExcelInput/, "keydown handler maps excel tab to normalizeExcelInput"); assert.match(keydownBlock[0], /layout:\s*renderLayoutJson/, "keydown handler maps layout tab to renderLayoutJson"); assert.match(keydownBlock[0], /markdown:\s*convertHtmlToMarkdown/, "keydown handler maps markdown tab to convertHtmlToMarkdown"); assert.match(keydownBlock[0], /event\.preventDefault\(\)/, "keydown handler prevents default newline"); const statusBarElement = html.match(/<div id="statusBar"[^>]*>/); assert.ok(statusBarElement, "#statusBar element exists"); assert.match(statusBarElement[0], /class="status-bar"/, "#statusBar has the status-bar class"); assert.match(statusBarElement[0], /data-state="idle"/, "#statusBar starts in idle state"); assert.match(statusBarElement[0], /role="status"/, "#statusBar has role=status for assistive tech"); assert.match(statusBarElement[0], /aria-live="polite"/, "#statusBar uses polite live region"); assert.match(html, /<span class="status-prefix">— ОЖИДАНИЕ —<\/span>\s*<span class="status-message">Ожидание вставки<\/span>/, "#statusBar has prefix + message spans"); assert.doesNotMatch(html, /<div id="status"\s/, "old #status element is gone from the header"); // Version/date are auto-bumped by the pre-commit hook (tools/bump-version.mjs), // so assert the stamp's structure, not a literal version. assert.match(html, /<header>[\s\S]*<div class="release-meta">\s*<a class="contact-link" href="https:\/\/sberchat\.sberbank\.ru\/@MMRatnikov" target="_blank" rel="noopener noreferrer">По любым вопросам обращайтесь<\/a>\s*<span class="version-meta" aria-label="Версия \d+\.\d+\.\d+ от [^"]+">\s*<span class="version-number">v\d+\.\d+\.\d+<\/span>\s*<time class="version-date" datetime="\d{4}-\d{2}-\d{2}">\d{2}\.\d{2}\.\d{4}<\/time>\s*<\/span>\s*<\/div>\s*<\/header>/, "header ends with a visible version stamp below the contact link"); assert.match(html, /\.release-meta\s*\{[\s\S]*flex-direction:\s*column;[\s\S]*margin-left:\s*auto;/, "release metadata stacks the version below the contact link"); assert.match(html, /\.version-number\s*\{[\s\S]*border:\s*1px solid var\(--accent\);[\s\S]*background:\s*var\(--accent-soft\);[\s\S]*font-weight:\s*700;/, "version number is rendered as a high-contrast release stamp"); assert.match(html, /<button id="clearLayout"[^>]*>Очистить<\/button>/, "Layout tab has a Clear button"); assert.match(html, /el\.clearLayout\.addEventListener\("click",/, "Layout Clear button is wired"); const clearLayoutHandler = html.match(/el\.clearLayout\.addEventListener\("click",\s*\(\)\s*=>\s*\{[\s\S]*?\}\);/); assert.ok(clearLayoutHandler, "Layout Clear handler block parses"); assert.match(clearLayoutHandler[0], /el\.layoutJsonInput\.value\s*=\s*""/, "Layout Clear resets input"); assert.match(clearLayoutHandler[0], /el\.layoutHtmlOutput\.value\s*=\s*""/, "Layout Clear resets HTML output"); assert.match(clearLayoutHandler[0], /renderIntoPreview\(el\.layoutPreview,\s*""\)/, "Layout Clear resets preview via renderIntoPreview"); assert.match(clearLayoutHandler[0], /el\.layoutMeta\.textContent\s*=\s*"Layout ещё не отрендерен"/, "Layout Clear resets meta"); const clearExcelHandler = html.match(/el\.clearExcelInput\.addEventListener\("click",\s*\(\)\s*=>\s*\{[\s\S]*?\}\);/); assert.ok(clearExcelHandler, "Excel Clear handler block parses"); assert.match(clearExcelHandler[0], /state\.excelSourceHtml\s*=\s*""/, "Excel Clear resets stored source"); assert.match(clearExcelHandler[0], /el\.excelOutput\.value\s*=\s*""/, "Excel Clear resets output"); assert.match(clearExcelHandler[0], /el\.excelMeta\.textContent\s*=\s*"Таблица ещё не сконвертирована"/, "Excel Clear resets meta"); assert.match(clearExcelHandler[0], /el\.excelPasteZone\.innerHTML\s*=\s*""/, "Excel Clear resets paste zone"); assert.match(clearExcelHandler[0], /setPromptOutputEmpty\(el\.excelOutput,\s*el\.excelOutputEmpty\)/, "Excel Clear restores blocked output empty state"); const clearMarkdownHandler = html.match(/el\.clearMarkdown\.addEventListener\("click",\s*\(\)\s*=>\s*\{[\s\S]*?\}\);/); assert.ok(clearMarkdownHandler, "Markdown Clear handler block parses"); assert.match(clearMarkdownHandler[0], /el\.markdownHtmlInput\.value\s*=\s*""/, "Markdown Clear resets input"); assert.match(clearMarkdownHandler[0], /el\.markdownOutput\.value\s*=\s*""/, "Markdown Clear resets output"); assert.match(clearMarkdownHandler[0], /el\.markdownMeta\.textContent\s*=\s*"HTML ещё не сконвертирован"/, "Markdown Clear resets meta"); assert.match(clearMarkdownHandler[0], /setPromptOutputEmpty\(el\.markdownOutput,\s*el\.markdownOutputEmpty\)/, "Markdown Clear restores blocked output empty state"); assert.match(html, /data-tool-tab="md-to-wiki"/, "MD to Wiki tab nav button exists"); assert.match(html, /<section id="mdToWikiTool" class="tool-view" hidden>/, "MD to Wiki tool section exists"); for (const id of ["mdToWikiInput", "mdToWikiPreview", "mdToWikiHtmlOutput", "mdToWikiMeta", "clearMdToWiki", "convertMdToWiki", "loadMdToWikiProtocolExample", "saveMdToWikiHtml", "copyMdToWikiRich"]) { assert.match(html, new RegExp(`id="${id}"`), `${id} element exists`); } assert.doesNotMatch(html, /id="useCurrentMd"/, "Markdown-to-Wiki no longer has Use Current"); assert.match(html, /<div id="mdToWikiPreview"[^>]*>\s*<div class="preview-empty" data-empty-for="md-to-wiki">/, "#mdToWikiPreview contains a .preview-empty child"); assert.match(html, /<p class="preview-empty-hint">Вставьте Markdown — здесь появится preview<\/p>/, "MD-to-Wiki empty hint exists"); assert.match(html, /\.preview-skeleton\s*\{[\s\S]*display:\s*grid/, ".preview-skeleton uses a grid layout"); assert.match(html, /"md-to-wiki": document\.getElementById\("mdToWikiTool"\)/, "toolViews maps md-to-wiki"); const clearMdToWikiHandler = html.match(/el\.clearMdToWiki\.addEventListener\("click",\s*\(\)\s*=>\s*\{[\s\S]*?\}\);/); assert.ok(clearMdToWikiHandler, "MD-to-Wiki Clear handler block parses"); assert.match(clearMdToWikiHandler[0], /el\.mdToWikiInput\.value\s*=\s*""/, "MD-to-Wiki Clear resets input"); assert.match(clearMdToWikiHandler[0], /renderIntoPreview\(el\.mdToWikiPreview,\s*""\)/, "MD-to-Wiki Clear resets preview via renderIntoPreview"); assert.match(clearMdToWikiHandler[0], /el\.mdToWikiHtmlOutput\.value\s*=\s*""/, "MD-to-Wiki Clear resets HTML output"); assert.match(clearMdToWikiHandler[0], /el\.mdToWikiMeta\.textContent\s*=\s*"Markdown ещё не сконвертирован"/, "MD-to-Wiki Clear resets meta"); assert.match(html, /el\.clearMdToWiki\.addEventListener\("click"/, "clearMdToWiki handler is wired"); assert.match(html, /function mdInlineToWiki\(text(?:,\s*depth\s*=\s*\d+)?\)/, "mdInlineToWiki function exists"); assert.match(html, /function markdownToWikiHtml\(source\)/, "markdownToWikiHtml function exists"); assert.match(html, /function convertMdToWiki\(\)/, "convertMdToWiki function exists"); const convertFn = html.match(/function convertMdToWiki\(\)\s*\{[\s\S]*?\n \}/); assert.ok(convertFn, "convertMdToWiki block parses"); assert.match(convertFn[0], /markdownToWikiHtml\(/, "convertMdToWiki calls markdownToWikiHtml"); assert.match(convertFn[0], /renderIntoPreview\(el\.mdToWikiPreview/, "convertMdToWiki updates preview through renderIntoPreview"); assert.match(html, /function convertMdToWiki\(\)\s*\{[\s\S]*setPreviewEmpty\(el\.mdToWikiPreview/, "convertMdToWiki toggles preview-empty"); assert.match(convertFn[0], /el\.mdToWikiHtmlOutput\.value\s*=\s*formatHtml/, "convertMdToWiki stores formatted html for save"); assert.match(html, /"md-to-wiki":\s*convertMdToWiki/, "hotkey map binds md-to-wiki to convertMdToWiki"); assert.match(html, /"meet-protocol":\s*prepareMeetProtocolPrompt/, "hotkey map binds meeting protocol to prompt preparation"); assert.match(html, /async function copyMdToWikiRich\(event\)/, "copyMdToWikiRich function exists with button event"); const copyMdToWikiRichFn = html.match(/async function copyMdToWikiRich\(event\)\s*\{[\s\S]*?\n \}/); assert.ok(copyMdToWikiRichFn, "copyMdToWikiRich block parses"); assert.match(copyMdToWikiRichFn[0], /el\.mdToWikiHtmlOutput\.value/, "copyMdToWikiRich copies the clean stored HTML, not the preview innerHTML with its hidden placeholder"); assert.doesNotMatch(copyMdToWikiRichFn[0], /el\.mdToWikiPreview\.innerHTML/, "copyMdToWikiRich never copies preview DOM with its hidden placeholder"); assert.doesNotMatch(copyMdToWikiRichFn[0], /innerText/, "copyMdToWikiRich no longer derives plain text from the preview"); assert.match(html, /async function saveMdToWikiHtml\(event\)/, "saveMdToWikiHtml function exists with button event"); assert.match(html, /<button id="convertMdToWiki">Convert<\/button>/, "MD-to-Wiki Convert is a neutral button (auto-runs on paste)"); assert.match(html, /<button id="loadMdToWikiProtocolExample" type="button">Пример Протокола<\/button>/, "MD-to-Wiki toolbar has Protocol example button"); assert.match(html, /<button id="copyMdToWikiRich" class="btn-primary">Copy Back<\/button>/, "MD-to-Wiki primary action is Copy Back"); assert.match(html, /el\.convertMdToWiki\.addEventListener\("click", convertMdToWiki\)/, "convert button wired"); assert.match(html, /el\.loadMdToWikiProtocolExample\.addEventListener\("click", loadMdToWikiProtocolExample\)/, "MD-to-Wiki Protocol example button is wired"); assert.match(html, /el\.saveMdToWikiHtml\.addEventListener\("click",\s*\(event\)\s*=>\s*saveMdToWikiHtml\(event\)\)/, "save button wired with button feedback"); assert.match(html, /el\.copyMdToWikiRich\.addEventListener\("click",\s*\(event\)\s*=>\s*copyMdToWikiRich\(event\)\)/, "copy button wired with button feedback"); assert.doesNotMatch(html, /Use Current/, "Use Current controls are removed from the lab"); assert.doesNotMatch(html, /useCurrent[A-Za-z]*/, "Use Current wiring is removed from the lab"); assert.match(html, /<section id="meetProtocolTool" class="tool-view">/, "Meeting Protocol tool section exists and is initially visible"); for (const id of ["meetTranscriptInput", "meetProtocolOutput", "meetProtocolMeta", "prepareMeetProtocol", "showMeetProtocolPrompt", "clearMeetProtocol"]) { assert.match(html, new RegExp(`id="${id}"`), `${id} element exists`); } assert.doesNotMatch(html, /id="copyMeetProtocol"/, "Meeting Protocol does not keep a duplicate copy-result button"); assert.match(html, /<div class="meet-prompt-selector">\s*<span class="meet-prompt-modes-label">Промт<\/span>\s*<div class="tabs meet-prompt-modes" role="group" aria-label="Выбор промта встречи">\s*<button type="button" data-meet-prompt-mode="knowledge-base" aria-pressed="false">Для базы знаний<\/button>\s*<button type="button" data-meet-prompt-mode="brief" aria-pressed="true">Краткая информация<\/button>\s*<\/div>\s*<\/div>/, "Meet Protocol defaults to brief mode in an explicitly labelled prompt switch"); assert.match(html, /\.meet-prompt-modes\s*\{[\s\S]*width:\s*350px;[\s\S]*border-radius:\s*999px;/, "meeting prompt switch has a stable pill track"); assert.match(html, /\.meet-prompt-modes::before\s*\{[\s\S]*width:\s*calc\(\(100% - 6px\) \/ 2\);[\s\S]*transition:\s*transform 180ms/, "meeting prompt switch has a sliding selection indicator"); assert.match(html, /\.meet-prompt-modes:has\(\[data-meet-prompt-mode="brief"\]\[aria-pressed="true"\]\)::before\s*\{\s*transform:\s*translateX\(100%\);/, "brief mode moves the prompt switch indicator"); assert.match(html, /\.tool-workspace,\s*\.kb-workspace,\s*\.clipboard-layout\s*\{[\s\S]*padding:\s*8px;/, "workspaces use a thin gray outer frame"); assert.match(html, /@media \(max-width: 900px\)[\s\S]*\.tool-workspace,\s*\.kb-workspace,\s*\.clipboard-layout\s*\{[\s\S]*padding:\s*8px;/, "mobile workspaces keep the same thin gray outer frame"); assert.doesNotMatch(html, /#meetProtocolTool \.tool-workspace\s*\{/, "meeting workspace keeps the standard full workspace height"); assert.match(html, /\.large-textarea,\s*\.layout-preview,\s*\.prompt-output-shell\s*\{\s*min-height:\s*430px;/, "mobile work fields keep their full working height"); assert.match(html, /<div class="meet-prompt-selector">[\s\S]*?<div class="tabs meet-prompt-modes"[\s\S]*?<\/div>\s*<\/div>\s*<button id="buildMeetProtocol">Создать промт<\/button>/, "Meet Protocol prompt switch is followed directly by the build action"); assert.match(html, /\.meet-prompt-modes button\s*\{[\s\S]*white-space:\s*nowrap/, "meeting prompt mode labels do not split inside their buttons"); const meetExternalGroup = html.match(/<span class="external-group-label">открыть в:<\/span>(?:\s*<button class="btn-external meet-llm-link"[\s\S]*?<\/button>){5}/); assert.ok(meetExternalGroup, "Meet Protocol external group has five LLM buttons in sequence"); assert.match(html, /el\.buildMeetProtocol\.addEventListener\("click", prepareMeetProtocolPrompt\)/, "Meeting Protocol Создать промт button builds without copying"); assert.match(html, /<div class="panel-title">\s*<h2>Транскрибация<\/h2>\s*<button id="showMeetProtocolPrompt" class="panel-title-link" type="button">Промт для LLM<\/button>/, "Meeting Protocol prompt reference lives in the transcript panel title"); assert.match(html, /<button class="btn-external meet-llm-link" type="button" data-meet-llm-url="https:\/\/chat\.qwen\.ai\/" aria-label="Открыть в Qwen"><svg class="llm-ico"[^>]*><use href="#ico-qwen"/, "Meeting Protocol toolbar has a Qwen launcher with brand icon"); assert.match(html, /<button class="btn-external meet-llm-link" type="button" data-meet-llm-url="https:\/\/chat\.z\.ai\/" aria-label="Открыть в Z\.ai"><svg class="llm-ico"[^>]*><use href="#ico-zai"/, "Meeting Protocol Z.ai launcher carries its brand icon"); assert.match(html, /<h2>Результат<\/h2>/, "Meeting Protocol output panel has a clear title"); assert.match(html, /<div class="prompt-output-shell" data-output-shell="meet-protocol">[\s\S]*<textarea id="meetProtocolOutput"[\s\S]*<div id="meetProtocolEmpty" class="preview-empty prompt-output-empty" data-empty-for="meet-protocol-output">/, "Meeting Protocol output has a preview-style empty state"); assert.match(html, /<p class="preview-empty-hint">Создайте промт — здесь появится итоговый промт<\/p>/, "Meeting Protocol output empty hint explains the prompt result"); assert.doesNotMatch(html, /\.prompt-output-shell > textarea:focus ~ \.prompt-output-empty/, "output empty states stay visible when empty output fields are clicked"); const setPromptOutputEmptyFn = html.match(/function setPromptOutputEmpty\(output,\s*emptyEl\)\s*\{[\s\S]*?\n \}/); assert.ok(setPromptOutputEmptyFn, "setPromptOutputEmpty helper parses"); assert.match(setPromptOutputEmptyFn[0], /const hasValue = Boolean\(output\.value\.trim\(\)\)/, "output empty helper reads the actual output value"); assert.match(setPromptOutputEmptyFn[0], /emptyEl\.hidden\s*=\s*hasValue/, "output empty helper hides the empty state only when data exists"); assert.match(setPromptOutputEmptyFn[0], /output\.disabled\s*=\s*!hasValue/, "output empty helper blocks empty output fields until data appears"); for (const [output, empty] of [ ["excelOutput", "excelOutputEmpty"], ["meetProtocolOutput", "meetProtocolEmpty"], ["kbPromptOutput", "kbPromptEmpty"], ["goalExtractionPromptOutput", "goalExtractionPromptEmpty"], ["markdownOutput", "markdownOutputEmpty"] ]) { assert.match(html, new RegExp(`setPromptOutputEmpty\\(el\\.${output},\\s*el\\.${empty}\\);`), `${output} is initialized as a blocked empty output`); } assert.match(html, /id="meetProtocolPromptModal"/, "meeting prompt modal exists"); assert.match(html, /id="meetProtocolPromptText"/, "meeting prompt modal has text container"); assert.match(html, /id="copyMeetProtocolPrompt"/, "meeting prompt modal has copy button"); assert.match(html, /id="closeMeetProtocolPrompt"/, "meeting prompt modal has close button"); assert.match(html, /"meet-protocol": document\.getElementById\("meetProtocolTool"\)/, "toolViews maps meeting protocol"); assert.match(html, /function cleanMeetTranscript\(source\)/, "meeting transcript cleaner exists"); const cleanMeetTranscriptFn = html.match(/function cleanMeetTranscript\(source\)\s*\{[\s\S]*?\n \}/); assert.ok(cleanMeetTranscriptFn, "cleanMeetTranscript block parses"); assert.match(cleanMeetTranscriptFn[0], /replace\(\s*\/\^\\ufeff\?\\d\{4\}-\\d\{2\}-\\d\{2\}\[T\\s\]\+\\d\{2\}:\\d\{2\}:\\d\{2\}\\s\+-\\s\+\/gm/, "cleaner removes leading meeting timestamps (space or T separator)"); assert.match(cleanMeetTranscriptFn[0], /replace\(\s*\/\\s\*\\\(распознано\\\):\/g/, "cleaner removes recognized marker after speaker names"); assert.match(cleanMeetTranscriptFn[0], /const meetingStartedAt =/, "cleaner preserves first meeting timestamp"); assert.match(cleanMeetTranscriptFn[0], /const videoStartedAt = rawTranscript\.match/, "cleaner detects exported video transcript headers"); assert.match(cleanMeetTranscriptFn[0], /const videoTitle =/, "cleaner preserves exported video transcript titles"); assert.match(cleanMeetTranscriptFn[0], /standaloneTimecodes/, "cleaner detects standalone video timecode lines"); assert.match(cleanMeetTranscriptFn[0], /Дата и время встречи:/, "cleaner exposes preserved timestamp to the LLM"); assert.match(cleanMeetTranscriptFn[0], /Название встречи:/, "cleaner exposes preserved meeting title to the LLM"); assert.match(cleanMeetTranscriptFn[0], /Формат транскрибации:/, "cleaner marks video transcripts as chronological meeting conversations"); const cleanMeetTranscript = new Function(`${cleanMeetTranscriptFn[0]}; return cleanMeetTranscript;`)(); assert.equal( cleanMeetTranscript("2026-05-08 13:06:52 - Иван Иванов (распознано): Коллеги, добрый день.\n2026-05-08 13:07:01 - Мария Петрова: Начинаем."), "Дата и время встречи: 2026-05-08 13:06:52\n\nИван Иванов: Коллеги, добрый день.\nМария Петрова: Начинаем.", "cleanMeetTranscript removes space-separated date timestamps and recognized markers" ); assert.equal( cleanMeetTranscript("2026-05-08T13:06:52 - Иван Иванов (распознано): Коллеги, добрый день."), "Дата и время встречи: 2026-05-08 13:06:52\n\nИван Иванов: Коллеги, добрый день.", "cleanMeetTranscript removes T-separated date timestamps" ); const cleanedVideoTranscript = cleanMeetTranscript("Текстовое представление видео «Митап. Кросс-цели 2H 2026. «Реализовать управление доступом для новых сценариев в AI native ландшафте» 05.05.2026 16:59:31 MSK».\n\n \n\n00:00:32\nСвобода.\n\n00:00:51\nПонятно, понятно.\n\n00:04:30\nКоллеги, добрый день."); assert.equal( cleanedVideoTranscript, "Дата и время встречи: 05.05.2026 16:59:31 MSK\nНазвание встречи: Митап. Кросс-цели 2H 2026. «Реализовать управление доступом для новых сценариев в AI native ландшафте»\nФормат транскрибации: хронологическое общение участников встречи без разметки спикеров.\n\nСвобода.\n\nПонятно, понятно.\n\nКоллеги, добрый день.", "cleanMeetTranscript preserves exported video titles and removes standalone timecodes" ); assert.doesNotMatch(cleanedVideoTranscript, /Текстовое представление видео|^00:\d{2}:\d{2}$/m, "cleaned video transcript has no video markers"); assert.match(html, /const MEET_PROTOCOL_PROMPT_TEXT = "/, "MEET_PROTOCOL_PROMPT_TEXT is declared as a JSON-string literal"); const meetPromptDeclaration = html.match(/const MEET_PROTOCOL_PROMPT_TEXT = "(?:\\.|[^"\\])*";/); assert.ok(meetPromptDeclaration, "MEET_PROTOCOL_PROMPT_TEXT declaration parses as a single string literal"); assert.ok(meetPromptDeclaration[0].length > 1000, `MEET_PROTOCOL_PROMPT_TEXT looks too short (${meetPromptDeclaration[0].length} chars) — did meet-protocol.md inline correctly?`); assert.match(meetPromptDeclaration[0], /операционный ассистент/, "MEET_PROTOCOL_PROMPT_TEXT contains the meeting prompt"); assert.match(meetPromptDeclaration[0], /Сводка встречи:/, "MEET_PROTOCOL_PROMPT_TEXT requires a meeting summary header"); assert.match(meetPromptDeclaration[0], /Дата:/, "MEET_PROTOCOL_PROMPT_TEXT requires a date header line"); assert.match(meetPromptDeclaration[0], /ГГГГ\.ММ\.ДД/, "MEET_PROTOCOL_PROMPT_TEXT requires dot-separated date format"); assert.doesNotMatch(meetPromptDeclaration[0], /YYYY-MM-DD/, "MEET_PROTOCOL_PROMPT_TEXT does not require ISO date format in the output header"); assert.doesNotMatch(meetPromptDeclaration[0], /Участники \(активные\):/, "MEET_PROTOCOL_PROMPT_TEXT does not require active participants in the header"); assert.match(meetPromptDeclaration[0], /Формат:/, "MEET_PROTOCOL_PROMPT_TEXT requires meeting format"); assert.match(meetPromptDeclaration[0], /не придумывай тему встречи/, "MEET_PROTOCOL_PROMPT_TEXT forbids guessed meeting titles"); assert.match(meetPromptDeclaration[0], /Не указывай и не оценивай количество участников встречи/, "MEET_PROTOCOL_PROMPT_TEXT forbids participant counts"); assert.doesNotMatch(meetPromptDeclaration[0], /~100 участников|около 100 участников|примерный масштаб/, "MEET_PROTOCOL_PROMPT_TEXT does not suggest participant count or scale"); assert.match(meetPromptDeclaration[0], /строго 13 основных разделов/, "MEET_PROTOCOL_PROMPT_TEXT requires exactly 13 main sections"); assert.match(meetPromptDeclaration[0], /1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13/, "MEET_PROTOCOL_PROMPT_TEXT lists all 13 required sections"); assert.match(meetPromptDeclaration[0], /2\.1 сохраняй как подпункт внутри раздела 2/, "MEET_PROTOCOL_PROMPT_TEXT keeps 2.1 under section 2"); assert.match(meetPromptDeclaration[0], /### 2\.1\. Обновления по ранее принятым решениям/, "MEET_PROTOCOL_PROMPT_TEXT defines 2.1 as a subsection"); assert.equal((meetPromptDeclaration[0].match(/# Текст встречи/g) || []).length, 1, "knowledge-base meeting prompt has one transcript heading"); assert.equal(meetPromptDeclaration[0].includes("<meeting_transcript>\\n[вставь текст встречи]\\n</meeting_transcript>"), true, "knowledge-base meeting prompt owns a transcript replacement marker"); assert.doesNotMatch(meetPromptDeclaration[0], /(^|\\n)## 2\.1\./, "MEET_PROTOCOL_PROMPT_TEXT does not define 2.1 as a main section"); assert.doesNotMatch(meetPromptDeclaration[0], /Необязательный раздел/, "MEET_PROTOCOL_PROMPT_TEXT has no optional sections"); assert.doesNotMatch(meetPromptDeclaration[0], /пропусти раздел целиком/, "MEET_PROTOCOL_PROMPT_TEXT no longer tells the model to drop sections"); assert.match(html, /const MEETING_NARRATIVE_PROMPT_TEXT = "/, "MEETING_NARRATIVE_PROMPT_TEXT is declared as a JSON-string literal"); const meetingNarrativePromptDeclaration = html.match(/const MEETING_NARRATIVE_PROMPT_TEXT = "(?:\\.|[^"\\])*";/); assert.ok(meetingNarrativePromptDeclaration, "MEETING_NARRATIVE_PROMPT_TEXT declaration parses as a single string literal"); assert.ok(meetingNarrativePromptDeclaration[0].length > 1000, `MEETING_NARRATIVE_PROMPT_TEXT looks too short (${meetingNarrativePromptDeclaration[0].length} chars)`); assert.match(meetingNarrativePromptDeclaration[0], /содержательный рассказ по итогам встречи/, "meeting narrative prompt contains its distinctive title"); assert.match(meetingNarrativePromptDeclaration[0], /не более чем за 7 минут/, "meeting narrative prompt preserves the reading-time requirement"); assert.match(html, /const MEET_PROMPT_CONFIGS = Object\.freeze\(\{[\s\S]*"knowledge-base"[\s\S]*MEET_PROTOCOL_PROMPT_TEXT[\s\S]*brief[\s\S]*MEETING_NARRATIVE_PROMPT_TEXT/, "meeting prompt modes map to both inlined prompt sources"); assert.match(html, /meetPromptMode:\s*"brief"/, "meeting prompt defaults to the brief mode"); assert.match(html, /meetPromptModeButtons:\s*Array\.from\(document\.querySelectorAll\("\[data-meet-prompt-mode\]"\)\)/, "meeting prompt mode buttons are wired"); assert.match(html, /function selectMeetPromptMode\(mode\)/, "meeting prompt mode selection handler exists"); const selectMeetPromptModeFn = html.match(/function selectMeetPromptMode\(mode\)\s*\{[\s\S]*?\n \}/); assert.ok(selectMeetPromptModeFn, "selectMeetPromptMode block parses"); assert.match(selectMeetPromptModeFn[0], /prepareMeetProtocolPrompt\(\)/, "switching mode rebuilds an existing transcript prompt"); assert.match(html, /button\.addEventListener\("click", \(\) => selectMeetPromptMode\(button\.dataset\.meetPromptMode\)\)/, "meeting prompt mode buttons select their configured mode"); assert.match(html, /function prepareMeetProtocolPrompt\(\)/, "meeting protocol prepare action exists"); const prepareMeetProtocolFn = html.match(/function prepareMeetProtocolPrompt\(\)\s*\{[\s\S]*?\n \}/); assert.ok(prepareMeetProtocolFn, "prepareMeetProtocolPrompt block parses"); assert.match(prepareMeetProtocolFn[0], /const config = getMeetPromptConfig\(\)/, "prepare action reads the selected meeting prompt config"); assert.match(prepareMeetProtocolFn[0], /const prompt = config\.text\.trim\(\)/, "prepare action prefixes the result with the selected prompt text"); assert.match(prepareMeetProtocolFn[0], /cleanMeetTranscript\(el\.meetTranscriptInput\.value\)/, "prepare action cleans the transcript input"); assert.match(prepareMeetProtocolFn[0], /el\.meetProtocolOutput\.value\s*=/, "prepare action writes output textarea"); assert.match(prepareMeetProtocolFn[0], /setPromptOutputEmpty\(el\.meetProtocolOutput,\s*el\.meetProtocolEmpty\)/, "prepare action updates meeting output empty state"); assert.match(html, /async function copyMeetProtocolOutput\(event\)/, "meeting protocol copy action exists with button event"); assert.match(html, /navigator\.clipboard\.writeText\(el\.meetProtocolOutput\.value\)/, "meeting protocol copy writes prompt text"); assert.match(html, /async function copyMeetProtocolAndOpen\(url,\s*button\)/, "meeting protocol Copy & Open action exists"); const copyAndOpenFn = html.match(/async function copyMeetProtocolAndOpen\(url,\s*button\)\s*\{[\s\S]*?\n \}/); assert.ok(copyAndOpenFn, "copyMeetProtocolAndOpen block parses"); assert.match(copyAndOpenFn[0], /const copied = await copyMeetProtocolOutput\(\)/, "Copy & Open copies the prepared prompt first"); assert.match(copyAndOpenFn[0], /if \(!copied\) return/, "Copy & Open skips launching the LLM when copy fails"); assert.match(copyAndOpenFn[0], /window\.open\(url, "_blank", "noopener,noreferrer"\)/, "Copy & Open opens selected LLM safely"); assert.match(copyAndOpenFn[0], /setTimeout\(\(\) => window\.open\(url, "_blank", "noopener,noreferrer"\), 420\)/, "Copy & Open waits for the short flash before opening selected LLM"); assert.match(html, /document\.querySelectorAll\("\.meet-llm-link"\)\.forEach/, "LLM Copy & Open buttons are wired"); assert.match(html, /button\.dataset\.meetLlmUrl/, "LLM Copy & Open buttons read their URL from data attribute"); assert.match(html, /function openMeetProtocolPromptModal/, "meeting prompt modal open handler exists"); assert.match(html, /function closeMeetProtocolPromptModal/, "meeting prompt modal close handler exists"); assert.match(html, /function copyMeetProtocolPromptText/, "meeting prompt modal copy handler exists"); assert.match(html, /el\.showMeetProtocolPrompt\.addEventListener\("click", openMeetProtocolPromptModal\)/, "meeting prompt open is wired"); assert.match(html, /el\.copyMeetProtocolPrompt\.addEventListener\("click", copyMeetProtocolPromptText\)/, "meeting prompt copy is wired"); assert.match(html, /copyTextWithFallback\(config\.text,/, "meeting prompt modal copies the selected prompt text"); const clearMeetProtocolHandler = html.match(/el\.clearMeetProtocol\.addEventListener\("click",\s*\(\)\s*=>\s*\{[\s\S]*?\}\);/); assert.ok(clearMeetProtocolHandler, "Meeting Protocol Clear handler block parses"); assert.match(clearMeetProtocolHandler[0], /el\.meetTranscriptInput\.value\s*=\s*""/, "Meeting Protocol Clear resets input"); assert.match(clearMeetProtocolHandler[0], /el\.meetProtocolOutput\.value\s*=\s*""/, "Meeting Protocol Clear resets output"); assert.match(clearMeetProtocolHandler[0], /el\.meetProtocolMeta\.textContent\s*=\s*"Промт ещё не собран"/, "Meeting Protocol Clear resets meta"); assert.match(clearMeetProtocolHandler[0], /setPromptOutputEmpty\(el\.meetProtocolOutput,\s*el\.meetProtocolEmpty\)/, "Meeting Protocol Clear restores output empty state"); assert.match(html, /el\.prepareMeetProtocol\.addEventListener\("click",\s*\(event\)\s*=>\s*copyMeetProtocolOutput\(event\)\)/, "meeting protocol primary button copies + flashes"); // Tab info popovers for (const key of ["clipboard", "excel", "layout", "meet-protocol", "knowledge-base", "goal-extraction", "markdown", "md-to-wiki"]) { assert.match(html, new RegExp(`data-tool-tab="${key}"[\\s\\S]*<span class="tab-info"`), `tab button includes info icon for ${key}`); } assert.match(html, /id="tabInfoPopover"/, "shared tab-info popover element exists"); assert.match(html, /id="tabInfoPopoverTitle"/, "tab-info popover title exists"); assert.match(html, /id="tabInfoPopoverBody"/, "tab-info popover body exists"); assert.match(html, /const TAB_DESCRIPTIONS\s*=\s*\{/, "TAB_DESCRIPTIONS literal exists"); const tabDescriptionsBlock = html.match(/const TAB_DESCRIPTIONS\s*=\s*\{[\s\S]*?\n \};/); assert.ok(tabDescriptionsBlock, "TAB_DESCRIPTIONS literal block parses"); for (const key of ["clipboard", "excel", "layout", "meet-protocol", "knowledge-base", "goal-extraction", "markdown", "md-to-wiki"]) { assert.match(tabDescriptionsBlock[0], new RegExp(`["']${key}["']\\s*:`), `TAB_DESCRIPTIONS has entry for ${key}`); } const goalExtractionDescription = tabDescriptionsBlock[0].match(/"goal-extraction":\s*\{[\s\S]*?\n \}/)?.[0] ?? ""; assert.doesNotMatch(goalExtractionDescription, /goal-template|schema|v1/i, "Goal Extraction tab info explains the user task without schema jargon"); assert.match(goalExtractionDescription, /исходные материалы/, "Goal Extraction tab info says it reads source materials"); assert.match(goalExtractionDescription, /собирает факты/, "Goal Extraction tab info says it extracts facts"); assert.match(goalExtractionDescription, /где применяется/, "Goal Extraction tab info explains applicability extraction"); assert.match(goalExtractionDescription, /сроки/, "Goal Extraction tab info explains timeline extraction"); assert.match(goalExtractionDescription, /метрики/, "Goal Extraction tab info explains metrics extraction"); assert.match(goalExtractionDescription, /ответственные/, "Goal Extraction tab info explains ownership extraction"); assert.match(goalExtractionDescription, /риски/, "Goal Extraction tab info explains risk extraction"); assert.match(html, /function showTabInfoPopover/, "tab-info popover open handler exists"); assert.match(html, /function hideTabInfoPopover/, "tab-info popover close handler exists"); assert.match(html, /\.tab-info\b/, "tab-info CSS class is defined"); assert.match(html, /\.popover\s*\{/, "popover CSS rule exists"); assert.doesNotMatch(html, /<button class="tab-info"/, "tab info is not a separate button"); assert.match(html, /data-tool-tab="layout"[\s\S]*<span class="tab-info"[^>]*>i<\/span>[\s\S]*<\/button>/, "layout info icon is inside the tab button"); assert.match(html, /data-tool-tab="clipboard"[\s\S]*<span class="tab-info"[^>]*>i<\/span>[\s\S]*<\/button>/, "clipboard info icon is inside the tab button"); assert.match(html, /\.tool-tabs button\s*\{[\s\S]*border:\s*1px solid var\(--line\)/, "tab buttons have a visible outline"); assert.match(html, /\.tool-tabs button\s*\{[\s\S]*display:\s*inline-flex/, "tab buttons layout label and info icon together"); assert.match(html, /\.tool-tabs \.tab-info\s*\{/, "tab-info icon is styled as part of tab button"); assert.match(html, /event\.target\.closest\("\.tab-info"\)[\s\S]*toggleTabInfoPopover\(tab\)/, "clicking the in-button info icon opens tab details"); assert.match(html, /const key = button\.dataset\.toolTab/, "tab info popover reads the owning tab key"); assert.match(html, /\.paste-zone:empty::before/, "clipboard paste zone uses a non-payload placeholder"); assert.match(html, /\.excel-paste-zone:empty::before/, "excel paste zone uses a non-payload placeholder"); assert.match(html, /--tab-font-size:/, "tab font size token is declared"); assert.match(html, /--placeholder-font-size:\s*var\(--tab-font-size\);/, "placeholder font size follows tab font size"); assert.match(html, /\.tool-tabs button\s*\{[\s\S]*font-size:\s*var\(--tab-font-size\)/, "tab buttons use the shared tab font size"); assert.match(html, /\.paste-zone,\s*\n\s*\.excel-paste-zone\s*\{[\s\S]*font-size:\s*var\(--placeholder-font-size\)/, "contenteditable placeholders match tab font size"); assert.match(html, /textarea::placeholder\s*\{[\s\S]*font-size:\s*var\(--placeholder-font-size\)/, "textarea placeholders match tab font size"); assert.match(html, /textarea:focus::placeholder\s*\{[\s\S]*color:\s*transparent/, "textarea helper text disappears on focus"); assert.match(html, /id="layoutJsonInput"[\s\S]*class="paste-target-helper">Принимаются два формата: layout JSON с массивом blocks и JSON цели/, "layout input explains that both layout JSON and goal JSON are accepted"); assert.match(html, /id="excelPasteZone"[\s\S]*class="paste-target-helper">Вставьте wiki-таблицу/, "excel paste zone explains automatic conversion inside the field overlay"); assert.match(html, /id="pasteZone"[\s\S]*class="paste-target-helper">Вставьте фрагмент wiki/, "clipboard paste zone explains the clipboard workflow inside the field overlay"); assert.match(html, /id="layoutJsonInput"[\s\S]*class="paste-target-helper">[^<]*Собрать промт[^<]*Render[^<]*Copy Back/, "layout input lists creation step (Собрать промт) before paste/render workflow"); // Goal JSON is rendered on the same tab: the format is detected, not chosen by the user. assert.match(html, /function isGoalTemplateJson\(parsed\)/, "layout tab detects goal-template.v1 input"); assert.match(html, /parsed\.schema_version === "goal-template\.v1"/, "goal detection keys off schema_version"); assert.match(html, /layout = goalToLayout\(parsed, \{ profile \}\)/, "goal JSON is mapped to layout JSON before rendering"); assert.match(html, /id="layoutGoalProfile"/, "layout toolbar exposes the goal page profile selector"); assert.match(html, /<option value="audit">/, "goal page profile offers the audit variant"); assert.match(html, /el\.layoutGoalProfile\.addEventListener\("change"/, "changing the profile re-renders the page"); assert.match(html, /function goalToLayout\(goal, \{ profile = "public" \} = \{\}\)/, "goal-to-layout module is inlined into the lab"); assert.match(html, /id="layoutJsonInput"[\s\S]*class="paste-target-helper">[^<]*Пример Протокола[^<]*Markdown протокола[^<]*preview/, "layout input explains that Protocol example loads Markdown and renders preview"); assert.doesNotMatch(html, /placeholder="[^"]*text\/html[^"]*TSV[^"]*Convert[^"]*Copy Excel/, "removed raw excel input guidance is not visible"); assert.match(html, /id="markdownHtmlInput"[\s\S]*class="paste-target-helper">[^<]*text\/html[^<]*Markdown[^<]*автоматически[^<]*Copy Markdown/, "HTML-to-Markdown input gives detailed paste and action guidance"); assert.match(html, /id="mdToWikiInput"[\s\S]*class="paste-target-helper">[^<]*Markdown[^<]*таблицы[^<]*Copy Back/, "Markdown-to-Wiki input gives detailed paste and action guidance"); assert.match(html, /id="meetTranscriptInput"[\s\S]*class="paste-target-helper">[^<]*штампы времени[^<]*\(распознано\)[^<]*Copy Result/, "Meeting Protocol input gives detailed transcript and LLM workflow guidance"); assert.match(html, /id="meetTranscriptInput"[\s\S]*class="paste-target-helper">[^<]*полный промт/, "Meeting Protocol input describes the generated full prompt without a filename"); assert.doesNotMatch(html, /PromtMeet|PromptMeet/i, "Meeting Protocol UI does not reference removed prompt filenames"); assert.match(html, /\.tool-workspace,\s*\n\s*\.clipboard-layout\s*\{[\s\S]*gap:\s*var\(--panel-gap\);/, "all tab workspaces use the same panel gap"); assert.match(html, /\.panel\s*\{[\s\S]*border-radius:\s*var\(--panel-radius\);/, "all tab panels use the shared panel radius"); assert.match(html, /\.panel-title\s*\{[\s\S]*min-height:\s*var\(--panel-title-height\);/, "all tab panel titles use the shared title height"); assert.match(html, /\.full-body,\s*\n\s*\.layout-output,\s*\n\s*\.panel-body,\s*\n\s*\.raw-body\s*\{[\s\S]*padding:\s*var\(--panel-padding\);/, "panel bodies use one shared padding rule"); // Layout prompt builder (toolbar button + modal) assert.match(html, /<button id="buildLayoutPromptFromText" class="btn-soft">Нет JSON\? Собрать промт<\/button>/, "Layout toolbar exposes the prompt-builder button"); assert.doesNotMatch(html, /id="showLayoutPrompt"/, "old panel-title prompt link is removed"); assert.match(html, /<div class="panel-title">\s*<h2>Layout JSON<\/h2>\s*<\/div>/, "Layout JSON panel title no longer carries a prompt link"); assert.match(html, /id="layoutPromptModal"/, "layout prompt modal exists"); assert.match(html, /id="layoutPromptSource"/, "layout prompt modal has a source-text field"); assert.doesNotMatch(html, /id="layoutPromptText"/, "assembled-prompt display is removed"); assert.doesNotMatch(html, /id="rebuildLayoutPrompt"/, "standalone Собрать button is removed"); // Action buttons live in a top toolbar (app style), not a bottom footer. assert.match(html, /<div class="toolbar prompt-modal-toolbar">\s*<button id="copyLayoutPrompt" type="button" disabled>Копировать<\/button>/, "copy button sits first in a top modal toolbar and starts disabled"); assert.match(html, /id="closeLayoutPrompt"/, "layout prompt modal has close button"); assert.match(html, /class="prompt-modal-backdrop"/, "layout prompt modal has backdrop"); assert.match(html, /role="group" aria-labelledby="layoutLlmGroupLabel"/, "external LLM buttons form a labelled group"); assert.match(html, /class="btn-external layout-llm-link"[^>]*data-layout-llm-url="https:\/\/chat\.z\.ai\/"[^>]*aria-label="[^"]+"[^>]*disabled><svg class="llm-ico"[^>]*><use href="#ico-zai"/, "external LLM buttons have aria-labels, brand icons and start disabled"); assert.match(html, /class="btn-external layout-llm-link"[^>]*data-layout-llm-url="https:\/\/chat\.qwen\.ai\/"[^>]*aria-label="[^"]+"[^>]*disabled><svg class="llm-ico"[^>]*><use href="#ico-qwen"/, "Layout modal includes a Qwen launcher with brand icon"); assert.match(html, /\.btn-soft\s*\{/, "btn-soft CSS rule exists"); assert.match(html, /\.prompt-modal-source\s*\{/, "prompt-modal-source CSS rule exists"); assert.match(html, /\.prompt-modal-toolbar\s*\{/, "prompt-modal-toolbar CSS rule exists"); assert.match(html, /\.prompt-modal-backdrop\s*\{/, "prompt-modal-backdrop CSS rule exists"); assert.match(html, /\.prompt-modal-panel\s*\{/, "prompt-modal-panel CSS rule exists"); assert.match(html, /function buildLayoutPromptText[\s\S]*LLM_PROMPT_TEXT/, "prompt assembler builds from LLM_PROMPT_TEXT"); assert.match(html, /<исходный_текст>/, "assembler wraps the source text in delimiter tags"); assert.match(html, /function updateLayoutPromptControls[\s\S]*layoutPromptSource\.value\.trim\(\)[\s\S]*disabled = !hasText/, "controls enable only when source text is present"); assert.match(html, /function openLayoutPromptModal[\s\S]*layoutPromptSource\.focus\(\)/, "opening the modal focuses the source field"); assert.match(html, /function closeLayoutPromptModal/, "modal close handler exists"); assert.match(html, /function copyLayoutPromptText\(event\)\s*\{\s*if \(!el\.layoutPromptSource\.value\.trim\(\)\)/, "copy is guarded against empty source"); assert.match(html, /function copyLayoutPromptAndOpen/, "copy-and-open handler exists"); assert.match(html, /el\.buildLayoutPromptFromText\.addEventListener\("click", openLayoutPromptModal\)/, "prompt-builder open is wired"); assert.match(html, /el\.layoutPromptSource\.addEventListener\("input", updateLayoutPromptControls\)/, "source input toggles button state"); assert.match(html, /el\.copyLayoutPrompt\.addEventListener\("click", copyLayoutPromptText\)/, "prompt copy is wired"); assert.match(html, /querySelectorAll\("\.layout-llm-link"\)\.forEach/, "external LLM buttons are wired"); assert.match(html, /copyTextWithFallback\(prompt,/, "modal copy reuses copyTextWithFallback on the assembled prompt"); // Embedded LLM prompt assert.match(html, /const LLM_PROMPT_TEXT = "/, "LLM_PROMPT_TEXT is declared as a JSON-string literal"); const promptDeclaration = html.match(/const LLM_PROMPT_TEXT = "(?:\\.|[^"\\])*";/); assert.ok(promptDeclaration, "LLM_PROMPT_TEXT declaration parses as a single string literal"); assert.ok(promptDeclaration[0].length > 1000, `LLM_PROMPT_TEXT looks too short (${promptDeclaration[0].length} chars) — did wiki-layout-full.md inline correctly?`); assert.match(promptDeclaration[0], /Wiki layout — full prompt/, "LLM_PROMPT_TEXT contains the prompt header"); assert.match(promptDeclaration[0], /\$schema/, "LLM_PROMPT_TEXT mentions the schema"); // Embedded knowledge-base update prompt assert.match(html, /const KB_UPDATE_PROMPT_TEXT = "/, "KB_UPDATE_PROMPT_TEXT is declared as a JSON-string literal"); const kbPromptDeclaration = html.match(/const KB_UPDATE_PROMPT_TEXT = "(?:\\.|[^"\\])*";/); assert.ok(kbPromptDeclaration, "KB_UPDATE_PROMPT_TEXT declaration parses as a single string literal"); assert.ok(kbPromptDeclaration[0].length > 1000, `KB_UPDATE_PROMPT_TEXT looks too short (${kbPromptDeclaration[0].length} chars) — did knowledge-base-update.md inline correctly?`); assert.match(kbPromptDeclaration[0], /редактор внутренней базы знаний/, "KB_UPDATE_PROMPT_TEXT contains the knowledge-base prompt opener"); assert.match(kbPromptDeclaration[0], /<ТЕКУЩАЯ_БАЗА_ЗНАНИЙ>/, "KB_UPDATE_PROMPT_TEXT documents the current-base block"); assert.match(kbPromptDeclaration[0], /<НОВЫЙ_ПРОТОКОЛ_ВСТРЕЧИ>/, "KB_UPDATE_PROMPT_TEXT documents the new-protocol block"); // Embedded goal prompt system prompts assert.match(html, /const GOAL_EXTRACTION_PROMPT_TEXT = "/, "GOAL_EXTRACTION_PROMPT_TEXT is declared as a JSON-string literal"); const goalExtractionPromptDeclaration = html.match(/const GOAL_EXTRACTION_PROMPT_TEXT = "(?:\\.|[^"\\])*";/); assert.ok(goalExtractionPromptDeclaration, "GOAL_EXTRACTION_PROMPT_TEXT declaration parses as a single string literal"); assert.ok(goalExtractionPromptDeclaration[0].length > 1000, `GOAL_EXTRACTION_PROMPT_TEXT looks too short (${goalExtractionPromptDeclaration[0].length} chars) — did goal-extraction.system.md inline correctly?`); assert.match(goalExtractionPromptDeclaration[0], /Goal Template Extraction/, "GOAL_EXTRACTION_PROMPT_TEXT contains the extraction prompt header"); assert.match(goalExtractionPromptDeclaration[0], /goal-template\.v1/, "GOAL_EXTRACTION_PROMPT_TEXT targets goal-template.v1 JSON"); assert.match(goalExtractionPromptDeclaration[0], /--- ТЕКУЩИЙ JSON ---/, "GOAL_EXTRACTION_PROMPT_TEXT documents the optional current JSON block"); // Embedded knowledge-base example layout assert.match(html, /const KNOWLEDGE_BASE_LAYOUT_TEXT = "/, "KNOWLEDGE_BASE_LAYOUT_TEXT is declared as a JSON-string literal"); const kbLayoutDeclaration = html.match(/const KNOWLEDGE_BASE_LAYOUT_TEXT = "(?:\\.|[^"\\])*";/); assert.ok(kbLayoutDeclaration, "KNOWLEDGE_BASE_LAYOUT_TEXT declaration parses as a single string literal"); // Knowledge Base tab — nav button and section skeleton assert.match(html, /data-tool-tab="knowledge-base"/, "Knowledge Base tool tab exists"); assert.match(html, /data-tool-tab="knowledge-base" aria-controls="knowledgeBaseTool"/, "Knowledge Base nav button points at the right section"); assert.match(html, /<section id="knowledgeBaseTool" class="tool-view" hidden>/, "Knowledge Base tool section exists"); assert.match(html, /"knowledge-base": document\.getElementById\("knowledgeBaseTool"\)/, "toolViews maps knowledge-base"); // Knowledge Base tab — workspace layout and three textareas assert.match(html, /\.kb-workspace\s*\{[\s\S]*grid-template-columns: minmax\(320px, 1fr\) minmax\(320px, 1fr\)/, "kb workspace splits into two equal columns with a min-width"); assert.match(html, /\.kb-workspace\s*\{[\s\S]*min-height: calc\(100vh - 140px\)/, "kb workspace stretches to fill the viewport height"); assert.match(html, /\.kb-left\s*\{[\s\S]*grid-template-rows: minmax\(0, 1fr\) minmax\(0, 1fr\)/, "kb left column splits into two equal rows that can shrink"); for (const id of ["kbCurrentInput", "kbProtocolInput", "kbPromptOutput", "kbMeta"]) { assert.match(html, new RegExp(`id="${id}"`), `${id} element exists`); } assert.match(html, /<textarea id="kbPromptOutput"[^>]*readonly/, "kbPromptOutput is read-only"); assert.match(html, /<div class="prompt-output-shell" data-output-shell="knowledge-base">[\s\S]*<textarea id="kbPromptOutput"[\s\S]*<div id="kbPromptEmpty" class="preview-empty prompt-output-empty" data-empty-for="knowledge-base-output">/, "Knowledge Base output has a preview-style empty state"); assert.match(html, /<p class="preview-empty-hint">Создайте промт — здесь появится итоговый промт для базы знаний<\/p>/, "Knowledge Base output empty hint explains the prompt result"); assert.match(html, /<section id="knowledgeBaseTool"[\s\S]*<div class="kb-workspace">/, "kb workspace lives inside the kb tool section"); assert.match(html, /<div class="kb-workspace">[\s\S]*<div class="kb-left">[\s\S]*<\/div>[\s\S]*<section class="panel work-panel">[\s\S]*id="kbPromptOutput"/, "kb workspace has left column + right output panel"); // Knowledge Base tab — toolbar and prompt modal for (const id of ["showKbUpdatePrompt", "prepareKbPrompt", "clearKb", "kbUpdatePromptModal", "kbUpdatePromptText", "closeKbUpdatePrompt", "closeKbUpdatePromptFooter", "copyKbUpdatePrompt"]) { assert.match(html, new RegExp(`id="${id}"`), `${id} element exists`); } assert.match(html, /<div class="panel-title">\s*<h2>Текущая база знаний<\/h2>\s*<button id="showKbUpdatePrompt" class="panel-title-link" type="button">Промт для LLM<\/button>/, "Knowledge Base prompt reference lives in the current-base panel title"); assert.match(html, /<button class="btn-external kb-llm-link" type="button" data-kb-llm-url="https:\/\/chat\.qwen\.ai\/" aria-label="Открыть в Qwen"><svg class="llm-ico"[^>]*><use href="#ico-qwen"/, "Knowledge Base toolbar has a Qwen launcher with brand icon"); assert.match(html, /<div class="toolbar">\s*<button id="buildKbPrompt">Создать промт<\/button>\s*<button id="prepareKbPrompt" class="btn-primary">Copy Result<\/button>\s*<span class="llm-launchers">\s*<span class="external-group-label">открыть в:<\/span>(?:\s*<button class="btn-external kb-llm-link"[\s\S]*?<\/button>){5}\s*<\/span>\s*<button id="clearKb" class="btn-clear">Очистить<\/button>/, "Knowledge Base toolbar: build → Copy Result → LLM group in flow, Очистить pinned far right"); assert.match(html, /function buildKbPrompt\(\)/, "buildKbPrompt function exists"); assert.match(html, /el\.buildKbPrompt\.addEventListener\("click", buildKbPrompt\)/, "Knowledge Base Создать промт button is wired"); assert.match(html, /<button id="clearKb"[^>]*>Очистить<\/button>/, "Knowledge Base toolbar has Clear button"); // Knowledge Base tab — modal/LLM/clear handlers for (const key of ["kbCurrentInput", "kbProtocolInput", "kbPromptOutput", "kbPromptEmpty", "kbMeta", "showKbUpdatePrompt", "prepareKbPrompt", "clearKb", "kbUpdatePromptModal", "kbUpdatePromptText", "closeKbUpdatePrompt", "closeKbUpdatePromptFooter", "copyKbUpdatePrompt"]) { assert.match(html, new RegExp(`${key}: document\\.getElementById\\("${key}"\\)`), `el.${key} is wired`); } assert.match(html, /function openKbUpdatePromptModal/, "kb prompt modal open handler exists"); assert.match(html, /function closeKbUpdatePromptModal/, "kb prompt modal close handler exists"); assert.match(html, /function copyKbUpdatePromptText/, "kb prompt modal copy handler exists"); assert.match(html, /el\.kbUpdatePromptText\.textContent\s*=\s*KB_UPDATE_PROMPT_TEXT/, "kb prompt modal renders KB_UPDATE_PROMPT_TEXT"); assert.match(html, /el\.showKbUpdatePrompt\.addEventListener\("click", openKbUpdatePromptModal\)/, "kb prompt open is wired"); assert.match(html, /el\.copyKbUpdatePrompt\.addEventListener\("click", copyKbUpdatePromptText\)/, "kb prompt copy is wired"); assert.match(html, /el\.closeKbUpdatePrompt\.addEventListener\("click", closeKbUpdatePromptModal\)/, "kb prompt header-close is wired"); assert.match(html, /el\.closeKbUpdatePromptFooter\.addEventListener\("click", closeKbUpdatePromptModal\)/, "kb prompt footer-close is wired"); assert.match(html, /copyTextWithFallback\(KB_UPDATE_PROMPT_TEXT,/, "kb prompt copy reuses copyTextWithFallback"); assert.match(html, /document\.querySelectorAll\("\.kb-llm-link"\)\.forEach/, "kb LLM launcher buttons are wired in a loop"); assert.match(html, /button\.dataset\.kbLlmUrl/, "kb LLM launcher buttons read URL from dataset"); const clearKbHandler = html.match(/el\.clearKb\.addEventListener\("click",\s*\(\)\s*=>\s*\{[\s\S]*?\}\);/); assert.ok(clearKbHandler, "Knowledge Base Clear handler block parses"); assert.match(clearKbHandler[0], /el\.kbCurrentInput\.value\s*=\s*""/, "Knowledge Base Clear resets current base input"); assert.match(clearKbHandler[0], /el\.kbProtocolInput\.value\s*=\s*""/, "Knowledge Base Clear resets protocol input"); assert.match(clearKbHandler[0], /el\.kbPromptOutput\.value\s*=\s*""/, "Knowledge Base Clear resets prompt output"); assert.match(clearKbHandler[0], /el\.kbMeta\.textContent\s*=\s*"Промт ещё не собран"/, "Knowledge Base Clear resets meta"); assert.match(clearKbHandler[0], /setPromptOutputEmpty\(el\.kbPromptOutput,\s*el\.kbPromptEmpty\)/, "Knowledge Base Clear restores output empty state"); const keydownBlock2 = html.match(/document\.addEventListener\("keydown",[\s\S]*?\}\);/); assert.ok(keydownBlock2, "keydown listener still parses"); assert.match(keydownBlock2[0], /!el\.kbUpdatePromptModal\.hidden/, "ESC key closes the kb prompt modal when open"); // Knowledge Base tab — assemble/prepare/paste-to-markdown assert.match(html, /function pasteHtmlAsMarkdown\(event, textarea\)/, "kb paste-to-markdown helper exists"); assert.match(html, /function assembleKbPrompt\(\)/, "kb assemble function exists"); assert.match(html, /async function prepareKbPrompt\(event\)/, "kb prepare/copy function exists with button event"); assert.match(html, /async function copyKbPromptAndOpen\(url,\s*button\)/, "kb copy-and-open function exists"); const assembleKbFn = html.match(/function assembleKbPrompt\(\)\s*\{[\s\S]*?\n \}/); assert.ok(assembleKbFn, "assembleKbPrompt block parses"); assert.match(assembleKbFn[0], /KB_UPDATE_PROMPT_TEXT/, "assembleKbPrompt embeds the prompt text"); assert.match(assembleKbFn[0], /<ТЕКУЩАЯ_БАЗА_ЗНАНИЙ>/, "assembleKbPrompt wraps current base in the expected tag"); assert.match(assembleKbFn[0], /<НОВЫЙ_ПРОТОКОЛ_ВСТРЕЧИ>/, "assembleKbPrompt wraps protocol in the expected tag"); assert.match(assembleKbFn[0], /protocolValue\.trim\(\)/, "assembleKbPrompt guards on empty protocol via local variable"); assert.match(assembleKbFn[0], /el\.kbPromptOutput\.value\s*=/, "assembleKbPrompt writes to the output textarea"); assert.match(assembleKbFn[0], /setPromptOutputEmpty\(el\.kbPromptOutput,\s*el\.kbPromptEmpty\)/, "assembleKbPrompt updates output empty state"); const prepareKbFn = html.match(/async function prepareKbPrompt\(event\)\s*\{[\s\S]*?\n \}/); assert.ok(prepareKbFn, "prepareKbPrompt block parses"); assert.match(prepareKbFn[0], /assembleKbPrompt\(\)/, "prepareKbPrompt re-runs assembly first"); assert.match(prepareKbFn[0], /setStatus\("Нет протокола встречи для сборки промта", "warn"\)/, "prepareKbPrompt warns on empty output"); assert.match(prepareKbFn[0], /navigator\.clipboard\.writeText\(el\.kbPromptOutput\.value\)/, "prepareKbPrompt copies the assembled prompt"); const copyKbAndOpenFn = html.match(/async function copyKbPromptAndOpen\(url,\s*button\)\s*\{[\s\S]*?\n \}/); assert.ok(copyKbAndOpenFn, "copyKbPromptAndOpen block parses"); assert.match(copyKbAndOpenFn[0], /const copied = await prepareKbPrompt\(\)/, "copyKbPromptAndOpen prepares the prompt first"); assert.match(copyKbAndOpenFn[0], /if \(!copied\) return/, "copyKbPromptAndOpen skips launching the LLM when copy fails"); assert.match(copyKbAndOpenFn[0], /window\.open\(url, "_blank", "noopener,noreferrer"\)/, "copyKbPromptAndOpen opens selected LLM safely"); assert.match(copyKbAndOpenFn[0], /setTimeout\(\(\) => window\.open\(url, "_blank", "noopener,noreferrer"\), 420\)/, "copyKbPromptAndOpen waits for the short flash before opening selected LLM"); assert.match(html, /el\.prepareKbPrompt\.addEventListener\("click",\s*\(event\)\s*=>\s*prepareKbPrompt\(event\)\)/, "Copy Result button is wired to prepareKbPrompt with button feedback"); assert.match(html, /el\.kbCurrentInput\.addEventListener\("paste",[\s\S]*pasteHtmlAsMarkdown\(event, el\.kbCurrentInput\)/, "current-base paste runs through pasteHtmlAsMarkdown"); assert.match(html, /el\.kbProtocolInput\.addEventListener\("paste",[\s\S]*pasteHtmlAsMarkdown\(event, el\.kbProtocolInput\)/, "protocol paste runs through pasteHtmlAsMarkdown"); assert.match(html, /el\.kbCurrentInput\.addEventListener\("input", assembleKbPrompt\)/, "current-base input re-assembles"); assert.match(html, /el\.kbProtocolInput\.addEventListener\("input", assembleKbPrompt\)/, "protocol input re-assembles"); assert.match(html, /"knowledge-base":\s*prepareKbPrompt/, "Ctrl+Enter dispatch maps knowledge-base to prepareKbPrompt"); // Goal Extraction tab — nav, section, fields assert.match(html, /data-tool-tab="goal-extraction" aria-controls="goalExtractionTool"/, "Goal Extraction nav button points at the right section"); assert.match(html, /<section id="goalExtractionTool" class="tool-view" hidden>/, "Goal Extraction tool section exists"); assert.match(html, /"goal-extraction": document\.getElementById\("goalExtractionTool"\)/, "toolViews maps goal-extraction"); for (const id of [ "showGoalExtractionPrompt", "buildGoalExtractionPrompt", "prepareGoalExtractionPrompt", "clearGoalExtraction", "goalExtractionDataInput", "goalExtractionJsonInput", "goalExtractionPromptOutput", "goalExtractionPromptEmpty", "goalExtractionMeta", "goalExtractionPromptModal", "goalExtractionPromptText", "copyGoalExtractionPrompt", "closeGoalExtractionPrompt", "closeGoalExtractionPromptFooter" ]) { assert.match(html, new RegExp(`id="${id}"`), `${id} element exists`); } assert.match(html, /<h2>Данные по вопросу<\/h2>/, "Goal Extraction has one required data input panel"); assert.match(html, /<h2>Текущий JSON<\/h2>/, "Goal Extraction has optional current JSON panel"); assert.match(html, /<textarea id="goalExtractionPromptOutput"[^>]*readonly/, "goalExtractionPromptOutput is read-only"); assert.match(html, /<div class="prompt-output-shell" data-output-shell="goal-extraction">[\s\S]*<textarea id="goalExtractionPromptOutput"[\s\S]*<div id="goalExtractionPromptEmpty" class="preview-empty prompt-output-empty" data-empty-for="goal-extraction-output">/, "Goal Extraction output has a preview-style empty state"); assert.match(html, /<span class="external-group-label">открыть в:<\/span>(?:\s*<button class="btn-external goal-extraction-llm-link"[\s\S]*?<\/button>){5}/, "Goal Extraction toolbar has five LLM launchers in sequence"); assert.match(html, /<button class="btn-external goal-extraction-llm-link" type="button" data-goal-extraction-llm-url="https:\/\/chat\.qwen\.ai\/" aria-label="Открыть в Qwen"><svg class="llm-ico"[^>]*><use href="#ico-qwen"/, "Goal Extraction toolbar has a Qwen launcher with brand icon"); for (const key of [ "goalExtractionDataInput", "goalExtractionJsonInput", "goalExtractionPromptOutput", "goalExtractionPromptEmpty", "goalExtractionMeta", "showGoalExtractionPrompt", "buildGoalExtractionPrompt", "prepareGoalExtractionPrompt", "clearGoalExtraction", "goalExtractionPromptModal", "goalExtractionPromptText", "copyGoalExtractionPrompt", "closeGoalExtractionPrompt", "closeGoalExtractionPromptFooter" ]) { assert.match(html, new RegExp(`${key}: document\\.getElementById\\("${key}"\\)`), `el.${key} is wired`); } assert.match(html, /function openGoalExtractionPromptModal/, "Goal Extraction prompt modal open handler exists"); assert.match(html, /function closeGoalExtractionPromptModal/, "Goal Extraction prompt modal close handler exists"); assert.match(html, /function copyGoalExtractionPromptText/, "Goal Extraction prompt modal copy handler exists"); assert.match(html, /el\.goalExtractionPromptText\.textContent\s*=\s*GOAL_EXTRACTION_PROMPT_TEXT/, "Goal Extraction modal renders GOAL_EXTRACTION_PROMPT_TEXT"); assert.match(html, /copyTextWithFallback\(GOAL_EXTRACTION_PROMPT_TEXT,/, "Goal Extraction prompt copy reuses copyTextWithFallback"); assert.match(html, /document\.querySelectorAll\("\.goal-extraction-llm-link"\)\.forEach/, "Goal Extraction LLM launcher buttons are wired in a loop"); assert.match(html, /button\.dataset\.goalExtractionLlmUrl/, "Goal Extraction launcher buttons read URL from dataset"); assert.match(html, /function assembleGoalExtractionPrompt\(\)/, "Goal Extraction assemble function exists"); assert.match(html, /function buildGoalExtractionPrompt\(\)/, "Goal Extraction build function exists"); assert.match(html, /async function prepareGoalExtractionPrompt\(event\)/, "Goal Extraction prepare/copy function exists with button event"); assert.match(html, /async function copyGoalExtractionPromptAndOpen\(url,\s*button\)/, "Goal Extraction copy-and-open function exists"); const assembleGoalExtractionFn = html.match(/function assembleGoalExtractionPrompt\(\)\s*\{[\s\S]*?\n \}/); assert.ok(assembleGoalExtractionFn, "assembleGoalExtractionPrompt block parses"); assert.match(assembleGoalExtractionFn[0], /GOAL_EXTRACTION_PROMPT_TEXT/, "assembleGoalExtractionPrompt embeds the extraction system prompt"); assert.match(assembleGoalExtractionFn[0], /dataValue\.trim\(\)/, "assembleGoalExtractionPrompt guards on empty required data"); assert.match(assembleGoalExtractionFn[0], /--- ТЕКУЩИЙ JSON ---/, "assembleGoalExtractionPrompt wraps optional current JSON"); assert.match(assembleGoalExtractionFn[0], /--- ИСХОДНЫЙ ТЕКСТ ---/, "assembleGoalExtractionPrompt wraps pasted text in a neutral delimiter"); assert.doesNotMatch(assembleGoalExtractionFn[0], /--- ИСТОЧНИК: src_01 ---/, "assembleGoalExtractionPrompt no longer forces the whole input into a single source"); assert.match(assembleGoalExtractionFn[0], /ИСТОЧНИК:/, "assembleGoalExtractionPrompt passes hand-marked sources through unchanged"); assert.match(assembleGoalExtractionFn[0], /Сегодня: \$\{today\}/, "assembleGoalExtractionPrompt injects today's date for the model"); assert.match(assembleGoalExtractionFn[0], /el\.goalExtractionPromptOutput\.value\s*=/, "assembleGoalExtractionPrompt writes output textarea"); assert.match(assembleGoalExtractionFn[0], /setPromptOutputEmpty\(el\.goalExtractionPromptOutput,\s*el\.goalExtractionPromptEmpty\)/, "assembleGoalExtractionPrompt updates output empty state"); const prepareGoalExtractionFn = html.match(/async function prepareGoalExtractionPrompt\(event\)\s*\{[\s\S]*?\n \}/); assert.ok(prepareGoalExtractionFn, "prepareGoalExtractionPrompt block parses"); assert.match(prepareGoalExtractionFn[0], /assembleGoalExtractionPrompt\(\)/, "prepareGoalExtractionPrompt re-runs assembly first"); assert.match(prepareGoalExtractionFn[0], /setStatus\("Нет данных цели для сборки промта", "warn"\)/, "prepareGoalExtractionPrompt warns on empty required data"); assert.match(prepareGoalExtractionFn[0], /navigator\.clipboard\.writeText\(el\.goalExtractionPromptOutput\.value\)/, "prepareGoalExtractionPrompt copies the assembled prompt"); const copyGoalExtractionAndOpenFn = html.match(/async function copyGoalExtractionPromptAndOpen\(url,\s*button\)\s*\{[\s\S]*?\n \}/); assert.ok(copyGoalExtractionAndOpenFn, "copyGoalExtractionPromptAndOpen block parses"); assert.match(copyGoalExtractionAndOpenFn[0], /const copied = await prepareGoalExtractionPrompt\(\)/, "copyGoalExtractionPromptAndOpen prepares the prompt first"); assert.match(copyGoalExtractionAndOpenFn[0], /if \(!copied\) return/, "copyGoalExtractionPromptAndOpen skips launching the LLM when copy fails"); assert.match(copyGoalExtractionAndOpenFn[0], /setTimeout\(\(\) => window\.open\(url, "_blank", "noopener,noreferrer"\), 420\)/, "copyGoalExtractionPromptAndOpen waits for the short flash before opening selected LLM"); assert.match(html, /el\.goalExtractionDataInput\.addEventListener\("input", assembleGoalExtractionPrompt\)/, "Goal Extraction data input re-assembles"); assert.match(html, /el\.goalExtractionJsonInput\.addEventListener\("input", assembleGoalExtractionPrompt\)/, "Goal Extraction JSON input re-assembles"); assert.match(html, /el\.prepareGoalExtractionPrompt\.addEventListener\("click",\s*\(event\)\s*=>\s*prepareGoalExtractionPrompt\(event\)\)/, "Goal Extraction Copy Result button is wired"); const clearGoalExtractionHandler = html.match(/el\.clearGoalExtraction\.addEventListener\("click",\s*\(\)\s*=>\s*\{[\s\S]*?\}\);/); assert.ok(clearGoalExtractionHandler, "Goal Extraction Clear handler block parses"); assert.match(clearGoalExtractionHandler[0], /el\.goalExtractionDataInput\.value\s*=\s*""/, "Goal Extraction Clear resets data input"); assert.match(clearGoalExtractionHandler[0], /el\.goalExtractionJsonInput\.value\s*=\s*""/, "Goal Extraction Clear resets optional JSON input"); assert.match(clearGoalExtractionHandler[0], /el\.goalExtractionPromptOutput\.value\s*=\s*""/, "Goal Extraction Clear resets output"); assert.match(clearGoalExtractionHandler[0], /setPromptOutputEmpty\(el\.goalExtractionPromptOutput,\s*el\.goalExtractionPromptEmpty\)/, "Goal Extraction Clear restores output empty state"); assert.match(html, /"goal-extraction":\s*prepareGoalExtractionPrompt/, "Ctrl+Enter dispatch maps goal-extraction to prepareGoalExtractionPrompt"); // Обновление цели: сверка версий живёт на той же вкладке и работает без нейросети. assert.match(html, /id="goalDiffNewJson"/, "goal tab has an input for the updated JSON"); assert.match(html, /id="compareGoalVersions"/, "goal tab has the compare button"); assert.match(html, /id="goalDiffReport"/, "goal tab has a diff report container"); assert.match(html, /function compareGoalVersions\(\)/, "compareGoalVersions handler exists"); const compareGoalVersionsFn = html.match(/function compareGoalVersions\(\)\s*\{[\s\S]*?\n \}/); assert.ok(compareGoalVersionsFn, "compareGoalVersions block parses"); assert.match(compareGoalVersionsFn[0], /diffGoals\(previous, next\)/, "the comparison uses the deterministic diff module"); assert.match(compareGoalVersionsFn[0], /summary\.removed/, "the report highlights values that disappeared"); assert.match(compareGoalVersionsFn[0], /summary\.brokenSources/, "the report warns when source ids were rewritten"); assert.match(html, /function diffGoals\(previous, next\)/, "goal-diff module is inlined into the lab"); assert.match(html, /el\.compareGoalVersions\.addEventListener\("click", compareGoalVersions\)/, "compare button is wired"); assert.doesNotMatch(html, /goalReviewTool|GOAL_REVIEW_PROMPT_TEXT/, "the goal review tab is gone"); const goalExtractionUi = html.slice(html.indexOf('<section id="goalExtractionTool"'), html.indexOf('<section id="markdownTool"')); assert.doesNotMatch(goalExtractionUi, /goal-template|schema|v1/i, "Goal Extraction visible UI avoids schema jargon"); assert.match(goalExtractionUi, /исходные материалы по одной цели: документы, заметки, письма, протоколы/, "Goal Extraction data helper describes accepted source materials"); assert.match(goalExtractionUi, /Единственное обязательное поле вкладки/, "Goal Extraction data helper marks the required field"); assert.match(goalExtractionUi, /LLM обновит текущую версию, а не начнёт с нуля/, "Goal Extraction JSON helper explains incremental update"); const keydownBlockGoalPrompts = html.match(/document\.addEventListener\("keydown",[\s\S]*?\}\);/); assert.ok(keydownBlockGoalPrompts, "keydown listener parses for goal prompt modals"); assert.match(keydownBlockGoalPrompts[0], /!el\.goalExtractionPromptModal\.hidden/, "ESC key closes the Goal Extraction prompt modal when open"); // Layout tab — Knowledge Base example button assert.match(html, /<button id="loadKnowledgeBaseExample" type="button">Пример База Знаний<\/button>/, "Layout toolbar has Knowledge Base example button"); assert.match(html, /loadKnowledgeBaseExample: document\.getElementById\("loadKnowledgeBaseExample"\)/, "el.loadKnowledgeBaseExample is wired"); assert.match(html, /function loadKnowledgeBaseExample\(\)/, "loadKnowledgeBaseExample function exists"); const loadKbExampleFn = html.match(/function loadKnowledgeBaseExample\(\)\s*\{[\s\S]*?\n \}/); assert.ok(loadKbExampleFn, "loadKnowledgeBaseExample block parses"); assert.match(loadKbExampleFn[0], /el\.layoutJsonInput\.value\s*=\s*KNOWLEDGE_BASE_LAYOUT_TEXT/, "loadKnowledgeBaseExample loads the inlined JSON"); assert.match(loadKbExampleFn[0], /renderLayoutJson\(\)/, "loadKnowledgeBaseExample triggers a render"); assert.match(html, /el\.loadKnowledgeBaseExample\.addEventListener\("click", loadKnowledgeBaseExample\)/, "Knowledge Base example button click is wired"); // Layout tab — Protocol Markdown example button assert.match(html, /<button id="loadProtocolExample" type="button">Пример Протокола<\/button>/, "Layout toolbar has Protocol example button"); assert.match(html, /const PROTOCOL_EXAMPLE_MARKDOWN_TEXT = "/, "protocol example Markdown is inlined"); const protocolExampleDeclaration = html.match(/const PROTOCOL_EXAMPLE_MARKDOWN_TEXT = "(?:\\.|[^"\\])*";/); assert.ok(protocolExampleDeclaration, "PROTOCOL_EXAMPLE_MARKDOWN_TEXT declaration parses as a single string literal"); const protocolExampleText = JSON.parse(protocolExampleDeclaration[0].slice("const PROTOCOL_EXAMPLE_MARKDOWN_TEXT = ".length, -1)); assert.match(protocolExampleText, /## 1\. Краткий итог встречи/, "Protocol example contains the meeting protocol Markdown"); assert.match(protocolExampleText, /## 2\.1\. Обновления по ранее принятым решениям/, "Protocol example keeps the 2.1 Markdown subsection"); assert.match(html, /loadProtocolExample: document\.getElementById\("loadProtocolExample"\)/, "el.loadProtocolExample is wired"); assert.match(html, /function loadProtocolExample\(\)/, "loadProtocolExample function exists"); const loadProtocolExampleFn = html.match(/function loadProtocolExample\(\)\s*\{[\s\S]*?\n \}/); assert.ok(loadProtocolExampleFn, "loadProtocolExample block parses"); assert.match(loadProtocolExampleFn[0], /markdownToWikiHtml\(markdown\)/, "loadProtocolExample renders Markdown through markdownToWikiHtml"); assert.doesNotMatch(loadProtocolExampleFn[0], /el\.layoutJsonInput\.value\s*=\s*markdown/, "loadProtocolExample does not put Markdown into the Layout JSON input"); assert.match(loadProtocolExampleFn[0], /renderIntoPreview\(el\.layoutPreview,\s*html\)/, "loadProtocolExample renders into the Layout preview"); assert.match(loadProtocolExampleFn[0], /el\.layoutHtmlOutput\.value\s*=\s*formatHtml\(html\)/, "loadProtocolExample stores rendered HTML for Save HTML and Copy Back"); assert.match(html, /el\.loadProtocolExample\.addEventListener\("click", loadProtocolExample\)/, "Protocol example button click is wired"); assert.match(html, /function loadMdToWikiProtocolExample\(\)/, "loadMdToWikiProtocolExample function exists"); const loadMdToWikiProtocolExampleFn = html.match(/function loadMdToWikiProtocolExample\(\)\s*\{[\s\S]*?\n \}/); assert.ok(loadMdToWikiProtocolExampleFn, "loadMdToWikiProtocolExample block parses"); assert.match(loadMdToWikiProtocolExampleFn[0], /el\.mdToWikiInput\.value\s*=\s*PROTOCOL_EXAMPLE_MARKDOWN_TEXT/, "MD-to-Wiki Protocol example loads the inlined Markdown"); assert.match(loadMdToWikiProtocolExampleFn[0], /convertMdToWiki\(\)/, "MD-to-Wiki Protocol example triggers conversion"); // Layout toolbar order: existing example + new example sit between spacers assert.match(html, /<button id="loadLayoutExample" type="button">Пример<\/button>\s*<button id="loadKnowledgeBaseExample" type="button">Пример База Знаний<\/button>\s*<button id="loadProtocolExample" type="button">Пример Протокола<\/button>/, "example buttons cluster together"); // Knowledge Base example — sanity check the inlined JSON shape const kbLayoutValue = JSON.parse(JSON.parse(kbLayoutDeclaration[0].slice("const KNOWLEDGE_BASE_LAYOUT_TEXT = ".length, -1))); assert.equal(typeof kbLayoutValue, "object", "Knowledge Base layout is a wiki-layout-v1 document object"); assert.ok(Array.isArray(kbLayoutValue.blocks), "Knowledge Base layout has a top-level blocks array"); assert.ok(kbLayoutValue.blocks.length >= 7, `Knowledge Base layout has at least 7 top blocks (heading h1 + 3 section pairs + tabs + 1 trailing section). Got ${kbLayoutValue.blocks.length}`); const kbTabsBlock = kbLayoutValue.blocks.find((block) => block && block.type === "tabs"); assert.ok(kbTabsBlock, "Knowledge Base layout contains a tabs block"); assert.deepEqual( kbTabsBlock.tabs.map((tab) => tab.label), ["Инструкции и процедуры", "FAQ и справка", "Процессы и решения", "Инструменты и ссылки", "Черновики знаний"], "tabs labels match the five knowledge-card sections in the documented order" ); const kbInstructions = kbTabsBlock.tabs[0]; assert.ok(kbInstructions.blocks.some((block) => block && block.type === "panel" && block.title === "5-шаговый процесс прохождения цели по КМД"), "Инструкции tab contains the 5-step process card"); assert.ok(kbInstructions.blocks.every((block) => !block || block.type !== "heading"), "no heading blocks live inside the tabs (per layout contract)"); // Knowledge Base example — render-time check: bold prefixes must render as real <strong>, not escaped. const kbInstructionsCards = kbInstructions.blocks.filter((block) => block && block.type === "panel"); assert.ok(kbInstructionsCards.length >= 1, "Инструкции tab has at least one panel card"); const kbStrongInlinesInJson = JSON.stringify(kbLayoutValue).match(/"type":\s*"strong"/g) || []; assert.ok(kbStrongInlinesInJson.length >= 18, `expected at least 18 inline strong runs across all cards (Суть/Подробное знание/Что нужно уточнить × cards), got ${kbStrongInlinesInJson.length}`); const kbEscapedStrongInJson = JSON.stringify(kbLayoutValue).match(/<strong>/g) || []; assert.equal(kbEscapedStrongInJson.length, 0, "no paragraph .text should embed literal <strong> tags — use inlines instead"); // Embedded font faces (self-hosted IBM Plex) assert.match(html, /@font-face\s*\{[^}]*font-family:\s*'IBM Plex Mono'[^}]*format\('woff2'\);unicode-range:/, "IBM Plex Mono is inlined as base64 woff2 with a unicode-range subset"); assert.match(html, /@font-face\s*\{[^}]*font-family:\s*'IBM Plex Sans'[^}]*font-weight:100 700[^}]*format\('woff2'\);unicode-range:/, "IBM Plex Sans is inlined as a variable font covering weights 100-700"); assert.match(html, /@font-face\s*\{[^}]*font-family:\s*'IBM Plex Sans'[^}]*unicode-range:U\+0301, U\+0400-045F/, "IBM Plex Sans ships a Cyrillic subset face"); assert.match(html, /@font-face\s*\{[^}]*font-family:\s*'IBM Plex Sans'[^}]*unicode-range:U\+0000-00FF/, "IBM Plex Sans ships a Latin subset face"); assert.match(html, /\.panel-title-link\s*\{[\s\S]*?text-decoration:\s*underline/, "panel-title prompt links use the quiet underlined style"); assert.ok( (html.match(/@font-face\s*\{/g) || []).length >= 8, "at least 8 @font-face rules are inlined (Mono 3 weights + variable Sans, cyrillic and latin subsets each)" );