Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions integrations/deepseek-harness/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,15 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop{background:#f3f0e9;color:

function place() {
const settings = findSettingsTrigger();
if (!settings || !settings.parentElement) {
const row = settings?.parentElement;
const settingsArea = row?.parentElement;
const footArea = settingsArea?.parentElement;
Comment on lines +132 to +133

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve placement for pre-alpha.5 sidebar layouts

The new mount logic unconditionally assumes alpha.5's button -> triggerRow -> settingsArea -> footArea nesting, although the documented compatibility range still starts at DSH 0.1.0-rc.6 (docs/refactoring.md:69). On earlier supported layouts where the settings button has fewer wrappers, these parent hops identify a sidebar or application container as footArea, so insertBefore mounts the background control outside the footer and it can disappear or be laid out incorrectly. Detect the relevant ancestors structurally or retain the previous direct-parent fallback when the alpha.5 shape is absent.

Useful? React with 👍 / 👎.

if (!settings || !settingsArea || !footArea) {
if (host.parentElement) host.remove();
return;
}
if (host.parentElement !== settings.parentElement || host.nextElementSibling !== settings) {
settings.parentElement.insertBefore(host, settings);
if (host.parentElement !== footArea || host.nextElementSibling !== settingsArea) {
footArea.insertBefore(host, settingsArea);
}
host.classList.toggle("rail", settings.getBoundingClientRect().width <= 40);
if (!pop.hidden) placePop();
Expand Down
2 changes: 1 addition & 1 deletion integrations/deepseek-harness/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "beauticode-dsh",
"version": "1.0.22",
"version": "1.0.23",
"description": "Cordis plugin: image/video backgrounds for DeepSeek Harness web.",
"type": "module",
"main": "./index.mjs",
Expand Down
274 changes: 274 additions & 0 deletions integrations/deepseek-harness/test/console.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import test from "node:test";
import vm from "node:vm";

/**
* Minimal DOM for console.js placement. Mirrors DSH 0.1.2-alpha.5:
* footArea > settingsArea > triggerRow (horizontal flex) > settings button.
* Putting #beauticode-console inside triggerRow squeezes the settings button
* to zero width, which is Issue #37's flicker loop.
*/
class FakeNode {
constructor(tagName, document) {
this.tagName = String(tagName).toUpperCase();
this.document = document;
this.id = "";
this.className = "";
this.parentElement = null;
this.children = [];
this.attributes = new Map();
this.dataset = {};
this.style = {};
this.hidden = false;
this._innerHTML = "";
this.textContent = "";
this.listeners = new Map();
this.classList = {
toggle: (name, force) => {
const names = new Set(this.className.split(/\s+/).filter(Boolean));
const on = force === undefined ? !names.has(name) : Boolean(force);
if (on) names.add(name);
else names.delete(name);
this.className = [...names].join(" ");
return on;
},
};
}

get nextElementSibling() {
if (!this.parentElement) return null;
const siblings = this.parentElement.children;
return siblings[siblings.indexOf(this) + 1] ?? null;
}

setAttribute(name, value) {
this.attributes.set(name, String(value));
if (name === "id") this.id = String(value);
if (name === "class") this.className = String(value);
if (name.startsWith("data-")) {
const key = name
.slice(5)
.replace(/-([a-z])/g, (_all, ch) => ch.toUpperCase());
this.dataset[key] = String(value);
}
}

getAttribute(name) {
if (name === "id") return this.id || null;
if (name === "class") return this.className || null;
return this.attributes.get(name) ?? null;
}

hasAttribute(name) {
return this.getAttribute(name) != null;
}

set innerHTML(html) {
this._innerHTML = String(html);
for (const child of [...this.children]) child.remove();
const re = /<([a-z0-9]+)([^>]*)>/gi;
let match;
while ((match = re.exec(this._innerHTML))) {
const tag = match[1].toLowerCase();
if (["svg", "path", "rect", "circle", "strong", "small", "h2"].includes(tag)) {
continue;
}
const attrs = match[2];
const classMatch = attrs.match(/class="([^"]*)"/);
if (tag === "span" && !classMatch) continue;
const child = this.document.createElement(tag);
if (classMatch) child.className = classMatch[1];
for (const attr of attrs.matchAll(/([a-z0-9:-]+)="([^"]*)"/gi)) {
if (attr[1] === "class") continue;
child.setAttribute(attr[1], attr[2]);
}
this.append(child);
}
}

get innerHTML() {
return this._innerHTML;
}

append(...nodes) {
for (const node of nodes) {
node.remove();
node.parentElement = this;
this.children.push(node);
}
}

insertBefore(node, ref) {
node.remove();
node.parentElement = this;
const index = ref ? this.children.indexOf(ref) : -1;
if (index >= 0) this.children.splice(index, 0, node);
else this.children.push(node);
return node;
}

remove() {
if (!this.parentElement) return;
const siblings = this.parentElement.children;
const index = siblings.indexOf(this);
if (index >= 0) siblings.splice(index, 1);
this.parentElement = null;
}

addEventListener(name, handler) {
const list = this.listeners.get(name) ?? [];
list.push(handler);
this.listeners.set(name, list);
}

contains(node) {
for (let current = node; current; current = current.parentElement) {
if (current === this) return true;
}
return false;
}

matches(selector) {
const attr = selector.match(/^(\w+)?\[([^=\]]+)=["']([^"']+)["']\]$/);
if (attr) {
const [, tag, name, value] = attr;
if (tag && this.tagName !== tag.toUpperCase()) return false;
return this.getAttribute(name) === value;
}
if (selector.startsWith(".")) {
return this.className.split(/\s+/).includes(selector.slice(1));
}
if (selector.startsWith("#")) return this.id === selector.slice(1);
return this.tagName === selector.toUpperCase();
}

querySelectorAll(selector) {
const matches = [];
for (const child of this.children) {
if (child.matches(selector)) matches.push(child);
matches.push(...child.querySelectorAll(selector));
}
return matches;
}

querySelector(selector) {
return this.querySelectorAll(selector)[0] ?? null;
}

getBoundingClientRect() {
if (this.getAttribute("aria-haspopup") !== "dialog") {
return { width: 36, height: 36, left: 8, top: 724, bottom: 760 };
}
const squeezed = this.parentElement?.children.some(
(child) => child.id === "beauticode-console",
);
if (squeezed) {
return { width: 0, height: 0, left: 8, top: 0, bottom: 0 };
}
return { width: 36, height: 36, left: 8, top: 724, bottom: 760 };
}
}

function createConsoleDocument() {
const document = {
createElement(tagName) {
const node = new FakeNode(tagName, document);
if (tagName === "input") node.type = "";
return node;
},
addEventListener() {},
};
const documentElement = new FakeNode("html", document);
const head = new FakeNode("head", document);
const body = new FakeNode("body", document);
document.documentElement = documentElement;
document.head = head;
document.body = body;
documentElement.append(head, body);
document.querySelectorAll = (selector) => documentElement.querySelectorAll(selector);
document.getElementById = (id) => documentElement.querySelector(`#${id}`);
return document;
}

function mountAlpha5Sidebar(document) {
const footArea = document.createElement("div");
footArea.id = "foot-area";
const settingsArea = document.createElement("div");
settingsArea.id = "settings-area";
const triggerRow = document.createElement("div");
triggerRow.id = "trigger-row";
triggerRow.style.display = "flex";
triggerRow.style.flexDirection = "row";
const settings = document.createElement("button");
settings.id = "dsh-settings";
settings.setAttribute("aria-haspopup", "dialog");
triggerRow.append(settings);
settingsArea.append(triggerRow);
footArea.append(settingsArea);
document.body.append(footArea);
return { footArea, settingsArea, triggerRow, settings };
}

async function loadConsole(document) {
const source = await fs.readFile(new URL("../console.js", import.meta.url), "utf8");
const ticks = [];
const context = {
window: null,
document,
MutationObserver: class {
observe() {}
},
addEventListener() {},
innerHeight: 800,
setInterval: (fn) => {
ticks.push(fn);
return ticks.length;
},
fetch: async () => ({ ok: false, json: async () => ({}) }),
};
context.window = context;
context.globalThis = context;
vm.runInNewContext(source, context);
return {
tick() {
for (const fn of ticks) fn();
},
};
}

test("console mounts above the settings area instead of inside the trigger row", async () => {
const document = createConsoleDocument();
const { footArea, settingsArea, triggerRow, settings } = mountAlpha5Sidebar(document);
const runtime = await loadConsole(document);

const snapshots = [];
const capture = () => {
const host = document.getElementById("beauticode-console");
snapshots.push({
parent: host?.parentElement?.id ?? null,
next: host?.nextElementSibling?.id ?? null,
settingsWidth: settings.getBoundingClientRect().width,
inTriggerRow: triggerRow.children.includes(host),
});
};

capture();
for (let i = 0; i < 6; i += 1) {
runtime.tick();
capture();
}

const host = document.getElementById("beauticode-console");
assert.equal(host?.parentElement?.id, "foot-area");
assert.equal(host?.nextElementSibling?.id, "settings-area");
assert.equal(triggerRow.children.map((child) => child.id).join(","), "dsh-settings");
assert.equal(host.parentElement, footArea);
assert.equal(host.nextElementSibling, settingsArea);
for (const snapshot of snapshots) {
assert.equal(snapshot.parent, "foot-area");
assert.equal(snapshot.next, "settings-area");
assert.equal(snapshot.settingsWidth, 36);
assert.equal(snapshot.inTriggerRow, false);
}
});
2 changes: 1 addition & 1 deletion integrations/deepseek-harness/test/ui-host.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ test("plugin injects a compact sidebar console script", async (t) => {
assert.doesNotMatch(source, /data-act="infernal"/);
assert.match(source, /data-act="gallery"/);
assert.match(source, /builtin-gallery/);
assert.match(source, /insertBefore/);
assert.match(source, /footArea\.insertBefore\(host, settingsArea\)/);
assert.match(source, /fileInput\.type = "file"/);
assert.doesNotMatch(source, /#beauticode-console\{[^}]*color-scheme/);
});
Expand Down
Loading