一键导入
frontend-standards
Complete JavaScript, CSS, accessibility, event architecture, bundle pipeline, and admin UI coding standards
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Complete JavaScript, CSS, accessibility, event architecture, bundle pipeline, and admin UI coding standards
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Search the web for information using the WebSearch tool
Demonstrates governance blocking when plaintext secrets are detected in tool inputs
A simple demonstration skill that searches the web for information
Demonstration skill that attempts to use a plaintext secret, designed to be blocked by governance hooks
List, message, configure, and restart agents via the systemprompt CLI
View traffic, costs, agent stats, and bot detection via the systemprompt CLI
| name | Frontend Standards |
| description | Complete JavaScript, CSS, accessibility, event architecture, bundle pipeline, and admin UI coding standards |
systemprompt.io is a world-class Rust programming brand. Every frontend file must be instantly recognizable as idiomatic modern JavaScript as Addy Osmani would write it: performance-conscious, pattern-driven, zero-abstraction-debt.
Run just publish after changes. Verify zero console errors on every admin page.
Prefer functional composition, destructuring, and declarative patterns over imperative control flow.
const names = items.filter(item => item.active).map(item => item.name);
const value = opt ?? computeDefault();
const { id, name, enabled = true } = config;
const endpoint = `/plugins/${encodeURIComponent(id)}`;
| Anti-Pattern | Idiomatic |
|---|---|
for (var i = 0; ...) loop building array | .filter().map() chain |
if (x !== null && x !== undefined) | x ?? fallback |
str + str + str concatenation | Template literals `${a}/${b}` |
function(x) { return x.id; } | Arrow x => x.id |
Nested if/else chains | Early returns or ternary |
| Manual DOM string building | Template literal with app.escapeHtml() |
arr.indexOf(x) !== -1 | arr.includes(x) |
| Callback pyramids | async/await with try/catch |
| Metric | Limit |
|---|---|
| Source file length | 200 lines |
| Function length | 40 lines |
| Parameters | 4 |
| Nesting depth | 3 levels |
| Cyclomatic complexity | 10 per function |
| Document-level event listeners per module | 0 (use event hub) |
| DOM queries per function | 3 (cache references) |
| Construct | Resolution |
|---|---|
document.addEventListener(...) | Register via app.events.on(type, selector, handler) |
var | Use const (preferred) or let |
== / != | Use === / !== |
innerHTML with user content | Use textContent or app.escapeHtml() + innerHTML |
Inline comments (//) | Delete -- code documents itself through naming |
Block comments (/* */, /** */) | Delete -- no JSDoc, no section markers |
| TODO / FIXME / HACK comments | Fix immediately or don't write |
console.log / console.warn / console.error | Use app.Toast.show() or remove |
| Global scope (no IIFE) | Wrap in (function(app) { 'use strict'; ... })(window.AdminApp) |
| Magic numbers / strings | Use named constants at module top |
| Commented-out code | Delete -- git has history |
setTimeout for sequencing | Use events, promises, or requestAnimationFrame |
stopPropagation() / stopImmediatePropagation() | Never needed with centralized event hub |
Raw fetch() | Use app.api() for all API calls |
alert() / confirm() / prompt() | Use app.shared.showConfirmDialog() or app.Toast |
eval() / new Function() | Forbidden |
Every file uses the IIFE pattern with strict mode:
(function(app) {
'use strict';
const API_PATH = '/plugins';
function initPluginsPage() {
app.events.on('click', '[data-action="delete"]', handleDelete);
app.events.on('change', '[data-action="toggle"]', handleToggle);
}
async function handleDelete(e, el) {
const id = el.dataset.entityId;
app.shared.showConfirmDialog('Delete plugin?', 'This cannot be undone.', 'Delete', async () => {
try {
await app.api(`${API_PATH}/${encodeURIComponent(id)}`, { method: 'DELETE' });
app.Toast.show('Plugin deleted', 'success');
window.location.reload();
} catch (err) {
app.Toast.show('Failed to delete plugin', 'error');
}
});
}
app.initPluginsPage = initPluginsPage;
})(window.AdminApp);
All interactivity via the centralized event hub. Zero direct addEventListener on document:
app.events.on('click', '.action-trigger', handleActionClick, { exclusive: true });
app.events.on('click', '[data-delete]', handleDelete);
app.events.on('change', '.toggle-switch input', handleToggle);
All async operations wrapped in try/catch with user feedback:
async function deleteItem(id) {
try {
await app.api(`/items/${encodeURIComponent(id)}`, { method: 'DELETE' });
app.Toast.show('Item deleted', 'success');
} catch (err) {
app.Toast.show('Failed to delete item', 'error');
}
}
All user-generated content through app.escapeHtml() before insertion via innerHTML:
container.innerHTML = `<span class="user-name">${app.escapeHtml(user.name)}</span>`;
Always via app.api(). Never raw fetch:
const data = await app.api('/plugins');
await app.api(`/plugins/${id}`, { method: 'PUT', body: JSON.stringify(payload) });
await app.api(`/plugins/${id}`, { method: 'DELETE' });
| Prefix | Purpose | Returns |
|---|---|---|
init | Page initialization entry point | void |
render | Creates/updates DOM | void |
handle | Event handler callback | void |
fetch | API data retrieval | Promise |
create | Constructs new entity | Promise |
update | Modifies existing entity | Promise |
delete | Removes entity | Promise |
is / has | Boolean check | boolean |
toggle | Switches binary state | void |
| Type | Convention | Example |
|---|---|---|
| Functions, variables | camelCase | handleClick, userName |
| Constants | SCREAMING_SNAKE_CASE | MAX_RETRIES, API_PATH |
| DOM elements | Suffix with El or role | containerEl, submitBtn |
| Booleans | Prefix with is/has/should | isOpen, hasPermission |
Allowed: id, url, api, btn, el, fn, ctx, req, res, msg, err, cfg, img, nav, col, idx, mcp, sse
Every interactive element must be keyboard-accessible and screen-reader-friendly.
| Element | Required |
|---|---|
| Icon-only buttons | aria-label="descriptive text" |
| Toggle buttons (open/close) | aria-expanded="true/false" |
| Menu triggers | aria-haspopup="true" + aria-expanded |
| Loading states | aria-busy="true" on container |
| Live regions (toasts) | role="alert" or aria-live="polite" |
| Modals/dialogs | role="dialog" + aria-modal="true" + aria-labelledby |
| Rule | Rationale |
|---|---|
All <button> elements need accessible names | Screen readers announce button purpose |
SVG icons need aria-hidden="true" | Prevents screen readers from reading SVG markup |
| Color is never the only indicator | Use icons, text, or patterns alongside color |
Interactive elements are <button> or <a> | Never attach click handlers to <div> or <span> |
| Minimum touch target: 44x44px | Ensures usability on touch devices |
All CSS files must declare their @layer. The cascade order is defined once in tokens.css:
@layer tokens, reset, base, components, utilities, responsive;
| Construct | Resolution |
|---|---|
!important | Fix specificity with layers or more specific selector |
| Hardcoded colors | Use var(--color-*) from tokens.css |
| Hardcoded spacing | Use var(--space-*) from tokens.css |
| Hardcoded font sizes | Use var(--text-*) from tokens.css |
| Hardcoded border radius | Use var(--radius-*) from tokens.css |
| Hardcoded shadows | Use var(--shadow-*) from tokens.css |
| Vendor prefixes | Browser support is modern-only |
| Inline styles in templates | Use CSS classes |
float for layout | Use flexbox or grid |
id selectors for styling | Use class selectors |
| Commented-out CSS | Delete -- git has history |
BEM-lite: component-element with state modifiers via class composition.
| Pattern | Example |
|---|---|
| Component | .install-widget |
| Element | .install-widget-header |
| State | .install-widget.open |
| Global state | .is-active, .is-loading |
| JavaScript hook | data-action="delete" (never style data-*) |
just publish
Runs in strict order:
bundle_admin_css -- Concatenate CSS per CSS_MODULE_ORDER in Rustbundle_admin_js -- Concatenate JS per bundle-order.txt + per-page bundlescopy_extension_assets -- Copy bundles to web/dist/content_prerender -- Generate static content pagesAdmin pages are SSR'd at runtime from .hbs templates in storage/files/admin/templates/; there is no precompile step.
| File Type | Steps |
|---|---|
| New JS module | 1. Create file. 2. Add to bundle-order.txt. 3. Add to bundles/*.txt. 4. just publish |
| New CSS module | 1. Create file with @layer. 2. Add to CSS_MODULE_ORDER in Rust. 3. just build && just publish |
Before every just publish:
document.addEventListener outside events.js, sidebar-toggle.js, login.js, dashboard-sse.jsvar declarations@layerconsole.log, console.warn, or console.errortry/catch with user feedbackapp.escapeHtml()aria-labelaria-hidden="true"rg 'document\.addEventListener' storage/files/js/admin/ --glob '!events.js' --glob '!sidebar-toggle.js' --glob '!login.js' --glob '!dashboard-sse.js' --glob '!admin-*.js'
rg '\bvar\b ' storage/files/js/admin/ --glob '!admin-*.js'
rg 'console\.(log|warn|error)' storage/files/js/admin/ --glob '!admin-*.js'
rg '!important' storage/files/css/admin/
rg '^\s*//' storage/files/js/admin/ --glob '!admin-*.js'