Move Zen controls above the conversation

Mount Chat and Zen controls on the conversation root at top-right so they cannot overlap the scroll-to-bottom affordance. Hide the semantic conversation header in Zen mode and keep the exit control independent from composer visibility.
This commit is contained in:
Шурупов Илья Викторович 2026-09-14 11:16:56 +03:00
parent a0170862e1
commit ebd1ea84ea
3 changed files with 22 additions and 17 deletions

View file

@ -218,20 +218,17 @@ window.__ModuleLoader__.load({
rules.push(
'[data-sidebar-collapsed] [class*="_sidebarCol"] [class*="_regionArea"]{display:none !important;}',
'[data-composer-seat]{position:relative}',
'.dsh-chat-controls{position:absolute;right:24px;bottom:calc(100% + 6px);z-index:12;display:flex;align-items:center;gap:6px}',
'[data-phase]{position:relative}',
'.dsh-chat-controls{position:absolute;right:24px;top:56px;z-index:12;display:flex;align-items:center;gap:6px}',
'.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%)}',
'[data-composer-seat][data-dsh-composer-collapsed="1"]{min-height:38px;justify-content:center}',
'[data-composer-seat][data-dsh-composer-collapsed="1"]>:not(.dsh-chat-controls){display:none !important}',
'[data-composer-seat][data-dsh-composer-collapsed="1"]>.dsh-chat-controls{position:static;align-self:flex-end;margin:4px 24px 6px 0}',
'[data-composer-seat][data-dsh-composer-collapsed="1"]{display:none !important}',
'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]>[class*="_header"]{display:none !important}',
'html[data-dsh-zen="1"] [data-composer-seat]{min-height:38px;justify-content:center}',
'html[data-dsh-zen="1"] [data-composer-seat]>:not(.dsh-chat-controls){display:none !important}',
'html[data-dsh-zen="1"] [data-composer-seat]>.dsh-chat-controls{position:static;align-self:flex-end;margin:4px 24px 6px 0}',
'html[data-dsh-zen="1"] [data-phase] header[class*="_header"]{display:none !important}',
'html[data-dsh-zen="1"] [data-composer-seat]{display:none !important}',
'html[data-dsh-zen="1"] .dsh-chat-controls{top:12px}',
'html[data-dsh-zen="1"] .dsh-composer-collapse-toggle{display:none !important}'
);
@ -270,14 +267,16 @@ window.__ModuleLoader__.load({
document.querySelectorAll("[data-composer-seat]").forEach((seat) => {
if (composerCollapsed) seat.setAttribute("data-dsh-composer-collapsed", "1");
else seat.removeAttribute("data-dsh-composer-collapsed");
const collapse = seat.querySelector(".dsh-composer-collapse-toggle");
});
document.querySelectorAll(".dsh-chat-controls").forEach((controls) => {
const collapse = controls.querySelector(".dsh-composer-collapse-toggle");
if (collapse) {
const collapseLabel = composerCollapsed ? "⌃ Chat" : "⌄ Chat";
if (collapse.textContent !== collapseLabel) collapse.textContent = collapseLabel;
collapse.title = composerCollapsed ? "Expand bottom chat bar" : "Collapse bottom chat bar";
collapse.setAttribute("aria-expanded", composerCollapsed ? "false" : "true");
}
const zen = seat.querySelector(".dsh-zen-toggle");
const zen = controls.querySelector(".dsh-zen-toggle");
if (zen) {
const zenLabel = zenActive ? "Exit Zen" : "Zen";
if (zen.textContent !== zenLabel) zen.textContent = zenLabel;
@ -321,8 +320,8 @@ window.__ModuleLoader__.load({
}
function enhanceChatControls() {
document.querySelectorAll("[data-composer-seat]").forEach((seat) => {
if (!seat.querySelector(":scope > .dsh-chat-controls")) seat.appendChild(makeChatControls());
document.querySelectorAll("[data-phase]").forEach((conversation) => {
if (!conversation.querySelector(":scope > .dsh-chat-controls")) conversation.appendChild(makeChatControls());
});
updateChatControls();
}

View file

@ -1,7 +1,7 @@
{
"name": "dsh-ui-tweaks",
"description": "dsh web plugin: cohesive sidebar, header, composer-collapse, and Zen-mode controls for the DSH Web chat interface. Client-only.",
"version": "0.2.0",
"version": "0.2.1",
"private": true,
"type": "module",
"engines": {

View file

@ -70,9 +70,12 @@ class Element {
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 seat = new Element('div')
seat.setAttribute('data-composer-seat', '')
body.appendChild(seat)
conversation.appendChild(seat)
body.appendChild(conversation)
const documentListeners = new Map()
const allElements = () => [documentElement, head, body, ...head.descendants(), ...body.descendants()]
@ -83,6 +86,7 @@ const document = {
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 === '.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 []
@ -122,7 +126,7 @@ const plugin = definition.factory(() => {})
let cleanup
plugin.apply({ effect: (start) => { cleanup = start() } })
const controls = seat.querySelector(':scope > .dsh-chat-controls')
const controls = conversation.querySelector(':scope > .dsh-chat-controls')
assert(controls)
const collapse = controls.querySelector('.dsh-composer-collapse-toggle')
const zen = controls.querySelector('.dsh-zen-toggle')
@ -144,11 +148,13 @@ assert.equal(documentElement.getAttribute('data-dsh-zen'), null)
assert.equal(zen.textContent, 'Zen')
cleanup()
assert.equal(seat.querySelector(':scope > .dsh-chat-controls'), null)
assert.equal(conversation.querySelector(':scope > .dsh-chat-controls'), null)
assert.equal(seat.getAttribute('data-dsh-composer-collapsed'), 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{position:absolute;right:24px;top:56px'))
assert(source.includes('[data-phase] header[class*="_header"]{display:none !important}'))
console.log('UI controls test passed')