RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/TryGhost/Ghost/diff

Two files, one repository

TryGhost/Ghost ships 2 formats across 4 indexed files. The question worth asking is whether the second one says anything the first does not.

CompareAGENTS.md ↔ CLAUDE.md
A · AGENTS.md · 2464 wordsB · koenig/koenig-lexical/CLAUDE.md · 253 words
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections058100%
Commands13972%
Section tags311120%

What each file covers

Sections

0 shared · 58 only in A · 10 only in B
  • − AGENTS.md
  • − Package Manager
  • − Monorepo Structure
  • − ghost/* - Core Ghost packages
  • − apps/* - React-based UI applications
  • − koenig/* - Ghost editor (Koenig) packages
  • − packages/* - Shared workspace libraries
  • − e2e/ - End-to-end tests
  • − Common Commands
  • − Development
  • − Building
  • − Testing
  • − Unit tests (from root)
  • − Ghost core tests (from ghost/core/)
  • − These run on sqlite with no extra services. The Redis/MinIO/S3 adapter suites
  • − probe for their service and auto-skip when it's down (run `pnpm dev:storage`
  • − etc. to exercise them); they always run in CI, which starts the services.
  • − E2E browser tests (from root)
  • − Running a single test
  • − Watch a single DB-backed file (integration/e2e) — the default test:watch only
  • − covers unit tests, so point it at the DB config explicitly:
  • − Ember Admin tests (from the repository root)
  • − Run one Ember Admin test file. Paths are relative to apps/ember-admin.
  • − The explicit `1` supplies the numeric value required by the test script's
  • − trailing `--parallel` option before additional Ember Exam arguments.
  • − Linting
  • − Database
  • − Docker
  • − How `pnpm dev` works
  • − Start Ghost backend, Admin, Portal, and Docker services
  • − Add optional public apps (comments-ui, sodo-search, signup-form, admin-toolbar)
  • − Develop the Koenig editor against Ghost Admin (adds a koenig-lexical rebuild
  • − watcher + preview server; Admin loads the editor from your local build)
  • − With optional services (uses Docker Compose file composition)
  • − Everything available
  • − Architecture Patterns
  • − Admin Apps Integration (Micro-Frontend)
  • − Public Apps Integration
  • − i18n Architecture
  • − Build Dependencies (Nx)
  • − CSS Architecture
  • − TailwindCSS v4 Setup
  • − Entry Point
  • − Shade Styles
  • − Critical Rule: Embedded Apps Must NOT Import Shade Independently
  • − Public Apps
  • − Legacy Apps
  • − Code Guidelines
  • − Commit Messages
  • − ESLint Config
  • − When Working on Admin UI
  • − When Working on Public UI
  • − When Working on Backend
  • − Design System Usage
  • − Analytics (Tinybird)
  • − Troubleshooting
  • − Build Issues
  • − Test Issues
  • + Koenig Lexical Test Guide
  • + Test Commands
  • + Unit Tests
  • + Acceptance Tests (Playwright)
  • + All Tests
  • + AI-Friendly Testing
  • + Human-Friendly Testing
  • + Environment Variables
  • + Test Structure
  • + Development Workflow

Commands

1 shared · 39 only in A · 7 only in B
  • − pnpm run setup
  • − pnpm dev
  • − pnpm build
  • − pnpm build:clean
  • − pnpm test:watch
  • − pnpm test:integration
  • − pnpm test:e2e
  • − pnpm test:all
  • − pnpm test:single test/unit/path/to/test.test.js
  • − pnpm exec vitest -c vitest.config.db.ts test/integration/path/to/test.test.js
  • − pnpm nx run ghost-admin:test
  • − pnpm nx run ghost-admin:test -- 1 --file-path=tests/acceptance/editor/publish-flow-test.js
  • − pnpm lint
  • − pnpm knex-migrator migrate
  • − pnpm reset:data
  • − pnpm reset:data:empty
  • − pnpm docker:build
  • − pnpm docker:clean
  • − pnpm docker:down
  • − pnpm dev:public
  • − pnpm dev:lexical
  • − pnpm dev:analytics
  • − pnpm dev:storage
  • − pnpm dev:stripe
  • − pnpm dev:full
  • − pnpm dev:all
  • − pnpm --filter @tryghost/i18n translate
  • − pnpm --filter @tryghost/i18n lint:translations
  • − pnpm fix
  • − pnpm nx reset
  • − pnpm
  • − pnpm-workspace.yaml
  • − pnpm add
  • − node --conditions=source --import=tsx
  • − tsc
  • − node
  • − pnpm setup
  • − pnpm --filter @tryghost/i18n test
  • − eslint-plugin-ghost: catalog:
  • + pnpm test:unit:watch
  • + pnpm test:acceptance
  • + pnpm test:acceptance:quiet
  • + pnpm test:acceptance:headed
  • + pnpm test:acceptance:report
  • + pnpm test:slowmo
  • + pnpm test
  •   pnpm test:unit

Section tags

3 shared · 11 only in A · 1 only in B
  • − build
  • − lint-format
  • − code-style
  • − testing-strategy
  • − git-pr
  • − security
  • − dependencies
  • − database
  • − ui
  • − monorepo
  • − docs
  • + agent-behaviour
  •   setup
  •   test
  •   architecture

Line diff

+36 added−373 removed23 unchanged5.8% identical
TryGhost/Ghost · AGENTS.md
@@ −1 @@
1# AGENTS.md
2 
3This file provides guidance to AI Agents when working with code in this repository.
4 
5## Package Manager
6 
7**Always use `pnpm` for all commands.** This repository uses pnpm workspaces, not npm.
8 
9Shared dependency versions are pinned in `pnpm-workspace.yaml` under `catalog:` and referenced as `"pkg": "catalog:"` (or `catalog:<name>` for named catalogs). `catalogMode` is `strict`, so `pnpm add` routes new deps into the catalog automatically — don't inline the version.
10 
11## Monorepo Structure
12 
13Ghost is a pnpm + Nx monorepo with four workspace groups:
14 
15### ghost/* - Core Ghost packages
16- **ghost/core** - Main Ghost application (Node.js/Express backend)
17 - Core server: `ghost/core/core/server/`
18 - Frontend rendering: `ghost/core/core/frontend/`
19 
20### apps/* - React-based UI applications
21Two categories of apps:
22 
23**Admin Apps** (embedded in Ghost Admin):
24- `ember-admin` - Ember.js admin client (legacy, being migrated to React)
25- `admin` - The consolidated React admin shell, organized by domain (`src/{analytics,members,posts,tags,comments,automations,...}`)
26- `admin-x-settings`, `activitypub` - Settings and ActivityPub integration (route-composed into `admin`)
27- Built with Vite + React + `@tanstack/react-query`
28 
29**Public Apps** (served to site visitors):
30- `portal`, `comments-ui`, `signup-form`, `sodo-search`, `announcement-bar`
31- Built as UMD bundles, loaded via CDN in site themes
32 
33**Foundation Libraries**:
34- `admin-x-framework` - Shared API hooks, routing, utilities
35- `admin-x-design-system` - Legacy design system (being phased out)
36- `shade` - New design system (shadcn/ui + Radix UI + react-hook-form + zod)
37 
38### koenig/* - Ghost editor (Koenig) packages
39Merged from the former TryGhost/Koenig repo with full git history:
40 
41- **koenig-lexical** - The Lexical-based rich text editor UI. Bundled into
42 Ghost Admin at build time (`apps/ember-admin` copies its UMD build into admin
43 assets; `apps/admin` imports it directly)
44- **kg-*** - Editor support packages: server-side renderers and converters
45 consumed by `ghost/core` (kg-default-nodes, kg-lexical-html-renderer,
46 kg-html-to-lexical, ...) plus frontend helpers (kg-unsplash-selector)
47 
48All Koenig packages resolve via `workspace:` — nothing in dev, CI, or the
49release archive installs them from npm. They are published to npm for
50external consumers only, automatically as part of the Ghost release lane
51(see `publish_koenig_packages` in ci.yml).
52 
53**Zero-build dev via the `source` export condition.** The `kg-*` libraries
54consumed by `ghost/core` (and `packages/parse-email-address`) declare a `source`
55condition in their `package.json` `exports` that points at the raw
56`src/*.ts`, listed *before* `types`/`import`/`require`:
57 
58```jsonc
59".": {
60 "source": "./src/index.ts", // dev/test: read raw TS
61 "types": "./build/esm/index.d.ts",
62 "import": "./build/esm/index.js",
63 "require": "./build/cjs/index.js" // prod/published: compiled JS
64}
65```
66 
67`ghost/core`'s dev runner (`nodemon.json`: `node --conditions=source --import=tsx`)
68and its Vitest configs (`resolve.conditions: ['source', 'node']` +
69`--import tsx --conditions=source`) activate this condition, so a source change
70in a `kg-*` package is picked up with **no `tsc` rebuild**. Production and the
71published npm tarball run plain `node`, which ignores `source` and uses
72`build/` — and `src/` is excluded from each package's `files` array, so it is
73never shipped. When adding a new backend-consumed TS workspace package, copy
74this `exports` shape (see `packages/parse-email-address`) so it works build-free
75in dev from day one; keep the `^build` graph for `tsc`/type-checking and prod.
76 
77### packages/* - Shared workspace libraries
78Backend and shared libraries consumed via `workspace:` — not published to npm:
79 
80- **i18n** - Centralized internationalization for all apps
81- **parse-email-address** - Email address parsing (see the `source` export
82 condition above)
83- **adapters/** - Adapter base classes (`adapter-base-*`: scheduling, storage,
84 SSO, redirects, route settings)
85- **custom-field-types**, **testing** - Shared field-type definitions and test
86 helpers
87- **_template** - Scaffold for new packages; excluded from the workspace
88 
89### e2e/ - End-to-end tests
90- Playwright-based E2E tests with Docker container isolation
91- See `e2e/CLAUDE.md` for detailed testing guidance
92 
93## Common Commands
94 
95### Development
96```bash
97corepack enable pnpm # Enable corepack to use the correct pnpm version
98pnpm run setup # First-time setup (installs deps + submodules + builds workspace packages)
99pnpm dev # Start development (Docker backend + host frontend dev servers)
100```
101 
102> **Fresh worktree / first run — run `pnpm setup` before anything else.** It installs deps and syncs submodules. `pnpm fix` does a clean reinstall if anything misbehaves after a branch switch.
103 
104### Building
105```bash
106pnpm build # Build all packages (Nx handles dependencies)
107pnpm build:clean # Clean build artifacts and rebuild
 
 
 
108```
109 
110### Testing
111```bash
112# Unit tests (from root)
113pnpm test:unit # Run all unit tests in all packages
114pnpm test:watch # Watch mode — unified Vitest watcher (ghost/core + all apps)
115 
116# Ghost core tests (from ghost/core/)
117cd ghost/core
118pnpm test:unit # Unit tests only (Vitest, run once)
119pnpm test:watch # Watch mode — ghost/core unit tests only
120pnpm test:integration # Integration tests
121pnpm test:e2e # Server-side e2e suites (webhooks/server/frontend/api) — not browser
122pnpm test:all # All test types
123 
124# These run on sqlite with no extra services. The Redis/MinIO/S3 adapter suites
125# probe for their service and auto-skip when it's down (run `pnpm dev:storage`
126# etc. to exercise them); they always run in CI, which starts the services.
127 
128# E2E browser tests (from root)
129pnpm test:e2e # Run e2e/ Playwright tests
130 
131# Running a single test
132cd ghost/core
133pnpm test:single test/unit/path/to/test.test.js # routes test/unit/* → unit config, test/* → DB config
134 
135# Watch a single DB-backed file (integration/e2e) — the default test:watch only
136# covers unit tests, so point it at the DB config explicitly:
137pnpm exec vitest -c vitest.config.db.ts test/integration/path/to/test.test.js
138 
139# Ember Admin tests (from the repository root)
140pnpm nx run ghost-admin:test
141 
142# Run one Ember Admin test file. Paths are relative to apps/ember-admin.
143# The explicit `1` supplies the numeric value required by the test script's
144# trailing `--parallel` option before additional Ember Exam arguments.
145pnpm nx run ghost-admin:test -- 1 --file-path=tests/acceptance/editor/publish-flow-test.js
146```
147 
148> **Always run Ember Admin tests through Nx.** Running `ember test` or
149> `ember exam` directly from `apps/ember-admin` skips the dependency build
150> graph and commonly fails in fresh worktrees with missing outputs such as
151> `koenig-lexical.umd.js`, `@tryghost/admin-x-framework/hooks`, or
152> `@tryghost/kg-converters`. For focused runs, use Ember Exam's `--file-path`
153> as shown above rather than appending `--filter` to the package script.
154 
155### Linting
156```bash
157pnpm lint # Lint all packages
158cd ghost/core && pnpm lint # Lint Ghost core (server, shared, frontend, tests)
159cd apps/ember-admin && pnpm lint # Lint Ember admin
160```
161 
162### Database
163```bash
164pnpm knex-migrator migrate # Run database migrations
165pnpm reset:data # Reset database with test data (1000 members, 100 posts) (requires pnpm dev running)
166pnpm reset:data:empty # Reset database with no data (requires pnpm dev running)
167```
168 
169### Docker
170```bash
171pnpm docker:build # Build Docker images
172pnpm docker:clean # Stop containers, remove volumes and local images
173pnpm docker:down # Stop containers
174```
175 
176### How `pnpm dev` works
177 
178The `pnpm dev` command uses a **hybrid Docker + host development** setup:
 
 
179 
180**What runs in Docker:**
181- Ghost Core backend (with hot-reload via mounted source)
182- MySQL, Redis, Mailpit
183- Caddy gateway/reverse proxy
184 
185**What runs on host by default:**
186- Admin, legacy Ember admin, Portal, and foundation library dev watchers
187- Optional public UMD app watchers can be added when needed
188 
189**Setup:**
190```bash
191# Start Ghost backend, Admin, Portal, and Docker services
192pnpm dev
193 
194# Add optional public apps (comments-ui, sodo-search, signup-form, admin-toolbar)
195pnpm dev:public
 
196 
197# Develop the Koenig editor against Ghost Admin (adds a koenig-lexical rebuild
198# watcher + preview server; Admin loads the editor from your local build)
199pnpm dev:lexical
200 
201# With optional services (uses Docker Compose file composition)
202pnpm dev:analytics # Include Tinybird analytics
203pnpm dev:storage # Include MinIO S3-compatible object storage
204pnpm dev:stripe # Include Stripe webhook forwarding
205pnpm dev:full # Include analytics, storage, Stripe, and public app watchers
206 
207# Everything available
208pnpm dev:all #
209```
210 
211**Accessing Services:**
212- Ghost: `http://localhost:2368` (database: `ghost_dev`)
213- Mailpit UI: `http://localhost:8025` (email testing)
214- MySQL: `localhost:3306`
215- Redis: `localhost:6379`
216- Tinybird: `http://localhost:7181` (when analytics enabled)
217- MinIO Console: `http://localhost:9001` (when storage enabled)
218- MinIO S3 API: `http://localhost:9000` (when storage enabled)
219 
220## Architecture Patterns
221 
222### Admin Apps Integration (Micro-Frontend)
223 
224**Build Process:**
2251. Admin-x React apps build to `apps/*/dist` using Vite
2262. `apps/ember-admin/lib/asset-delivery` copies them to `ghost/core/core/built/admin/assets/*`
2273. Ghost admin serves from `/ghost/assets/{app-name}/{app-name}.js`
228 
229**Runtime Loading:**
230- Ember admin uses `AdminXComponent` to dynamically import React apps
231- React components wrapped in Suspense with error boundaries
232- Apps receive config via `additionalProps()` method
233 
234### Public Apps Integration
235 
236- Built as UMD bundles to `apps/*/umd/*.min.js`
237- Loaded via `<script>` tags in theme templates (injected by `{{ghost_head}}`)
238- Configuration passed via data attributes
239 
240### i18n Architecture
241 
242**Centralized Translations:**
243- Single source: `packages/i18n/locales/{locale}/{namespace}.json`
244- Namespaces: `ghost`, `portal`, `signup-form`, `comments`, `search`
245- 60+ supported locales
246- Context descriptions: `packages/i18n/locales/context.json` — every key must have a non-empty description
247 
248**Translation Workflow:**
249```bash
250pnpm --filter @tryghost/i18n translate # Extract keys from source, update all locale files + context.json
251pnpm --filter @tryghost/i18n lint:translations # Validate interpolation variables across locales
252```
253 
254`translate` is run as part of `pnpm --filter @tryghost/i18n test`. In CI, it fails if translation keys or `context.json` are out of date (`failOnUpdate: process.env.CI`). Always run `pnpm --filter @tryghost/i18n translate` after adding or changing `t()` calls.
255 
256**Rules for Translation Keys:**
2571. **Never split sentences across multiple `t()` calls.** Translators cannot reorder words across separate keys. Instead, use `@doist/react-interpolate` to embed React elements (links, bold, etc.) within a single translatable string.
2582. **Always provide context descriptions.** When adding a new key, add a description in `context.json` explaining where the string appears and what it does. CI will reject empty descriptions.
2593. **Use interpolation for dynamic values.** Ghost uses `{variable}` syntax: `t('Welcome back, {name}!', {name: firstname})`
2604. **Use `<tag>` syntax for inline elements.** Combined with `@doist/react-interpolate`: `t('Click <a>here</a> to retry')` with `mapping={{ a: <a href="..." /> }}`
261 
262**Correct pattern (using Interpolate):**
263```jsx
264import Interpolate from '@doist/react-interpolate';
265 
266<Interpolate
267 mapping={{ a: <a href={link} /> }}
268 string={t('Could not sign in. <a>Click here to retry</a>')}
269/>
270```
271 
272**Incorrect pattern (split sentences):**
273```jsx
274// BAD: translators cannot reorder "Click here to retry" relative to the first sentence
275{t('Could not sign in.')} <a href={link}>{t('Click here to retry')}</a>
276```
277 
278See `apps/portal/src/components/pages/email-receiving-faq.js` for a canonical example of correct `Interpolate` usage.
279 
280### Build Dependencies (Nx)
281 
282Critical build order (Nx handles automatically):
2831. `shade` + `admin-x-design-system` build
2842. `admin-x-framework` builds (depends on #1)
2853. Admin apps build (depend on #2)
2864. `apps/ember-admin` builds (depends on #3, copies via asset-delivery)
2875. `ghost/core` serves admin build
288 
289## CSS Architecture
290 
291### TailwindCSS v4 Setup
292 
293Ghost Admin uses **TailwindCSS v4** via the `@tailwindcss/vite` plugin. CSS processing is centralized — only `apps/admin/vite.config.ts` loads the `@tailwindcss/vite` plugin. All embedded React apps (activitypub, admin-x-settings, admin-x-design-system) are scanned from this single entry point.
294 
295### Entry Point
296 
297`apps/admin/src/index.css` is the main CSS entry point. It contains:
298- `@source` directives that scan class usage in shade, activitypub, admin-x-settings, admin-x-design-system, and kg-unsplash-selector
299- `@import "@tryghost/shade/styles.css"` which loads the Shade design system styles
300 
301### Shade Styles
302 
303`apps/shade/styles.css` uses **unlayered** Tailwind imports:
304```css
305@import "tailwindcss/theme.css";
306@import "./preflight.css";
307@import "tailwindcss/utilities.css";
308@import "tw-animate-css";
309@import "./tailwind.theme.css";
310```
311 
312**Why unlayered:** Ember's legacy CSS (`.flex`, `.hidden`, etc.) is unlayered. If Tailwind utilities were in a `@layer`, they would lose to Ember's unlayered CSS in the cascade. Keeping both unlayered means source order determines specificity.
313 
314Theme tokens/variants/animations are defined in CSS (`apps/shade/tailwind.theme.css` + runtime vars in `styles.css`), so there is no JS `@config` bridge in the Admin runtime lane. `tw-animate-css` is the v4 replacement for `tailwindcss-animate`.
315 
316### Critical Rule: Embedded Apps Must NOT Import Shade Independently
317 
318Apps consumed via `@source` (activitypub, admin-x-settings) must **NOT** import `@tryghost/shade/styles.css` in their own CSS. Doing so causes duplicate Tailwind utilities and cascade conflicts. All Tailwind CSS is generated once via the admin entry point.
319 
320### Public Apps
321 
322Public-facing apps (`comments-ui`, `signup-form`, `sodo-search`, `portal`, `announcement-bar`) remain on **TailwindCSS v3**. They are built as UMD bundles for CDN distribution and are independent of the admin CSS pipeline.
323 
324### Legacy Apps
325 
326`admin-x-design-system` and `admin-x-settings` are consumed via `@source` in admin's centralized v4 pipeline for production, and both packages build with CSS-first Tailwind v4 setup.
327 
328## Code Guidelines
329 
330### Commit Messages
331When the user asks you to create a commit or draft a commit message, load and follow the `commit` skill from `.agents/skills/commit`.
332 
333### ESLint Config
334Source of truth: two internal config packages — [`@internal/cfg-eslint`](configs/eslint/index.mjs) (shared rule atoms + the `nodeLibConfig` factory for Node libs) and [`@internal/cfg-eslint-react`](configs/eslint-react/index.mjs) (the `reactAppConfig` factory for every `apps/*` workspace). Both factories are synchronous and have full JSDoc with `@example`s; hover the call site in your editor. Consume them by name — declare the package as a `workspace:*` devDependency.
335 
336Minimal example for a new admin React app (`apps/new-feature/eslint.config.js`):
337 
338```js
339import {reactAppConfig} from '@internal/cfg-eslint-react';
340export default reactAppConfig({
341 tailwindCssPath: `${import.meta.dirname}/../admin/src/index.css`,
342 shadeRestricted: true
343});
344```
345 
346Conventions:
347- **Rules are `'error'` or `'off'` — never `'warn'`.** Warnings get ignored and pollute output. Applies to every workspace covered by the factories above + the standalones; `e2e/` has its own setup (see [e2e/CLAUDE.md](e2e/CLAUDE.md)) and currently still uses warn-level Playwright rules — a separate cleanup.
348- **Params prefixed `legacy*`** (`legacyTailwindV3ConfigPath`, `legacyJsTsSplit`) are escape hatches for migrations that haven't shipped yet. Intentional and visible — PRs to remove them are scoped.
349- **Standalone configs** (`ghost/core`, `apps/ember-admin`, `apps/admin-toolbar`) exist because their rule sets genuinely don't fit a factory — read the file directly. They import shared atoms (`correctnessRules`, `nodeLibRules`, `localFilenamesPlugin`, `strictLinterOptions`) from `@internal/cfg-eslint`.
350- **Plugin deps**: a workspace must declare every eslint plugin its config resolves. Two cases:
351 - *Factory consumers* only import a factory, which supplies its plugins as objects from the config package — so they need just the config package (`@internal/cfg-eslint` / `@internal/cfg-eslint-react`) as a `workspace:*` devDependency, not the individual plugins.
352 - *Hand-rolled configs* (the standalones above, plus the inline configs in `koenig/kg-*` and `e2e/`) `import` plugins directly, so each must list those plugins in its own `devDependencies` — most commonly `eslint-plugin-ghost: catalog:`. Don't rely on the root hoisting a plugin for you; there are no eslint plugins left in the root `package.json` (only `eslint` itself and `globals`, which the root config uses).
353 - Exception: Tailwind — a workspace that uses it must list `tailwindcss` as its own (dev)Dependency regardless (the settings-based resolver requires it locally), and the legacy v3 apps pin `eslint-plugin-tailwindcss` via `catalog:tailwind3`.
354 
355### When Working on Admin UI
356- **New features:** Build in React in `apps/admin` (domain folders under `src/`)
357- **Use:** `admin-x-framework` for API hooks (`useBrowse`, `useEdit`, etc.)
358- **Use:** `shade` design system for new components (not admin-x-design-system)
359- **Translations:** Add to `packages/i18n/locales/en/ghost.json`
360 
361### When Working on Public UI
362- **Edit:** `apps/portal`, `apps/comments-ui`, etc.
363- **Translations:** Separate namespaces (`portal.json`, `comments.json`)
364- **Build:** UMD bundles for CDN distribution
365 
366### When Working on Backend
367- **Core logic:** `ghost/core/core/server/`
368- **Database Schema:** `ghost/core/core/server/data/schema/`
369- **API routes:** `ghost/core/core/server/api/`
370- **Services:** `ghost/core/core/server/services/`
371- **Models:** `ghost/core/core/server/models/`
372- **Frontend & theme rendering:** `ghost/core/core/frontend/`
373 
374### Design System Usage
375- **New components:** Use `shade` (shadcn/ui-inspired)
376- **Legacy:** `admin-x-design-system` (being phased out, avoid for new work)
377 
378### Analytics (Tinybird)
379- **Local development:** `pnpm dev:analytics` (starts Tinybird + MySQL)
380- **Config:** Add Tinybird config to `ghost/core/config.development.json`
381- **Scripts:** `ghost/core/core/server/data/tinybird/scripts/`
382- **Datafiles:** `ghost/core/core/server/data/tinybird/`
383 
384## Troubleshooting
385 
386### Build Issues
387```bash
388pnpm fix # Clean cache + node_modules + reinstall
389pnpm build:clean # Clean build artifacts
390pnpm nx reset # Reset Nx cache
391```
392 
393### Test Issues
394- **E2E failures:** Check `e2e/CLAUDE.md` for debugging tips
395- **Docker issues:** `pnpm docker:clean && pnpm docker:build`
396 
TryGhost/Ghost · koenig/koenig-lexical/CLAUDE.md
@@ +1 @@
1# Koenig Lexical Test Guide
2 
3## Test Commands
4 
5### Unit Tests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6```bash
7pnpm test:unit # Run unit tests once
8pnpm test:unit:watch # Run unit tests in watch mode
 
9```
10 
11### Acceptance Tests (Playwright)
 
 
12```bash
13pnpm test:acceptance # Run Playwright tests (headless, list reporter)
14pnpm test:acceptance:quiet # Minimal output, failures only
15pnpm test:acceptance:headed # Run with browser UI visible
16pnpm test:acceptance:report # Run with HTML report
17pnpm test:slowmo # Slow motion + UI
18```
19 
20### All Tests
21```bash
22pnpm test # Run unit + acceptance tests, then lint
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23```
24 
25## AI-Friendly Testing
 
 
 
 
 
26 
27The test runner has been configured to work well with AI agents:
 
 
 
 
 
28 
29- **Default behavior**: Headless mode with list reporter (no browser UI, no web pages)
30- **Quiet mode**: Use `pnpm test:acceptance:quiet` for minimal output (only shows failures)
31- **Clean exit**: Tests complete without hanging processes or opening browsers
32- **Clear output**: List reporter provides clear pass/fail information
 
 
33 
34## Human-Friendly Testing
 
 
 
 
 
35 
36For debugging and development:
37 
38- Use `pnpm test:acceptance:headed` to see the browser UI
39- Use `pnpm test:acceptance:report` to generate an HTML report
40- Use `pnpm test:slowmo` for slow-motion debugging
41 
42## Environment Variables
 
 
 
43 
44- `PLAYWRIGHT_HEADED=true` - Show browser UI
45- `PLAYWRIGHT_HTML_REPORT=true` - Generate HTML report
46- `PLAYWRIGHT_SLOWMO=100` - Slow motion delay (ms)
47 
48## Test Structure
 
 
 
49 
50- `test/unit/` - Unit tests (Vitest)
51- `test/e2e/` - Acceptance tests (Playwright, `test:acceptance` target)
52- `test/utils/` - Shared test utilities
53 
54## Development Workflow
 
 
55 
561. Run unit tests during development: `pnpm test:unit:watch`
572. Run acceptance tests before committing: `pnpm test:acceptance`
583. Use headed mode for debugging: `pnpm test:acceptance:headed`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59 
@@ −1 +1 @@
1−# AGENTS.md
1+# Koenig Lexical Test Guide
22  
3−This file provides guidance to AI Agents when working with code in this repository.
3+## Test Commands
44  
5−## Package Manager
6− 
7−**Always use `pnpm` for all commands.** This repository uses pnpm workspaces, not npm.
8− 
9−Shared dependency versions are pinned in `pnpm-workspace.yaml` under `catalog:` and referenced as `"pkg": "catalog:"` (or `catalog:<name>` for named catalogs). `catalogMode` is `strict`, so `pnpm add` routes new deps into the catalog automatically — don't inline the version.
10− 
11−## Monorepo Structure
12− 
13−Ghost is a pnpm + Nx monorepo with four workspace groups:
14− 
15−### ghost/* - Core Ghost packages
16−- **ghost/core** - Main Ghost application (Node.js/Express backend)
17− - Core server: `ghost/core/core/server/`
18− - Frontend rendering: `ghost/core/core/frontend/`
19− 
20−### apps/* - React-based UI applications
21−Two categories of apps:
22− 
23−**Admin Apps** (embedded in Ghost Admin):
24−- `ember-admin` - Ember.js admin client (legacy, being migrated to React)
25−- `admin` - The consolidated React admin shell, organized by domain (`src/{analytics,members,posts,tags,comments,automations,...}`)
26−- `admin-x-settings`, `activitypub` - Settings and ActivityPub integration (route-composed into `admin`)
27−- Built with Vite + React + `@tanstack/react-query`
28− 
29−**Public Apps** (served to site visitors):
30−- `portal`, `comments-ui`, `signup-form`, `sodo-search`, `announcement-bar`
31−- Built as UMD bundles, loaded via CDN in site themes
32− 
33−**Foundation Libraries**:
34−- `admin-x-framework` - Shared API hooks, routing, utilities
35−- `admin-x-design-system` - Legacy design system (being phased out)
36−- `shade` - New design system (shadcn/ui + Radix UI + react-hook-form + zod)
37− 
38−### koenig/* - Ghost editor (Koenig) packages
39−Merged from the former TryGhost/Koenig repo with full git history:
40− 
41−- **koenig-lexical** - The Lexical-based rich text editor UI. Bundled into
42− Ghost Admin at build time (`apps/ember-admin` copies its UMD build into admin
43− assets; `apps/admin` imports it directly)
44−- **kg-*** - Editor support packages: server-side renderers and converters
45− consumed by `ghost/core` (kg-default-nodes, kg-lexical-html-renderer,
46− kg-html-to-lexical, ...) plus frontend helpers (kg-unsplash-selector)
47− 
48−All Koenig packages resolve via `workspace:` — nothing in dev, CI, or the
49−release archive installs them from npm. They are published to npm for
50−external consumers only, automatically as part of the Ghost release lane
51−(see `publish_koenig_packages` in ci.yml).
52− 
53−**Zero-build dev via the `source` export condition.** The `kg-*` libraries
54−consumed by `ghost/core` (and `packages/parse-email-address`) declare a `source`
55−condition in their `package.json` `exports` that points at the raw
56−`src/*.ts`, listed *before* `types`/`import`/`require`:
57− 
58−```jsonc
59−".": {
60− "source": "./src/index.ts", // dev/test: read raw TS
61− "types": "./build/esm/index.d.ts",
62− "import": "./build/esm/index.js",
63− "require": "./build/cjs/index.js" // prod/published: compiled JS
64−}
65−```
66− 
67−`ghost/core`'s dev runner (`nodemon.json`: `node --conditions=source --import=tsx`)
68−and its Vitest configs (`resolve.conditions: ['source', 'node']` +
69−`--import tsx --conditions=source`) activate this condition, so a source change
70−in a `kg-*` package is picked up with **no `tsc` rebuild**. Production and the
71−published npm tarball run plain `node`, which ignores `source` and uses
72−`build/` — and `src/` is excluded from each package's `files` array, so it is
73−never shipped. When adding a new backend-consumed TS workspace package, copy
74−this `exports` shape (see `packages/parse-email-address`) so it works build-free
75−in dev from day one; keep the `^build` graph for `tsc`/type-checking and prod.
76− 
77−### packages/* - Shared workspace libraries
78−Backend and shared libraries consumed via `workspace:` — not published to npm:
79− 
80−- **i18n** - Centralized internationalization for all apps
81−- **parse-email-address** - Email address parsing (see the `source` export
82− condition above)
83−- **adapters/** - Adapter base classes (`adapter-base-*`: scheduling, storage,
84− SSO, redirects, route settings)
85−- **custom-field-types**, **testing** - Shared field-type definitions and test
86− helpers
87−- **_template** - Scaffold for new packages; excluded from the workspace
88− 
89−### e2e/ - End-to-end tests
90−- Playwright-based E2E tests with Docker container isolation
91−- See `e2e/CLAUDE.md` for detailed testing guidance
92− 
93−## Common Commands
94− 
95−### Development
5+### Unit Tests
966 ```bash
97−corepack enable pnpm # Enable corepack to use the correct pnpm version
98−pnpm run setup # First-time setup (installs deps + submodules + builds workspace packages)
99−pnpm dev # Start development (Docker backend + host frontend dev servers)
7+pnpm test:unit # Run unit tests once
8+pnpm test:unit:watch # Run unit tests in watch mode
1009 ```
10110  
102−> **Fresh worktree / first run — run `pnpm setup` before anything else.** It installs deps and syncs submodules. `pnpm fix` does a clean reinstall if anything misbehaves after a branch switch.
103− 
104−### Building
11+### Acceptance Tests (Playwright)
10512 ```bash
106−pnpm build # Build all packages (Nx handles dependencies)
107−pnpm build:clean # Clean build artifacts and rebuild
13+pnpm test:acceptance # Run Playwright tests (headless, list reporter)
14+pnpm test:acceptance:quiet # Minimal output, failures only
15+pnpm test:acceptance:headed # Run with browser UI visible
16+pnpm test:acceptance:report # Run with HTML report
17+pnpm test:slowmo # Slow motion + UI
10818 ```
10919  
110−### Testing
20+### All Tests
11121 ```bash
112−# Unit tests (from root)
113−pnpm test:unit # Run all unit tests in all packages
114−pnpm test:watch # Watch mode — unified Vitest watcher (ghost/core + all apps)
115− 
116−# Ghost core tests (from ghost/core/)
117−cd ghost/core
118−pnpm test:unit # Unit tests only (Vitest, run once)
119−pnpm test:watch # Watch mode — ghost/core unit tests only
120−pnpm test:integration # Integration tests
121−pnpm test:e2e # Server-side e2e suites (webhooks/server/frontend/api) — not browser
122−pnpm test:all # All test types
123− 
124−# These run on sqlite with no extra services. The Redis/MinIO/S3 adapter suites
125−# probe for their service and auto-skip when it's down (run `pnpm dev:storage`
126−# etc. to exercise them); they always run in CI, which starts the services.
127− 
128−# E2E browser tests (from root)
129−pnpm test:e2e # Run e2e/ Playwright tests
130− 
131−# Running a single test
132−cd ghost/core
133−pnpm test:single test/unit/path/to/test.test.js # routes test/unit/* → unit config, test/* → DB config
134− 
135−# Watch a single DB-backed file (integration/e2e) — the default test:watch only
136−# covers unit tests, so point it at the DB config explicitly:
137−pnpm exec vitest -c vitest.config.db.ts test/integration/path/to/test.test.js
138− 
139−# Ember Admin tests (from the repository root)
140−pnpm nx run ghost-admin:test
141− 
142−# Run one Ember Admin test file. Paths are relative to apps/ember-admin.
143−# The explicit `1` supplies the numeric value required by the test script's
144−# trailing `--parallel` option before additional Ember Exam arguments.
145−pnpm nx run ghost-admin:test -- 1 --file-path=tests/acceptance/editor/publish-flow-test.js
22+pnpm test # Run unit + acceptance tests, then lint
14623 ```
14724  
148−> **Always run Ember Admin tests through Nx.** Running `ember test` or
149−> `ember exam` directly from `apps/ember-admin` skips the dependency build
150−> graph and commonly fails in fresh worktrees with missing outputs such as
151−> `koenig-lexical.umd.js`, `@tryghost/admin-x-framework/hooks`, or
152−> `@tryghost/kg-converters`. For focused runs, use Ember Exam's `--file-path`
153−> as shown above rather than appending `--filter` to the package script.
25+## AI-Friendly Testing
15426  
155−### Linting
156−```bash
157−pnpm lint # Lint all packages
158−cd ghost/core && pnpm lint # Lint Ghost core (server, shared, frontend, tests)
159−cd apps/ember-admin && pnpm lint # Lint Ember admin
160−```
27+The test runner has been configured to work well with AI agents:
16128  
162−### Database
163−```bash
164−pnpm knex-migrator migrate # Run database migrations
165−pnpm reset:data # Reset database with test data (1000 members, 100 posts) (requires pnpm dev running)
166−pnpm reset:data:empty # Reset database with no data (requires pnpm dev running)
167−```
29+- **Default behavior**: Headless mode with list reporter (no browser UI, no web pages)
30+- **Quiet mode**: Use `pnpm test:acceptance:quiet` for minimal output (only shows failures)
31+- **Clean exit**: Tests complete without hanging processes or opening browsers
32+- **Clear output**: List reporter provides clear pass/fail information
16833  
169−### Docker
170−```bash
171−pnpm docker:build # Build Docker images
172−pnpm docker:clean # Stop containers, remove volumes and local images
173−pnpm docker:down # Stop containers
174−```
34+## Human-Friendly Testing
17535  
176−### How `pnpm dev` works
36+For debugging and development:
17737  
178−The `pnpm dev` command uses a **hybrid Docker + host development** setup:
38+- Use `pnpm test:acceptance:headed` to see the browser UI
39+- Use `pnpm test:acceptance:report` to generate an HTML report
40+- Use `pnpm test:slowmo` for slow-motion debugging
17941  
180−**What runs in Docker:**
181−- Ghost Core backend (with hot-reload via mounted source)
182−- MySQL, Redis, Mailpit
183−- Caddy gateway/reverse proxy
42+## Environment Variables
18443  
185−**What runs on host by default:**
186−- Admin, legacy Ember admin, Portal, and foundation library dev watchers
187−- Optional public UMD app watchers can be added when needed
44+- `PLAYWRIGHT_HEADED=true` - Show browser UI
45+- `PLAYWRIGHT_HTML_REPORT=true` - Generate HTML report
46+- `PLAYWRIGHT_SLOWMO=100` - Slow motion delay (ms)
18847  
189−**Setup:**
190−```bash
191−# Start Ghost backend, Admin, Portal, and Docker services
192−pnpm dev
48+## Test Structure
19349  
194−# Add optional public apps (comments-ui, sodo-search, signup-form, admin-toolbar)
195−pnpm dev:public
50+- `test/unit/` - Unit tests (Vitest)
51+- `test/e2e/` - Acceptance tests (Playwright, `test:acceptance` target)
52+- `test/utils/` - Shared test utilities
19653  
197−# Develop the Koenig editor against Ghost Admin (adds a koenig-lexical rebuild
198−# watcher + preview server; Admin loads the editor from your local build)
199−pnpm dev:lexical
54+## Development Workflow
20055  
201−# With optional services (uses Docker Compose file composition)
202−pnpm dev:analytics # Include Tinybird analytics
203−pnpm dev:storage # Include MinIO S3-compatible object storage
204−pnpm dev:stripe # Include Stripe webhook forwarding
205−pnpm dev:full # Include analytics, storage, Stripe, and public app watchers
206− 
207−# Everything available
208−pnpm dev:all #
209−```
210− 
211−**Accessing Services:**
212−- Ghost: `http://localhost:2368` (database: `ghost_dev`)
213−- Mailpit UI: `http://localhost:8025` (email testing)
214−- MySQL: `localhost:3306`
215−- Redis: `localhost:6379`
216−- Tinybird: `http://localhost:7181` (when analytics enabled)
217−- MinIO Console: `http://localhost:9001` (when storage enabled)
218−- MinIO S3 API: `http://localhost:9000` (when storage enabled)
219− 
220−## Architecture Patterns
221− 
222−### Admin Apps Integration (Micro-Frontend)
223− 
224−**Build Process:**
225−1. Admin-x React apps build to `apps/*/dist` using Vite
226−2. `apps/ember-admin/lib/asset-delivery` copies them to `ghost/core/core/built/admin/assets/*`
227−3. Ghost admin serves from `/ghost/assets/{app-name}/{app-name}.js`
228− 
229−**Runtime Loading:**
230−- Ember admin uses `AdminXComponent` to dynamically import React apps
231−- React components wrapped in Suspense with error boundaries
232−- Apps receive config via `additionalProps()` method
233− 
234−### Public Apps Integration
235− 
236−- Built as UMD bundles to `apps/*/umd/*.min.js`
237−- Loaded via `<script>` tags in theme templates (injected by `{{ghost_head}}`)
238−- Configuration passed via data attributes
239− 
240−### i18n Architecture
241− 
242−**Centralized Translations:**
243−- Single source: `packages/i18n/locales/{locale}/{namespace}.json`
244−- Namespaces: `ghost`, `portal`, `signup-form`, `comments`, `search`
245−- 60+ supported locales
246−- Context descriptions: `packages/i18n/locales/context.json` — every key must have a non-empty description
247− 
248−**Translation Workflow:**
249−```bash
250−pnpm --filter @tryghost/i18n translate # Extract keys from source, update all locale files + context.json
251−pnpm --filter @tryghost/i18n lint:translations # Validate interpolation variables across locales
252−```
253− 
254−`translate` is run as part of `pnpm --filter @tryghost/i18n test`. In CI, it fails if translation keys or `context.json` are out of date (`failOnUpdate: process.env.CI`). Always run `pnpm --filter @tryghost/i18n translate` after adding or changing `t()` calls.
255− 
256−**Rules for Translation Keys:**
257−1. **Never split sentences across multiple `t()` calls.** Translators cannot reorder words across separate keys. Instead, use `@doist/react-interpolate` to embed React elements (links, bold, etc.) within a single translatable string.
258−2. **Always provide context descriptions.** When adding a new key, add a description in `context.json` explaining where the string appears and what it does. CI will reject empty descriptions.
259−3. **Use interpolation for dynamic values.** Ghost uses `{variable}` syntax: `t('Welcome back, {name}!', {name: firstname})`
260−4. **Use `<tag>` syntax for inline elements.** Combined with `@doist/react-interpolate`: `t('Click <a>here</a> to retry')` with `mapping={{ a: <a href="..." /> }}`
261− 
262−**Correct pattern (using Interpolate):**
263−```jsx
264−import Interpolate from '@doist/react-interpolate';
265− 
266−<Interpolate
267− mapping={{ a: <a href={link} /> }}
268− string={t('Could not sign in. <a>Click here to retry</a>')}
269−/>
270−```
271− 
272−**Incorrect pattern (split sentences):**
273−```jsx
274−// BAD: translators cannot reorder "Click here to retry" relative to the first sentence
275−{t('Could not sign in.')} <a href={link}>{t('Click here to retry')}</a>
276−```
277− 
278−See `apps/portal/src/components/pages/email-receiving-faq.js` for a canonical example of correct `Interpolate` usage.
279− 
280−### Build Dependencies (Nx)
281− 
282−Critical build order (Nx handles automatically):
283−1. `shade` + `admin-x-design-system` build
284−2. `admin-x-framework` builds (depends on #1)
285−3. Admin apps build (depend on #2)
286−4. `apps/ember-admin` builds (depends on #3, copies via asset-delivery)
287−5. `ghost/core` serves admin build
288− 
289−## CSS Architecture
290− 
291−### TailwindCSS v4 Setup
292− 
293−Ghost Admin uses **TailwindCSS v4** via the `@tailwindcss/vite` plugin. CSS processing is centralized — only `apps/admin/vite.config.ts` loads the `@tailwindcss/vite` plugin. All embedded React apps (activitypub, admin-x-settings, admin-x-design-system) are scanned from this single entry point.
294− 
295−### Entry Point
296− 
297−`apps/admin/src/index.css` is the main CSS entry point. It contains:
298−- `@source` directives that scan class usage in shade, activitypub, admin-x-settings, admin-x-design-system, and kg-unsplash-selector
299−- `@import "@tryghost/shade/styles.css"` which loads the Shade design system styles
300− 
301−### Shade Styles
302− 
303−`apps/shade/styles.css` uses **unlayered** Tailwind imports:
304−```css
305−@import "tailwindcss/theme.css";
306−@import "./preflight.css";
307−@import "tailwindcss/utilities.css";
308−@import "tw-animate-css";
309−@import "./tailwind.theme.css";
310−```
311− 
312−**Why unlayered:** Ember's legacy CSS (`.flex`, `.hidden`, etc.) is unlayered. If Tailwind utilities were in a `@layer`, they would lose to Ember's unlayered CSS in the cascade. Keeping both unlayered means source order determines specificity.
313− 
314−Theme tokens/variants/animations are defined in CSS (`apps/shade/tailwind.theme.css` + runtime vars in `styles.css`), so there is no JS `@config` bridge in the Admin runtime lane. `tw-animate-css` is the v4 replacement for `tailwindcss-animate`.
315− 
316−### Critical Rule: Embedded Apps Must NOT Import Shade Independently
317− 
318−Apps consumed via `@source` (activitypub, admin-x-settings) must **NOT** import `@tryghost/shade/styles.css` in their own CSS. Doing so causes duplicate Tailwind utilities and cascade conflicts. All Tailwind CSS is generated once via the admin entry point.
319− 
320−### Public Apps
321− 
322−Public-facing apps (`comments-ui`, `signup-form`, `sodo-search`, `portal`, `announcement-bar`) remain on **TailwindCSS v3**. They are built as UMD bundles for CDN distribution and are independent of the admin CSS pipeline.
323− 
324−### Legacy Apps
325− 
326−`admin-x-design-system` and `admin-x-settings` are consumed via `@source` in admin's centralized v4 pipeline for production, and both packages build with CSS-first Tailwind v4 setup.
327− 
328−## Code Guidelines
329− 
330−### Commit Messages
331−When the user asks you to create a commit or draft a commit message, load and follow the `commit` skill from `.agents/skills/commit`.
332− 
333−### ESLint Config
334−Source of truth: two internal config packages — [`@internal/cfg-eslint`](configs/eslint/index.mjs) (shared rule atoms + the `nodeLibConfig` factory for Node libs) and [`@internal/cfg-eslint-react`](configs/eslint-react/index.mjs) (the `reactAppConfig` factory for every `apps/*` workspace). Both factories are synchronous and have full JSDoc with `@example`s; hover the call site in your editor. Consume them by name — declare the package as a `workspace:*` devDependency.
335− 
336−Minimal example for a new admin React app (`apps/new-feature/eslint.config.js`):
337− 
338−```js
339−import {reactAppConfig} from '@internal/cfg-eslint-react';
340−export default reactAppConfig({
341− tailwindCssPath: `${import.meta.dirname}/../admin/src/index.css`,
342− shadeRestricted: true
343−});
344−```
345− 
346−Conventions:
347−- **Rules are `'error'` or `'off'` — never `'warn'`.** Warnings get ignored and pollute output. Applies to every workspace covered by the factories above + the standalones; `e2e/` has its own setup (see [e2e/CLAUDE.md](e2e/CLAUDE.md)) and currently still uses warn-level Playwright rules — a separate cleanup.
348−- **Params prefixed `legacy*`** (`legacyTailwindV3ConfigPath`, `legacyJsTsSplit`) are escape hatches for migrations that haven't shipped yet. Intentional and visible — PRs to remove them are scoped.
349−- **Standalone configs** (`ghost/core`, `apps/ember-admin`, `apps/admin-toolbar`) exist because their rule sets genuinely don't fit a factory — read the file directly. They import shared atoms (`correctnessRules`, `nodeLibRules`, `localFilenamesPlugin`, `strictLinterOptions`) from `@internal/cfg-eslint`.
350−- **Plugin deps**: a workspace must declare every eslint plugin its config resolves. Two cases:
351− - *Factory consumers* only import a factory, which supplies its plugins as objects from the config package — so they need just the config package (`@internal/cfg-eslint` / `@internal/cfg-eslint-react`) as a `workspace:*` devDependency, not the individual plugins.
352− - *Hand-rolled configs* (the standalones above, plus the inline configs in `koenig/kg-*` and `e2e/`) `import` plugins directly, so each must list those plugins in its own `devDependencies` — most commonly `eslint-plugin-ghost: catalog:`. Don't rely on the root hoisting a plugin for you; there are no eslint plugins left in the root `package.json` (only `eslint` itself and `globals`, which the root config uses).
353− - Exception: Tailwind — a workspace that uses it must list `tailwindcss` as its own (dev)Dependency regardless (the settings-based resolver requires it locally), and the legacy v3 apps pin `eslint-plugin-tailwindcss` via `catalog:tailwind3`.
354− 
355−### When Working on Admin UI
356−- **New features:** Build in React in `apps/admin` (domain folders under `src/`)
357−- **Use:** `admin-x-framework` for API hooks (`useBrowse`, `useEdit`, etc.)
358−- **Use:** `shade` design system for new components (not admin-x-design-system)
359−- **Translations:** Add to `packages/i18n/locales/en/ghost.json`
360− 
361−### When Working on Public UI
362−- **Edit:** `apps/portal`, `apps/comments-ui`, etc.
363−- **Translations:** Separate namespaces (`portal.json`, `comments.json`)
364−- **Build:** UMD bundles for CDN distribution
365− 
366−### When Working on Backend
367−- **Core logic:** `ghost/core/core/server/`
368−- **Database Schema:** `ghost/core/core/server/data/schema/`
369−- **API routes:** `ghost/core/core/server/api/`
370−- **Services:** `ghost/core/core/server/services/`
371−- **Models:** `ghost/core/core/server/models/`
372−- **Frontend & theme rendering:** `ghost/core/core/frontend/`
373− 
374−### Design System Usage
375−- **New components:** Use `shade` (shadcn/ui-inspired)
376−- **Legacy:** `admin-x-design-system` (being phased out, avoid for new work)
377− 
378−### Analytics (Tinybird)
379−- **Local development:** `pnpm dev:analytics` (starts Tinybird + MySQL)
380−- **Config:** Add Tinybird config to `ghost/core/config.development.json`
381−- **Scripts:** `ghost/core/core/server/data/tinybird/scripts/`
382−- **Datafiles:** `ghost/core/core/server/data/tinybird/`
383− 
384−## Troubleshooting
385− 
386−### Build Issues
387−```bash
388−pnpm fix # Clean cache + node_modules + reinstall
389−pnpm build:clean # Clean build artifacts
390−pnpm nx reset # Reset Nx cache
391−```
392− 
393−### Test Issues
394−- **E2E failures:** Check `e2e/CLAUDE.md` for debugging tips
395−- **Docker issues:** `pnpm docker:clean && pnpm docker:build`
56+1. Run unit tests during development: `pnpm test:unit:watch`
57+2. Run acceptance tests before committing: `pnpm test:acceptance`
58+3. Use headed mode for debugging: `pnpm test:acceptance:headed`
39659  
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack