Update agent skills
CI / Lint (push) Successful in 1m0s
CI / Test (push) Successful in 1m11s

This commit is contained in:
2026-08-20 07:38:45 -06:00
parent 2d547dde87
commit 1387f955d0
8 changed files with 2506 additions and 330 deletions
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -149,7 +149,7 @@ Each rule file contains:
Ember has excellent accessibility support through community addons: Ember has excellent accessibility support through community addons:
- **ember-a11y-testing** - Automated accessibility testing in your test suite - **ember-a11y-testing** - Automated accessibility testing in your test suite
- **ember-a11y** - Route announcements and focus management - **ember-a11y-refocus** - Route announcements and focus management
- **ember-focus-trap** - Focus trapping for modals and dialogs - **ember-focus-trap** - Focus trapping for modals and dialogs
- **ember-page-title** - Accessible page title management - **ember-page-title** - Accessible page title management
- **Platform-native validation** - Use browser's Constraint Validation API for accessible form validation - **Platform-native validation** - Use browser's Constraint Validation API for accessible form validation
@@ -69,6 +69,14 @@ import { setupGlobalA11yHooks } from 'ember-a11y-testing/test-support';
setupGlobalA11yHooks(); // Runs on every test automatically setupGlobalA11yHooks(); // Runs on every test automatically
``` ```
### Leave All Rules Enabled
Keep the default ember-a11y-testing and axe-core rules turned on. Avoid disabling rules globally or excluding specific rules to bypass failures without a documented remediation plan.
When teams suppress accessibility rules without a time-boxed remediation plan, they hide real defects, accumulate technical debt, and make regressions harder to detect. A skipped rule can allow a serious accessibility defect to ship to production, especially for problems involving forms, keyboard access, focus management, semantics, or ARIA usage.
If you must suppress a rule temporarily, treat it as an exception: document why it is needed, scope it as narrowly as possible, and create follow-up work to restore the rule quickly.
ember-a11y-testing catches issues like missing labels, insufficient color contrast, invalid ARIA, and keyboard navigation problems automatically. ember-a11y-testing catches issues like missing labels, insufficient color contrast, invalid ARIA, and keyboard navigation problems automatically.
Reference: [ember-a11y-testing](https://github.com/ember-a11y/ember-a11y-testing) Reference: [ember-a11y-testing](https://github.com/ember-a11y/ember-a11y-testing)
@@ -35,9 +35,7 @@ All form inputs must have associated labels, and validation errors should be ann
<div> <div>
<label for="email-input"> <label for="email-input">
Email Address Email Address
{{#if this.isEmailRequired}} <span aria-hidden="true">*</span>
<span aria-label="required">*</span>
{{/if}}
</label> </label>
<input <input
@@ -19,49 +19,86 @@ export default class Router extends EmberRouter {
} }
``` ```
**Correct (using a11y-announcer library - recommended):** **Correct (using ember-a11y-refocus library - recommended):**
Use the [a11y-announcer](https://github.com/ember-a11y/a11y-announcer) library for robust route announcements: Use the [ember-a11y-refocus](https://github.com/ember-a11y/ember-a11y-refocus) library for robust route announcements, route transition focus management, and a bypass block (aka skip link).
```bash ```bash
ember install @ember-a11y/a11y-announcer pnpm add ember-a11y-refocus
``` ```
```javascript Or with npm:
// app/router.js
import EmberRouter from '@ember/routing/router';
import config from './config/environment';
export default class Router extends EmberRouter { ```bash
location = config.locationType; npm install ember-a11y-refocus
rootURL = config.rootURL;
}
Router.map(function () {
this.route('about');
this.route('dashboard');
this.route('posts', function () {
this.route('post', { path: '/:post_id' });
});
});
``` ```
The a11y-announcer library automatically handles route announcements. For custom announcements in your routes: Use the addon by rendering `NavigationNarrator` in your application layout and ensuring your primary content has `id="main"`.
```handlebars
{{! app/templates/application.hbs }}
<header>
<NavigationNarrator />
{{! other header content }}
</header>
<main id='main'>
{{outlet}}
</main>
```
If you are using GJS or GTS, import the component directly:
```glimmer-js
import { NavigationNarrator } from 'ember-a11y-refocus';
<template>
<header>
<NavigationNarrator />
</header>
<main id="main">
{{outlet}}
</main>
</template>
```
The addon ships minimal styles for the skip link and navigation message:
```javascript ```javascript
// app/routes/dashboard.js // app/app.js or app/app.ts
import Route from '@ember/routing/route'; import 'ember-a11y-refocus/styles/navigation-narrator.css';
import { service } from '@ember/service'; ```
export default class DashboardRoute extends Route { If you need to customize which transitions count as a route change, pass a validator function to `NavigationNarrator`:
@service announcer;
afterModel() { ```javascript
this.announcer.announce('Loaded dashboard with latest data'); // app/controllers/application.js
import Controller from '@ember/controller';
import { defaultValidator } from 'ember-a11y-refocus';
export default class ApplicationController extends Controller {
myCustomValidator(transition) {
if (transition.from?.name === 'special') {
return false;
}
return defaultValidator(transition);
} }
} }
``` ```
```handlebars
{{! app/templates/application.hbs }}
<header>
<NavigationNarrator @routeChangeValidator={{this.myCustomValidator}} />
</header>
<main id='main'>
{{outlet}}
</main>
```
**Alternative: DIY approach with ARIA live regions:** **Alternative: DIY approach with ARIA live regions:**
If you prefer not to use a library, you can implement route announcements yourself: If you prefer not to use a library, you can implement route announcements yourself:
@@ -150,25 +187,6 @@ export default class ApplicationRoute extends Route {
} }
``` ```
**Alternative: Use ember-page-title with announcements:**
```bash
ember install ember-page-title
```
```glimmer-js
// app/routes/dashboard.gjs
import { pageTitle } from 'ember-page-title';
<template>
{{pageTitle "Dashboard"}}
<div class="dashboard">
{{outlet}}
</div>
</template>
```
Route announcements ensure screen reader users know when navigation occurs, improving the overall accessibility experience. Route announcements ensure screen reader users know when navigation occurs, improving the overall accessibility experience.
Reference: [Ember Accessibility - Page Titles](https://guides.emberjs.com/release/accessibility/page-template-considerations/) Reference: [Ember Accessibility - Page Titles](https://guides.emberjs.com/release/accessibility/page-template-considerations/)
@@ -11,8 +11,8 @@ Implement intelligent model caching strategies to reduce redundant API calls and
**Incorrect (always fetches fresh data):** **Incorrect (always fetches fresh data):**
```glimmer-js ```javascript
// app/routes/post.gjs // app/routes/post.js
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { service } from '@ember/service'; import { service } from '@ember/service';
@@ -23,21 +23,24 @@ export default class PostRoute extends Route {
// Always makes API call, even if we just loaded this post // Always makes API call, even if we just loaded this post
return this.store.request({ url: `/posts/${params.post_id}` }); return this.store.request({ url: `/posts/${params.post_id}` });
} }
<template>
<article>
<h1>{{@model.title}}</h1>
<div>{{@model.content}}</div>
</article>
{{outlet}}
</template>
} }
``` ```
```glimmer-js
// app/templates/post.gjs
<template>
<article>
<h1>{{@model.title}}</h1>
<div>{{@model.content}}</div>
</article>
{{outlet}}
</template>
```
**Correct (with smart caching):** **Correct (with smart caching):**
```glimmer-js ```javascript
// app/routes/post.gjs // app/routes/post.js
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { service } from '@ember/service'; import { service } from '@ember/service';
@@ -68,17 +71,20 @@ export default class PostRoute extends Route {
const fiveMinutes = 5 * 60 * 1000; const fiveMinutes = 5 * 60 * 1000;
return Date.now() - cacheTime < fiveMinutes; return Date.now() - cacheTime < fiveMinutes;
} }
<template>
<article>
<h1>{{@model.title}}</h1>
<div>{{@model.content}}</div>
</article>
{{outlet}}
</template>
} }
``` ```
```glimmer-js
// app/templates/post.gjs
<template>
<article>
<h1>{{@model.title}}</h1>
<div>{{@model.content}}</div>
</article>
{{outlet}}
</template>
```
**Service-based caching layer:** **Service-based caching layer:**
```javascript ```javascript
@@ -123,8 +129,8 @@ export default class PostCacheService extends Service {
} }
``` ```
```glimmer-js ```javascript
// app/routes/post.gjs // app/routes/post.js
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { service } from '@ember/service'; import { service } from '@ember/service';
@@ -141,21 +147,24 @@ export default class PostRoute extends Route {
const params = this.paramsFor('post'); const params = this.paramsFor('post');
await this.postCache.getPost(params.post_id, { forceRefresh: true }); await this.postCache.getPost(params.post_id, { forceRefresh: true });
} }
<template>
<article>
<h1>{{@model.title}}</h1>
<div>{{@model.content}}</div>
</article>
{{outlet}}
</template>
} }
``` ```
```glimmer-js
// app/templates/post.gjs
<template>
<article>
<h1>{{@model.title}}</h1>
<div>{{@model.content}}</div>
</article>
{{outlet}}
</template>
```
**Using query params for cache control:** **Using query params for cache control:**
```glimmer-js ```javascript
// app/routes/posts.gjs // app/routes/posts.js
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { service } from '@ember/service'; import { service } from '@ember/service';
@@ -174,28 +183,31 @@ export default class PostsRoute extends Route {
options, options,
}); });
} }
<template>
<div class="posts">
<button {{on "click" (fn this.refresh)}}>
Refresh
</button>
<ul>
{{#each @model as |post|}}
<li>{{post.title}}</li>
{{/each}}
</ul>
</div>
{{outlet}}
</template>
} }
``` ```
```glimmer-js
// app/templates/posts.gjs
<template>
<div class="posts">
<button {{on "click" (fn this.refresh)}}>
Refresh
</button>
<ul>
{{#each @model as |post|}}
<li>{{post.title}}</li>
{{/each}}
</ul>
</div>
{{outlet}}
</template>
```
**Background refresh pattern:** **Background refresh pattern:**
```glimmer-js ```javascript
// app/routes/dashboard.gjs // app/routes/dashboard.js
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { service } from '@ember/service'; import { service } from '@ember/service';
@@ -214,17 +226,20 @@ export default class DashboardRoute extends Route {
return cached || this.store.request({ url: '/dashboard' }); return cached || this.store.request({ url: '/dashboard' });
} }
<template>
<div class="dashboard">
<h1>Dashboard</h1>
<div>Stats: {{@model.stats}}</div>
</div>
{{outlet}}
</template>
} }
``` ```
```glimmer-js
// app/templates/dashboard.gjs
<template>
<div class="dashboard">
<h1>Dashboard</h1>
<div>Stats: {{@model.stats}}</div>
</div>
{{outlet}}
</template>
```
Smart caching reduces server load, improves perceived performance, and provides better offline support while keeping data fresh. Smart caching reduces server load, improves perceived performance, and provides better offline support while keeping data fresh.
Reference: [WarpDrive Caching](https://warp-drive.io/) Reference: [WarpDrive Caching](https://warp-drive.io/)
@@ -1,38 +1,15 @@
--- ---
title: Use Route Templates with Co-located Syntax title: Use Separate Route and Template Files
impact: MEDIUM-HIGH impact: MEDIUM-HIGH
impactDescription: Better code organization and maintainability impactDescription: Better code organization and maintainability
tags: routes, templates, gjs, co-location tags: routes, templates, gjs, file-conventions
--- ---
## Use Route Templates with Co-located Syntax ## Use Separate Route and Template Files
Use co-located route templates with modern gjs syntax for better organization and maintainability. Keep route logic in `app/routes/*.js` and route templates in `app/templates/*.gjs`. Route classes imported from `@ember/routing/route` do not support inline `<template>` blocks.
**Incorrect (separate template file - old pattern):** **Incorrect (inline template inside a route class):**
```glimmer-js
// app/routes/posts.js (separate file)
import Route from '@ember/routing/route';
export default class PostsRoute extends Route {
model() {
return this.store.request({ url: '/posts' });
}
}
// app/templates/posts.gjs (separate template file)
<template>
<h1>Posts</h1>
<ul>
{{#each @model as |post|}}
<li>{{post.title}}</li>
{{/each}}
</ul>
</template>
```
**Correct (co-located route template):**
```glimmer-js ```glimmer-js
// app/routes/posts.gjs // app/routes/posts.gjs
@@ -56,10 +33,37 @@ export default class PostsRoute extends Route {
} }
``` ```
**With loading and error states:** **Correct (separate route module and template file):**
```javascript
// app/routes/posts.js
import Route from '@ember/routing/route';
export default class PostsRoute extends Route {
model() {
return this.store.request({ url: '/posts' });
}
}
```
```glimmer-js ```glimmer-js
// app/routes/posts.gjs // app/templates/posts.gjs
<template>
<h1>Posts</h1>
<ul>
{{#each @model as |post|}}
<li>{{post.title}}</li>
{{/each}}
</ul>
{{outlet}}
</template>
```
**With a separate template file for route UI:**
```javascript
// app/routes/posts.js
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { service } from '@ember/service'; import { service } from '@ember/service';
@@ -69,29 +73,32 @@ export default class PostsRoute extends Route {
model() { model() {
return this.store.request({ url: '/posts' }); return this.store.request({ url: '/posts' });
} }
<template>
<div class="posts-page">
<h1>Posts</h1>
{{#if @model}}
<ul>
{{#each @model as |post|}}
<li>{{post.title}}</li>
{{/each}}
</ul>
{{/if}}
{{outlet}}
</div>
</template>
} }
``` ```
```glimmer-js
// app/templates/posts.gjs
<template>
<div class="posts-page">
<h1>Posts</h1>
{{#if @model}}
<ul>
{{#each @model as |post|}}
<li>{{post.title}}</li>
{{/each}}
</ul>
{{/if}}
{{outlet}}
</div>
</template>
```
**Template-only routes:** **Template-only routes:**
```glimmer-js ```glimmer-js
// app/routes/about.gjs // app/templates/about.gjs
<template> <template>
<div class="about-page"> <div class="about-page">
<h1>About Us</h1> <h1>About Us</h1>
@@ -100,6 +107,6 @@ export default class PostsRoute extends Route {
</template> </template>
``` ```
Co-located route templates keep route logic and presentation together, making the codebase easier to navigate and maintain. Keeping route classes and route templates in their conventional files matches Ember's supported routing model and makes examples easier to apply in real apps.
Reference: [Ember Routes](https://guides.emberjs.com/release/routing/) Reference: [Ember Routes](https://guides.emberjs.com/release/routing/)
+4 -1
View File
@@ -4,16 +4,19 @@
"ember-best-practices": { "ember-best-practices": {
"source": "nullvoxpopuli/agent-skills", "source": "nullvoxpopuli/agent-skills",
"sourceType": "github", "sourceType": "github",
"computedHash": "7909c3def6c4ddefb358d1973cf724269ede9f6cdba1dd2888e4e6072a897f3e" "skillPath": "skills/ember-best-practices/SKILL.md",
"computedHash": "34e82668e2fa1e6b025ac8cf1015634484bf5d8fbc80e8aa642489d2df79498c"
}, },
"nak": { "nak": {
"source": "soapbox-pub/nostr-skills", "source": "soapbox-pub/nostr-skills",
"sourceType": "github", "sourceType": "github",
"skillPath": "skills/nak/SKILL.md",
"computedHash": "710d3f3945ff421ed2b7f40ecd32c5e263bc029d43fe8f4fd1491a8013c7389a" "computedHash": "710d3f3945ff421ed2b7f40ecd32c5e263bc029d43fe8f4fd1491a8013c7389a"
}, },
"nostr": { "nostr": {
"source": "soapbox-pub/nostr-skills", "source": "soapbox-pub/nostr-skills",
"sourceType": "github", "sourceType": "github",
"skillPath": "skills/nostr/SKILL.md",
"computedHash": "e1e6834c18d18a5deef4cd9555f6eee0fc0b968acf1c619253999eda76beab8e" "computedHash": "e1e6834c18d18a5deef4cd9555f6eee0fc0b968acf1c619253999eda76beab8e"
} }
} }