DSH-Arch-Setup/plugins/dsh-ui-tweaks/lib/client.js
Шурупов Илья Викторович 9db94f8aa7 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.
2026-09-14 18:29:06 +03:00

363 lines
16 KiB
JavaScript

/**
* dsh-ui-tweaks — browser half.
*
* A single client-only plugin that bundles six independent UI tweaks, each
* toggleable in the CONFIG block below:
*
* 0) BASE_BG — soften the near-black base background token
* (`--dsw-alias-bg-base`, #151517 by default). Drives the main content and,
* via feature (1), the sidebar, so both tone together.
*
* 1) MATCH_SIDEBAR_BG — the left sidebar root paints
* `background: var(--dsw-specific-sidebar-fill)` while the main content
* paints `var(--dsw-alias-bg-base)`. We alias the former token to the
* latter so the sidebar shares the transcript background (theme-agnostic).
*
* 2) SIDEBAR_BRAND_TEXT / HIDE_NEW_SESSION — keep the sidebar's logo row so
* its NATIVE collapse/expand toggle stays available (that toggle is what
* the compact-sidebar tweak used to remove), but replace the DeepSeek brand
* logo with plain text (e.g. "Harness"), and optionally hide the New
* Session button. Sidebar CSS-module classes are content-hashed, so we
* match the stable suffix; the brand button is pinpointed with
* `[class*="_brand"]:has([class*="_brandIdentity"])`.
*
* 3) COLLAPSE_HEADER — collapse the conversation-header utility pills
* (Session log, Stop All, Resume…, Git) behind one `⋯` dropdown. We never
* move or synthetically click the real buttons — their React handlers stay
* intact; we only restyle the `.headerUtilities` container into a panel and
* add a vanilla trigger. A MutationObserver re-applies after React
* re-renders. Open/close state lives in `data-*` attrs React won't clobber.
*
* 4) PANEL_BG — tone the elevated surfaces so they sit close to the base
* background: the "Message the agent" composer (`--dsw-specific-input-major`),
* code blocks (`--dsw-alias-markdown-code-block` + `…-banner`), the Ongoing
* Goal bar (`--dsw-specific-tip`), and generic panels/dropdowns
* (`--dsw-alias-bg-layer-1/2`). All are overridden to one shade.
*
* 5) STYLE_PROCESSED_LIKE_PROMPT — restyle the smooth-stream "Processed <time>"
* collapsible (stable class `dshss-processed`) to match the prompt composer:
* full-width rounded pill, thin border, panel background, soft shadow.
*
* All static CSS shares one <style>; feature 3 also adds dynamic wiring.
* Everything is HMR-safe via a single combined cleanup.
* @module dsh-ui-tweaks/client
*/
window.__ModuleLoader__.load({
id: "dsh-ui-tweaks",
factory: (require) => {
var module = { exports: {} };
var exports = module.exports;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
// ---- CONFIG (edit here) -----------------------------------------------
// (0) Background tone — the app's base background (harness default
// #151517). This drives the main content AND, via feature (1), the
// sidebar, so both change together. Set to "" to keep the harness default.
const BASE_BG = "#121317";
// (1) Sidebar background — paint the sidebar with the main content fill.
const MATCH_SIDEBAR_BG = true;
// Fill the sidebar takes. Default = main content bg (seamless). Use any
// color or var, e.g. "var(--dsw-alias-bg-layer-2)" for a subtle tint.
const SIDEBAR_FILL = "var(--dsw-alias-bg-base)";
// (2) Sidebar header — keep the logo row (and its native collapse/expand
// toggle button), but replace the DeepSeek brand logo with plain text.
// Set to "" to show no brand text at all (leaving only the toggle).
const SIDEBAR_BRAND_TEXT = "Harness";
// Remove the New Session button.
const HIDE_NEW_SESSION = true;
// (3) Header menu — collapse header utility buttons into one dropdown.
const COLLAPSE_HEADER = true;
// Label shown on the collapsed trigger button.
const TRIGGER_LABEL = "⋯";
// Close the dropdown after clicking one of its items.
const CLOSE_ON_ITEM_CLICK = true;
// (4) Panel + prompt surfaces — tone the elevated panels (the composer,
// code blocks, the Ongoing Goal bar, and generic dropdowns/inputs) to sit
// a touch off the base background. Default = slightly lighter than BASE_BG.
// Set to "" to leave the harness defaults.
const PANEL_BG = "#24262b";
// (5) Processed bar — restyle the smooth-stream "Processed <time>"
// collapsible (class `dshss-processed`, its own plugin's FOLD_CSS) to look
// like the prompt composer: full-width rounded pill with a thin border,
// the panel background, and a soft shadow.
const STYLE_PROCESSED_LIKE_PROMPT = true;
// -----------------------------------------------------------------------
const STYLE_ID = "dsh-ui-tweaks-style";
/** Assemble the combined static CSS for whichever features are enabled. */
function buildCss() {
const rules = [];
// (0) base background tone (main content + sidebar via feature 1)
if (BASE_BG) {
rules.push("*{--dsw-alias-bg-base:" + BASE_BG + " !important;}");
}
// (4) elevated panel + prompt surfaces (composer, code blocks, goal bar,
// generic panels) — one shade so they sit close to the base background.
if (PANEL_BG) {
rules.push(
"*{" +
"--dsw-specific-input-major:" + PANEL_BG + " !important;" + // composer card
"--dsw-alias-markdown-code-block:" + PANEL_BG + " !important;" + // code block body
"--dsw-alias-markdown-code-block-banner:" + PANEL_BG + " !important;" +// code block copy bar
"--dsw-specific-tip:" + PANEL_BG + " !important;" + // Ongoing Goal bar
"--dsw-alias-bg-layer-1:" + PANEL_BG + " !important;" + // generic panels/inputs
"--dsw-alias-bg-layer-2:" + PANEL_BG + " !important;" + // generic elevated
"}"
);
}
// (1) sidebar fill
if (MATCH_SIDEBAR_BG) {
rules.push("*{--dsw-specific-sidebar-fill:" + SIDEBAR_FILL + " !important;}");
}
// (2) sidebar header: keep the native collapse toggle, restyle the brand.
// Hide the original brand contents (logo mark + wordmark, text or svg).
rules.push('[class*="_brandIdentity"]{display:none !important;}');
if (SIDEBAR_BRAND_TEXT) {
// JSON.stringify safely double-quotes/escapes the label for CSS content.
const label = JSON.stringify(String(SIDEBAR_BRAND_TEXT));
rules.push(
// The brand BUTTON is the only element that *contains* the identity,
// so `:has()` pinpoints it regardless of class hashing or nesting.
'[class*="_brand"]:has([class*="_brandIdentity"]){overflow:visible !important;}',
'[class*="_brand"]:has([class*="_brandIdentity"])::after{content:' + label +
";font-size:18px;font-weight:600;letter-spacing:.04em;" +
"color:var(--dsw-alias-label-primary);white-space:nowrap;}",
// In the collapsed rail, hide the brand so only the toggle remains.
'[class*="_collapsed"] [class*="_brand"]:has([class*="_brandIdentity"]){display:none !important;}'
);
} else {
rules.push('[class*="_brand"]:has([class*="_brandIdentity"]){display:none !important;}');
}
if (HIDE_NEW_SESSION) {
rules.push('[class*="_root"] [class*="_newSession"]{display:none !important;}');
}
// (3) header menu (static parts)
if (COLLAPSE_HEADER) {
rules.push(
// host anchors the absolutely-positioned panel
'[data-dsh-hdr-menu="host"]{position:relative}',
// the utility row becomes a dropdown panel
'[data-dsh-hdr-menu="panel"]{' +
"position:absolute;top:calc(100% + 8px);right:0;z-index:2147483000;" +
"margin-left:0 !important;flex-direction:column !important;" +
"align-items:stretch !important;gap:6px !important;padding:8px;min-width:210px;" +
"background:var(--dsw-alias-bg-elevated, var(--dsw-specific-sidebar-fill, var(--dsw-alias-bg-base,#1f1f1f)));" +
"border:1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12));" +
"border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.45)}",
// hidden unless the host is marked open
'[data-dsh-hdr-menu="host"]:not([data-open="1"]) [data-dsh-hdr-menu="panel"]{display:none !important}',
// stack items full width, left aligned
'[data-dsh-hdr-menu="panel"] > *{width:100%;box-sizing:border-box;justify-content:flex-start !important}',
// known grouped entry (fleet-control) stacks vertically too
'[data-dsh-hdr-menu="panel"] .dsh-fleet-control{flex-direction:column;align-items:stretch;gap:6px;width:100%}',
'[data-dsh-hdr-menu="panel"] .dsh-fleet-control > *{width:100%;justify-content:flex-start}',
// the trigger pill
".dsh-hdr-menu-trigger{border:1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12));" +
"height:32px;min-width:32px;color:var(--dsw-alias-label-primary);background:transparent;" +
"border-radius:18px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;" +
"gap:5px;padding:6px 12px;font-size:15px;line-height:20px;white-space:nowrap;" +
"font-family:var(--dsw-font-family)}",
".dsh-hdr-menu-trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}",
'.dsh-hdr-menu-trigger[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover)}'
);
}
// (5) Processed bar restyled like the prompt composer (.uV2eYG_card:
// 1px border, input-major bg, 22px radius, shadow-lv2, full width). The
// `dshss-processed` class is stable (its own plugin's FOLD_CSS), so we
// override those exact declarations with !important.
if (STYLE_PROCESSED_LIKE_PROMPT) {
rules.push(
// Collapsed pill — looks like the prompt composer. Label + chevron
// stay grouped on the left (flex-start) so the arrow sits right next
// to the text; a tiny spacer keeps the box full width.
".dshss-processed{" +
"box-sizing:border-box !important;" +
"width:100% !important;max-width:var(--dsh-chat-content-width) !important;" +
"align-self:center !important;" +
"justify-content:flex-start !important;gap:8px !important;" +
"min-height:0 !important;padding:5px 14px !important;" +
"border:1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-border-l2, rgba(255,255,255,.12))) !important;" +
"border-radius:22px !important;" +
"background:var(--dsw-specific-input-major) !important;" +
"box-shadow:var(--dsw-shadow-lv2) !important;" +
"color:var(--dsw-alias-label-secondary) !important;}",
// Keep the chevron aligned to the text baseline, not stretched.
".dshss-processed .dshss-processed-chevron{align-self:center !important;margin:0 !important;}",
".dshss-processed:hover{" +
"background:color-mix(in srgb, var(--dsw-specific-input-major), #ffffff 6%) !important;" +
"border-color:var(--dsw-alias-border-l2, rgba(255,255,255,.18)) !important;" +
"color:var(--dsw-alias-label-primary) !important;}",
// Expanded — drop the pill and blend into the page background: use the
// base bg, no border, no shadow, so the opened section reads as inline
// content rather than a floating capsule.
'.dshss-processed[aria-expanded="true"]{' +
"background:var(--dsw-alias-bg-base) !important;" +
"border-color:transparent !important;" +
"box-shadow:none !important;" +
"color:var(--dsw-alias-label-primary) !important;}",
// Expanded hover — subtle highlight since it otherwise blends into bg.
'.dshss-processed[aria-expanded="true"]:hover{' +
"background:color-mix(in srgb, var(--dsw-alias-bg-base), #ffffff 6%) !important;}"
);
}
rules.push('[data-sidebar-collapsed] [class*="_sidebarCol"] [class*="_regionArea"]{display:none !important;}');
return rules.join("");
}
/** Insert (or update) the shared override <style>. */
function installStyle() {
let el = document.getElementById(STYLE_ID);
if (!el) {
el = document.createElement("style");
el.id = STYLE_ID;
el.dataset.plugin = "dsh-ui-tweaks";
(document.head || document.documentElement).appendChild(el);
}
el.textContent = buildCss();
}
// ---- (3) header-menu dynamic behavior ---------------------------------
/** Close every open header menu (optionally sparing one host). */
function closeAll(except) {
document.querySelectorAll('[data-dsh-hdr-menu="host"][data-open="1"]').forEach((host) => {
if (host === except) return;
host.removeAttribute("data-open");
const t = host.querySelector(".dsh-hdr-menu-trigger");
if (t) t.setAttribute("aria-expanded", "false");
});
}
/** Build the trigger button for a given host (the .headerActions element). */
function makeTrigger(host) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "dsh-hdr-menu-trigger";
btn.textContent = TRIGGER_LABEL;
btn.title = "Session actions";
btn.setAttribute("aria-label", "Session actions menu");
btn.setAttribute("aria-expanded", "false");
btn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
const open = host.getAttribute("data-open") === "1";
closeAll(host);
if (open) {
host.removeAttribute("data-open");
btn.setAttribute("aria-expanded", "false");
} else {
host.setAttribute("data-open", "1");
btn.setAttribute("aria-expanded", "true");
}
});
return btn;
}
/** Enhance every header-utilities region currently in the DOM (idempotent). */
function enhance() {
const panels = document.querySelectorAll('[class*="_headerUtilities"]');
panels.forEach((panel) => {
const host = panel.parentElement;
if (!host) return;
panel.setAttribute("data-dsh-hdr-menu", "panel");
host.setAttribute("data-dsh-hdr-menu", "host");
// close-on-item-click wiring (once)
if (CLOSE_ON_ITEM_CLICK && panel.getAttribute("data-dsh-hdr-wired") !== "1") {
panel.setAttribute("data-dsh-hdr-wired", "1");
panel.addEventListener("click", (e) => {
if (e.target && e.target.closest("button,a,[role='menuitem'],[role='button']")) {
// let the real handler run, then close on next tick
setTimeout(() => closeAll(), 0);
}
});
}
// ensure exactly one trigger, placed right after the panel
if (!host.querySelector(".dsh-hdr-menu-trigger")) {
const trigger = makeTrigger(host);
if (panel.nextSibling) host.insertBefore(trigger, panel.nextSibling);
else host.appendChild(trigger);
}
});
}
let scheduled = false;
function scheduleEnhance() {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(() => {
scheduled = false;
enhance();
});
}
/** No client services required — pure DOM/CSS. */
const inject = [];
/** Client plugin body: install CSS, then wire the header dropdown if on. */
function apply(ctx) {
const start = () => {
installStyle();
// 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);
observer.observe(document.body, { childList: true, subtree: true });
const onDocClick = (e) => {
const host = e.target && e.target.closest && e.target.closest('[data-dsh-hdr-menu="host"]');
if (!host) closeAll();
};
const onKey = (e) => {
if (e.key === "Escape") closeAll();
};
document.addEventListener("click", onDocClick, true);
document.addEventListener("keydown", onKey, true);
return () => {
observer.disconnect();
document.removeEventListener("click", onDocClick, true);
document.removeEventListener("keydown", onKey, true);
closeAll();
const style = document.getElementById(STYLE_ID);
if (style) style.remove();
document.querySelectorAll(".dsh-hdr-menu-trigger").forEach((n) => n.remove());
document.querySelectorAll('[data-dsh-hdr-menu]').forEach((n) => {
n.removeAttribute("data-dsh-hdr-menu");
n.removeAttribute("data-open");
n.removeAttribute("data-dsh-hdr-wired");
});
};
};
if (ctx && typeof ctx.effect === "function") ctx.effect(start, "dsh-ui-tweaks: sidebar + header tweaks");
else start();
}
exports.apply = apply;
exports.inject = inject;
return module.exports;
},
});