Update agent skills
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user