feat: add configurable operator appearance and layout
This commit is contained in:
@@ -78,6 +78,7 @@ const evidence = {
|
||||
inferredFromAuthoritativeState: true,
|
||||
nativeRoute: "LegacyReadyIntent => _controller.Current"
|
||||
},
|
||||
preGuardStartupSettle: null,
|
||||
blockedMessages: [],
|
||||
observedMessagesAfterGuard: []
|
||||
},
|
||||
@@ -162,6 +163,66 @@ async function evaluate(expression) {
|
||||
return response.result?.value;
|
||||
}
|
||||
|
||||
async function waitForSettledStartup() {
|
||||
const settleDeadline = Math.min(Date.now() + 10_000, deadline);
|
||||
let stableSignature = null;
|
||||
let stableSince = 0;
|
||||
let lastSample = null;
|
||||
while (Date.now() < settleDeadline) {
|
||||
lastSample = await evaluate(`(() => {
|
||||
const currentMenu = Array.from(document.querySelectorAll(
|
||||
"#workspace-navigation .workspace-nav-item[aria-current='page']"));
|
||||
const region = document.getElementById("workspace-region");
|
||||
return {
|
||||
readyState: document.readyState,
|
||||
bodyBusy: document.body?.getAttribute("aria-busy") || null,
|
||||
bridgeReady: typeof window.chrome?.webview?.postMessage === "function",
|
||||
connectionText: (document.getElementById("playout-connection-state")?.textContent || "").trim(),
|
||||
playoutStatus: (document.getElementById("playout-status")?.textContent || "").trim(),
|
||||
activeWorkspace: region?.dataset?.activeWorkspace || null,
|
||||
activeMarketTab: region?.dataset?.activeMarketTab || null,
|
||||
startWorkspace: document.body?.dataset?.startWorkspace || null,
|
||||
workspaceTitle: (document.getElementById("workspace-title")?.textContent || "").trim(),
|
||||
currentMenu: currentMenu.map(element => element.id || element.dataset?.tabId || null)
|
||||
};
|
||||
})()`);
|
||||
const ready = lastSample?.readyState === "complete" &&
|
||||
lastSample.bodyBusy === "false" && lastSample.bridgeReady === true &&
|
||||
lastSample.connectionText === "DRY RUN" &&
|
||||
lastSample.playoutStatus.startsWith("IDLE") &&
|
||||
["stock", "catalog", "settings"].includes(lastSample.activeWorkspace) &&
|
||||
["stockCut", "lastWorkspace"].includes(lastSample.startWorkspace) &&
|
||||
lastSample.workspaceTitle.length > 0 && lastSample.currentMenu.length === 1;
|
||||
const signature = ready ? JSON.stringify({
|
||||
activeWorkspace: lastSample.activeWorkspace,
|
||||
activeMarketTab: lastSample.activeMarketTab,
|
||||
startWorkspace: lastSample.startWorkspace,
|
||||
workspaceTitle: lastSample.workspaceTitle,
|
||||
currentMenu: lastSample.currentMenu
|
||||
}) : null;
|
||||
if (signature && signature === stableSignature) {
|
||||
if (Date.now() - stableSince >= 400) {
|
||||
return {
|
||||
stableMilliseconds: Date.now() - stableSince,
|
||||
activeWorkspace: lastSample.activeWorkspace,
|
||||
activeMarketTab: lastSample.activeMarketTab,
|
||||
startWorkspace: lastSample.startWorkspace,
|
||||
workspaceTitle: lastSample.workspaceTitle,
|
||||
currentMenu: lastSample.currentMenu
|
||||
};
|
||||
}
|
||||
} else {
|
||||
stableSignature = signature;
|
||||
stableSince = signature ? Date.now() : 0;
|
||||
}
|
||||
await sleep(75);
|
||||
}
|
||||
if (Date.now() >= deadline) {
|
||||
fail("OUTCOME_UNKNOWN", "Startup did not settle before the observation deadline; no guard was installed.");
|
||||
}
|
||||
fail("KNOWN_FAILURE", `Startup did not reach a stable DryRun workspace: ${JSON.stringify(lastSample)}`);
|
||||
}
|
||||
|
||||
async function installNoIntentGuard() {
|
||||
const installed = await evaluate(`(() => {
|
||||
const bridge = window.chrome?.webview;
|
||||
@@ -212,6 +273,7 @@ async function readShell() {
|
||||
bounds.left < document.documentElement.clientWidth &&
|
||||
bounds.top < document.documentElement.clientHeight &&
|
||||
bounds.display !== "none" && bounds.visibility !== "hidden");
|
||||
const operatorShell = document.querySelector(".operator-shell");
|
||||
const workspaceRegion = document.getElementById("workspace-region");
|
||||
const navigationToggle = document.getElementById("workspace-nav-toggle");
|
||||
const stockTab = document.getElementById("workspace-stock-tab");
|
||||
@@ -220,8 +282,11 @@ async function readShell() {
|
||||
const stockWorkspace = document.getElementById("stock-workspace");
|
||||
const catalogWorkspace = document.getElementById("catalog-workspace");
|
||||
const settingsWorkspace = document.getElementById("settings-workspace");
|
||||
const splitter = document.getElementById("workspace-splitter");
|
||||
const schedule = document.querySelector(".playlist-panel");
|
||||
const shellBounds = rectangle(operatorShell);
|
||||
const workspaceBounds = rectangle(workspaceRegion);
|
||||
const splitterBounds = rectangle(splitter);
|
||||
const scheduleBounds = rectangle(schedule);
|
||||
const scheduleCenter = scheduleBounds ? {
|
||||
x: scheduleBounds.left + scheduleBounds.width / 2,
|
||||
@@ -249,12 +314,15 @@ async function readShell() {
|
||||
},
|
||||
layout: {
|
||||
activeWorkspace: workspaceRegion?.dataset?.activeWorkspace || null,
|
||||
navigationAriaExpanded: navigationToggle?.getAttribute("aria-expanded") || null,
|
||||
navigationCollapsedClass: workspaceRegion?.classList.contains("nav-collapsed") === true,
|
||||
navigationExpanded: navigationToggle?.getAttribute("aria-expanded") === "true" &&
|
||||
workspaceRegion?.classList.contains("nav-collapsed") !== true,
|
||||
stockTabCurrent: stockTab?.getAttribute("aria-current") || null,
|
||||
marketTabCount: marketTabs.length,
|
||||
marketTabsCurrent: marketTabs.map(button => ({
|
||||
tabId: button.dataset.tabId,
|
||||
label: (button.querySelector(".workspace-nav-label")?.textContent || "").trim(),
|
||||
current: button.getAttribute("aria-current") || null,
|
||||
hasIcon: Boolean(button.querySelector("svg")),
|
||||
hasLabel: Boolean(button.querySelector(".workspace-nav-label"))
|
||||
@@ -265,18 +333,42 @@ async function readShell() {
|
||||
stockWorkspaceVisible: isVisible(stockWorkspace, rectangle(stockWorkspace)) &&
|
||||
stockWorkspace?.getAttribute("aria-hidden") === "false" &&
|
||||
stockWorkspace?.hasAttribute("inert") !== true,
|
||||
stockWorkspaceHidden: stockWorkspace?.hidden === true &&
|
||||
stockWorkspace?.getAttribute("aria-hidden") === "true" &&
|
||||
stockWorkspace?.hasAttribute("inert") === true,
|
||||
catalogWorkspaceVisible: isVisible(catalogWorkspace, rectangle(catalogWorkspace)) &&
|
||||
catalogWorkspace?.getAttribute("aria-hidden") === "false" &&
|
||||
catalogWorkspace?.hasAttribute("inert") !== true,
|
||||
catalogWorkspaceHidden: catalogWorkspace?.hidden === true &&
|
||||
catalogWorkspace?.getAttribute("aria-hidden") === "true" &&
|
||||
catalogWorkspace?.hasAttribute("inert") === true,
|
||||
settingsWorkspaceVisible: isVisible(settingsWorkspace, rectangle(settingsWorkspace)) &&
|
||||
settingsWorkspace?.getAttribute("aria-hidden") === "false" &&
|
||||
settingsWorkspace?.hasAttribute("inert") !== true,
|
||||
settingsWorkspaceHidden: settingsWorkspace?.hidden === true &&
|
||||
settingsWorkspace?.getAttribute("aria-hidden") === "true" &&
|
||||
settingsWorkspace?.hasAttribute("inert") === true,
|
||||
workspaceTitle: text("workspace-title"),
|
||||
shell: shellBounds,
|
||||
workspace: workspaceBounds,
|
||||
splitter: splitterBounds,
|
||||
splitterState: splitter ? {
|
||||
role: splitter.getAttribute("role"),
|
||||
orientation: splitter.getAttribute("aria-orientation"),
|
||||
controls: splitter.getAttribute("aria-controls"),
|
||||
valueMinimum: Number(splitter.getAttribute("aria-valuemin")),
|
||||
valueMaximum: Number(splitter.getAttribute("aria-valuemax")),
|
||||
valueNow: Number(splitter.getAttribute("aria-valuenow")),
|
||||
tabIndex: splitter.tabIndex
|
||||
} : null,
|
||||
schedule: scheduleBounds,
|
||||
workspaceToScheduleWidthRatio: workspaceBounds && scheduleBounds &&
|
||||
scheduleBounds.width > 0
|
||||
? workspaceBounds.width / scheduleBounds.width
|
||||
: null,
|
||||
scheduleShare: shellBounds && scheduleBounds && shellBounds.width > 0
|
||||
? scheduleBounds.width / shellBounds.width
|
||||
: null,
|
||||
scheduleVisible: isVisible(schedule, scheduleBounds),
|
||||
scheduleHitTestVisible: Boolean(scheduleHit &&
|
||||
(scheduleHit === schedule || schedule?.contains(scheduleHit)))
|
||||
@@ -298,22 +390,53 @@ function assertShell(shell) {
|
||||
fail("KNOWN_FAILURE", "The package shell did not remain rendered DryRun/IDLE with zero guarded native intents.");
|
||||
}
|
||||
const layout = shell.layout;
|
||||
if (!layout || layout.activeWorkspace !== "stock" ||
|
||||
layout.navigationExpanded !== true ||
|
||||
layout.stockTabCurrent !== "page" || layout.settingsTabCurrent !== "false" ||
|
||||
layout.marketTabCount !== 10 ||
|
||||
layout.marketTabsCurrent.some(tab => tab.current !== "false" ||
|
||||
tab.hasIcon !== true || tab.hasLabel !== true) ||
|
||||
layout.currentMenuItemCount !== 1 ||
|
||||
layout.stockWorkspaceVisible !== true || layout.catalogWorkspaceHidden !== true ||
|
||||
layout.settingsWorkspaceHidden !== true) {
|
||||
fail("KNOWN_FAILURE", "The package did not render the expanded navigation with stock as the sole initial workspace.");
|
||||
const currentMarkets = layout?.marketTabsCurrent?.filter(tab => tab.current === "page") || [];
|
||||
const navigationExpansionConsistent = layout &&
|
||||
((layout.navigationAriaExpanded === "true" && layout.navigationCollapsedClass === false) ||
|
||||
(layout.navigationAriaExpanded === "false" && layout.navigationCollapsedClass === true));
|
||||
const commonNavigationOk = layout && navigationExpansionConsistent &&
|
||||
layout.marketTabCount === 10 && layout.currentMenuItemCount === 1 &&
|
||||
layout.marketTabsCurrent.every(tab => tab.hasIcon === true && tab.hasLabel === true);
|
||||
const stockWorkspaceOk = layout?.activeWorkspace === "stock" &&
|
||||
layout.stockTabCurrent === "page" && layout.settingsTabCurrent === "false" &&
|
||||
currentMarkets.length === 0 && layout.stockWorkspaceVisible === true &&
|
||||
layout.catalogWorkspaceHidden === true && layout.settingsWorkspaceHidden === true &&
|
||||
layout.workspaceTitle === "종목·컷";
|
||||
const settingsWorkspaceOk = layout?.activeWorkspace === "settings" &&
|
||||
layout.stockTabCurrent === "false" && layout.settingsTabCurrent === "page" &&
|
||||
currentMarkets.length === 0 && layout.stockWorkspaceHidden === true &&
|
||||
layout.catalogWorkspaceHidden === true && layout.settingsWorkspaceVisible === true &&
|
||||
layout.workspaceTitle === "설정";
|
||||
const catalogWorkspaceOk = layout?.activeWorkspace === "catalog" &&
|
||||
layout.stockTabCurrent === "false" && layout.settingsTabCurrent === "false" &&
|
||||
currentMarkets.length === 1 && layout.stockWorkspaceHidden === true &&
|
||||
layout.catalogWorkspaceVisible === true && layout.settingsWorkspaceHidden === true &&
|
||||
layout.workspaceTitle === currentMarkets[0]?.label;
|
||||
if (!commonNavigationOk ||
|
||||
[stockWorkspaceOk, settingsWorkspaceOk, catalogWorkspaceOk].filter(Boolean).length !== 1) {
|
||||
fail("KNOWN_FAILURE", "The package did not render one internally consistent initial workspace and menu selection.");
|
||||
}
|
||||
const splitterState = layout.splitterState;
|
||||
if (layout.scheduleVisible !== true || layout.scheduleHitTestVisible !== true ||
|
||||
!layout.shell || !layout.workspace || !layout.splitter || !layout.schedule ||
|
||||
!Number.isFinite(layout.workspaceToScheduleWidthRatio) ||
|
||||
layout.workspaceToScheduleWidthRatio < 1.60 ||
|
||||
layout.workspaceToScheduleWidthRatio > 1.90) {
|
||||
fail("KNOWN_FAILURE", "The package did not render a visible right schedule beside the approximately 1.75:1 workspace layout.");
|
||||
layout.workspaceToScheduleWidthRatio < 1.05 ||
|
||||
layout.workspaceToScheduleWidthRatio > 1.95 ||
|
||||
!Number.isFinite(layout.scheduleShare) ||
|
||||
layout.scheduleShare < 0.33 || layout.scheduleShare > 0.49 ||
|
||||
splitterState?.role !== "separator" || splitterState.orientation !== "vertical" ||
|
||||
splitterState.controls !== "workspace-region" || splitterState.tabIndex !== 0 ||
|
||||
!Number.isFinite(splitterState.valueMinimum) || splitterState.valueMinimum < 34 ||
|
||||
!Number.isFinite(splitterState.valueMaximum) || splitterState.valueMaximum > 48 ||
|
||||
!Number.isFinite(splitterState.valueNow) ||
|
||||
splitterState.valueNow < splitterState.valueMinimum ||
|
||||
splitterState.valueNow > splitterState.valueMaximum ||
|
||||
Math.abs(layout.workspace.right - layout.splitter.left) > 1.5 ||
|
||||
Math.abs(layout.splitter.right - layout.schedule.left) > 1.5 ||
|
||||
Math.abs(layout.schedule.right - layout.shell.right) > 1.5 ||
|
||||
Math.abs(layout.splitter.top - layout.shell.top) > 1.5 ||
|
||||
Math.abs(layout.splitter.bottom - layout.shell.bottom) > 1.5) {
|
||||
fail("KNOWN_FAILURE", "The package did not render a visible right schedule beside the adjustable workspace layout.");
|
||||
}
|
||||
for (const [name, control] of Object.entries(shell.controls || {})) {
|
||||
if (!control?.present) fail("KNOWN_FAILURE", `Required shell control ${name} is missing.`);
|
||||
@@ -335,6 +458,7 @@ try {
|
||||
await connect(target);
|
||||
await rpc("Runtime.enable");
|
||||
await rpc("Page.enable");
|
||||
evidence.safety.preGuardStartupSettle = await waitForSettledStartup();
|
||||
await installNoIntentGuard();
|
||||
await sleep(500);
|
||||
evidence.shell = await readShell();
|
||||
|
||||
Reference in New Issue
Block a user