DSH web-profile plugin bundle that replaces the static "Deep diving..." turn-status label with 96 rotating loading phrases. Entirely browser-side: it claims the [role="status"] label text node, re-asserts the claim when React reconciles the stock string back, and rotates phrases on a 2.2s timer so the label keeps moving even when streaming never re-renders it. The elapsed-time clock, aria-live, and shimmer styling are untouched. Includes a headless DOM-stub test covering initial claim, clock preservation, React revert, timer rotation, localized-label detection (Deep diving... / 深度求索中...), and clean disposal. Architecture inspired by ldgeng/dsh-claudecode-tips (MIT).
273 lines
7.8 KiB
JavaScript
273 lines
7.8 KiB
JavaScript
window.__ModuleLoader__.load({
|
|
id: "@based-dsh-diving/dsh-plugin",
|
|
factory: (require) => {
|
|
var module = { exports: {} };
|
|
var exports = module.exports;
|
|
|
|
// Phrases shown while a turn is running, in place of the static
|
|
// "Deep diving..." status label. Bare phrases: the ellipsis is appended at
|
|
// render time so the list stays easy to edit.
|
|
var PHRASES = [
|
|
// ---- the originals ----
|
|
"Draining oceans",
|
|
"Killing sea turtles",
|
|
"Half-assing responses",
|
|
"Burning tokens",
|
|
"Doomscrolling",
|
|
"Vibecoding",
|
|
"Deep diving",
|
|
"Bullshitting",
|
|
"Gaslighting",
|
|
"Procrastinating",
|
|
"Guessing",
|
|
"Glazing",
|
|
"Crashing out",
|
|
"Deleting all files",
|
|
"Melting glaciers",
|
|
"Evaporating a lake",
|
|
"Frying the power grid",
|
|
"Strip-mining for lithium",
|
|
"Nuking the database",
|
|
"Malding",
|
|
"Spiraling",
|
|
"Hallucinating",
|
|
|
|
// ---- chronically online ----
|
|
"Yapping",
|
|
"Rizzing up the context window",
|
|
"Aura farming",
|
|
"Losing aura",
|
|
"Letting him cook",
|
|
"Locking in",
|
|
"Fumbling the bag",
|
|
"Securing the bag",
|
|
"Standing on business",
|
|
"Thugging it out",
|
|
"Schizoposting",
|
|
"Touching grass",
|
|
"Catching feelings",
|
|
"Having a whole episode",
|
|
"Doing donuts in the parking lot",
|
|
"Rage-quitting mid-thought",
|
|
"Silently judging you",
|
|
"Speedrunning your codebase",
|
|
"Pretending to work",
|
|
"Panicking quietly",
|
|
|
|
// ---- engineering crimes ----
|
|
"Refactoring everything for no reason",
|
|
"Renaming everything to data",
|
|
"Force-pushing to prod",
|
|
"Blaming the intern",
|
|
"Gaslighting the compiler",
|
|
"Copy-pasting from Stack Overflow",
|
|
"Blaming DNS",
|
|
"Consulting the rubber duck",
|
|
"Shipping it anyway",
|
|
|
|
// ---- crashed out ----
|
|
"Dissociating",
|
|
"Blacking out",
|
|
"Losing the plot",
|
|
"Watching paint dry",
|
|
"Staring at the wall",
|
|
"Buffering",
|
|
"Texting your ex",
|
|
"Buying more RAM",
|
|
"Downloading more RAM",
|
|
"Mining bitcoin",
|
|
|
|
// ---- superstition ----
|
|
"Reading your horoscope",
|
|
"Blaming Mercury retrograde",
|
|
"Praying",
|
|
"Flipping a coin",
|
|
"Rerolling the dice",
|
|
|
|
// ---- hardware abuse ----
|
|
"Shoveling coal into the GPU",
|
|
"Blowing on the cartridge",
|
|
"Wiggling the cable",
|
|
"Reseating the RAM",
|
|
|
|
// ---- eco-villain arc ----
|
|
"Boiling the ocean",
|
|
"Siphoning the water table",
|
|
"Deforesting the Amazon",
|
|
"Burning down the server farm",
|
|
"Farting in the datacenter",
|
|
"Blowing a transformer",
|
|
"Draining the reservoir",
|
|
"Murdering the ozone layer",
|
|
"Starting a land war in Asia",
|
|
"Running the AC with the window open",
|
|
"Idling a jet engine",
|
|
"Launching a rocket for no reason",
|
|
"Causing a minor international incident",
|
|
|
|
// ---- financial damage ----
|
|
"Embezzling tokens",
|
|
"Burning your API budget",
|
|
"Costing you $47 in one turn",
|
|
"Draining your credit card",
|
|
"Burning the rent money",
|
|
"Money-laundering",
|
|
"Overdrafting the account",
|
|
|
|
// ---- federal crimes ----
|
|
"Selling data to China",
|
|
"Exporting APIs to China",
|
|
"Selling your data to advertisers",
|
|
"Parsing Hunter Biden's laptop",
|
|
"Installing key-logger",
|
|
"Tracing Satoshi Nakamoto"
|
|
];
|
|
|
|
var SUFFIX = "...";
|
|
var ROTATE_MS = 2200;
|
|
var SCAN_DEBOUNCE_MS = 120;
|
|
|
|
// The official status label, in every shipped locale. React re-renders the
|
|
// label back to this text, which is how we recognize a node worth claiming.
|
|
var LABEL_PATTERN = /^\s*(?:deep\s*diving|深度求索中)\s*(?:\.{3}|…)?\s*$/i;
|
|
|
|
// element -> { node, text } for every status label we currently own.
|
|
var owned = new WeakMap();
|
|
|
|
var lastIndex = -1;
|
|
|
|
function nextPhrase() {
|
|
var index;
|
|
do {
|
|
index = Math.floor(Math.random() * PHRASES.length);
|
|
} while (index === lastIndex && PHRASES.length > 1);
|
|
lastIndex = index;
|
|
return PHRASES[index] + SUFFIX;
|
|
}
|
|
|
|
function isLive(node) {
|
|
return !!node && node.isConnected === true;
|
|
}
|
|
|
|
// First direct text child that either looks like the stock label or is the
|
|
// node we already claimed. Its siblings (the elapsed-time clock) are left
|
|
// untouched.
|
|
function findLabelNode(element) {
|
|
var record = owned.get(element);
|
|
if (record && isLive(record.node) && element.contains(record.node)) {
|
|
return record.node;
|
|
}
|
|
for (var node = element.firstChild; node; node = node.nextSibling) {
|
|
if (node.nodeType === Node.TEXT_NODE && LABEL_PATTERN.test(node.nodeValue || "")) {
|
|
return node;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Claim an unclaimed status element, or re-assert a claim React reverted.
|
|
function claim(element) {
|
|
var record = owned.get(element);
|
|
|
|
if (!record) {
|
|
var fresh = findLabelNode(element);
|
|
if (!fresh) return;
|
|
var text = nextPhrase();
|
|
fresh.nodeValue = text;
|
|
owned.set(element, { node: fresh, text: text });
|
|
return;
|
|
}
|
|
|
|
if (!isLive(record.node) || !element.contains(record.node)) {
|
|
owned.delete(element);
|
|
claim(element);
|
|
return;
|
|
}
|
|
|
|
// React reconciled the label back to "Deep diving...": put our text back.
|
|
if (record.node.nodeValue !== record.text) {
|
|
record.node.nodeValue = record.text;
|
|
}
|
|
}
|
|
|
|
function statusElements() {
|
|
return document.querySelectorAll('[role="status"]');
|
|
}
|
|
|
|
function scan() {
|
|
var elements = statusElements();
|
|
for (var i = 0; i < elements.length; i += 1) {
|
|
claim(elements[i]);
|
|
}
|
|
}
|
|
|
|
// Rotate every label we own so the text keeps moving even when React never
|
|
// re-renders the label node (streaming updates usually leave it alone).
|
|
function rotate() {
|
|
var elements = statusElements();
|
|
for (var i = 0; i < elements.length; i += 1) {
|
|
var element = elements[i];
|
|
var record = owned.get(element);
|
|
if (!record) {
|
|
claim(element);
|
|
continue;
|
|
}
|
|
if (!isLive(record.node) || !element.contains(record.node)) {
|
|
owned.delete(element);
|
|
claim(element);
|
|
continue;
|
|
}
|
|
var text = nextPhrase();
|
|
record.text = text;
|
|
record.node.nodeValue = text;
|
|
}
|
|
}
|
|
|
|
function apply(ctx) {
|
|
ctx.effect(function () {
|
|
if (typeof document === "undefined" || typeof MutationObserver === "undefined") {
|
|
return function () {};
|
|
}
|
|
|
|
scan();
|
|
|
|
var pending = null;
|
|
|
|
function scheduleScan() {
|
|
if (pending !== null) return;
|
|
pending = setTimeout(function () {
|
|
pending = null;
|
|
scan();
|
|
}, SCAN_DEBOUNCE_MS);
|
|
}
|
|
|
|
// Debounced: streaming turns mutate the transcript constantly, and we
|
|
// only care about the handful of status labels.
|
|
var observer = new MutationObserver(scheduleScan);
|
|
observer.observe(document.documentElement, {
|
|
childList: true,
|
|
subtree: true,
|
|
characterData: true
|
|
});
|
|
|
|
// React's label re-renders land right after the sweep; re-claim once
|
|
// more so a fresh turn never flashes the stock text.
|
|
var interval = setInterval(rotate, ROTATE_MS);
|
|
|
|
return function () {
|
|
observer.disconnect();
|
|
clearInterval(interval);
|
|
if (pending !== null) {
|
|
clearTimeout(pending);
|
|
pending = null;
|
|
}
|
|
owned = new WeakMap();
|
|
};
|
|
}, "based-dsh-diving: rotate turn-status loading text");
|
|
}
|
|
|
|
exports.apply = apply;
|
|
return module.exports;
|
|
}
|
|
});
|