From 9db94f8aa7f9260a62256ae6e8e4cf00c356d824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A8=D1=83=D1=80=D1=83=D0=BF=D0=BE=D0=B2=20=D0=98=D0=BB?= =?UTF-8?q?=D1=8C=D1=8F=20=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D1=87?= Date: Mon, 14 Sep 2026 18:29:06 +0300 Subject: [PATCH] Remove experimental chat and Zen controls Roll back the Chat collapse, Zen mode, composer reflow repair, and related regression fixture after they interfered with the native prompt. Keep only the collapsed-sidebar agent-tree hiding selector. --- plugins/dsh-ui-tweaks/lib/client.js | 161 ++----------------- plugins/dsh-ui-tweaks/package.json | 4 +- test/run.sh | 1 - test/ui-controls.test.mjs | 237 ---------------------------- 4 files changed, 14 insertions(+), 389 deletions(-) delete mode 100644 test/ui-controls.test.mjs diff --git a/plugins/dsh-ui-tweaks/lib/client.js b/plugins/dsh-ui-tweaks/lib/client.js index d8a9e47..60476c6 100644 --- a/plugins/dsh-ui-tweaks/lib/client.js +++ b/plugins/dsh-ui-tweaks/lib/client.js @@ -89,7 +89,6 @@ window.__ModuleLoader__.load({ // ----------------------------------------------------------------------- const STYLE_ID = "dsh-ui-tweaks-style"; - let zenActive = false; /** Assemble the combined static CSS for whichever features are enabled. */ function buildCss() { @@ -214,19 +213,7 @@ window.__ModuleLoader__.load({ ); } - rules.push( - '[data-sidebar-collapsed] [class*="_sidebarCol"] [class*="_regionArea"]{display:none !important;}', - '[data-phase]{position:relative}', - '.dsh-chat-controls{z-index:12;display:flex;flex:none;align-items:center;gap:6px;margin-left:8px}', - '.dsh-chat-control{height:28px;min-width:28px;padding:0 9px;border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.12));border-radius:14px;background:var(--dsw-specific-input-major);color:var(--dsw-alias-label-secondary);font:var(--dsw-font-xs-13);cursor:pointer;box-shadow:var(--dsw-shadow-lv1)}', - '.dsh-chat-control:hover{color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--dsw-specific-input-major),#fff 6%)}', - 'html[data-dsh-zen="1"] [class*="_frame"][style*="grid-template-columns"]{grid-template-columns:0 minmax(0,1fr) 0 !important}', - 'html[data-dsh-zen="1"] [class*="_sidebarCol"],html[data-dsh-zen="1"] [class*="_detailsCol"]{visibility:hidden !important;pointer-events:none !important;border:0 !important}', - 'html[data-dsh-zen="1"] [class*="_handle"]{display:none !important}', - 'html[data-dsh-zen="1"] [data-phase] header[class*="_header"]{display:none !important}', - 'html[data-dsh-zen="1"] [data-composer-seat]{position:absolute !important;left:0;right:0;bottom:0;visibility:hidden !important;pointer-events:none !important}', - '.dsh-chat-controls[data-floating="1"]{position:absolute;right:24px;top:12px;margin-left:0}' - ); + rules.push('[data-sidebar-collapsed] [class*="_sidebarCol"] [class*="_regionArea"]{display:none !important;}'); return rules.join(""); } @@ -245,133 +232,6 @@ window.__ModuleLoader__.load({ // ---- (3) header-menu dynamic behavior --------------------------------- - function restoreNativeComposer() { - try { - window.localStorage.removeItem("dsh-ui-tweaks:composer-collapsed"); - } catch {} - document.querySelectorAll("[data-composer-seat]").forEach((seat) => seat.removeAttribute("data-dsh-composer-collapsed")); - } - - function repairNativeComposerLayouts() { - document.querySelectorAll("[data-input-scroll]").forEach((scrollport) => { - if (scrollport.getAttribute("data-dsh-composer-repaired") === "1") return; - scrollport.setAttribute("data-dsh-composer-repaired", "1"); - requestAnimationFrame(() => requestAnimationFrame(() => { - if (!scrollport.isConnected) return; - const mirror = scrollport.querySelector("[data-input-mirror]"); - const input = scrollport.querySelector("textarea"); - if (!mirror || !input) return; - const mirrorPosition = mirror.style.position; - mirror.style.position = "absolute"; - mirror.offsetHeight; - scrollport.offsetHeight; - mirror.style.position = mirrorPosition; - mirror.offsetHeight; - const inputHeight = input.style.height; - input.style.height = `${String(input.clientHeight + 1)}px`; - input.offsetHeight; - input.style.height = inputHeight; - input.offsetHeight; - const seat = scrollport.closest("[data-composer-seat]"); - seat?.parentElement?.style.setProperty("--dsh-composer-height", `${String(seat.offsetHeight)}px`); - })); - }); - } - - function updateChatControls() { - document.querySelectorAll(".dsh-chat-controls").forEach((controls) => { - const zen = controls.querySelector(".dsh-zen-toggle"); - if (zen) { - const zenLabel = zenActive ? "Exit Zen" : "Zen"; - if (zen.textContent !== zenLabel) zen.textContent = zenLabel; - zen.title = zenActive ? "Exit Zen mode" : "Enter Zen mode"; - zen.setAttribute("aria-pressed", zenActive ? "true" : "false"); - } - }); - } - - function conversationRoots() { - const roots = []; - document.querySelectorAll("[data-composer-seat]").forEach((seat) => { - const conversation = seat.closest("[data-phase]"); - if (conversation && !roots.includes(conversation)) roots.push(conversation); - }); - return roots; - } - - function normalizeChatControls() { - const roots = conversationRoots(); - const validRoots = new Set(roots); - const owners = new Map(); - document.querySelectorAll(".dsh-chat-controls").forEach((controls) => { - const owner = controls.closest("[data-phase]"); - if (!validRoots.has(owner) || owners.has(owner)) { - controls.remove(); - return; - } - owners.set(owner, controls); - }); - roots.forEach((conversation) => { - if (owners.has(conversation)) return; - const controls = makeChatControls(); - conversation.appendChild(controls); - owners.set(conversation, controls); - }); - return roots; - } - - function placeChatControls(roots = conversationRoots()) { - roots.forEach((conversation) => { - const controls = Array.from(conversation.querySelectorAll(".dsh-chat-controls")).find((candidate) => candidate.closest("[data-phase]") === conversation); - if (!controls) return; - const git = conversation.querySelector(".dsh-git-working-dirs"); - const utilities = conversation.querySelector('header [class*="_headerUtilities"]'); - const anchor = git ?? utilities; - if (!zenActive && anchor?.parentElement) { - controls.removeAttribute("data-floating"); - if (controls.parentElement !== anchor.parentElement) anchor.parentElement.insertBefore(controls, anchor); - return; - } - controls.setAttribute("data-floating", "1"); - if (controls.parentElement !== conversation) conversation.appendChild(controls); - }); - } - - function setZenActive(active) { - zenActive = active; - if (active) document.documentElement.setAttribute("data-dsh-zen", "1"); - else document.documentElement.removeAttribute("data-dsh-zen"); - placeChatControls(normalizeChatControls()); - updateChatControls(); - } - - function makeChatControls() { - const controls = document.createElement("div"); - controls.className = "dsh-chat-controls"; - - const zen = document.createElement("button"); - zen.type = "button"; - zen.className = "dsh-chat-control dsh-zen-toggle"; - zen.setAttribute("aria-label", "Toggle Zen mode"); - zen.addEventListener("click", () => setZenActive(!zenActive)); - - controls.append(zen); - return controls; - } - - function enhanceChatControls() { - const roots = normalizeChatControls(); - placeChatControls(roots); - updateChatControls(); - } - - function removeChatControls() { - setZenActive(false); - document.querySelectorAll(".dsh-chat-controls").forEach((controls) => controls.remove()); - document.querySelectorAll('[data-dsh-composer-repaired="1"]').forEach((scrollport) => scrollport.removeAttribute("data-dsh-composer-repaired")); - restoreNativeComposer(); - } - /** Close every open header menu (optionally sparing one host). */ function closeAll(except) { document.querySelectorAll('[data-dsh-hdr-menu="host"][data-open="1"]').forEach((host) => { @@ -409,9 +269,6 @@ window.__ModuleLoader__.load({ /** Enhance every header-utilities region currently in the DOM (idempotent). */ function enhance() { - repairNativeComposerLayouts(); - enhanceChatControls(); - if (!COLLAPSE_HEADER) return; const panels = document.querySelectorAll('[class*="_headerUtilities"]'); panels.forEach((panel) => { const host = panel.parentElement; @@ -454,7 +311,16 @@ window.__ModuleLoader__.load({ function apply(ctx) { const start = () => { installStyle(); - restoreNativeComposer(); + + // Features 1 & 2 are pure CSS — nothing more to wire. + if (!COLLAPSE_HEADER) { + return () => { + const style = document.getElementById(STYLE_ID); + if (style) style.remove(); + }; + } + + // Feature 3 — dynamic header dropdown. enhance(); const observer = new MutationObserver(scheduleEnhance); @@ -465,9 +331,7 @@ window.__ModuleLoader__.load({ if (!host) closeAll(); }; const onKey = (e) => { - if (e.key !== "Escape") return; - closeAll(); - if (zenActive) setZenActive(false); + if (e.key === "Escape") closeAll(); }; document.addEventListener("click", onDocClick, true); document.addEventListener("keydown", onKey, true); @@ -477,7 +341,6 @@ window.__ModuleLoader__.load({ document.removeEventListener("click", onDocClick, true); document.removeEventListener("keydown", onKey, true); closeAll(); - removeChatControls(); const style = document.getElementById(STYLE_ID); if (style) style.remove(); document.querySelectorAll(".dsh-hdr-menu-trigger").forEach((n) => n.remove()); diff --git a/plugins/dsh-ui-tweaks/package.json b/plugins/dsh-ui-tweaks/package.json index 6901310..1ca205c 100644 --- a/plugins/dsh-ui-tweaks/package.json +++ b/plugins/dsh-ui-tweaks/package.json @@ -1,7 +1,7 @@ { "name": "dsh-ui-tweaks", - "description": "dsh web plugin: cohesive sidebar, header, and Zen-mode controls for the DSH Web chat interface. Client-only.", - "version": "0.3.1", + "description": "dsh web plugin: sidebar appearance, collapsed-tree hiding, and conversation-header utility menu tweaks without modifying the chat composer. Client-only.", + "version": "0.4.0", "private": true, "type": "module", "engines": { diff --git a/test/run.sh b/test/run.sh index 9fe29d1..229117f 100755 --- a/test/run.sh +++ b/test/run.sh @@ -7,7 +7,6 @@ cd "$repository_directory" bash -n install.sh uninstall.sh bin/dsh-app bin/free-port-3080.sh test/*.sh node --check scripts/apply-compatibility-patches.mjs node test/profile-manifest.test.mjs -node test/ui-controls.test.mjs python3 test/editor-artifacts.test.py python3 test/editor-runtime.test.py python3 test/cloudflared-artifact.test.py diff --git a/test/ui-controls.test.mjs b/test/ui-controls.test.mjs deleted file mode 100644 index 6ab3709..0000000 --- a/test/ui-controls.test.mjs +++ /dev/null @@ -1,237 +0,0 @@ -import assert from 'node:assert/strict' -import { readFile } from 'node:fs/promises' -import vm from 'node:vm' - -class Element { - constructor(tagName) { - this.tagName = tagName - this.attributes = new Map() - this.children = [] - this.listeners = new Map() - this.dataset = {} - this.parentElement = null - this.className = '' - this.textContent = '' - this.title = '' - this.type = '' - this.clientHeight = 32 - this.offsetHeight = 32 - this.isConnected = true - const properties = new Map() - this.style = { - position: '', - height: '', - setProperty: (name, value) => properties.set(name, String(value)), - getPropertyValue: (name) => properties.get(name) ?? '', - } - } - - append(...children) { - for (const child of children) this.appendChild(child) - } - - appendChild(child) { - if (child.parentElement) child.parentElement.children = child.parentElement.children.filter((candidate) => candidate !== child) - child.parentElement = this - this.children.push(child) - return child - } - - insertBefore(child, reference) { - if (child.parentElement) child.parentElement.children = child.parentElement.children.filter((candidate) => candidate !== child) - const index = this.children.indexOf(reference) - child.parentElement = this - this.children.splice(index < 0 ? this.children.length : index, 0, child) - return child - } - - addEventListener(name, listener) { - this.listeners.set(name, listener) - } - - click() { - this.listeners.get('click')?.({ preventDefault() {}, stopPropagation() {}, target: this }) - } - - setAttribute(name, value) { - this.attributes.set(name, String(value)) - } - - getAttribute(name) { - return this.attributes.get(name) ?? null - } - - removeAttribute(name) { - this.attributes.delete(name) - } - - querySelector(selector) { - if (selector === ':scope > .dsh-chat-controls') return this.children.find((child) => child.hasClass('dsh-chat-controls')) ?? null - if (selector === 'header [class*="_headerUtilities"]') return this.descendants().find((child) => child.tagName === 'div' && child.className.includes('_headerUtilities')) ?? null - if (selector === '[data-input-mirror]') return this.descendants().find((child) => child.attributes.has('data-input-mirror')) ?? null - if (selector === 'textarea') return this.descendants().find((child) => child.tagName === 'textarea') ?? null - if (selector.startsWith('.')) return this.descendants().find((child) => child.hasClass(selector.slice(1))) ?? null - return null - } - - querySelectorAll(selector) { - if (selector.startsWith('.')) return this.descendants().filter((child) => child.hasClass(selector.slice(1))) - return [] - } - - closest(selector) { - let current = this - while (current) { - if (selector === '[data-phase]' && current.attributes.has('data-phase')) return current - if (selector === '[data-composer-seat]' && current.attributes.has('data-composer-seat')) return current - current = current.parentElement - } - return null - } - - hasClass(name) { - return this.className.split(/\s+/).includes(name) - } - - descendants() { - return this.children.flatMap((child) => [child, ...child.descendants()]) - } - - remove() { - if (!this.parentElement) return - this.parentElement.children = this.parentElement.children.filter((child) => child !== this) - this.parentElement = null - } -} - -const documentElement = new Element('html') -const head = new Element('head') -const body = new Element('body') -const conversation = new Element('div') -conversation.setAttribute('data-phase', 'active') -const header = new Element('header') -const titleRow = new Element('div') -const headerActions = new Element('div') -const git = new Element('div') -git.className = 'dsh-git-working-dirs' -const utilities = new Element('div') -utilities.className = 'fixture_headerUtilities' -headerActions.appendChild(git) -titleRow.append(headerActions, utilities) -header.appendChild(titleRow) -const seat = new Element('div') -seat.setAttribute('data-composer-seat', '') -seat.setAttribute('data-dsh-composer-collapsed', '1') -const inputScroll = new Element('div') -inputScroll.setAttribute('data-input-scroll', '') -const input = new Element('textarea') -const mirror = new Element('div') -mirror.setAttribute('data-input-mirror', '') -inputScroll.append(input, mirror) -seat.appendChild(inputScroll) -const nestedPhase = new Element('div') -nestedPhase.setAttribute('data-phase', 'streaming') -const strayControls = new Element('div') -strayControls.className = 'dsh-chat-controls' -nestedPhase.appendChild(strayControls) -conversation.append(header, nestedPhase, seat) -body.appendChild(conversation) -const documentListeners = new Map() - -const allElements = () => [documentElement, head, body, ...head.descendants(), ...body.descendants()] -const document = { - documentElement, - head, - body, - createElement: (tagName) => new Element(tagName), - getElementById: (id) => allElements().find((element) => element.id === id) ?? null, - querySelectorAll(selector) { - if (selector === '[data-phase]') return allElements().filter((element) => element.attributes.has('data-phase')) - if (selector === '[data-composer-seat]') return allElements().filter((element) => element.attributes.has('data-composer-seat')) - if (selector === '[data-input-scroll]') return allElements().filter((element) => element.attributes.has('data-input-scroll')) - if (selector === '[data-dsh-composer-repaired="1"]') return allElements().filter((element) => element.getAttribute('data-dsh-composer-repaired') === '1') - if (selector === '.dsh-chat-controls') return allElements().filter((element) => element.hasClass('dsh-chat-controls')) - if (selector === '.dsh-hdr-menu-trigger' || selector === '[data-dsh-hdr-menu]' || selector === '[class*="_headerUtilities"]') return [] - return [] - }, - addEventListener: (name, listener) => documentListeners.set(name, listener), - removeEventListener: (name) => documentListeners.delete(name), -} - -const storage = new Map([['dsh-ui-tweaks:composer-collapsed', '1']]) -const window = { - document, - localStorage: { - getItem: (key) => storage.get(key) ?? null, - setItem: (key, value) => storage.set(key, String(value)), - removeItem: (key) => storage.delete(key), - }, -} -window.window = window -let definition -window.__ModuleLoader__ = { load: (candidate) => { definition = candidate } } - -class MutationObserver { - observe() {} - disconnect() {} -} - -const source = await readFile(new URL('../plugins/dsh-ui-tweaks/lib/client.js', import.meta.url), 'utf8') -vm.runInNewContext(source, { - window, - document, - MutationObserver, - requestAnimationFrame: (callback) => callback(), - setTimeout, -}) - -const plugin = definition.factory(() => {}) -let cleanup -plugin.apply({ effect: (start) => { cleanup = start() } }) - -const controls = conversation.querySelector('.dsh-chat-controls') -assert(controls) -assert.equal(document.querySelectorAll('.dsh-chat-controls').length, 1) -assert.equal(strayControls.parentElement, null) -assert.equal(controls.parentElement, headerActions) -assert(controls.parentElement.children.indexOf(controls) < controls.parentElement.children.indexOf(git)) -const zen = controls.querySelector('.dsh-zen-toggle') -assert.equal(controls.querySelector('.dsh-composer-collapse-toggle'), null) -assert.equal(zen.textContent, 'Zen') -assert.equal(seat.getAttribute('data-dsh-composer-collapsed'), null) -assert.equal(storage.has('dsh-ui-tweaks:composer-collapsed'), false) -assert.equal(inputScroll.getAttribute('data-dsh-composer-repaired'), '1') -assert.equal(mirror.style.position, '') -assert.equal(input.style.height, '') -assert.equal(conversation.style.getPropertyValue('--dsh-composer-height'), '32px') - -zen.click() -assert.equal(documentElement.getAttribute('data-dsh-zen'), '1') -assert.equal(zen.textContent, 'Exit Zen') -assert.equal(seat.getAttribute('data-dsh-composer-collapsed'), null) -assert.equal(controls.parentElement, conversation) -assert.equal(controls.getAttribute('data-floating'), '1') - -documentListeners.get('keydown')?.({ key: 'Escape' }) -assert.equal(documentElement.getAttribute('data-dsh-zen'), null) -assert.equal(zen.textContent, 'Zen') -assert.equal(controls.parentElement, headerActions) -assert(controls.parentElement.children.indexOf(controls) < controls.parentElement.children.indexOf(git)) -assert.equal(controls.getAttribute('data-floating'), null) - -cleanup() -assert.equal(conversation.querySelector('.dsh-chat-controls'), null) -assert.equal(seat.getAttribute('data-dsh-composer-collapsed'), null) -assert.equal(inputScroll.getAttribute('data-dsh-composer-repaired'), null) -assert.equal(documentElement.getAttribute('data-dsh-zen'), null) -assert(source.includes('[data-sidebar-collapsed] [class*="_sidebarCol"] [class*="_regionArea"]')) -assert(source.includes('[class*="_detailsCol"]{visibility:hidden !important;pointer-events:none !important;border:0 !important}')) -assert(!source.includes('[class*="_detailsCol"],html[data-dsh-zen="1"] [class*="_handle"]{display:none')) -assert(source.includes('.dsh-chat-controls{z-index:12;display:flex;flex:none')) -assert(source.includes('.dsh-chat-controls[data-floating="1"]{position:absolute;right:24px;top:12px')) -assert(!source.includes('dsh-composer-collapse-toggle')) -assert(!source.includes('[data-composer-seat][data-dsh-composer-collapsed="1"]')) -assert(source.includes('[data-composer-seat]{position:absolute !important;left:0;right:0;bottom:0;visibility:hidden')) -assert(source.includes('[data-phase] header[class*="_header"]{display:none !important}')) - -console.log('UI controls test passed')