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.
363 lines
16 KiB
JavaScript
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;
|
|
},
|
|
});
|