skills/typescript/SKILL.md
TypeScript and JavaScript standards. Sourced from [abix-/chromium-extensions](https://github.com/abix-/chromium-extensions) (Hush + filter-anything-everywhere). Use when writing TS/JS, including browser extension bootstrap shims, MV3 service workers, and small web frontends.
npx skillsauth add abix-/claude-blueprints typescriptInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Provenance note: This skill is canonical TS/JS best-practices,
not user-style. No user-authored TypeScript exists. The 31K TS that
GitHub reports for
abix-/chromium-extensions
all lives in the vendored filter-anything-everywhere/ subdirectory
(upstream extension). Hush itself is Rust+WASM with thin plain-JS
bootstrap shims, NOT TypeScript.
Mixed authorship in
abix-/chromium-extensions:
filter-anything-everywhere/. Vendored TS/jQuery/Jest extension.
Used here as a reference for tsconfig.json, narrowing patterns,
and MV2/3 extension layout. Not user style.hush/. User-authored, but it's Rust + WASM. The .js
files (background.js, mainworld.js, content.js) are tiny
event-bridging shims around the WASM core, not TypeScript at all.Use this skill as a canonical reference when writing TS or JS in this stack. Don't expect it to encode user style; there isn't one to mine yet.
The decision between TS and plain JS for new extension work is not "always TS." Tiny bootstrap shims and content scripts can stay plain JS because adding a build step costs more than it saves. This is the Hush pattern.
hush/background.js),
content scripts that just bridge events, main-world page hooks
(hush/mainworld.js). These need to load synchronously at
document_start, can't afford a build step, and call WASM for the
real work.The filter-anything-everywhere/tsconfig.json baseline:
{
"compilerOptions": {
"target": "es2020",
"module": "ESNext",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"rootDir": "./extension"
},
"exclude": ["node_modules", "**/*.spec.ts"]
}
strict: true always. Non-negotiable.target: es2020 for browser; bump to es2022+ only when targeting
evergreen Node.skipLibCheck: true to avoid third-party .d.ts noise blocking
your build.forceConsistentCasingInFileNames: true keeps macOS/Linux/Windows
builds identical.filter-anything-everywhere:
export function getCanonicalHostname(name:string) {
if (name.startsWith('www.')) return name.substring(4);
return name;
}
Tight, no space before :. Match the existing style.type Action =
| { kind: 'block' }
| { kind: 'allow'; pattern: string };
unknown over any. Narrow with type guards (x instanceof X,
typeof x === 'string').interface for object shapes that can be extended; type for
unions and aliases. Pick one per shape and stay consistent.as const for literal narrowness on config objects.as Foo only at trust boundaries (JSON parse, DOM lookup); inside
the codebase, write a guard.function getInputElement(): HTMLInputElement {
const e = document.getElementById('input');
if (!(e instanceof HTMLInputElement)) {
console.log('expected input to be an HTMLInputElement', e);
throw new Error('Expected input to be an HTMLInputElement');
}
return e;
}
instanceof for DOM elements. The type system already knows.null further than
the boundary.async function addWord(word: string) {
const items = await GetOptions();
items.blacklist[word] = true;
await chrome.storage.local.set({blacklist: items.blacklist});
await rerender();
}
async/await everywhere. No .then chains in new code.Promise.all for independent parallel work.void cmd(); to make
intent explicit.$('#btn').click(async () => { ... }).The Hush patterns are the reference:
Background service worker bootstrap (hush/background.js):
(async () => {
try {
const result = await migrateConfigSchema(chrome.storage.local);
if (!result.skipped) console.log(`[Hush bg] migrated v${result.fromVersion} -> v${result.toVersion}`);
await initWasm({ module_or_path: "./dist/pkg/hush_bg.wasm" });
try { initEngine(); } catch (e) { console.error("[Hush bg] initEngine threw", e); }
hushBackgroundMain();
} catch (e) {
console.error("[Hush bg] bootstrap failed", e);
}
})();
[Hush bg]) so multi-component
extensions are debuggable.Main-world content scripts (hush/mainworld.js):
(() => { ... })()) to avoid leaking globals into
the page.window.__app_state__ only when you actually need page-script
visibility; otherwise keep state inside the closure.try { ... } catch (e) { /* ignore - detached document */ } for
ops that fail benignly after navigation.Storage:
chrome.storage.local is async, returns a Promise in MV3.Manifest:
"web_accessible_resources" entry for any file the page loads
(WASM, main-world JS).fetch over libraries (axios, node-fetch). Standard in all
browsers and Node 18+.URL / URLSearchParams over manual parsing.structuredClone(x) over JSON.parse(JSON.stringify(x)).crypto.randomUUID() over npm uuid packages.Intl.NumberFormat / Intl.DateTimeFormat over moment/date-fns
for display.Pattern seen in mainworld.js:
const v = el && el.dataset && el.dataset.hushSpoof;
if (!v) return false;
const parts = String(v).split(",");
String(x),
Number(x), Boolean(x) to normalize before use.filter-anything-everywhere uses. Tests
named foo.spec.ts, co-located with sources.eslint-config-google + eslint-config-prettier
is the chosen baseline (filter-anything-everywhere/package.json).prettier --write . before commit. Same config across the project.husky + lint-staged for pre-commit if the project has many
contributors. Skip for solo projects.mainworld.js header is the model: it
explains why two install phases exist before any code starts.// TODO(handle): with a name; // FIXME: for known-broken.Set / Map over plain objects for keyed lookups.for (let i = 0; i < arr.length; i++) is fine for hot paths;
for...of is fine everywhere else. Don't write forEach if you
need break.requestAnimationFrame for DOM-dependent timing, never setTimeout(fn, 16).V8 powers Chrome, Edge, Node.js, Deno (and most extension contexts). Knowing its rules pays off for hot paths.
delete obj.prop in hot objects. Triggers transition to
dictionary mode. Set to undefined if you must clear.new Array(n) or fill via Array.from,
not push in a loop, when length is known. V8 reallocates the
backing store ~once per doubling but can avoid it.Float32Array, Uint8Array, etc.) for
numerical data. Tightly packed, predictable, no boxing.+ is fine; V8 uses cons-strings. Avoid
repeated s = s + char in a loop only because the cons tree gets
deep; reach for an array + .join('') past ~1000 chars.JSON.parse on a string is faster than building the object
literally when the data is more than a few hundred items.
V8 has a fast-path parser.with, eval, arguments in hot functions. Each
inhibits inlining.Promise.resolve().then(...)
fires before setTimeout(..., 0). Long microtask chains starve
rendering.queueMicrotask(fn) for "run after current code but before
next event" without creating a Promise.setTimeout(..., 0) has a 4ms minimum in most browsers
(clamped). Use MessageChannel.port.postMessage(...) for true
near-zero delay.requestIdleCallback for background work that should yield
to user interactions. Not available in all Node versions.await in tight loops when work is independent.
Promise.all([...]) runs in parallel.console.time / timeEnd for ad-hoc measurement. Pair with
performance.now() for sub-ms precision.performance.mark + performance.measure to surface in the
DevTools timeline.node --prof then node --prof-process for V8
profiling. --inspect to attach Chrome DevTools to Node.webpack-bundle-analyzer / esbuild --analyze / source maps
explorer to see what's shipping. Code-split routes.chrome.storage.chrome.storage.local is async and has quota (~10MB).
session is in-memory, cleared on browser close.offsetWidth (or any layout
property) flushes pending mutations and forces sync layout.
Don't interleave reads and writes; batch reads, then writes.IntersectionObserver / MutationObserver for reactive DOM
watching; far cheaper than setInterval polling.hostname.ts exports getCanonicalHostname,
nothing else..js suffix in TS import paths when module: ESNext (required by
some bundlers): import {x} from './hostname.js' even though the
source is .ts.var. Use const, then let only when reassignment is real.==. Always ===.Function.prototype.bind in hot paths. Arrow capture is faster and
clearer.any outside trust boundaries. unknown then narrow.interface for non-object shapes. Use type.filter-anything-everywhere uses it because
it's legacy; new projects should not.await in a .js script tag. Modules only.chrome.storage or a Map.development
YAML standards for config files, Ansible playbooks, k8s manifests, GitHub Actions, docker-compose, and any project config. Built from the YAML 1.2 spec, yamllint defaults, and the practical pitfalls (Norway problem, type coercion, anchor gotchas).
development
--- name: ueforge description: ueforge framework: the base layer every UE4SS Rust mod in the Grounded2Mods workspace builds on. Authoritative on the composition model (Effect/Trigger/Skill), the Def/Registry/Instance/Controller pattern, hot reload, discovery, hardening doctrine, and the five framework modules (rpg, stacks, difficulty, inventory, damage). Use when writing or modifying code under `ueforge/` in [abix-/Grounded2Mods](https://github.com/abix-/Grounded2Mods), or when promoting a patte
development
--- name: schedule1 description: Modding Schedule 1 (TVGS, IL2CPP Unity + MelonLoader + Harmony). Authoritative on Schedule 1 game specifics: engine type, MelonLoader/Il2CppInterop references, eMployee mod root-cause findings, vanilla CookRoutine + StartMixingStationBehaviour internals, certainty-tracking discipline. Mod code lives in [`abix-/Schedule1Mods`](https://github.com/abix-/Schedule1Mods) (the `EmployeeReset` sidecar is the current shipped mod). Not for playing the game. user-invocable:
development
Pattern for an embedded HTTP control plane in a long-running process (game mod, simulator, GUI app, daemon) that exposes ALL runtime state plus the ability to drive ANY in-process operation. The first thing to build in a new project. It enables research, investigation, prototyping, and TDD. Use when starting any modding/embedding/long-running-process project, when adding observability or test surfaces to an existing one, or when answering "how do I see/poke this thing at runtime".