Skill: Frontend Module
SkillWeb & browsingAdding or modifying vanilla JavaScript modules in public/app/. Every change must ship with colocated `*.test.js` Vitest tests, keep patch coverage ≥ 90% on changed lines, add or update a Playwright spec when the behavior is user-visible, and pass `make fix`, `make test-unit`, `make test-integration`, and `make test-e2e` before submission.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Skill: Frontend Module skill
What this skill tells your AI
The instructions your AI receives, as published by exelearning/exelearning in .agents/skills/frontend-module/SKILL.md and read by ahel’s review.
When to Use
Adding or modifying vanilla JavaScript modules in public/app/.
Key Files
public/app/workarea/— main workarea UI (menus, modals, interface, project)public/app/yjs/— Yjs client-side modules (YjsDocumentManager, YjsStructureBinding, AssetManager)public/app/core/— core application logicpublic/app/editor/— TinyMCE editor settingspublic/app/rest/— REST client helperspublic/app/common/— shared modulespublic/app/locate/— localization helperspublic/app/utils/— utility helpersvitest.config.mts— test runner config (happy-dom environment)
Test Pattern
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
beforeEach(() => { vi.useFakeTimers(); });
afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); });
it('reveals element after configured delay', () => {
const fadeIn = vi.fn();
myModule.revealAfterDelay(element, 3000);
vi.advanceTimersByTime(2999);
expect(fadeIn).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
expect(fadeIn).toHaveBeenCalledOnce();
});
DOM Testing
beforeEach(() => {
document.body.innerHTML = `<div id="container"><button class="action-btn"></button></div>`;
});
Ownership-Based UI
When rendering UI that depends on user roles, test both owner and non-owner states:
describe('Shared projects must not expose delete UI', () => {
const sharedOde = { odeId: 'shared-1', role: 'editor' }; // not 'owner'
it('hides delete button for non-owners', () => { /* ... */ });
it('shows delete button for owners', () => { /* ... */ });
});
Commands
npx vitest run public/app/path/file.test.js # Run single test
npx vitest run public/app/path/file.test.js --coverage # With coverage
npx vitest run public/app/path/file.test.js -t "name" # Specific test
make test-frontend # All frontend tests
make fix # Lint
Gotchas
bun testwill fail on frontend files — always usenpx vitest run. Frontend needs happy-dom (configured invitest.config.mts). "window is not defined" means you used the wrong runner.- JavaScript type coercion — never subtract strings (e.g.,
dateA - dateBon ISO strings returnsNaN). Always convert explicitly:new Date(dateA).getTime() - new Date(dateB).getTime(). - Inline styles are forbidden — never use
element.style.x. All UI styles go in SCSS underassets/styles/. - All strings must be translated — use
_()for GUI,c_()for content. No hardcoded English (see i18n skill). - Every
.jsmust have a.test.js— CI coverage gate will fail without it. - Never call
/api/v1/*— internal frontend uses Yjs + WebSocket. v1 is for external integrations only. - Deferred execution in collaborative mode — if the user is actively editing an iDevice, defer page reloads until the editor closes. Use the
_deferredPageReloadpattern fromYjsProjectBridge. Example: PR #1540. - Filename decoding — always wrap
decodeURIComponent()in try/catch with a fallback to the raw string.
Done When
-
.test.jscreated alongside.jsfile -
npx vitest run <test-file>passes at 80%+ coverage - All user-facing strings wrapped in
_()orc_() - No inline styles — CSS in
assets/styles/ - Type conversions are explicit (no implicit coercion)
-
make fixpasses clean
Signals
- GitHub stars
- 142
- Forks
- 32
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
frontend-module- Source
- github.com/exelearning/exelearning