페이지 블록: 접근성 갖춘 15개 섹션(히어로, 가격, JSON-LD FAQ 등), 관리자 편집기, 확장 가능한 등록
설치
genpm add @core/blocks포함 내용
- src/lib/blocks/에 소스 코드, 파일 25개. (52.7kB)
- src/lib/blocks/AGENTS.md에 AI 규칙, 그리고 IDE 규칙 파일.
- @core/admin, @core/contracts, @core/db, @core/media, @core/rich-text, @core/ui을(를) 자동으로 해결합니다.
README
이 패키지에는 README가 없습니다.
이것이 AI가 src/lib/blocks에서 작업할 때 읽는 내용 그대로입니다. 그 외에는 컨텍스트에 아무것도 추가되지 않습니다.
@core/blocks — rules for AI agents
Purpose
Page sections that editors combine from the admin: hero, features, benefits, testimonials, logos, pricing, FAQ (with FAQPage JSON-LD), call to action, gallery, video, stats, comparison, guarantee, rich text and form. Each block is a zod schema + editor fields + a React component. Other packages and kits add their own blocks (product grid, latest posts…) to the same registry. Blocks are stored as JSON inside @core/content entries; no tables of its own.
Map
index.ts—defineBlock,createBlockRegistry,coreBlocks(and each block),RenderBlocks,collectMediaIds.core/*.ts— one file per block.parts.ts—Section,Heading,Img,CtaLink,JsonLdfor new blocks.media.ts—loadBlockMedia(blocks, registry)(server: one query to @core/media).editor.ts—'use client':blocksRenderer(blockMeta(registry))forAdminApp.blocks.css— styles on @core/ui tokens.
Integration
- Create
src/genpm/blocks.ts:export const blocks = createBlockRegistry([...coreBlocks /*, kit blocks */]); - Add a field to a content collection: schema
blocks: blocks.schema, admin fieldblocks.field('blocks', 'Sections'). - Admin: compute
blockMeta(blocks)on the server and pass it to the client component that renders<AdminApp renderers={{ blocks: blocksRenderer(meta), /* richText, media… */ }} />(never import the registry in client code). - Page (server component):
const media = await loadBlockMedia(page.data.blocks, blocks); return <RenderBlocks blocks={page.data.blocks} registry={blocks} media={media} renderForm={(key) => <Form formKey={key} />} embeds={hasConsent('marketing') ? 'iframe' : 'link'} />; - Styles: import
src/lib/ui/ui.cssthensrc/lib/blocks/blocks.cssin the root layout. - Verify: a page with every block passes axe and has one
h1; a block with an unknown type renders nothing and warns in dev.
Conventions
- A new block = new file with
defineBlock+ entry in the registry. Never addif (type === …)toRenderBlocks. - Use
SectionandHeadingwithctx.level(items one level below); only blocks withpageTitlemay become theh1. - Images are media ids resolved with
loadBlockMedia; links go through thehrefschema (nojavascript:). - Text in blocks is plain text; use the
rich-textblock for formatting.
Don't
- Don't render user HTML (
dangerouslySetInnerHTML) except viaJsonLd, which escapes<,>and&. - Don't load video iframes without marketing consent (
embeds: 'link'is the default). - Don't invent testimonials, ratings or statistics; the editor help texts remind users these must be real and verifiable.
# @core/blocks — rules for AI agents
## Purpose
Page sections that editors combine from the admin: hero, features, benefits, testimonials, logos, pricing, FAQ (with
FAQPage JSON-LD), call to action, gallery, video, stats, comparison, guarantee, rich text and form. Each block is a
zod schema + editor fields + a React component. Other packages and kits add their own blocks (product grid, latest
posts…) to the same registry. Blocks are stored as JSON inside @core/content entries; no tables of its own.
## Map
- `index.ts` — `defineBlock`, `createBlockRegistry`, `coreBlocks` (and each block), `RenderBlocks`, `collectMediaIds`.
- `core/*.ts` — one file per block. `parts.ts` — `Section`, `Heading`, `Img`, `CtaLink`, `JsonLd` for new blocks.
- `media.ts` — `loadBlockMedia(blocks, registry)` (server: one query to @core/media).
- `editor.ts` — `'use client'`: `blocksRenderer(blockMeta(registry))` for `AdminApp`. `blocks.css` — styles on @core/ui tokens.
## Integration
1. Create `src/genpm/blocks.ts`: `export const blocks = createBlockRegistry([...coreBlocks /*, kit blocks */]);`
2. Add a field to a content collection: schema `blocks: blocks.schema`, admin field `blocks.field('blocks', 'Sections')`.
3. Admin: compute `blockMeta(blocks)` on the server and pass it to the client component that renders
`<AdminApp renderers={{ blocks: blocksRenderer(meta), /* richText, media… */ }} />` (never import the registry in client code).
4. Page (server component):
```tsx
const media = await loadBlockMedia(page.data.blocks, blocks);
return <RenderBlocks blocks={page.data.blocks} registry={blocks} media={media}
renderForm={(key) => <Form formKey={key} />} embeds={hasConsent('marketing') ? 'iframe' : 'link'} />;
```
5. Styles: import `src/lib/ui/ui.css` then `src/lib/blocks/blocks.css` in the root layout.
6. Verify: a page with every block passes axe and has one `h1`; a block with an unknown type renders nothing and warns in dev.
## Conventions
- A new block = new file with `defineBlock` + entry in the registry. Never add `if (type === …)` to `RenderBlocks`.
- Use `Section` and `Heading` with `ctx.level` (items one level below); only blocks with `pageTitle` may become the `h1`.
- Images are media ids resolved with `loadBlockMedia`; links go through the `href` schema (no `javascript:`).
- Text in blocks is plain text; use the `rich-text` block for formatting.
## Don't
- Don't render user HTML (`dangerouslySetInnerHTML`) except via `JsonLd`, which escapes `<`, `>` and `&`.
- Don't load video iframes without marketing consent (`embeds: 'link'` is the default).
- Don't invent testimonials, ratings or statistics; the editor help texts remind users these must be real and verifiable.
.genpmignore 적용 후 주입될 정확한 트리입니다. 고정 대상:
// Render de una lista de bloques. Sin condicionales por tipo: cada bloque se pinta con el componente de su definición.
import { createElement as h, Fragment, type ReactNode } from 'react';
import type { ResolvedImage } from '../rich-text/index.ts';
import type { BlockContext, BlockInstance, BlockRegistry } from './define.ts';
const dev = () => typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production';
export type RenderBlocksProps = {
blocks: BlockInstance[] | null | undefined;
registry: BlockRegistry;
/** Imágenes de @core/media ya resueltas (`await loadBlockMedia(blocks, registry)`). */
media?: Record<string, ResolvedImage>;
/** `true` si la página ya tiene su `h1` fuera de los bloques: ningún bloque usará `h1`. */
hasPageTitle?: boolean;
/** Nivel de encabezado de los bloques (2 por defecto). */
level?: 2 | 3 | 4;
embeds?: 'link' | 'iframe';
siteHost?: string;
renderForm?: (formKey: string) => ReactNode;
/** Aviso para bloques desconocidos o inválidos (por defecto `console.warn` solo en desarrollo). */
onSkip?: (block: BlockInstance, reason: 'unknown' | 'invalid') => void;
};
/** `<RenderBlocks blocks={page.data.blocks} registry={blocks} media={media} />` */
export function RenderBlocks(p: RenderBlocksProps): ReactNode {
const image = (id: unknown): ResolvedImage | null => (typeof id === 'string' && p.media?.[id]) || null;
const skip = p.onSkip ?? ((b, reason) => dev() && console.warn(`[blocks] skipped ${reason} block "${b.type}" (${b.id})`));
let titleUsed = !!p.hasPageTitle;
let first = true;
const out: ReactNode[] = [];
for (const b of p.blocks ?? []) {
if (b.hidden) continue;
const def = p.registry.get(b.type);
if (!def) {
skip(b, 'unknown');
continue;
}
const parsed = def.schema.safeParse(b.data);
if (!parsed.success) {
skip(b, 'invalid');
continue;
}
const isTitle = !titleUsed && !!def.pageTitle?.(parsed.data);
if (isTitle) titleUsed = true;
const ctx: BlockContext = {
id: `b-${b.id.replace(/[^\w-]/g, '')}`,
level: isTitle ? 1 : (p.level ?? 2),
image,
embeds: p.embeds ?? 'link',
first,
...(p.siteHost && { siteHost: p.siteHost }),
...(p.renderForm && { renderForm: p.renderForm }),
};
first = false;
out.push(h(Fragment, { key: b.id }, h(def.Component, { data: parsed.data, ctx })));
}
return h(Fragment, null, ...out);
}
이 패키지는 MCP 서버를 선언하지 않습니다.
| 버전 | 커밋 | 게시일 | 검사 |
|---|---|---|---|
| 1.0.0 | a255d6d | 4시간 전 | 검사 통과 |
- genpm
- @core/admin ^1.0.0@core/contracts ^1.0.0@core/db ^1.0.0@core/media ^1.0.0@core/rich-text ^1.0.0@core/ui ^1.0.0
- 제안됨
- GenPM은 npm 명령을 제안하고, 동의한 경우에만 실행합니다.
- 사용하는 패키지 (1)
- @core/kit-cms ^1.0.0
- 검사
- 검사 통과 · 문제 0건
- 커밋
- v1.0.0 → a255d6d08201d6342d07ad41d96123ea1407a5f1 · 가져온 뒤 검증됨
- 스크립트
- 없음. GenPM은 패키지 코드를 절대 실행하지 않습니다.
- 라이선스
- MIT
- 품질
- 100/100
- 인정된 라이선스충족
- AGENTS.md에 목적 설명충족
- AGENTS.md에 통합 단계충족
- AGENTS.md에 규칙 또는 금지 사항충족
- 테스트 포함충족
- 보안 검사 통과충족
- 최근 6개월 내 게시충족
- 인증된 게시자충족
- 요약과 키워드충족
- 신고
- 문제가 있나요?