Files
based-dsh-diving/client.js
T
mward4 1500f44bed v0.1.3: support the DSH 0.2.0 running indicator, keep 0.1.7/legacy working
DSH 0.2.0-rc.1 moved the live running label out of button[data-turn-process]
(which now carries only completed text) and into a new div[data-chat-running]
indicator rendered through TextShimmer.

- Discover the label host newest-layout-first: [data-chat-running], then
  [data-turn-process], then [role="status"] as a legacy-only fallback.
- Rewrite every matching text node in a host, not just the first: TextShimmer
  renders the label twice (visible layer + aria-hidden decorative overlay), and
  leaving one behind paints the stock wording back on top of the phrase.
- Skip nested live regions while scanning, so the screen-reader announcement
  survives now that it sits inside the label host.
- Extend hostOf() to the new hook so the per-second rewrite is still restored
  synchronously, before paint.

Tests cover all three layouts, including 0.2.0 precedence over a finished
process row and both shimmer layers staying in sync.
2026-09-29 00:30:22 -04:00

409 lines
13 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 every DOM layout shipped
// so far:
//
// DSH <= 0.1.5 "Deep diving..." / "深度求索中..."
// DSH 0.1.7 "Deep diving for 12s" / "深度求索中,用时12s"
// DSH >= 0.2.0 "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 -> { nodes, text } for every label we currently own. A host
// can carry more than one copy of the label: TextShimmer renders the text in
// a visible layer plus an aria-hidden overlay, and both must move together or
// the overlay paints the stock wording back on top of the rotating phrase.
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, newest layout first.
*
* [data-chat-running] DSH >= 0.2.0: the blue "running" indicator that
* carries the live "Deep diving for 12s..." label.
* [data-turn-process] DSH 0.1.7: the collapsible process row, which
* then carried the running label.
* [role="status"] DSH <= 0.1.5: the label *was* the status text.
*
* Later releases leave [role="status"] to a visually hidden screen-reader
* announcement, so it is only ever used as a last-resort fallback, and never
* when a newer hook is present.
*/
function labelHosts() {
var layouts = ["[data-chat-running]", "[data-turn-process]"];
for (var i = 0; i < layouts.length; i += 1) {
var found = document.querySelectorAll(layouts[i]);
if (found.length > 0) return found;
}
return document.querySelectorAll('[role="status"]');
}
/** Live-region element: DSH's screen-reader announcement, never rewritten. */
function isAnnouncement(element) {
return typeof element.getAttribute === "function" &&
element.getAttribute("role") === "status";
}
/**
* Every text descendant still carrying the stock running label.
*
* Returns a list rather than one node because TextShimmer duplicates the
* label into a decorative overlay; skipping nested live regions keeps the
* visually hidden announcement out of it.
*/
function findLabelNodes(host) {
var nodes = [];
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 || "")) nodes.push(child);
} else if (child.nodeType === 1) {
if (!isAnnouncement(child)) stack.push(child);
}
}
}
return nodes;
}
/**
* 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 alive = [];
for (var i = 0; i < record.nodes.length; i += 1) {
if (isLive(record.nodes[i]) && host.contains(record.nodes[i])) alive.push(record.nodes[i]);
}
if (alive.length > 0) {
record.nodes = alive;
for (var j = 0; j < alive.length; j += 1) {
var current = alive[j].nodeValue || "";
if (current === record.text) continue;
if (RUNNING_LABEL.test(current)) {
alive[j].nodeValue = record.text;
continue;
}
// The label changed meaning (the turn finished): hands off for good.
// Staying claimed would fight the final "Worked for 12s" copy.
owned.delete(host);
return;
}
return;
}
owned.delete(host);
}
var fresh = findLabelNodes(host);
if (fresh.length === 0) return;
var text = nextPhrase();
for (var k = 0; k < fresh.length; k += 1) fresh[k].nodeValue = text;
owned.set(host, { nodes: fresh, text: text });
}
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;
}
var stale = false;
for (var j = 0; j < record.nodes.length; j += 1) {
var node = record.nodes[j];
if (!isLive(node) || !host.contains(node)) { stale = true; break; }
var current = node.nodeValue || "";
if (current !== record.text && !RUNNING_LABEL.test(current)) { stale = true; break; }
}
if (stale) {
owned.delete(host);
claim(host);
continue;
}
var text = nextPhrase();
record.text = text;
for (var k = 0; k < record.nodes.length; k += 1) record.nodes[k].nodeValue = text;
}
}
/** Nearest running-status host 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-chat-running], [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 rewrites the live duration every second (in
// 0.2.0 that is two shimmer layers at once). 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;
}
});