Merge pull request #473 from Jaxx7594/bring-back-the-cat

feat(bring back the cat!): Brings back the legacy 404 page
This commit is contained in:
Aden Lindsay
2026-08-24 13:13:58 +09:30
committed by GitHub
14 changed files with 453 additions and 0 deletions
+63
View File
@@ -53,6 +53,10 @@ if (document.childNodes[1]) {
init();
}
// The 404 page is a bare document with no second childNode, so it must be
// booted unconditionally (outside the gate above). It self-guards on title.
bootErrorPage();
if (import.meta.env.DEV) {
window.addEventListener("unhandledrejection", (event) => {
recoverFromStaleDevModuleGraph(event.reason);
@@ -138,6 +142,65 @@ async function init() {
}
}
// The 404 page is a standalone document that never passes the SPA gate above,
// so the normal plugin path never boots there; start just the classic kitten
// 404 plugin so it renders without any SPA work.
async function bootErrorPage() {
if (IsSEQTAPage) return;
// Runs at document_start, so <title> and body are not available yet; wait
// for the DOM before testing the 404 title.
await new Promise<void>((resolve) => {
if (document.readyState !== "loading") {
resolve();
return;
}
document.addEventListener("DOMContentLoaded", () => resolve(), {
once: true,
});
});
const is404Page =
/404/.test(document.title) || /not found/i.test(document.title);
if (!is404Page) return;
const stored = await browser.storage.local.get([
"onoff",
"plugin.error-page-kitten.settings",
]);
if ((stored.onoff ?? true) === false) return;
const kittenEnabled = (
stored["plugin.error-page-kitten.settings"] as
| { enabled?: boolean }
| undefined
)?.enabled;
if (kittenEnabled === false) return;
hideRebranded404();
try {
const { pluginManager } = await import("@/plugins/index");
await pluginManager.startPlugin("error-page-kitten");
} catch (error) {
// Restore the rebranded page so the user is not left with a blank document.
document
.querySelectorAll<HTMLElement>(".bsplus-kitten-404-hidden")
.forEach((el) => {
el.classList.remove("bsplus-kitten-404-hidden");
el.style.display = "";
});
console.error("[BetterSEQTA+] Failed to boot 404 page:", error);
}
}
function hideRebranded404() {
const message = document.querySelector<HTMLElement>(".message");
if (message) {
message.classList.add("bsplus-kitten-404-hidden");
message.style.display = "none";
}
}
function replaceIcons() {
document
.querySelectorAll<HTMLLinkElement>('link[rel*="icon"]')
+1
View File
@@ -43,6 +43,7 @@
"web_accessible_resources": [
{
"resources": [
"resources/error-page/*",
"resources/icons/*",
"resources/update-image.webp",
"resources/pdfjs/pdf.worker.min.mjs",
@@ -0,0 +1,115 @@
/**
* @jest-environment jsdom
*/
/// <reference types="jest" />
import type { PluginAPI } from "@/plugins/core/types";
import errorPageKittenPlugin from "./index";
const REBRANDED_404_BODY = `
<div class="message">
<h1>Page not found</h1>
<p>We can't find the page you're looking for.</p>
<p>It may have been moved, deleted or the link might be out of date.</p>
<p class="error-ref">Ref: 404</p>
</div>
`;
const api = {
settings: { loaded: Promise.resolve() },
} as unknown as PluginAPI<{}>;
async function startPlugin(): Promise<() => void> {
const cleanup = (await errorPageKittenPlugin.run(api)) ?? (() => {});
return cleanup;
}
function getCard(): HTMLElement | null {
// The card container shares its class with <html>, so scope to body.
return document.body.querySelector<HTMLElement>(".bsplus-kitten-404");
}
function expectKittenRendered(): void {
const card = getCard();
expect(card).not.toBeNull();
expect(document.documentElement.classList).toContain("bsplus-kitten-404");
expect(card?.querySelector("h1")?.textContent).toBe("404 Not Found");
// The original document's title.
expect(document.title).toBe("404 Not Found");
// The reference page's exact vendor-prefixed banner CSS is shipped at runtime.
const stripeStyle = Array.from(
document.head.querySelectorAll("style"),
).find((s) => s.textContent?.includes("-webkit-repeating-linear-gradient"));
expect(stripeStyle?.textContent).toContain(
"repeating-linear-gradient(45deg",
);
const img = card?.querySelector(".kitten img") as HTMLImageElement | null;
expect(img?.getAttribute("alt")).toBeNull();
expect(img?.src).toContain("kitten");
const flickrLink = card?.querySelector(
".attrib a",
) as HTMLAnchorElement | null;
expect(flickrLink?.textContent).toBe("by storyvillegirl");
expect(flickrLink?.href).toBe(
"http://www.flickr.com/photos/bibbit/2756165489/",
);
const seqtaLink = card?.querySelector("a[href='http://www.seqta.com.au']");
expect(seqtaLink?.textContent).toBe("SEQTA");
// Original rebranded message is hidden.
expect(
document.querySelector(".message")?.classList.contains(
"bsplus-kitten-404-hidden",
),
).toBe(true);
}
describe("errorPageKitten", () => {
beforeEach(() => {
document.body.innerHTML = "";
document.title = "";
document.documentElement.classList.remove("bsplus-kitten-404");
});
it("renders the classic kitten 404 card over the rebranded page", async () => {
document.title = "Page not found";
document.body.innerHTML = REBRANDED_404_BODY;
const cleanup = await startPlugin();
expectKittenRendered();
cleanup();
expect(getCard()).toBeNull();
expect(document.title).toBe("Page not found");
expect(
Array.from(document.head.querySelectorAll("style")).some((s) =>
s.textContent?.includes("-webkit-repeating-linear-gradient"),
),
).toBe(false);
expect(
document.documentElement.classList.contains("bsplus-kitten-404"),
).toBe(false);
expect(
document
.querySelector(".message")
?.classList.contains("bsplus-kitten-404-hidden"),
).toBe(false);
});
it("does not render inside the SPA (no .message, has #container)", async () => {
document.title = "SEQTA";
document.body.innerHTML = '<div id="container"></div>';
await startPlugin();
expect(getCard()).toBeNull();
});
it("does not render on an unrelated page", async () => {
document.title = "Home";
document.body.innerHTML = '<div id="app"></div>';
await startPlugin();
expect(getCard()).toBeNull();
});
});
@@ -0,0 +1,132 @@
import type { Plugin } from "@/plugins/core/types";
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
import kittenPng from "@/resources/error-page/kitten.png";
import styles from "./styles.css?inline";
const KITTEN_IMG = resolveExtensionAssetUrl(kittenPng);
const KITTEN_CLASS = "bsplus-kitten-404";
// The reference page's exact h1 background declarations, in the original
// order. Each engine keeps the last declaration it understands (-webkit in
// Blink, -moz in Gecko, the legacy standard otherwise), so shipping them
// byte-for-byte reproduces the reference banner on every engine. Lives in a
// JS string because the CSS minifier drops vendor-prefixed declarations.
const H1_STRIPE_CSS =
"html.bsplus-kitten-404 .bsplus-kitten-404>h1{background-image:-moz-repeating-linear-gradient(135deg, rgba(0,0,0,0), rgba(0,0,0,0) 8px, rgba(0,0,0,0.05) 8px, rgba(0,0,0,0.05) 16px), -moz-linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.1));background-image:-webkit-repeating-linear-gradient(135deg, rgba(0,0,0,0), rgba(0,0,0,0) 8px, rgba(0,0,0,0.05) 8px, rgba(0,0,0,0.05) 16px), -webkit-linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.1));background-image:-o-repeating-linear-gradient(135deg, rgba(0,0,0,0), rgba(0,0,0,0) 8px, rgba(0,0,0,0.05) 8px, rgba(0,0,0,0.05) 16px), -o-linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.1));background-image:repeating-linear-gradient(45deg, rgba(0,0,0,0), rgba(0,0,0,0) 8px, rgba(0,0,0,0.05) 8px, rgba(0,0,0,0.05) 16px), linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.1))}";
// The 404 page (legacy and rebranded) is a standalone document built around a
// single `.message` block; the real app mounts a `#container`. Require the
// former and rule out the latter so the plugin never renders inside the app.
function is404Page(): boolean {
if (document.getElementById("container")) return false;
const heading = document.querySelector(".message > h1");
const text = heading?.textContent ?? document.title;
return /not found/i.test(text) || /404/.test(document.title);
}
function buildCard(): HTMLElement {
const card = document.createElement("div");
card.className = KITTEN_CLASS;
const h1 = document.createElement("h1");
h1.textContent = "404 Not Found";
card.appendChild(h1);
const p1 = document.createElement("p");
p1.textContent =
"Sorry — the resource you are looking for could not be found.";
card.appendChild(p1);
const p2 = document.createElement("p");
p2.textContent =
"If you believe you are seeing this message in error, please contact your IT department.";
card.appendChild(p2);
const kitten = document.createElement("div");
kitten.className = "kitten";
const kittenLine = document.createElement("p");
kittenLine.textContent = "I did find this picture of a kitten for you, though!";
kitten.appendChild(kittenLine);
const img = document.createElement("img");
img.src = KITTEN_IMG;
kitten.appendChild(img);
const attrib = document.createElement("div");
attrib.className = "attrib";
attrib.append("CC-BY-SA ");
const link = document.createElement("a");
link.href = "http://www.flickr.com/photos/bibbit/2756165489/";
link.textContent = "by storyvillegirl";
attrib.appendChild(link);
kitten.appendChild(attrib);
card.appendChild(kitten);
const seqtaLink = document.createElement("a");
seqtaLink.href = "http://www.seqta.com.au";
seqtaLink.textContent = "SEQTA";
card.appendChild(seqtaLink);
return card;
}
const errorPageKittenPlugin: Plugin = {
id: "error-page-kitten",
name: "Classic 404 Page",
description: "Brings back SEQTA's old kitten 404 page",
version: "1.0.0",
settings: {},
disableToggle: true,
defaultEnabled: true,
styles,
run: async (_api) => {
let card: HTMLElement | null = null;
let hasBuilt = false;
let originalTitle = document.title;
let stripeStyle: HTMLStyleElement | null = null;
const render = () => {
if (!is404Page()) return;
document.querySelector(".message")?.classList.add("bsplus-kitten-404-hidden");
if (hasBuilt) return;
const body = document.body;
if (!body) return;
hasBuilt = true;
document.documentElement.classList.add(KITTEN_CLASS);
document.title = "404 Not Found";
// Appended after the manager-injected styles so these same-specificity
// declarations win over the compiled baseline.
stripeStyle = document.createElement("style");
stripeStyle.textContent = H1_STRIPE_CSS;
document.head.appendChild(stripeStyle);
card = buildCard();
body.appendChild(card);
};
// Body may not be parsed yet at document_start; render now and again on
// DOM ready as a safety net.
render();
document.addEventListener("DOMContentLoaded", render, { once: true });
return () => {
document.removeEventListener("DOMContentLoaded", render);
document.documentElement.classList.remove(KITTEN_CLASS);
document
.querySelectorAll(".bsplus-kitten-404-hidden")
.forEach((el) => el.classList.remove("bsplus-kitten-404-hidden"));
document.title = originalTitle;
stripeStyle?.remove();
stripeStyle = null;
card?.remove();
card = null;
};
},
};
export default errorPageKittenPlugin;
@@ -0,0 +1,14 @@
import { defineLazyPlugin } from "../../core/dynamicLoader";
import styles from "./styles.css?inline";
export default defineLazyPlugin({
id: "error-page-kitten",
name: "Classic 404 Page",
description: "Brings back SEQTA's old kitten 404 page",
version: "1.0.0",
settings: {},
disableToggle: true,
defaultEnabled: true,
styles,
loader: () => import("./index"),
});
@@ -0,0 +1,113 @@
/* Classic SEQTA 404 page, restored one-to-one over the rebranded document.
Scoped under html.bsplus-kitten-404 so it can never leak into the SPA;
the card element itself carries the .bsplus-kitten-404 class. */
/* The rebranded page styles <body> (Arial / flex / 25vh top / line-height
1.5 / letter-spacing). line-height and letter-spacing are reset because
the rebranded body sets them and the classic page uses browser defaults. */
html.bsplus-kitten-404 {
background: #333;
}
html.bsplus-kitten-404 body {
background: #333;
font-family: 'Trebuchet MS', sans-serif;
font-size: 12pt;
color: #aaa;
margin: 8px;
padding: 0;
display: block;
line-height: normal;
letter-spacing: normal;
}
/* Hide the rebranded page content while the classic card is shown. */
html.bsplus-kitten-404 .bsplus-kitten-404-hidden {
display: none !important;
}
html.bsplus-kitten-404 .bsplus-kitten-404 {
width: 600px;
margin: 64px auto;
background: #222;
border-radius: 4px;
box-shadow: -1px -1px rgba(0, 0, 0, 0.5),
1px 1px rgba(255, 255, 255, 0.2);
padding: 0 0 8px;
text-align: center;
}
html.bsplus-kitten-404 .bsplus-kitten-404 > h1 {
background: #612;
/* Valid modern baseline; the reference's exact vendor-prefixed declarations
are injected at runtime (H1_STRIPE_CSS in index.ts), which win over this.
The vendor-prefixed lines can't live in this file because the CSS
minifier drops them. */
background-image: repeating-linear-gradient(
135deg,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 0) 8px,
rgba(0, 0, 0, 0.05) 8px,
rgba(0, 0, 0, 0.05) 16px
),
linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1));
color: #fff;
padding: 8px;
border-radius: 4px 4px 0 0;
text-shadow: 0 1px rgba(0, 0, 0, 0.5);
font-size: 150%;
font-weight: normal;
margin: 0 0 16px;
}
html.bsplus-kitten-404 .bsplus-kitten-404 > p {
padding: 8px;
margin: 0;
text-shadow: 0 1px rgba(0, 0, 0, 0.5);
font-style: italic;
}
html.bsplus-kitten-404 .bsplus-kitten-404 > p > a {
color: #729fcf;
text-decoration: none;
}
html.bsplus-kitten-404 .bsplus-kitten-404 > .kitten {
font-size: 70%;
padding: 24px 0;
}
html.bsplus-kitten-404 .bsplus-kitten-404 > .kitten > p {
color: #666;
font-style: italic;
margin: 0 0 8px;
}
html.bsplus-kitten-404 .bsplus-kitten-404 > .kitten > .attrib {
color: #333;
font-size: 80%;
}
html.bsplus-kitten-404 .bsplus-kitten-404 > .kitten > .attrib > a {
color: inherit;
text-decoration: none;
}
html.bsplus-kitten-404 .bsplus-kitten-404 > .kitten > img {
display: inline-block;
border-radius: 4px;
box-shadow: 0 0 4px #000;
}
html.bsplus-kitten-404 .bsplus-kitten-404 > a {
display: block;
padding: 8px;
margin: 0 8px 0 auto;
width: 100px;
text-align: center;
color: #444;
background: #111;
text-decoration: none;
font-size: 80%;
border-radius: 4px;
box-shadow: inset 1px 1px rgba(0, 0, 0, 0.5);
}
+3
View File
@@ -26,6 +26,9 @@ interface StorageChange<T = any> {
/** Phased plugin startup: critical UI first, light DOM next, heavy plugins last. */
const PLUGIN_START_PHASES: readonly string[][] = [
// 404 page boot is a tiny standalone path; start it first so the kitten
// card renders before any SPA work. A no-op on normal SEQTA pages.
["error-page-kitten"],
["themes", "animated-background"],
[
"timetable",
+2
View File
@@ -16,6 +16,7 @@ import messageFoldersPluginLazy from "./built-in/messageFolders/lazy";
import enhancedNavigationPluginLazy from "./built-in/enhancedNavigation/lazy";
import globalSearchPluginLazy from "./built-in/globalSearch/lazy";
import gradeAnalyticsPluginLazy from "./built-in/gradeAnalytics/lazy";
import errorPageKittenPluginLazy from "./built-in/errorPageKitten/lazy";
// Initialize plugin manager
const pluginManager = PluginManager.getInstance();
@@ -36,6 +37,7 @@ pluginManager.registerPlugin(messageFoldersPluginLazy);
pluginManager.registerPlugin(enhancedNavigationPluginLazy);
pluginManager.registerPlugin(globalSearchPluginLazy);
pluginManager.registerPlugin(gradeAnalyticsPluginLazy);
pluginManager.registerPlugin(errorPageKittenPluginLazy);
export async function initializePlugins(): Promise<void> {
await pluginManager.startAllPlugins();
+1
View File
@@ -37,4 +37,5 @@ export const SYNCABLE_PLUGIN_SETTING_DEFAULTS: Record<
passiveIndexing: true,
},
"grade-analytics": { cacheTtlHours: 24 },
"error-page-kitten": {},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

+2
View File
@@ -0,0 +1,2 @@
// Jest stub for Vite asset imports (png, etc.): Vite returns a URL string.
export default "/resources/error-page/kitten.png";
+2
View File
@@ -0,0 +1,2 @@
// Jest stub for Vite's `?inline` css imports: returns a CSS string.
export default "/* test inline css */";
+3
View File
@@ -32,6 +32,9 @@ export default {
storage: { local, onChanged },
runtime: {
sendMessage: jest.fn(async () => undefined),
getURL: jest.fn(
(path: string) => `chrome-extension://test/${String(path).replace(/^\/+/, "")}`,
),
},
};