ima-claude 2.18.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +55 -9
  2. package/dist/cli.js +5 -1
  3. package/package.json +1 -1
  4. package/plugins/ima-claude/.claude-plugin/plugin.json +2 -2
  5. package/plugins/ima-claude/agents/explorer.md +29 -15
  6. package/plugins/ima-claude/agents/implementer.md +58 -13
  7. package/plugins/ima-claude/agents/memory.md +19 -19
  8. package/plugins/ima-claude/agents/reviewer.md +56 -34
  9. package/plugins/ima-claude/agents/tester.md +59 -16
  10. package/plugins/ima-claude/agents/wp-developer.md +66 -21
  11. package/plugins/ima-claude/hooks/bootstrap.sh +42 -44
  12. package/plugins/ima-claude/hooks/prompt_coach_digest.md +14 -17
  13. package/plugins/ima-claude/hooks/prompt_coach_system.md +10 -12
  14. package/plugins/ima-claude/personalities/README.md +17 -6
  15. package/plugins/ima-claude/personalities/enable-efficient.md +61 -0
  16. package/plugins/ima-claude/personalities/enable-terse.md +71 -0
  17. package/plugins/ima-claude/skills/agentic-workflows/SKILL.md +97 -0
  18. package/plugins/ima-claude/skills/agentic-workflows/references/phases/deliver.md +181 -0
  19. package/plugins/ima-claude/skills/agentic-workflows/references/phases/draft.md +99 -0
  20. package/plugins/ima-claude/skills/agentic-workflows/references/phases/gather.md +130 -0
  21. package/plugins/ima-claude/skills/agentic-workflows/references/phases/outline.md +106 -0
  22. package/plugins/ima-claude/skills/agentic-workflows/references/phases/review.md +137 -0
  23. package/plugins/ima-claude/skills/agentic-workflows/references/standards/draft-format.md +159 -0
  24. package/plugins/ima-claude/skills/agentic-workflows/references/standards/editorial-standards.md +160 -0
  25. package/plugins/ima-claude/skills/agentic-workflows/references/standards/outline-format.md +110 -0
  26. package/plugins/ima-claude/skills/agentic-workflows/references/templates/avada-construction-guide.md +263 -0
  27. package/plugins/ima-claude/skills/agentic-workflows/references/templates/avada-webinar-example.txt +275 -0
  28. package/plugins/ima-claude/skills/agentic-workflows/references/templates/cta-block-catalog.md +169 -0
  29. package/plugins/ima-claude/skills/agentic-workflows/references/templates/espo-email-preparation.md +241 -0
  30. package/plugins/ima-claude/skills/agentic-workflows/references/templates/webinar-recap-email-espo.html +339 -0
  31. package/plugins/ima-claude/skills/agentic-workflows/references/templates/webinar-reminder-email-espo.html +458 -0
  32. package/plugins/ima-claude/skills/agentic-workflows/references/workflows/editorial/webinar-summary.md +81 -0
  33. package/plugins/ima-claude/skills/architect/SKILL.md +54 -168
  34. package/plugins/ima-claude/skills/compound-bridge/SKILL.md +41 -94
  35. package/plugins/ima-claude/skills/design-to-code/SKILL.md +91 -0
  36. package/plugins/ima-claude/skills/design-to-code/references/guardrails.md +46 -0
  37. package/plugins/ima-claude/skills/design-to-code/references/phase-a-design-to-prompt.md +141 -0
  38. package/plugins/ima-claude/skills/design-to-code/references/phase-b-prompt-to-code.md +155 -0
  39. package/plugins/ima-claude/skills/design-to-code/references/prompt-template.md +95 -0
  40. package/plugins/ima-claude/skills/discourse/SKILL.md +79 -194
  41. package/plugins/ima-claude/skills/discourse-admin/SKILL.md +41 -103
  42. package/plugins/ima-claude/skills/docs-organize/SKILL.md +63 -203
  43. package/plugins/ima-claude/skills/ember-discourse/SKILL.md +90 -200
  44. package/plugins/ima-claude/skills/espocrm/SKILL.md +14 -23
  45. package/plugins/ima-claude/skills/espocrm-api/SKILL.md +79 -192
  46. package/plugins/ima-claude/skills/functional-programmer/SKILL.md +33 -237
  47. package/plugins/ima-claude/skills/gh-cli/SKILL.md +26 -65
  48. package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +71 -104
  49. package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +32 -22
  50. package/plugins/ima-claude/skills/ima-brand/SKILL.md +18 -23
  51. package/plugins/ima-claude/skills/ima-copywriting/SKILL.md +68 -179
  52. package/plugins/ima-claude/skills/ima-doc2pdf/SKILL.md +32 -102
  53. package/plugins/ima-claude/skills/ima-editorial-scorecard/SKILL.md +38 -63
  54. package/plugins/ima-claude/skills/ima-editorial-workflow/SKILL.md +69 -114
  55. package/plugins/ima-claude/skills/ima-email-creator/SKILL.md +16 -22
  56. package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +21 -37
  57. package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +39 -120
  58. package/plugins/ima-claude/skills/jquery/SKILL.md +107 -233
  59. package/plugins/ima-claude/skills/js-fp/SKILL.md +75 -296
  60. package/plugins/ima-claude/skills/js-fp-api/SKILL.md +52 -162
  61. package/plugins/ima-claude/skills/js-fp-react/SKILL.md +47 -270
  62. package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +55 -209
  63. package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +59 -204
  64. package/plugins/ima-claude/skills/livecanvas/SKILL.md +19 -32
  65. package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +146 -136
  66. package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-attachments.md +115 -0
  67. package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-auth.md +103 -0
  68. package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-bulk.md +149 -0
  69. package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-misc.md +195 -0
  70. package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-sprints.md +158 -0
  71. package/plugins/ima-claude/skills/mcp-context7/SKILL.md +32 -64
  72. package/plugins/ima-claude/skills/mcp-gitea/SKILL.md +98 -188
  73. package/plugins/ima-claude/skills/mcp-github/SKILL.md +60 -124
  74. package/plugins/ima-claude/skills/mcp-memory/SKILL.md +1 -177
  75. package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +58 -115
  76. package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +32 -87
  77. package/plugins/ima-claude/skills/mcp-serena/SKILL.md +54 -80
  78. package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +40 -63
  79. package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +75 -116
  80. package/plugins/ima-claude/skills/php-authnet/SKILL.md +32 -65
  81. package/plugins/ima-claude/skills/php-fp/SKILL.md +50 -129
  82. package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +25 -73
  83. package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +103 -463
  84. package/plugins/ima-claude/skills/playwright/SKILL.md +69 -220
  85. package/plugins/ima-claude/skills/prompt-starter/SKILL.md +35 -82
  86. package/plugins/ima-claude/skills/prompt-starter/references/code-review.md +38 -0
  87. package/plugins/ima-claude/skills/py-fp/SKILL.md +78 -384
  88. package/plugins/ima-claude/skills/quasar-fp/SKILL.md +54 -255
  89. package/plugins/ima-claude/skills/quickstart/SKILL.md +7 -11
  90. package/plugins/ima-claude/skills/rails/SKILL.md +63 -184
  91. package/plugins/ima-claude/skills/resume-session/SKILL.md +14 -35
  92. package/plugins/ima-claude/skills/rg/SKILL.md +61 -146
  93. package/plugins/ima-claude/skills/ruby-fp/SKILL.md +66 -163
  94. package/plugins/ima-claude/skills/save-session/SKILL.md +10 -39
  95. package/plugins/ima-claude/skills/scorecard/SKILL.md +24 -38
  96. package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +42 -71
  97. package/plugins/ima-claude/skills/skill-creator/SKILL.md +79 -250
  98. package/plugins/ima-claude/skills/task-master/SKILL.md +11 -31
  99. package/plugins/ima-claude/skills/task-planner/SKILL.md +44 -153
  100. package/plugins/ima-claude/skills/task-runner/SKILL.md +61 -143
  101. package/plugins/ima-claude/skills/unit-testing/SKILL.md +59 -134
  102. package/plugins/ima-claude/skills/wp-ddev/SKILL.md +38 -120
  103. package/plugins/ima-claude/skills/wp-local/SKILL.md +26 -108
@@ -5,29 +5,9 @@ description: "Ember/Glimmer component development for Discourse plugins - gjs, a
5
5
 
6
6
  # Ember for Discourse Plugins
7
7
 
8
- Discourse runs Ember Octane with Glimmer components. The plugin extension model is: `apiInitializer` → `renderInOutlet` → `.gjs` component. Everything else is either legacy or an internal Discourse concern.
8
+ Discourse runs Ember Octane with Glimmer components. Extension model: `apiInitializer` → `renderInOutlet` → `.gjs` component.
9
9
 
10
- ## When to Use This Skill
11
-
12
- - Adding frontend UI to a Discourse plugin
13
- - Building admin panel components for a plugin
14
- - Injecting content into Discourse's UI via plugin outlets
15
- - Migrating old `decorateWidget` / raw handlebars to modern Glimmer
16
-
17
- ## Core Philosophy
18
-
19
- > Glimmer components are close to pure functions: `(args, services) → template`. Minimize `@tracked` state. Prefer derived values over stored state.
20
-
21
- FP lens applied to Ember:
22
- - **Components** are view functions — args in, DOM out
23
- - **`@tracked`** is isolated reactive state — use sparingly, only what truly changes
24
- - **Derived values** via getters — no duplicate state, always in sync
25
- - **Actions** are the imperative shell — side effects live in methods, not templates
26
- - **Services** are dependency injection — inject, don't import singletons
27
-
28
- **Foundation**: Reference `../discourse/SKILL.md` for the Ruby side of plugin development.
29
-
30
- ## The Modern Stack (use these)
10
+ ## Modern Stack
31
11
 
32
12
  | What | Modern | Deprecated / Avoid |
33
13
  |------|--------|--------------------|
@@ -39,9 +19,19 @@ FP lens applied to Ember:
39
19
  | Services | `@service` decorator | `Ember.inject.service()` |
40
20
  | Event handling | `{{on "click" this.handler}}` | `{{action "handler"}}` |
41
21
 
42
- ## .gjs: The File Format
22
+ ## FP Lens
43
23
 
44
- `.gjs` (Glimmer JS) combines the component class and template in one file. It's the current Discourse standard for new components.
24
+ - Components are view functions args in, DOM out
25
+ - `@tracked` = isolated reactive state — use sparingly, only for state the component owns and mutates
26
+ - Derived values via getters — no duplicate state
27
+ - Actions are the imperative shell — side effects in methods, not templates
28
+ - Services are DI — inject via `@service`, don't import singletons
29
+
30
+ **Related**: `../discourse/SKILL.md` for Ruby plugin side.
31
+
32
+ ## .gjs Format
33
+
34
+ `.gjs` combines class + template in one file. Standard for all new components.
45
35
 
46
36
  ```gjs
47
37
  // assets/javascripts/discourse/components/my-component.gjs
@@ -57,7 +47,7 @@ export default class MyComponent extends Component {
57
47
 
58
48
  @tracked isExpanded = false;
59
49
 
60
- // Derived value — getter, not stored state
50
+ // Derived — getter, not @tracked
61
51
  get greeting() {
62
52
  return this.currentUser
63
53
  ? `Welcome back, ${this.currentUser.username}!`
@@ -72,26 +62,21 @@ export default class MyComponent extends Component {
72
62
  <template>
73
63
  <div class="my-component">
74
64
  <p>{{this.greeting}}</p>
75
-
76
65
  <DButton
77
66
  @action={{this.toggleExpanded}}
78
67
  @label={{if this.isExpanded "collapse" "expand"}}
79
68
  />
80
-
81
69
  {{#if this.isExpanded}}
82
- <div class="my-component__body">
83
- {{yield}}
84
- </div>
70
+ <div class="my-component__body">{{yield}}</div>
85
71
  {{/if}}
86
72
  </div>
87
73
  </template>
88
74
  }
89
75
  ```
90
76
 
91
- ### Template-only component (no JS needed)
77
+ Template-only (no class needed):
92
78
 
93
79
  ```gjs
94
- // When a component is purely presentational — no class required
95
80
  <template>
96
81
  <div class="badge-card">
97
82
  <img src={{@badge.image_url}} alt={{@badge.name}} />
@@ -100,9 +85,9 @@ export default class MyComponent extends Component {
100
85
  </template>
101
86
  ```
102
87
 
103
- ## apiInitializer: The Plugin Entry Point
88
+ ## apiInitializer
104
89
 
105
- Every plugin's JS starts here. One initializer file per plugin feature area.
90
+ One initializer file per plugin feature area.
106
91
 
107
92
  ```javascript
108
93
  // assets/javascripts/discourse/initializers/my-plugin.js
@@ -111,17 +96,14 @@ import MyBanner from "../components/my-banner";
111
96
  import MyComponent from "../components/my-component";
112
97
 
113
98
  export default apiInitializer((api) => {
114
- // Render into a plugin outlet
115
99
  api.renderInOutlet("discovery-list-container-top", MyBanner);
116
-
117
- // shouldRender on the component class controls conditional rendering
118
100
  api.renderInOutlet("topic-above-post-stream", MyComponent);
119
101
  });
120
102
  ```
121
103
 
122
- ## Plugin Outlets: Where to Inject Content
104
+ ## Plugin Outlets
123
105
 
124
- Plugin outlets are pre-defined hooks in Discourse's templates. Use `api.renderInOutlet` to inject at them.
106
+ Inject at pre-defined template hooks via `api.renderInOutlet`.
125
107
 
126
108
  ```gjs
127
109
  // assets/javascripts/discourse/components/my-banner.gjs
@@ -131,21 +113,18 @@ import { service } from "@ember/service";
131
113
  export default class MyBanner extends Component {
132
114
  @service currentUser;
133
115
 
134
- // Static method controls whether this component renders at all.
135
- // outletArgs contains context from where the outlet sits in the DOM.
116
+ // Controls whether component renders. outletArgs = DOM context.
136
117
  static shouldRender(outletArgs, helper) {
137
118
  return helper.siteSettings.my_plugin_enabled && helper.currentUser;
138
119
  }
139
120
 
140
121
  <template>
141
- <div class="my-banner">
142
- Welcome, {{this.currentUser.username}}!
143
- </div>
122
+ <div class="my-banner">Welcome, {{this.currentUser.username}}!</div>
144
123
  </template>
145
124
  }
146
125
  ```
147
126
 
148
- ### Post stream outlets (Glimmer post stream — current)
127
+ Post stream outlets (Glimmer post stream — current):
149
128
 
150
129
  ```gjs
151
130
  import Component from "@glimmer/component";
@@ -156,9 +135,8 @@ export default apiInitializer((api) => {
156
135
  "post-content-cooked-html",
157
136
  class extends Component {
158
137
  static shouldRender(args) {
159
- return args.post.wiki; // args.post is the current post model
138
+ return args.post.wiki;
160
139
  }
161
-
162
140
  <template>
163
141
  <div class="wiki-notice">This post is a wiki</div>
164
142
  </template>
@@ -167,18 +145,14 @@ export default apiInitializer((api) => {
167
145
  });
168
146
  ```
169
147
 
170
- ### Finding outlet names
171
-
172
- Search Discourse core for `<PluginOutlet @name=`:
148
+ Find outlet names:
173
149
  ```bash
174
150
  rg '<PluginOutlet @name=' app/assets/javascripts/discourse/
175
151
  ```
176
152
 
177
- Common outlets: `discovery-list-container-top`, `topic-above-post-stream`,
178
- `above-main-container`, `header-icons`, `user-profile-primary`,
179
- `post-content-cooked-html`, `after-topic-list-area`.
153
+ Common outlets: `discovery-list-container-top`, `topic-above-post-stream`, `above-main-container`, `header-icons`, `user-profile-primary`, `post-content-cooked-html`, `after-topic-list-area`.
180
154
 
181
- ## Glimmer Component Patterns
155
+ ## Component Patterns
182
156
 
183
157
  ### Args vs State
184
158
 
@@ -187,11 +161,9 @@ import Component from "@glimmer/component";
187
161
  import { tracked } from "@glimmer/tracking";
188
162
 
189
163
  export default class UserCard extends Component {
190
- // @tracked only for state THIS component owns and mutates
191
- @tracked showDetails = false;
164
+ @tracked showDetails = false; // component owns this
192
165
 
193
- // Derived from args — a getter, never @tracked
194
- get displayName() {
166
+ get displayName() { // derived from args — never @tracked
195
167
  return this.args.user.name || this.args.user.username;
196
168
  }
197
169
 
@@ -202,29 +174,26 @@ export default class UserCard extends Component {
202
174
  <template>
203
175
  <div class="user-card {{if this.isStaff 'user-card--staff'}}">
204
176
  <h3>{{this.displayName}}</h3>
205
- {{#if this.showDetails}}
206
- <p>{{@user.bio_raw}}</p>
207
- {{/if}}
177
+ {{#if this.showDetails}}<p>{{@user.bio_raw}}</p>{{/if}}
208
178
  </div>
209
179
  </template>
210
180
  }
211
181
  ```
212
182
 
213
- ### Services — inject, don't import
183
+ ### Services
214
184
 
215
185
  ```gjs
216
186
  import Component from "@glimmer/component";
217
187
  import { service } from "@ember/service";
218
188
 
219
189
  export default class MyFeature extends Component {
220
- // Common Discourse services
221
- @service currentUser; // logged-in user (null if anonymous)
222
- @service siteSettings; // site configuration
223
- @service router; // programmatic navigation
224
- @service store; // Ember Data store
225
- @service session; // session data
226
- @service modal; // open modals
227
- @service toasts; // toast notifications (Discourse 3.2+)
190
+ @service currentUser; // logged-in user (null if anonymous)
191
+ @service siteSettings; // site configuration
192
+ @service router; // programmatic navigation
193
+ @service store; // Ember Data store
194
+ @service session; // session data
195
+ @service modal; // open modals
196
+ @service toasts; // toast notifications (Discourse 3.2+)
228
197
 
229
198
  get canUseFeature() {
230
199
  return this.currentUser?.trust_level >= 2
@@ -232,14 +201,12 @@ export default class MyFeature extends Component {
232
201
  }
233
202
 
234
203
  <template>
235
- {{#if this.canUseFeature}}
236
- ...
237
- {{/if}}
204
+ {{#if this.canUseFeature}}...{{/if}}
238
205
  </template>
239
206
  }
240
207
  ```
241
208
 
242
- ### Actions and events
209
+ ### Actions
243
210
 
244
211
  ```gjs
245
212
  import Component from "@glimmer/component";
@@ -250,43 +217,31 @@ export default class SearchBox extends Component {
250
217
  @tracked query = "";
251
218
  @tracked results = [];
252
219
 
253
- @action
254
- updateQuery(event) {
255
- this.query = event.target.value;
256
- }
220
+ @action updateQuery(event) { this.query = event.target.value; }
257
221
 
258
222
  @action
259
223
  async search() {
260
224
  if (!this.query.trim()) return;
261
- // side effects belong in @action methods, not in getters or templates
262
225
  this.results = await this.args.onSearch(this.query);
263
226
  }
264
227
 
265
228
  <template>
266
- <input
267
- type="text"
268
- value={{this.query}}
269
- {{on "input" this.updateQuery}}
270
- />
229
+ <input type="text" value={{this.query}} {{on "input" this.updateQuery}} />
271
230
  <button type="button" {{on "click" this.search}}>Search</button>
272
231
  </template>
273
232
  }
274
233
  ```
275
234
 
276
- ## Admin UI Components
235
+ ## Admin UI
277
236
 
278
- Admin components live in the `admin/` asset tree and use the same Glimmer patterns.
237
+ Admin components live in `admin/` asset tree, same Glimmer patterns.
279
238
 
280
239
  ```
281
240
  assets/javascripts/
282
- ├── discourse/
283
- │ └── initializers/
284
- │ └── my-plugin.js # user-facing outlets
241
+ ├── discourse/initializers/my-plugin.js
285
242
  └── admin/
286
- ├── components/
287
- └── my-plugin-admin.gjs # admin panel component
288
- └── routes/
289
- └── admin-plugins-my-plugin.js
243
+ ├── components/my-plugin-admin.gjs
244
+ └── routes/admin-plugins-my-plugin.js
290
245
  ```
291
246
 
292
247
  ```gjs
@@ -302,7 +257,6 @@ import LoadingSpinner from "discourse/components/loading-spinner";
302
257
 
303
258
  export default class MyPluginAdmin extends Component {
304
259
  @service currentUser;
305
-
306
260
  @tracked stats = null;
307
261
  @tracked isLoading = false;
308
262
 
@@ -322,40 +276,33 @@ export default class MyPluginAdmin extends Component {
322
276
  <template>
323
277
  <div class="my-plugin-admin">
324
278
  <h2>My Plugin Admin</h2>
325
-
326
279
  {{#if this.isLoading}}
327
280
  <LoadingSpinner />
328
281
  {{else if this.stats}}
329
282
  <p>Total users: {{this.stats.total_users}}</p>
330
283
  <p>Pending: {{this.stats.pending}}</p>
331
284
  {{/if}}
332
-
333
- <DButton
334
- @action={{this.loadStats}}
335
- @label="my_plugin.admin.load_stats"
336
- @disabled={{this.isLoading}}
337
- />
285
+ <DButton @action={{this.loadStats}} @label="my_plugin.admin.load_stats" @disabled={{this.isLoading}} />
338
286
  </div>
339
287
  </template>
340
288
  }
341
289
  ```
342
290
 
343
- ## AJAX: Talking to the Plugin Backend
291
+ ## AJAX
292
+
293
+ Always use `discourse/lib/ajax` — handles CSRF tokens, error formatting, session state automatically. Never use raw `fetch`.
344
294
 
345
295
  ```javascript
346
296
  import { ajax } from "discourse/lib/ajax";
347
297
  import { popupAjaxError } from "discourse/lib/ajax-error";
348
298
 
349
- // GET
350
299
  const data = await ajax("/my-plugin/endpoint.json");
351
300
 
352
- // POST — Discourse's ajax helper automatically includes the CSRF token
353
301
  const result = await ajax("/my-plugin/action", {
354
302
  type: "POST",
355
303
  data: { user_id: userId, value: someValue }
356
304
  });
357
305
 
358
- // Always handle errors with popupAjaxError for consistent UX
359
306
  try {
360
307
  await ajax("/my-plugin/action", { type: "DELETE" });
361
308
  } catch (e) {
@@ -363,134 +310,77 @@ try {
363
310
  }
364
311
  ```
365
312
 
366
- **Always use `discourse/lib/ajax`, never raw `fetch`.** The helper handles CSRF tokens, error formatting, and Discourse session state automatically.
367
-
368
- ## Security in Ember
369
-
370
- ### Client checks are UX only — backend enforces everything
371
-
372
- ```javascript
373
- // Hiding/showing UI based on role is fine:
374
- get showAdminTools() {
375
- return this.currentUser?.admin;
376
- }
377
-
378
- // But the BACKEND must enforce the same check on every request.
379
- // Client-side auth is trivially bypassed in browser devtools.
380
- // There is no such thing as client-side security.
381
- ```
382
-
383
- ### No raw HTML injection with user content
384
-
385
- ```handlebars
386
- {{!-- Safe — Glimmer auto-escapes output --}}
387
- {{user.bio}}
388
-
389
- {{!-- Unsafe — raw output, skip escaping only for server-sanitized HTML --}}
390
- {{! avoid triple-mustache with user-supplied content }}
391
-
392
- {{!-- For server-sanitized post content, Discourse provides: --}}
393
- <div>{{html-safe post.cooked}}</div>
394
- ```
395
-
396
- ### Content Security Policy
313
+ ## Security
397
314
 
398
- Discourse enforces CSP strictly. These patterns will break or be blocked:
399
- - Inline `<script>` tags in plugin templates
400
- - Dynamic code evaluation (`eval`, dynamic code strings)
401
- - Modifying `innerHTML` directly — use Glimmer templates instead
402
- - Importing from external CDNs — bundle or use Discourse's asset pipeline
315
+ - Client checks are UX only backend enforces everything. Client-side auth is trivially bypassed.
316
+ - No raw HTML with user content — Glimmer auto-escapes `{{user.bio}}`. Use `{{html-safe post.cooked}}` only for server-sanitized HTML.
317
+ - CSP: no inline `<script>`, no `eval`, no `innerHTML`, no external CDN imports.
403
318
 
404
- ## Deprecations to Actively Avoid
319
+ ## Deprecations
405
320
 
406
321
  ```javascript
407
- // DEPRECATED — old connector class pattern (shows deprecation warning)
408
- api.registerConnectorClass("outlet-name", "connector-name", {
409
- setupComponent(args, component) { ... }
410
- });
322
+ // DEPRECATED — connector class (shows deprecation warning)
323
+ api.registerConnectorClass("outlet-name", "connector-name", { ... });
411
324
 
412
- // DEPRECATED — widget system (actively being removed in 2025/2026)
325
+ // DEPRECATED — widget system (being removed 2025/2026)
413
326
  api.decorateWidget("post:after", (helper) => { ... });
414
327
  api.createWidget("my-widget", { ... });
415
328
 
416
- // DEPRECATED — raw handlebars files (.hbr, .raw.hbs)
417
- // Breaks with the Glimmer topic list (enabled by default 2025)
329
+ // DEPRECATED — raw handlebars (.hbr, .raw.hbs) — breaks Glimmer topic list (default 2025)
418
330
 
419
- // DEPRECATED — classic component base class
420
- import Component from "@ember/component"; // use @glimmer/component instead
331
+ // DEPRECATED — classic component
332
+ import Component from "@ember/component"; // use @glimmer/component
421
333
 
422
- // DEPRECATED — Ember object mutation helpers
423
- this.set("myProp", value); // use @tracked + direct assignment: this.myProp = value
424
- Ember.set(obj, "key", val); // same — direct assignment or @tracked
334
+ // DEPRECATED — Ember object mutation
335
+ this.set("myProp", value); // use @tracked + direct assignment
336
+ Ember.set(obj, "key", val);
425
337
  ```
426
338
 
427
- ## File Naming and Location
339
+ ## File Naming
428
340
 
429
341
  ```
430
342
  assets/javascripts/discourse/
431
- ├── initializers/
432
- │ └── my-plugin.js # apiInitializer — one per feature area
433
- ├── components/
434
- │ ├── my-feature.gjs # kebab-case filenames
435
- │ └── my-other-thing.gjs
436
- └── lib/
437
- └── my-utils.js # pure helpers, no Ember dependency
343
+ ├── initializers/my-plugin.js # apiInitializer — one per feature area
344
+ ├── components/my-feature.gjs # kebab-case
345
+ └── lib/my-utils.js # pure helpers, no Ember dependency
438
346
 
439
347
  assets/javascripts/admin/
440
- ├── components/
441
- └── admin-my-plugin.gjs # admin components prefix with "admin-"
442
- └── routes/
443
- └── admin-plugins-my-plugin.js
348
+ ├── components/admin-my-plugin.gjs # prefix admin components with "admin-"
349
+ └── routes/admin-plugins-my-plugin.js
444
350
  ```
445
351
 
446
- ## Practical Checklist
352
+ ## Checklist
447
353
 
448
- - [ ] Using `.gjs` (not `.hbs`/`.js` pairs) for new components
449
- - [ ] Entry point is `apiInitializer`, not a bare `withPluginApi` export
450
- - [ ] Plugin outlets via `api.renderInOutlet` — not `decorateWidget`
451
- - [ ] `@tracked` only for state the component owns not derived values
452
- - [ ] Derived values are getters, not `@tracked` properties
453
- - [ ] Services injected via `@service` — not imported as singletons
454
- - [ ] AJAX via `discourse/lib/ajax` — not raw `fetch`
455
- - [ ] Errors handled with `popupAjaxError`
456
- - [ ] No raw HTML output with user-supplied content
457
- - [ ] Backend enforces all authorization — client checks are UI-only
354
+ - [ ] `.gjs` (not `.hbs`/`.js` pairs) for new components
355
+ - [ ] `apiInitializer` entry point, not bare `withPluginApi`
356
+ - [ ] `api.renderInOutlet` — not `decorateWidget`
357
+ - [ ] `@tracked` only for component-owned state, not derived values
358
+ - [ ] Derived values are getters
359
+ - [ ] Services via `@service` — not imported singletons
360
+ - [ ] AJAX via `discourse/lib/ajax` — not `fetch`
361
+ - [ ] Errors via `popupAjaxError`
362
+ - [ ] No raw HTML with user-supplied content
363
+ - [ ] Backend enforces all authorization
458
364
 
459
- ## Quick Reference: Common Imports
365
+ ## Quick Reference: Imports
460
366
 
461
367
  ```javascript
462
- // Glimmer/Ember core
463
368
  import Component from "@glimmer/component";
464
369
  import { tracked } from "@glimmer/tracking";
465
370
  import { action } from "@ember/object";
466
371
  import { service } from "@ember/service";
467
-
468
- // Discourse plugin API
469
372
  import { apiInitializer } from "discourse/lib/api";
470
373
  import { ajax } from "discourse/lib/ajax";
471
374
  import { popupAjaxError } from "discourse/lib/ajax-error";
472
-
473
- // Discourse components
474
375
  import DButton from "discourse/components/d-button";
475
376
  import LoadingSpinner from "discourse/components/loading-spinner";
476
377
  import DModal from "discourse/components/d-modal";
477
-
478
- // i18n
479
- import { i18n } from "discourse-i18n"; // current (replaces the old I18n.t())
378
+ import { i18n } from "discourse-i18n"; // replaces I18n.t()
480
379
  ```
481
380
 
482
- ## When to Load Reference Files
483
-
484
- ### Admin UI Patterns
485
- **File**: [`references/admin-ui.md`](references/admin-ui.md)
486
- **Load when**: Building admin routes, tables, forms, settings UI
487
- **Contains**: Full admin route + component example, admin table patterns, settings form
488
-
489
- ### Plugin Outlet Reference
490
- **File**: [`references/outlets.md`](references/outlets.md)
491
- **Load when**: Need to find the right outlet or understand outletArgs context
492
- **Contains**: Common outlet names, outletArgs by context, shouldRender patterns
493
-
494
- ---
381
+ ## Reference Files
495
382
 
496
- **Evidence Base**: Discourse Developer Docs (2025), Discourse Meta dev posts (Glimmer post stream migration Q1 2025, topic list migration), Ember Octane Guides, Discourse CVE history.
383
+ | File | Load when |
384
+ |------|-----------|
385
+ | [`references/admin-ui.md`](references/admin-ui.md) | Building admin routes, tables, forms, settings UI |
386
+ | [`references/outlets.md`](references/outlets.md) | Finding outlet names, understanding outletArgs context |
@@ -11,39 +11,30 @@ description: >-
11
11
 
12
12
  # EspoCRM - Skill Family Router
13
13
 
14
- Routes EspoCRM work to the right child skill based on intent.
15
-
16
- **Target version**: v9.x (9.0+)
17
- **Architecture**: Entity-based REST API, PHP backend, Backbone.js frontend
14
+ **Target**: v9.x (9.0+) | **Architecture**: Entity-based REST API, PHP backend, Backbone.js frontend
18
15
 
19
16
  ## Decision Tree
20
17
 
21
18
  ```
22
19
  What are you doing with EspoCRM?
23
20
  ├── REST API calls (external integration)?
24
- │ → espocrm-api (primary) + php-fp or js-fp-api
25
- │ → Auth, CRUD, filtering, webhooks, mass ops
21
+ │ → espocrm-api + php-fp or js-fp-api
26
22
 
27
23
  ├── PHP extension development (hooks, services, custom entities)?
28
24
  │ → espocrm-extensions (Phase 2) + php-fp
29
- │ → ORM, hooks, services, DI, custom controllers, modules
30
25
 
31
26
  ├── Frontend/UI customization (views, fields, layouts)?
32
27
  │ → espocrm-ui (Phase 3)
33
- │ → Backbone views, Espo.Ajax, Handlebars templates
34
28
 
35
29
  └── Not sure / mixed?
36
- → Start with espocrm-api for data access
37
- → Route to extension/UI skill once scope is clear
30
+ → Start with espocrm-api, route to extension/UI once scope is clear
38
31
  ```
39
32
 
40
33
  ## Shared Context
41
34
 
42
- ### Entity-Based Model
43
- EspoCRM organizes data as **Entity Types** (Account, Contact, Lead, Opportunity, custom types). Every entity type gets automatic REST endpoints. Custom entities created via Entity Manager are immediately API-accessible.
35
+ Every Entity Type (Account, Contact, Lead, Opportunity, custom) gets automatic REST endpoints. Custom entities via Entity Manager are immediately API-accessible.
44
36
 
45
- ### Salesforce Mental Model
46
- For developers familiar with Salesforce, this mapping accelerates onboarding:
37
+ ### Salesforce Mapping
47
38
 
48
39
  | Salesforce | EspoCRM |
49
40
  |---|---|
@@ -56,19 +47,19 @@ For developers familiar with Salesforce, this mapping accelerates onboarding:
56
47
  | LWC / Visualforce | Custom Views (JS, extending base views) |
57
48
  | Platform Events / CDC | Webhooks ({Entity}.create, .update, .delete) |
58
49
  | Bulk API 2.0 | No equivalent (loop individual calls or use Import) |
59
- | Governor Limits | None (self-hosted, you manage resources) |
50
+ | Governor Limits | None (self-hosted) |
60
51
  | AppExchange | EspoCRM Extensions marketplace |
61
52
 
62
53
  ### Key Differences from Salesforce
63
- - **No SOQL** — queries use structured JSON WHERE filters (verbose but explicit)
64
- - **No Bulk API** — mass operations exist (massUpdate, massDelete) but no batch create
65
- - **No Composite API** — one request per operation
66
- - **No governor limits** — self-hosted, manage at server/proxy level
67
- - **Simpler auth** — API Key in one header vs. multi-step OAuth
68
- - **Metadata is JSON files** — no deployment steps, changes take effect on cache clear
69
54
 
70
- ### Documentation Lookup
71
- Use Context7 for live EspoCRM docs: `resolve-library-id("espocrm")` resolves to `/espocrm/documentation`.
55
+ - No SOQL — structured JSON WHERE filters
56
+ - No Bulk API mass ops (massUpdate, massDelete) but no batch create
57
+ - No Composite API — one request per operation
58
+ - No governor limits
59
+ - Simpler auth — API Key in one header
60
+ - Metadata is JSON files — changes take effect on cache clear
61
+
62
+ **Docs**: Use Context7 `resolve-library-id("espocrm")` → `/espocrm/documentation`
72
63
 
73
64
  ## Child Skill Status
74
65