feat: refine branded operator experience

Add the MBN-branded startup intro, semantic sidebar icons, and refined card-based operator visuals. Strengthen packaged UI and input smoke coverage for the updated experience.
This commit is contained in:
2026-07-24 01:26:13 +09:00
parent f43483533a
commit b050f2f06c
25 changed files with 2316 additions and 164 deletions

View File

@@ -85,6 +85,10 @@ test("original operator settings are recoverable and restored byte-for-byte", ()
assert.match(wrapper, /restoration is one-shot and was already attempted/u);
assert.match(wrapper, /Get-LegacyApplicationProcesses\)\.Count -ne 0/u);
assert.match(wrapper, /\[IO\.File\]::Replace\(/u);
assert.match(wrapper, /\$replacementBackupPath/u);
assert.match(wrapper, /\[IO\.File\]::Delete\(\$replacementBackupPath\)/u);
assert.doesNotMatch(wrapper,
/\[IO\.File\]::Replace\(\$temporaryPath,\s*\$OperatorSettingsPath,\s*\$null/u);
assert.match(wrapper, /\[IO\.File\]::Delete\(\$OperatorSettingsPath\)/u);
assert.match(wrapper, /exactOriginalBytesRestored = \$true/u);
assert.match(wrapper, /recoveryCopyRemoved/u);
@@ -96,6 +100,12 @@ test("original operator settings are recoverable and restored byte-for-byte", ()
assert.doesNotMatch(wrapper, /C:\\Users\\[^\\]+/iu);
});
test("default appearance evidence directory has one extension separator", () => {
assert.match(wrapper, /\[IO\.Path\]::GetFileNameWithoutExtension\(\$Output\)/u);
assert.doesNotMatch(wrapper,
/\[IO\.Path\]::ChangeExtension\(\$Output,\s*\$null\)\s*\+\s*'\.exchanges'/u);
});
test("appearance evidence is bounded, sealed, and never overwritten", () => {
assert.match(harness, /fs\.writeFileSync\(requestPath, requestBytes, \{ flag: "wx" \}\)/u);
assert.match(harness, /fs\.writeFileSync\(configuration\.screenshotPath, bytes, \{ flag: "wx" \}\)/u);

View File

@@ -95,8 +95,9 @@ test("appearance themes expose explicit light, dark, and Windows-following palet
/:root\[data-color-theme="dark"\],[\s\S]*?body\[data-color-theme="dark"\]\s*\{[\s\S]*?color-scheme:\s*dark/);
assert.match(styles,
/@media \(prefers-color-scheme:\s*dark\)\s*\{[\s\S]*?:root\[data-color-theme="system"\],[\s\S]*?body\[data-color-theme="system"\]/);
assert.match(styles, /--ui-brand:\s*#dc5826/i);
assert.match(styles, /\.brand-lockup strong\s*\{[^}]*color:\s*var\(--ui-brand\)/s);
assert.match(styles, /--ui-brand:\s*#f47b20/i);
assert.match(styles, /\.brand-wordmark-plate\s*\{[^}]*background:\s*#fff/s);
assert.match(styles, /\.brand-wordmark\s*\{[^}]*filter:\s*none/s);
assert.match(styles,
/\.playlist-state-legend \.legend-selected\s*\{\s*color:\s*var\(--ui-accent\)/);
assert.match(styles,
@@ -141,8 +142,8 @@ test("light palette keeps hints and small semantic labels readable", () => {
assert.ok(contrast(token("ui-warning"), token("ui-warning-soft")) >= 4.5);
});
test("view modes change catalog paint without changing operator row geometry", () => {
const start = styles.indexOf("/* View-mode presentation is paint-only:");
test("visual card mode creates a responsive graphic-action grid without touching operator rows", () => {
const start = styles.indexOf("/* View modes affect graphic actions only.");
const end = styles.indexOf("/* End view-mode presentation. */", start);
assert.ok(start >= 0 && end > start, "view-mode CSS must have an auditable boundary");
const viewModeRules = styles.slice(start, end);
@@ -151,11 +152,43 @@ test("view modes change catalog paint without changing operator row geometry", (
assert.ok(viewModeRules.includes(`data-view-mode="${mode}"`),
`${mode} mode must have explicit presentation rules`);
}
assert.match(viewModeRules, /\.catalog-section/);
assert.match(viewModeRules, /\.operator-catalog-results button/);
assert.doesNotMatch(viewModeRules, /\.playlist-(?:row|data-row|grid)/);
assert.match(viewModeRules, /\[data-visual-card-list="true"\]\s*\{/);
assert.match(viewModeRules, /grid-template-columns:\s*minmax\(0, 1fr\)/);
assert.match(viewModeRules,
/@container operator-workspace \(min-width:\s*520px\)[\s\S]*?repeat\(2, minmax\(0, 1fr\)\)/);
assert.match(viewModeRules,
/@container operator-workspace \(min-width:\s*820px\)[\s\S]*?repeat\(3, minmax\(0, 1fr\)\)/);
assert.match(viewModeRules,
/data-visual-card-context="comparison-actions"[\s\S]*?repeat\(auto-fit, minmax\(min\(230px, 100%\), 1fr\)\)/);
assert.match(viewModeRules,
/data-visual-card-context="theme-actions"[\s\S]*?repeat\(2, minmax\(0, 1fr\)\)/);
assert.doesNotMatch(viewModeRules,
/(?:^|[;{]\s*)(?:height|min-height|padding|margin|grid-template(?:-columns|-rows)?|display)\s*:/m);
/data-visual-card-context="expert-actions"[\s\S]{0,180}?repeat\([23],/);
assert.match(viewModeRules, /button\.visual-card\s*\{[\s\S]*?min-height:\s*62px/);
assert.doesNotMatch(viewModeRules, /grid-auto-rows:\s*minmax\(62px/);
assert.match(viewModeRules,
/\.comparison-actions\s*\{\s*display:\s*block;/);
assert.match(viewModeRules,
/@container operator-workspace \(max-width:\s*759px\)[\s\S]*?\.comparison-actions\s*\{\s*display:\s*grid;/);
assert.doesNotMatch(viewModeRules, /\.visual-card-(?:kicker|preview|state)/,
"generic previews, repeated category labels, and decorative state tiles add no information");
assert.match(viewModeRules,
/button\.visual-card:is\(\.selected,[\s\S]*?box-shadow:\s*inset 3px 0 var\(--ui-accent\)/);
assert.match(viewModeRules,
/summary\[data-tree-root\]\.tree-node-selected/);
assert.match(viewModeRules,
/summary\[data-tree-root\]:focus-visible\s*\{[\s\S]*?outline:\s*3px solid var\(--ui-accent\)/);
assert.match(viewModeRules,
/button\.visual-card:is\(\.unavailable, :disabled\):is\(/);
assert.match(viewModeRules, /@container operator-workspace \(max-width:\s*759px\)/);
assert.match(viewModeRules, /body\[data-view-mode="compact"\]/);
assert.doesNotMatch(viewModeRules,
/(?:#|\.)(?:stock-results|stock-result|cut-list|cut-row|playlist-row|playlist-data-row|playlist-grid|operator-catalog-draft|operator-catalog-results|operator-catalog-stock-results|overseas-period-actions|overseas-result-area)\b/);
assert.doesNotMatch(viewModeRules, /grid-auto-flow:\s*dense|\border\s*:/);
const cardButtonRule = viewModeRules.match(/button\.visual-card\s*\{([\s\S]*?)\n\}/);
assert.ok(cardButtonRule, "visual cards need one stable geometry rule");
assert.doesNotMatch(cardButtonRule[1], /transform\s*:/,
"hover or selection must not move the double-click hit target");
});
test("appearance controls and accessibility preferences retain clear feedback", () => {
@@ -170,7 +203,8 @@ test("appearance controls and accessibility preferences retain clear feedback",
test("workspace header keeps one concise title and the live native connection state", () => {
assert.match(markup,
/class="brand"[^>]*role="img"[^>]*aria-label="매일경제TV VRi"[\s\S]*?class="brand-symbol"[\s\S]*?<svg/);
/class="brand"[\s\S]*?class="brand-wordmark-plate"[\s\S]*?<img class="brand-wordmark" src="Brand\/logo\.png" width="176" height="43"[\s\S]*?alt="매일경제TV" draggable="false">[\s\S]*?class="brand-divider" aria-hidden="true"[\s\S]*?class="brand-product">V-STOCK<\/span>/);
assert.doesNotMatch(markup, /class="brand-symbol"|class="brand-lockup"|>VRi</);
assert.match(markup,
/class="workspace-heading"[\s\S]*?class="workspace-heading-icon"[\s\S]*?<div class="workspace-heading-copy">[\s\S]*?<h1 id="workspace-title">[^<]+<\/h1>/);
assert.doesNotMatch(markup, /<small>현재 작업<\/small>/);

View File

@@ -1514,8 +1514,12 @@ test("industry workspace sends index and opaque action intents only", () => {
assert.match(app, /send\("activate-industry-action"/);
assert.doesNotMatch(app, /swap\.textContent = "↕ 교환"/);
assert.match(app,
/summary\.textContent = industry\.market === "kospi" \? "\[코스피\]" : "\[코스닥\]"/);
/summary\.textContent = industry\.market === "kospi" \? "코스피" : "코스닥"/);
assert.match(app, /summary\.dataset\.industrySectionIndex = "0"/);
assert.match(industryRender, /markVisualCardList\(actionCards, "industry-actions"\)/);
assert.match(industryRender, /setVisualCardContent\(button, \{/);
assert.match(industryRender, /button\.dataset\.industryActionId = action\.actionId/);
assert.doesNotMatch(industryRender, /meta:\s*action\.category/);
assert.doesNotMatch(app, /const sections = new Map\(\)/);
assert.doesNotMatch(industryRender, /first\.readOnly = true/);
assert.doesNotMatch(industryRender, /second\.readOnly = true/);
@@ -1523,6 +1527,43 @@ test("industry workspace sends index and opaque action intents only", () => {
assert.match(industryRender, /second\.maxLength = 128/);
});
test("visual card renderers stay text-first and exclude narrow overseas actions", () => {
const helperStart = app.indexOf("function setVisualCardContent");
const helperEnd = app.indexOf("function treeLeafIdentity", helperStart);
assert.ok(helperStart >= 0 && helperEnd > helperStart);
const helper = app.slice(helperStart, helperEnd);
assert.match(helper, /button\.setAttribute\("aria-label", titleText\)/);
assert.doesNotMatch(helper,
/visual-card-(?:kicker|preview|state)|visualCardPreview|content\.preview/,
"a card without unique media must not invent a repeated preview or state ornament");
const cardRenderers = [
["fixed", "function renderFixedCatalog", "function markIndustryRowSelected"],
["industry", "function renderIndustry", "function updateOverseasWorkspace"],
["comparison", "function renderComparison", "function themeActionIsClickable"],
["theme", "function renderTheme", "function updateExpertWorkspace"],
["expert", "function renderExpert", "function updateTradingHaltWorkspace"],
["halt", "function renderTradingHalt", "const manualFinancialReferenceImages"]
];
for (const [name, startMarker, endMarker] of cardRenderers) {
const start = app.indexOf(startMarker);
const end = app.indexOf(endMarker, start);
assert.ok(start >= 0 && end > start, `${name} renderer slice must exist`);
const renderer = app.slice(start, end);
assert.match(renderer, /setVisualCardContent\(button, \{/,
`${name} action buttons must use the visual-card helper`);
assert.doesNotMatch(renderer, /\bkicker:|\bpreview:\s*true/,
`${name} cards must not repeat their section label or a generic preview`);
}
const overseasStart = app.indexOf("function renderOverseas");
const overseasEnd = app.indexOf("function updateComparisonWorkspace", overseasStart);
const overseas = app.slice(overseasStart, overseasEnd);
assert.match(overseas,
/button\.dataset\.overseasActionId = action\.actionId;\s*button\.textContent = label;/);
assert.doesNotMatch(overseas, /setVisualCardContent|markVisualCardList/);
});
test("UC2 row selection is immediate and its state receipt preserves the double-click target", () => {
const helperStart = app.indexOf("function markIndustryRowSelected");
const helperEnd = app.indexOf("function renderIndustry", helperStart);
@@ -1616,6 +1657,15 @@ test("comparison workspace sends opaque target, pair and action intents", () =>
assert.match(app, /\? "confirm-native-dialog"/);
assert.match(app, /send\("cancel-native-dialog", \{\}\)/);
assert.doesNotMatch(app, /send\([^\n]*stockCode/);
assert.match(app, /function comparisonUnavailableText\(reason\)/);
assert.match(app, /국내 종목 두 개를 선택해야 합니다/);
assert.match(app, /:\s*reason;\s*\n\s*}\s*\n\s*}\s*\n\s*\n\s*function renderComparison/);
const comparisonStart = app.indexOf("function renderComparison");
const comparisonEnd = app.indexOf("function renderTheme", comparisonStart);
const comparisonRender = app.slice(comparisonStart, comparisonEnd);
assert.doesNotMatch(comparisonRender, /meta:\s*action\.unavailableReason/);
assert.match(comparisonRender,
/comparisonUnavailableText\(action\.unavailableReason\)/);
});
test("theme workspace sends query, opaque selection, sort and action intents", () => {
@@ -1672,6 +1722,90 @@ test("theme expert and trading halt actions preserve original single click wirin
assert.match(clickDelegate, /send\("select-trading-halt"/);
});
test("visual card child spans preserve single and double click intent counts", () => {
const doubleClickStart = app.indexOf('categoryTree.addEventListener("dblclick"');
const clickStart = app.indexOf('categoryTree.addEventListener("click"', doubleClickStart + 1);
const changeStart = app.indexOf('categoryTree.addEventListener("change"', clickStart + 1);
assert.ok(doubleClickStart >= 0 && clickStart > doubleClickStart && changeStart > clickStart);
const listeners = {};
const selected = [];
const sent = [];
const categoryTree = {
addEventListener(type, listener) { listeners[type] = listener; },
querySelector() { return null; }
};
const send = (type, payload) => {
sent.push({ type, payload });
return true;
};
const install = new Function(
"categoryTree", "send", "treeInteractionLocked", "selectTreeNode",
app.slice(doubleClickStart, changeStart));
install(categoryTree, send, false, node => selected.push(node));
function nestedTarget(button, actionSelector, treeNode) {
return {
className: "visual-card-title",
closest(selector) {
if (treeNode && (selector === "[data-tree-node-key]" ||
selector === ".tree-item[data-tree-node-key]")) return button;
return selector === actionSelector ? button : null;
}
};
}
function inputEvent(target) {
return {
target,
detail: 1,
preventDefault() {}
};
}
for (const action of [
["button[data-action-id]", "actionId", "fixed-card", "activate-fixed-action"],
["button[data-industry-action-id]", "industryActionId", "industry-card",
"activate-industry-action"],
["button[data-comparison-action-id]", "comparisonActionId", "comparison-card",
"activate-comparison-action"]
]) {
const [selector, datasetKey, id, intent] = action;
const button = {
dataset: { [datasetKey]: id, treeNodeKey: id },
disabled: false,
getAttribute() { return null; }
};
const target = nestedTarget(button, selector, true);
const beforeSelected = selected.length;
const beforeSent = sent.length;
listeners.click(inputEvent(target));
assert.equal(selected.length, beforeSelected + 1);
assert.equal(sent.length, beforeSent, `${id} single click must only select`);
listeners.dblclick(inputEvent(target));
assert.equal(sent.length, beforeSent + 1);
assert.deepEqual(sent.at(-1), { type: intent, payload: { actionId: id } });
}
for (const action of [
["button[data-theme-action-id]", "themeActionId", "theme-card",
"activate-theme-action"],
["button[data-expert-action-id]", "expertActionId", "expert-card",
"activate-expert-action"],
["button[data-halt-action-id]", "haltActionId", "halt-card",
"activate-trading-halt-action"]
]) {
const [selector, datasetKey, id, intent] = action;
const button = { dataset: { [datasetKey]: id }, disabled: false };
const target = nestedTarget(button, selector, false);
const before = sent.length;
listeners.click(inputEvent(target));
assert.equal(sent.length, before + 1);
assert.deepEqual(sent.at(-1), { type: intent, payload: { actionId: id } });
listeners.dblclick(inputEvent(target));
assert.equal(sent.length, before + 1, `${id} double click must not duplicate its click intent`);
}
});
test("named DB playlists and playout use separate closed native intents", () => {
for (const intent of [
"refresh-named-playlists", "select-named-playlist",

View File

@@ -65,6 +65,7 @@ function pendingMenuFocusHarness() {
return tabId === "theme" ? pendingButton : null;
}
function refreshCurrentMenu() {}
function syncHeadingIcon() {}
function marketLabel() { return "테마"; }
${source}
return { syncMarket, hasPendingKeyboardMarketFocus };
@@ -262,6 +263,46 @@ test("package input physically resizes the splitter, verifies persistence, and r
/ArrowRight: \{ key: "ArrowRight", code: "ArrowRight", virtualKey: 39 \}/);
});
test("read-only package smoke seals the real visual-card screen and nested title selection", () => {
const start = packageInputHarness.indexOf(
"async function verifyVisualCardPresentation()");
const end = packageInputHarness.indexOf(
"async function playlistDropGeometry", start);
assert.ok(start >= 0 && end > start,
"visual-card package verification must be a bounded helper");
const visual = packageInputHarness.slice(start, end);
for (const contract of [
/selectMainTab\(\s*"overseas"/,
/button\[data-action-id="fixed-001"\]\.visual-card/,
/getComputedStyle\(list\)/,
/columnCount:\s*tracks\.length/,
/cardHeight:\s*button\.getBoundingClientRect\(\)\.height/,
/ariaLabel:\s*button\.getAttribute\("aria-label"\)/,
/hasPreview:\s*Boolean\(preview\)/,
/wordmarkSource:\s*wordmark\.getAttribute\("src"\)/,
/wordmarkNaturalWidth:\s*wordmark\.naturalWidth/,
/metrics\.wordmarkNaturalWidth !== 176/,
/metrics\.brandProduct !== "V-STOCK"/,
/metrics\.hasPreview \|\|\s*metrics\.hasState \|\| metrics\.hasKicker/,
/select fixed graphic from its visual-card title/,
/outboundMessageCount !== outboundBaseline/,
/checkpoint\("visual-card-presentation"/
]) assert.match(visual, contract);
assert.match(packageInputHarness,
/if \(readOnlyProfile\) \{\s*current = await verifyVisualCardPresentation\(\);/);
});
test("UI-only package smoke verifies the local customer wordmark is loaded and visible", () => {
assert.match(uiOnlySmokeHarness,
/const brandWordmark = document\.querySelector\("img\.brand-wordmark"\)/);
assert.match(uiOnlySmokeHarness,
/source:\s*brandWordmark\?\.getAttribute\("src"\)/);
assert.match(uiOnlySmokeHarness, /naturalWidth:\s*brandWordmark\?\.naturalWidth/);
assert.match(uiOnlySmokeHarness, /shell\.brand\.naturalWidth !== 176/);
assert.match(uiOnlySmokeHarness, /shell\.brand\.naturalHeight !== 43/);
assert.match(uiOnlySmokeHarness, /shell\.brand\.product !== "V-STOCK"/);
});
test("unified operator theme keeps clear semantic states and concise feedback", () => {
assert.match(styles,
/Unified operator theme: visual-only rules; geometry and native contracts stay intact/);
@@ -327,6 +368,116 @@ test("the complete sidebar is one labelled and collapsible navigation region", (
assert.match(markup, /id="workspace-nav-footer" class="workspace-nav-footer"/);
});
test("sidebar icons form one semantic SVG family and the heading follows the selected menu", () => {
const mappings = [
["id=\"workspace-stock-tab\"", "stock-cut"],
["data-tab-id=\"overseas\"", "overseas"],
["data-tab-id=\"exchange\"", "exchange"],
["data-tab-id=\"index\"", "index"],
["data-tab-id=\"kospiIndustry\"", "kospi"],
["data-tab-id=\"kosdaqIndustry\"", "kosdaq"],
["data-tab-id=\"comparison\"", "comparison"],
["data-tab-id=\"theme\"", "theme"],
["data-tab-id=\"overseasStocks\"", "overseas-stocks"],
["data-tab-id=\"expert\"", "expert"],
["data-tab-id=\"tradingHalt\"", "trading-halt"],
["id=\"workspace-settings-tab\"", "settings"]
];
for (const [anchor, icon] of mappings) {
const start = markup.indexOf(anchor);
const end = markup.indexOf("</button>", start);
assert.ok(start >= 0 && end > start, `missing menu button: ${anchor}`);
const button = markup.slice(start, end);
assert.match(button, new RegExp(`data-icon="${icon}"`));
assert.match(button, new RegExp(`href="#workspace-icon-${icon}"`));
}
const symbolIds = Array.from(
markup.matchAll(/<symbol id="workspace-icon-([^"]+)" viewBox="0 0 24 24">/g),
match => match[1]);
assert.deepEqual(symbolIds, [
"menu",
"stock-cut",
"overseas",
"exchange",
"index",
"kospi",
"kosdaq",
"comparison",
"theme",
"overseas-stocks",
"expert",
"trading-halt",
"settings"
]);
const kospi = markup.slice(
markup.indexOf("<symbol id=\"workspace-icon-kospi\""),
markup.indexOf("</symbol>", markup.indexOf("<symbol id=\"workspace-icon-kospi\"")));
const kosdaq = markup.slice(
markup.indexOf("<symbol id=\"workspace-icon-kosdaq\""),
markup.indexOf("</symbol>", markup.indexOf("<symbol id=\"workspace-icon-kosdaq\"")));
assert.match(kospi, /M4\.5 18V6h4a4 4 0 0 1 0 8h-4/);
assert.match(kospi, /M15 6h4M17 6v12M15 18h4/);
assert.match(kosdaq, /M2\.3 18V6h1\.6c2\.8 0 4\.3 2\.2 4\.3 6/);
assert.match(kosdaq, /M9 18l2\.5-12H13l2\.5 12M10 14h4\.5/);
assert.match(kosdaq, /<ellipse cx="19\.2" cy="12" rx="3\.1" ry="6" stroke-width="1\.65">/);
assert.match(kosdaq, /m19\.5 14\.5 3\.2 4/);
assert.doesNotMatch(kospi + kosdaq, /<text\b/);
assert.notEqual(kospi, kosdaq);
assert.match(styles, /\.workspace-icon-sprite\s*\{[\s\S]*?width:\s*0;[\s\S]*?height:\s*0;/);
assert.match(styles, /\.workspace-nav-icon\s*\{\s*shape-rendering:\s*geometricPrecision;\s*\}/);
assert.match(navigation,
/const titleIcon = document\.querySelector\("\.workspace-heading-icon"\)/);
assert.match(navigation,
/function syncHeadingIcon\(button\)[\s\S]*?source\.cloneNode\(true\)[\s\S]*?titleIcon\.replaceChildren\(next\)/);
assert.match(navigation,
/activeWorkspace = name;[\s\S]*?refreshCurrentMenu\(\);[\s\S]*?syncHeadingIcon\(selectedButton\);[\s\S]*?title\.textContent/);
assert.match(navigation,
/if \(activeWorkspace === "catalog"\)[\s\S]*?syncHeadingIcon\(button\);[\s\S]*?title\.textContent = marketLabel\(button\)/);
});
test("package smokes verify semantic icon painting, heading identity, and collapsed centering", () => {
for (const harness of [packageInputHarness, uiOnlySmokeHarness]) {
for (const [tabId, icon] of [
["overseas", "overseas"],
["exchange", "exchange"],
["index", "index"],
["kospiIndustry", "kospi"],
["kosdaqIndustry", "kosdaq"],
["comparison", "comparison"],
["theme", "theme"],
["overseasStocks", "overseas-stocks"],
["expert", "expert"],
["tradingHalt", "trading-halt"]
]) {
assert.match(harness, new RegExp(`${tabId}: "${icon}"`));
}
assert.match(harness, /svg\?\.getBBox\?\.\(\)/);
assert.match(harness, /reference:\s*svg\?\.querySelector\("use"\)\?\.getAttribute\("href"\)/);
assert.match(harness, /Number\.parseFloat\(style\.opacity\) > 0/);
assert.match(harness, /style\.stroke !== "none" \|\| style\.fill !== "none"/);
assert.match(harness, /rendered:\s*Boolean\(/);
assert.match(harness, /expectedHeadingIcon/);
assert.match(harness, /#workspace-icon-\$\{expectedHeadingIcon\}/);
}
assert.match(packageInputHarness, /navigationItems:\s*\[/);
assert.match(packageInputHarness, /iconCenterOffset:/);
assert.match(packageInputHarness, /iconCenterOffsetY:/);
assert.match(packageInputHarness, /item\.iconCenterOffset > 1\.5/);
assert.match(packageInputHarness, /item\.iconCenterOffsetY > 1\.5/);
assert.match(packageInputHarness, /item\.labelVisible !== false/);
assert.match(packageInputHarness, /collapsedIconCenterOffsets:/);
assert.match(packageInputHarness,
/tab\.icon\.key === baseline\.marketTabs\[index\]\.icon\.key/);
assert.match(uiOnlySmokeHarness, /layout\.stockTabIcon\.rendered === true/);
assert.match(uiOnlySmokeHarness, /layout\.settingsTabIcon\.rendered === true/);
assert.match(uiOnlySmokeHarness, /tab\.icon\.rendered === true/);
});
test("workspace navigation stores only the approved local presentation preferences", () => {
assert.match(navigation, /entry\.view\.hidden = !isCurrent/);
assert.match(navigation, /entry\.view\.toggleAttribute\("inert", !isCurrent\)/);