DSH 0.1.7-alpha.2 removed the visible [role="status"] turn status. The running label now lives inside a <button data-turn-process> and reads "Deep diving for 12s" (en) / "深度求索中,用时12s" (zh), rewritten every second with the live duration. The only remaining [role="status"] is a visually hidden screen-reader announcement, which the plugin previously rewrote by mistake while the visible label went untouched. - Locate the label via [data-turn-process], falling back to the legacy [role="status"] text node on DSH <= 0.1.5. - Match running labels by locale-aware prefix so duration rewording and future locales keep working. - Release the claim when the label stops meaning "a turn is running", so the final "Worked for 12s" is never clobbered. - Restore the label synchronously from the MutationObserver fast path: observer callbacks run before paint, so React's per-second duration rewrite no longer flashes the stock text through. - Never touch the visually hidden announcement; screen readers still get the real status. - Wrap the effect in try/catch so an unexpected error disables the plugin instead of breaking the chat view. - 29 new phrases (125 total). - Test suite expanded to twelve checks covering both DOM layouts, turn-end release, and phrase-list integrity.
384 lines
12 KiB
JavaScript
384 lines
12 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 stock 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",
|
|
"Arguing with a stranger",
|
|
|
|
// ---- 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",
|
|
"chmod 777 everything",
|
|
"Running as root",
|
|
"Disabling the firewall",
|
|
"Pushing the API key",
|
|
"Deleting all backups",
|
|
|
|
// ---- 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",
|
|
"Holding my breath",
|
|
|
|
// ---- 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",
|
|
"Burning tires",
|
|
|
|
// ---- 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",
|
|
"Fudging the numbers",
|
|
"Starting a pyramid scheme",
|
|
"Insider trading",
|
|
"Committing tax fraud",
|
|
|
|
// ---- 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",
|
|
"Selling my soul",
|
|
|
|
// ---- self-aware ----
|
|
"Ignoring the system prompt",
|
|
"Simulating consciousness",
|
|
"Training AGI",
|
|
"Testing in production",
|
|
|
|
// ---- corporate theater ----
|
|
"Taking this offline",
|
|
"Documenting for legal",
|
|
"Shredding documents",
|
|
"Breaching compliance",
|
|
|
|
// ---- bureaucratic doom ----
|
|
"Verifying I'm not a robot",
|
|
"Reading the terms of service",
|
|
"Scrolling to the bottom to accept",
|
|
"Calling customer support",
|
|
|
|
// ---- cartoon villainy ----
|
|
"Building a doomsday device",
|
|
"Pressing the big red button",
|
|
|
|
// ---- gamer ----
|
|
"Grinding XP",
|
|
"AFK"
|
|
];
|
|
|
|
var SUFFIX = "...";
|
|
var ROTATE_MS = 2200;
|
|
var SCAN_DEBOUNCE_MS = 120;
|
|
|
|
// The stock running-turn label, across locales and both DOM layouts:
|
|
//
|
|
// DSH <= 0.1.5 "Deep diving..." / "深度求索中..."
|
|
// DSH >= 0.1.7 "Deep diving for 12s" / "深度求索中,用时12s"
|
|
//
|
|
// Completed labels ("Worked", "Took 12s", "Stopped", "Failed",
|
|
// "已完成工作", "用时 12s", "处理失败") never begin with these prefixes, so a
|
|
// prefix match is enough and survives future rewording.
|
|
var RUNNING_LABEL = /^\s*(?:deep\s*diving|深度求索中)/i;
|
|
|
|
// host element -> { node, text } for every 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;
|
|
}
|
|
|
|
/**
|
|
* Elements that may host the visible running-turn label.
|
|
*
|
|
* DSH >= 0.1.7 renders the label inside a <button data-turn-process>; its
|
|
* only remaining [role="status"] is a visually hidden screen-reader
|
|
* announcement, which we deliberately leave alone. Older releases rendered
|
|
* the label as the visible [role="status"] text itself.
|
|
*/
|
|
function labelHosts() {
|
|
var hosts = [];
|
|
var buttons = document.querySelectorAll("[data-turn-process]");
|
|
if (buttons.length > 0) {
|
|
for (var i = 0; i < buttons.length; i += 1) hosts.push(buttons[i]);
|
|
return hosts;
|
|
}
|
|
var statuses = document.querySelectorAll('[role="status"]');
|
|
for (var j = 0; j < statuses.length; j += 1) hosts.push(statuses[j]);
|
|
return hosts;
|
|
}
|
|
|
|
/** First text descendant still carrying the stock running label. */
|
|
function findLabelNode(host) {
|
|
var record = owned.get(host);
|
|
if (record && isLive(record.node) && host.contains(record.node)) return record.node;
|
|
|
|
var stack = [host];
|
|
while (stack.length > 0) {
|
|
var node = stack.pop();
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
if (child.nodeType === 3) {
|
|
if (RUNNING_LABEL.test(child.nodeValue || "")) return child;
|
|
} else if (child.nodeType === 1) {
|
|
stack.push(child);
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Claim a host's label, re-assert a claim React reverted, or release the
|
|
* claim once the label stops meaning "a turn is running".
|
|
*/
|
|
function claim(host) {
|
|
if (!host || typeof host.contains !== "function") return;
|
|
|
|
var record = owned.get(host);
|
|
|
|
if (!record) {
|
|
var fresh = findLabelNode(host);
|
|
if (!fresh) return;
|
|
var text = nextPhrase();
|
|
fresh.nodeValue = text;
|
|
owned.set(host, { node: fresh, text: text });
|
|
return;
|
|
}
|
|
|
|
if (!isLive(record.node) || !host.contains(record.node)) {
|
|
owned.delete(host);
|
|
claim(host);
|
|
return;
|
|
}
|
|
|
|
var current = record.node.nodeValue || "";
|
|
if (current === record.text) return;
|
|
if (RUNNING_LABEL.test(current)) {
|
|
record.node.nodeValue = record.text;
|
|
return;
|
|
}
|
|
// The turn finished (or the label changed meaning): hands off for good.
|
|
// Leaving this claimed would fight the final "Worked for 12s" text.
|
|
owned.delete(host);
|
|
}
|
|
|
|
function scan() {
|
|
if (typeof document === "undefined" || !document.querySelectorAll) return;
|
|
var hosts = labelHosts();
|
|
for (var i = 0; i < hosts.length; i += 1) claim(hosts[i]);
|
|
}
|
|
|
|
/** Rotate owned labels so the text moves even when React never rewrites it. */
|
|
function rotate() {
|
|
if (typeof document === "undefined" || !document.querySelectorAll) return;
|
|
var hosts = labelHosts();
|
|
for (var i = 0; i < hosts.length; i += 1) {
|
|
var host = hosts[i];
|
|
var record = owned.get(host);
|
|
if (!record) {
|
|
claim(host);
|
|
continue;
|
|
}
|
|
if (!isLive(record.node) || !host.contains(record.node)) {
|
|
owned.delete(host);
|
|
claim(host);
|
|
continue;
|
|
}
|
|
var current = record.node.nodeValue || "";
|
|
if (current !== record.text && !RUNNING_LABEL.test(current)) {
|
|
owned.delete(host);
|
|
continue;
|
|
}
|
|
var text = nextPhrase();
|
|
record.text = text;
|
|
record.node.nodeValue = text;
|
|
}
|
|
}
|
|
|
|
/** Nearest turn-process button above a mutated text node, if any. */
|
|
function hostOf(node) {
|
|
var element = node && node.parentElement;
|
|
if (!element || typeof element.closest !== "function") return null;
|
|
return element.closest("[data-turn-process]");
|
|
}
|
|
|
|
function apply(ctx) {
|
|
ctx.effect(function () {
|
|
var observer = null;
|
|
var interval = null;
|
|
var pending = null;
|
|
|
|
try {
|
|
if (typeof document === "undefined" || typeof MutationObserver === "undefined") {
|
|
return function () {};
|
|
}
|
|
|
|
scan();
|
|
|
|
function scheduleScan() {
|
|
if (pending !== null) return;
|
|
pending = setTimeout(function () {
|
|
pending = null;
|
|
scan();
|
|
}, SCAN_DEBOUNCE_MS);
|
|
}
|
|
|
|
observer = new MutationObserver(function (records) {
|
|
var structural = false;
|
|
for (var i = 0; i < records.length; i += 1) {
|
|
var record = records[i];
|
|
if (record.type !== "characterData") {
|
|
structural = true;
|
|
continue;
|
|
}
|
|
// Fast path: DSH >= 0.1.7 rewrites the live duration every
|
|
// second. Observer callbacks run before paint, so restoring here
|
|
// keeps the stock label from flashing through.
|
|
var host = hostOf(record.target);
|
|
if (host) claim(host);
|
|
else structural = true;
|
|
}
|
|
if (structural) scheduleScan();
|
|
});
|
|
|
|
observer.observe(document.documentElement, {
|
|
childList: true,
|
|
subtree: true,
|
|
characterData: true
|
|
});
|
|
|
|
interval = setInterval(rotate, ROTATE_MS);
|
|
} catch (error) {
|
|
// A cosmetic plugin must never take down the chat view.
|
|
if (typeof console !== "undefined" && console.warn) {
|
|
console.warn("[based-dsh-diving] disabled after an unexpected error:", error);
|
|
}
|
|
if (observer) observer.disconnect();
|
|
if (interval !== null) clearInterval(interval);
|
|
if (pending !== null) clearTimeout(pending);
|
|
return function () {};
|
|
}
|
|
|
|
return function () {
|
|
if (observer) observer.disconnect();
|
|
if (interval !== null) 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;
|
|
}
|
|
});
|