diff --git a/AGENTS.md b/AGENTS.md index 4ee650360a1..42b6f935639 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -174,6 +174,7 @@ Conventions: - **Use:** `admin-x-framework` for API hooks (`useBrowse`, `useEdit`, etc.) - **Use:** `shade` design system for new components (not admin-x-design-system) - **Translations:** Add to `packages/i18n/locales/en/ghost.json` +- **Deploy skew:** Ghost Admin and Ghost core deploy independently. New admin UI that depends on new settings, endpoints, or config must feature-detect backend support (e.g. settings-key presence in the browse response, as in `social-accounts.tsx`) and hide or no-op when absent. Labs flags alone are not a deploy-skew guard. Add an acceptance test for the “backend not deployed yet” case. ### When Working on Public UI - **Edit:** `apps/portal`, `apps/comments-ui`, etc. @@ -187,6 +188,8 @@ Conventions: - **Services:** `ghost/core/core/server/services/` - **Models:** `ghost/core/core/server/models/` - **Frontend & theme rendering:** `ghost/core/core/frontend/` +- **TypeScript by default:** New code under `ghost/core/core/server/services/` is TypeScript unless extending an existing JS module. Follow the gifts/donations pattern: domain logic as `.ts` with named exports; thin CJS `index.js` / `*-wrapper.js` only where boot/`require` still needs them. +- **Service init:** New services get an explicit `init()` call from `ghost/core/core/boot.js` (same Promise.all as donations/gifts). Keep the wrapper’s `init()` idempotent so early callers are safe, but boot owns construction — not first request. ### Design System Usage - **New components:** Use `shade` (shadcn/ui-inspired) diff --git a/apps/shade/src/components/ui/card.tsx b/apps/shade/src/components/ui/card.tsx index 06c90daec1a..5f9e051cd1e 100644 --- a/apps/shade/src/components/ui/card.tsx +++ b/apps/shade/src/components/ui/card.tsx @@ -14,7 +14,7 @@ const cardVariants = cva( { variants: { variant: { - outline: 'rounded-xl border transition-all hover:shadow-xs', + outline: 'rounded-xl border border-border-default transition-all hover:shadow-xs', plain: '' } }, @@ -44,7 +44,7 @@ const cardHeaderVariants = cva( variants: { variant: { outline: 'p-6', - plain: 'border-b py-5' + plain: 'border-b border-border-default py-5' } }, defaultVariants: { @@ -98,7 +98,7 @@ const cardContentVariants = cva( variants: { variant: { outline: 'p-6 pt-0', - plain: 'border-b' + plain: 'border-b border-border-default' } }, defaultVariants: { @@ -159,7 +159,7 @@ const EmptyCard = React.forwardRef< >(({className, ...props}, ref) => (
)); diff --git a/apps/shade/test/unit/components/ui/card.test.tsx b/apps/shade/test/unit/components/ui/card.test.tsx index 9079efeb274..096928a1545 100644 --- a/apps/shade/test/unit/components/ui/card.test.tsx +++ b/apps/shade/test/unit/components/ui/card.test.tsx @@ -7,7 +7,8 @@ import { CardFooter, CardTitle, CardDescription, - CardContent + CardContent, + EmptyCard } from '../../../../src/components/ui/card'; import {render} from '../../utils/test-utils'; @@ -18,7 +19,7 @@ describe('Card Components', () => { assert.ok(card, 'Card should be rendered'); assert.equal(card.textContent, 'Card Content', 'Card should render its content'); - assert.ok(card.className.includes('rounded-xl border'), 'Should have outline variant styling'); + assert.ok(card.className.includes('rounded-xl border border-border-default'), 'Should have outline variant styling'); }); it('renders Card with plain variant', () => { @@ -36,6 +37,13 @@ describe('Card Components', () => { assert.ok(card.className.includes('custom-card-class'), 'Should have custom class'); }); + it('renders EmptyCard with the default container border', () => { + render(Empty Card); + + const card = screen.getByTestId('empty-card'); + assert.ok(card.className.includes('border border-border-default'), 'Should use the default container border'); + }); + it('renders CardHeader with correct styling based on Card variant', () => { render( @@ -57,7 +65,7 @@ describe('Card Components', () => { ); const header = screen.getByTestId('card-header'); - assert.ok(header.className.includes('border-b py-5'), 'Should have plain variant styling'); + assert.ok(header.className.includes('border-b border-border-default py-5'), 'Should have plain variant styling'); }); it('renders CardTitle with correct styling', () => { @@ -99,7 +107,7 @@ describe('Card Components', () => { ); const content = screen.getByTestId('card-content'); - assert.ok(content.className.includes('border-b'), 'Should have plain variant styling'); + assert.ok(content.className.includes('border-b border-border-default'), 'Should have plain variant styling'); }); it('renders CardFooter with correct styling based on Card variant', () => { @@ -145,4 +153,4 @@ describe('Card Components', () => { assert.ok(screen.getByTestId('card-content'), 'CardContent should be rendered'); assert.ok(screen.getByTestId('card-footer'), 'CardFooter should be rendered'); }); -}); \ No newline at end of file +}); diff --git a/apps/shade/theme-variables.css b/apps/shade/theme-variables.css index b1ceabd79c7..0321ca9046b 100644 --- a/apps/shade/theme-variables.css +++ b/apps/shade/theme-variables.css @@ -39,7 +39,7 @@ --text-tertiary: var(--color-gray-400); --text-inverse: var(--color-white); --border-subtle: var(--color-gray-200); - --border-default: var(--color-gray-200); + --border-default: color-mix(in oklab, var(--color-gray-200), var(--color-gray-300)); --border-strong: var(--color-gray-500); --focus-ring: var(--color-gray-600); --state-info: var(--color-blue-500); @@ -83,7 +83,7 @@ --control-surface: var(--surface-elevated); --control-readonly-surface: var(--color-gray-50); --control-disabled-surface: var(--color-gray-100); - --control-border: var(--color-gray-200); + --control-border: color-mix(in oklab, var(--color-gray-200), var(--color-gray-300)); --table-row-hover: var(--color-gray-50); --members-sticky-hover-bg: var(--table-row-hover); --mobile-navbar-height: 64px;