Imported from movingwoo/wtools (
AGENTS.md). Install upstream withnpx skills add movingwoo/wtools. Copyright stays with the author.
AGENTS.md
Scope
This file applies to the entire repository.
Project Overview
W-Tools is a collection of developer utilities that runs as a pure static site. It uses HTML, CSS, and vanilla JavaScript ES modules; the site itself has no build step, package manager, bundler, or linter. CI runs syntax/static validation and a Playwright browser suite (tests/, CI-only — never required for hosting or serving the site) that covers both rendering and per-tool input/output accuracy.
- Keep processing in the browser whenever possible. Do not introduce a backend or send user input to a server unless a feature inherently requires a network request and the UI makes that behavior clear.
- Keep all user-facing text in Korean.
- Preserve direct static hosting compatibility.
- Treat
FEATURES.mdas the feature inventory and update it whenever a tool is added, removed, or materially changed.
Repository Layout
index.html Page shell, global libraries, and the `js/main.js` entry point
css/style.css Shared responsive and light/dark theme styles
js/core.js Tool registry, shared UI builders, byte helpers, and lazy loaders
js/main.js Tool manifest registration, lazy module loader, hash router, sidebar, search, and generated home page
js/tool-manifest.js Generated search/home metadata and tool-to-module mapping
js/tools/*.js Category modules; each module registers multiple related tools
js/lib/** First-party, DOM-independent algorithms and format implementations
js/workers/** Module Worker entry points for expensive first-party implementations
assets/ Static images and icons
manifest.json PWA manifest (installability, icons, theme color)
sw.js Service worker; precaches the app shell, then network-first for offline support
tests/ Playwright browser tests (CI-only; own package.json, not part of the site)
tests/tools/ Per-tool input/output cases, one spec per `js/tools/` module
tests/helpers.js Shared UI driver and the table-driven `toolCases` runner
tests/fixtures.js Test material built at run time (images, certificates, keys)
tests/cdn-cache.js Fixture that serves lazily loaded CDN libraries from a local cache
scripts/ Dependency-free repository and static-site validation scripts
.github/workflows/ validate.yml on every PR and main push; nightly.yml once a day
FEATURES.md Feature inventory grouped by category
README.md User-facing project documentation
DEVELOPMENT_GUIDE.md Code boundaries, size budgets, dependency replacement, and release rules
Running and Validation
Run the site through an HTTP server because ES modules do not work correctly when index.html is opened with file://:
python3 -m http.server 8000
Then open http://localhost:8000 and validate changes manually.
CI (.github/workflows/validate.yml) checks JavaScript syntax, validates registrations and static assets, and runs the Playwright browser suite on every PR and every push to main. Nightly re-runs Chromium against the real CDN; scheduled compatibility and maintenance workflows cover the closest minimum engines and monthly dependency/standards review. CI uses digest-pinned official Playwright images, and validate_static.py keeps the primary image and package versions aligned. To run the browser tests locally, use the Node version pinned in both .node-version and tests/.node-version (22, the same major CI uses). The test package rejects other Node majors, and validate_static.py keeps the two version files aligned. With fnm or nvm installed, the version switches automatically on entering either the repository or tests/.
cd tests
npm ci
npm run test:collect -- --project=chromium # quick runner startup and test collection check
npx playwright install chromium
npx playwright test --project=chromium
npx playwright install firefox webkit # optional: add Firefox/WebKit smoke projects
npx playwright test # Chromium full + Firefox/WebKit smoke
npx playwright test --project=chromium tools/network.spec.js # one module
npx playwright test --project=chromium -g "subnet" # by case name
Browser Tests
The suite has two layers:
smoke.spec.jsandtools-render.spec.jscover the app shell, routing, search, and that every registered tool renders without console errors.tests/tools/<module>.spec.jscovers the output each tool produces, one spec perjs/tools/module.- Chromium runs the entire suite. Firefox and WebKit run
smoke.spec.js,tools-render.spec.js, andtools/media.spec.jssequentially in the same containerized CI job. Use--project=chromiumfor the normal local fast path.
Follow these conventions when adding cases:
- Declare cases as a table and run them with
toolCases('<module>', cases)fromtests/helpers.js. Write a plaintest(...)only when a case needs custom steps such as file upload, downloads, or a multi-step flow. The group name is what identifies the spec in failure output, because table-driven tests reporthelpers.jsas their location. - Verify time- or random-dependent tools by format (regex, length, range), never by exact value.
- Use published test vectors for anything backed by a standard (hash, cipher, fingerprint) and cross-check against a second implementation, such as a Node built-in, where practical.
- Build binary and secret material at run time in
tests/fixtures.js. Do not commit images, archives, or private keys. - Stub external network calls with
page.routeso a case never depends on a live service. - Console errors fail every test. Allow an expected one with
test.use({ allowConsoleErrors: ['...'] })and explain why in a comment.
CDN libraries loaded lazily by tools are cached on disk by tests/cdn-cache.js, so a CDN hiccup does not fail a run and repeat runs stay offline. The cache lives in tests/.lib-cache (gitignored; CI restores it with actions/cache), and the browser still validates every cached response against the SRI hash pinned in js/core.js. The same fixture disables service worker registration with an init script, because sw.js caches those same external hosts and a request a service worker handles is invisible to page.route. Do not replace this with Playwright's serviceWorkers: 'block' — that option injects an init script that reads navigator.serviceWorker, which is a SecurityError inside the empty-sandbox preview iframe the markdown tool builds, and the resulting pageerror fails the console guard. Browser tests do not touch the real CDN; .github/workflows/nightly.yml checks every registered CDN response, CORS header, and SRI pin once a day to catch a dead pin or a withdrawn package. It does not re-run the browser suite. A new browser test entry point must spread cdnCache into its test.extend({ ... }).
For a quick JavaScript syntax/module check on macOS, use:
/System/Library/Frameworks/JavaScriptCore.framework/Versions/Current/Helpers/jsc --module-file=js/tools/<file>.js
An error such as Can't find variable: TextEncoder is expected when the parsed module reaches browser-only APIs. Other syntax or module errors must be fixed.
For UI changes, check at minimum:
- The home page and sidebar render without console errors.
- Search finds the tool by its name, ID, description, and keywords.
- Direct navigation to
#/tool/<id>works, including a page reload. - The changed tool handles valid, empty, and invalid input.
- Copy, download, file upload, and async behavior work when relevant.
- The layout remains usable on narrow screens and in light and dark color schemes.
Architecture and Core APIs
Tools register themselves at module evaluation time:
tool({
id: 'my-tool',
cat: '문자열 / 텍스트',
name: '내 도구',
desc: '도구 설명',
keywords: '검색 키워드',
render(root) {
// Build the tool UI here.
},
});
- Use a unique, stable, lowercase kebab-case
id. - Set
catto an exact value fromcategoriesinjs/core.js. - Add the category module import to
js/main.jsonly when creating a new module. Registered tools automatically appear in routing, search, the sidebar, and the home page. - Assume
render(root)runs again each time the route opens. Keep state local to the render and clean up global listeners, timers, workers, and object URLs when necessary.
Prefer shared APIs from js/core.js instead of duplicating them:
makeIO(root, cfg)for standard input, options, actions, and output UI.h(tag, attrs, ...kids)for custom DOM construction. Use it for file-oriented or otherwise nonstandard interfaces.strToBytes,bytesToStr,bytesToHex,hexToBytes,bytesToB64,b64ToBytes,decodeInput, andencodeOutputfor byte conversions.kvTable,copyBtn,download, anddownloadZipfor common result actions.loadScript,loadCss, andLIBfor lazy-loaded third-party dependencies;loadModule(url)for a dependency published only as an ES module.
makeIO has an important input convention:
- With one input,
processreceives the input string directly:process(text, opts, actionId). - With multiple inputs,
processreceives an object keyed by input ID:process(inputs, opts, actionId). - Thrown errors are displayed in the output area, and
processmay return a Promise. Withcancelable: true,processreceives anAbortSignalas its fourth argument and the UI gains a cancel button. - Input changes run automatically by default. Use
autorun: falsefor expensive or explicitly triggered work, andrunOnLoad: trueonly when an initial result is useful. - Use
outputHTML: trueonly when returning trusted DOM nodes built by the application. Do not insert untrusted input withinnerHTML.
Implementation Conventions
- Write technical and developer-only documentation in English. Keep
README.md,TODO.md, and documents intended for end users in Korean; Korean strings may also appear in technical code examples when they intentionally demonstrate required user-facing copy. - Follow the style of the surrounding module: ES modules, two-space indentation, semicolons, single-quoted strings, and concise browser-native code.
- Make focused changes. Do not add a framework, build tooling, or package dependency for a small feature.
- Follow
DEVELOPMENT_GUIDE.mdfor module boundaries, size budgets, Worker use, external-dependency replacements, and release gates. - Prefer Web APIs and existing helpers. Do not replace an existing runtime dependency with another package and call it an internal implementation. If a new external runtime library is unavoidable for an unrelated feature, document the reason, load it lazily, pin it, and register it in
LIB. - Do not add eagerly loaded globals.
CryptoJSis a temporary legacy exception only until its replacement work is complete. - Stay within the browser baseline documented in
README.md(Chrome/Edge 110, Firefox 115, Safari 16.4). Regex lookbehind,structuredClone,findLast,toSorted/toReversed/with, and import maps are available;Intl.Segmenteris not (Firefox 125) and stays behind atypeofguard. Tool implementations are dynamically imported, so a parse failure is isolated to that module, but the minimum syntax policy still applies to every module and is checked byscripts/check_browser_compat.mjsplus the scheduled baseline-engine job. - Validate input and throw
Errorobjects with clear Korean messages. Avoid silent failures and unexplained coercion. - Preserve responsiveness, keyboard access, semantic labels, and the existing automatic light/dark theme.
- Revoke object URLs and stop timers or workers when their lifetime ends. Avoid blocking the main thread for large inputs when a chunked or asynchronous approach is practical.
- Keep only one-tool implementations of roughly 8 KiB or less in a category module. Put larger, shared, standard-sensitive, or Worker-backed first-party implementations under
js/lib/and keepjs/tools/focused on registration and UI adaptation. - Never commit secrets, private keys, generated user data, or local machine artifacts.
Adding or Changing a Tool
- Locate the matching category module under
js/tools/; create a new module only when no existing category fits. - Register the tool with
tool(...)and reusemakeIOor the shared DOM/helpers where appropriate. - Confirm the category string exists in
js/core.jsand the tool ID is not already registered. - Run
node scripts/generate_tool_manifest.mjsso the tool metadata and module mapping stay current. - Update the corresponding category in
FEATURES.md. UpdateREADME.mdas well if the public overview, setup, or architecture changed. - Add or update a case in
tests/tools/<module>.spec.jsthat covers the new or changed behavior. - Run the syntax check and perform relevant browser validation.
Change Discipline
- Preserve unrelated user changes in the working tree.
- Do not edit minified third-party code into the repository when a pinned CDN dependency is sufficient.
- Keep commits and patches limited to the requested behavior; avoid opportunistic large refactors.
- When browser support or a CDN is required, handle failure with a useful Korean message rather than leaving the tool in a broken state.