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.
This commit is contained in:
parent
35f26fa32b
commit
9db94f8aa7
4 changed files with 14 additions and 389 deletions
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
Loading…
Add table
Add a link
Reference in a new issue