# FourFoldLabs Crucible — Full AI Reference > Complete reference for all @fourfoldlabs packages, blocks, and Claude Code plugins. > Generated by `bun run docs` — do not edit manually. --- ## Block Catalog Summary 43 blocks across 8 categories. ### KPIs & Metrics > Single-value metric displays — numbers, trends, and comparisons. | Block | Tier | Since | Description | |-------|------|-------|-------------| | KpiCard | display | 0.2.0 | KPI metric card with default, simple, icon, comparison, progress, and with-link variants. | ### Charts > Visualizations powered by Recharts — line, bar, area, donut, and more. | Block | Tier | Since | Description | |-------|------|-------|-------------| | BarChartBlock | interactive | 0.1.0 | Bar chart with default, stacked, horizontal, grouped, and comparison toggle variants. | | AreaChartBlock | interactive | 0.1.0 | Area chart with gradient fill, stacked, step, and comparison variants with change badge. | | LineChartBlock | interactive | 0.3.0 | Line chart with default, dotted, step, and comparison variants with change badge and summary stats. | | DonutChartBlock | interactive | 0.3.0 | Donut/pie chart with center label, legend, tooltip, and active segment selection. | | SparklineBlock | display | 0.2.0 | Compact area sparkline with optional vertical time-boundary and horizontal goal reference lines. | | HealthMeter | display | 0.1.0 | Health/status monitor with default (progress bars), tracker (uptime segment bars), status-list (platform status page), and compact (summary indicators) variants. | ### Lists & Views > Tables, feeds, grids, timelines, calendars, and collection views. | Block | Tier | Since | Description | |-------|------|-------|-------------| | DataTable | display | 0.1.0 | Typed table with column definitions and optional per-row chat context. | | DataTableFull | interactive | 0.3.0 | Full-featured data table with sorting, pagination, row selection, bulk actions, row action menus, and sort style variants. | | ActivityFeed | display | 0.1.0 | Timestamped action feed with avatars. | | CommentBlock | controlled | 0.4.0 | Comment thread with thread, activity (timeline + system events), and compact (bubble) variants. Input, reactions, privacy labels. | | Timeline | display | 0.3.0 | Vertical timeline of events with timestamps, icons, and connecting lines. | | GridList | display | 0.3.0 | Responsive card grid with header, count badge, action slot, and full-card link support. Member, integration, workspace, and report card variants. | | CalendarView | interactive | 0.3.0 | Month and week calendar views with colored event chips, hourly time grid, and current-time indicator. | | KanbanBoard | interactive | 0.3.0 | Kanban board with columns, card slots, column limits, and move-between-columns via action menus. | | ProjectTimeline | controlled | 0.4.0 | Gantt-style horizontal week view with status badges, end-date resize, expandable detail panels, row grouping, and day/hour unit modes. Extended variant adds MS Project-style 6-week table+Gantt with collapsible groups. | ### Page Shells > App shells and page-level structural blocks — sidebars, top navs, headers. | Block | Tier | Since | Description | |-------|------|-------|-------------| | SidebarShell | interactive | 0.3.0 | Responsive sidebar app shell with collapsible nav groups, badges, tooltips, mobile drawer, and content area. | | TopNavShell | interactive | 0.3.0 | Responsive top navigation shell with horizontal links, active indicators, mobile drawer, and content area. | | SiteHeader | interactive | 0.22.0 | Marketing / website header with two rows (utility strip + main row), flat nav, optional CTAs, announcement banner slot, sticky-with-collapse-on-scroll behavior, and full-screen mobile drawer. For public-facing sites (schools, businesses), not app shells. | | PageHeader | display | 0.3.0 | Page-level heading with title, breadcrumbs, description, and action slot. | | SettingsSection | display | 0.3.0 | Labeled settings group with title, description, and content slot. | | CampaignHero | display | 0.8.0 | Full-width campaign hero with centered, split, overlay, and minimal layout variants. Supports background image/video, headline, subheadline, and dual CTAs. | | BlockPreview | interactive | 0.3.0 | Showcase container with per-variant title, description, and inline theme switcher for isolated previews. | ### Feedback > Dialogs, callouts, empty states, and guided flows. | Block | Tier | Since | Description | |-------|------|-------|-------------| | ConfirmDialog | controlled | 0.3.0 | Confirmation dialog with loading state, async confirm, and optional typed confirmation input. | | CommandPalette | controlled | 0.3.0 | Full command palette modal with grouped commands, shortcuts, and recent items. | | EmptyState | display | 0.3.0 | Zero-data placeholder with icon, heading, description, and optional CTA. | | AnnouncementBar | display | 0.7.0 | Full-width severity-colored announcement bar with optional icon, description, CTA link, and dismiss button. | | InsightCard | display | 0.1.0 | Highlighted callout with icon and type-based styling. | | OnboardingChecklist | controlled | 0.3.0 | Step-by-step checklist with progress bar and per-step CTAs. | ### Indicators > Inline status badges, trend arrows, progress markers, and tags. | Block | Tier | Since | Description | |-------|------|-------|-------------| | StatusBadge | display | 0.3.0 | Semantic colored badge with dot/outline variants and optional icon indicator. | | DeltaBadge | display | 0.3.0 | Trend indicator badge with directional arrow, auto-colored by sign. Filled, outline, and ghost variants. | | TagBadge | controlled | 0.3.0 | Removable filter tag with close button. Outline and filled variants. | | MiniBar | display | 0.1.0 | Inline progress bar for table cells or compact displays. | | ProgressSteps | display | 0.3.0 | Horizontal or vertical step indicator for multi-step processes. | ### Inputs & Controls > File uploads, filter bars, and action grids. | Block | Tier | Since | Description | |-------|------|-------|-------------| | FileUploadZone | controlled | 0.3.0 | Drag-and-drop file upload zone with progress indicators and file list. | | FilterBar | controlled | 0.3.0 | Filter toolbar with default, condensed, with-actions, stacked, and period-selector variants. | | QuickActions | controlled | 0.1.0 | Icon button grid for common tasks. | ### Content > CMS content primitives — cards, images, and quotes. Editable wrappers live in @fourfoldlabs/cms-ui. | Block | Tier | Since | Description | |-------|------|-------|-------------| | ContentCard | display | 0.21.0 | Content card with optional title, body, image, href, and pill-shaped stripe accent. Renders as a link when href is set. | | ContentCardArray | display | 0.21.0 | Ordered collection of ContentCards with grid, scroll-band, or chip-row layout. Bounded count driven by caller. | | ContentImage | display | 0.21.0 | Single image with optional caption and aspect ratio. Consumer passes the resolved URL — no media library coupling. | | ContentQuote | display | 0.21.0 | Callout quote with pull, testimonial, and scripture variants. Scripture variant uses serif italic with left accent bar. | | ProfileCard | display | 0.21.0 | Person card with a full-width photo at the top, name, optional role eyebrow, bio, stats row, and primary action. Initials fallback when no photo is provided. | | ProfileCardArray | display | 0.21.0 | Grid of ProfileCards. Pass-through defaults for imageAspect, stripe, and variant to per-item values. | --- ## Plugin Groupings ### fourfoldlabs-ai-layer > Packages: `@fourfoldlabs/ai`, `@fourfoldlabs/chat`, `@fourfoldlabs/mcp` ### fourfoldlabs-auth-access > Packages: `@fourfoldlabs/iam` ### fourfoldlabs-content-comms > Packages: `@fourfoldlabs/audit`, `@fourfoldlabs/cms`, `@fourfoldlabs/email`, `@fourfoldlabs/notifications` ### fourfoldlabs-features > Packages: `@fourfoldlabs/forms`, `@fourfoldlabs/integrations`, `@fourfoldlabs/webhooks`, `@fourfoldlabs/workflows` ### fourfoldlabs-foundation > Packages: `@fourfoldlabs/api-core`, `@fourfoldlabs/auth`, `@fourfoldlabs/blocks`, `@fourfoldlabs/shared`, `@fourfoldlabs/ui` ### fourfoldlabs-infra > Packages: `@fourfoldlabs/events`, `@fourfoldlabs/export`, `@fourfoldlabs/jobs` --- ## Packages ## @fourfoldlabs/ai **Description:** Anthropic client wrapper, streaming chat, context registry, insight cache **Version:** `0.22.2` **Depends on:** `@fourfoldlabs/shared` ### Consumer Guide (fourfoldlabs-ai-layer) --- name: fourfoldlabs-ai description: > Use when building AI features or streaming chat — createAnthropicClient config injection, streamChatResponse async generator, generateInsight, createInsightCache (Redis), buildSystemPrompt, processFileAttachments, chatRequestSchema. --- # @fourfoldlabs/ai ## Purpose Config-injected Anthropic client wrapper for the FourFoldLabs stack. Provides streaming chat via async generator, single-shot insight generation, Redis-backed insight caching with graceful degradation, a domain context handler registry, file attachment processing, and system prompt construction. Never reads `process.env` directly — all configuration is passed at construction time. ## Exports | Export | Signature | Purpose | |--------|-----------|---------| | `createAnthropicClient` | `(config?: AnthropicClientConfig) => AnthropicClient` | Create configured Anthropic client | | `generateInsight` | `(client, systemPrompt, userPrompt) => Promise` | Single-shot non-streaming completion | | `streamChatResponse` | `(client, request: ChatRequest) => AsyncGenerator` | Streaming chat via async generator | | `createContextHandlerRegistry` | `() => ContextHandlerRegistry` | Factory for domain context handlers | | `createInsightCache` | `(options: InsightCacheOptions) => { get, set }` | Redis-backed insight cache with fallback | | `computeCacheKey` | `(context, prompt) => string` | SHA-256 cache key from context + prompt | | `buildSystemPrompt` | `(basePrompt, context?: ContextAttachment[]) => string` | Append picked dashboard context to base prompt | | `processFileAttachments` | `(files: FileInput[]) => ContentBlock[]` | Convert data-URL file attachments to Anthropic content blocks | | `chatRequestSchema` | `ZodObject` | Zod schema for validating incoming chat API requests | | `fileSchema` | `ZodObject` | Zod schema for individual file attachments (max 10 MB, data URL) | ## Patterns ### createAnthropicClient Config injection pattern — never reads `process.env` directly. The `ANTHROPIC_API_KEY` env var is consumed by the underlying `@anthropic-ai/sdk` `Anthropic()` constructor, which the host app is responsible for setting. ```ts interface AnthropicClientConfig { model?: string // default: 'claude-sonnet-4-6' insightMaxTokens?: number // default: 300 (used by generateInsight) chatMaxTokens?: number // default: 2048 (used by streamChatResponse) } ``` Create once at app startup and pass via DI: ```ts const ai = createAnthropicClient({ model: 'claude-opus-4-5', chatMaxTokens: 4096 }) ``` `AnthropicClient` exposes `{ sdk, model, insightMaxTokens, chatMaxTokens }`. Never import `AnthropicClient` from the SDK directly in app code — always go through this factory. ### Context Handler Registry Domain teams register handlers that fetch entity-specific context strings. The registry is a plain record; handlers are async functions `(entityId: string) => Promise`. ```ts const registry = createContextHandlerRegistry() // Register once at startup registry.register('project', async (id) => { const project = await db.query.projects.findFirst({ where: eq(projects.id, id) }) return JSON.stringify(project) }) // Resolve at request time const handler = registry.get(contextType) // ContextHandler | undefined if (handler) { const contextText = await handler(entityId) } ``` `ContextHandlerRegistry` type is `ReturnType` — use it for type annotations in service layers. ### Streaming `streamChatResponse` is an async generator that yields text delta strings as they arrive from the Anthropic stream. Wire it directly to an SSE or chunked HTTP response. ```ts const request: ChatRequest = { systemPrompt: buildSystemPrompt(BASE_PROMPT, attachedContext), messages: [...history, { role: 'user', content: userMessage }], } for await (const chunk of streamChatResponse(ai, request)) { res.write(`data: ${JSON.stringify({ text: chunk })}\n\n`) } res.write('data: [DONE]\n\n') ``` `ChatRequest` uses `Anthropic.Messages.MessageParam[]` for `messages` — import the type from `@anthropic-ai/sdk` when building history. ### Insight Cache Redis-backed with graceful degradation — if `client` is `null` or any Redis operation throws, the cache silently returns `null` / skips the write. TTL defaults to 30 minutes. ```ts const cache = createInsightCache({ client: redis, // ioredis instance, or null to disable logger, // optional — must have .warn(obj, msg) ttlSeconds: 1800, // optional, default 30 min prefix: 'insight', // optional key prefix }) const key = computeCacheKey(contextText, userPrompt) const cached = await cache.get(key) if (cached) return cached const result = await generateInsight(ai, systemPrompt, userPrompt) await cache.set(key, result) ``` ### File Processor Converts `FileInput[]` (data-URL objects from the client) into Anthropic `ContentBlockParam[]`. Supported types: PNG, JPEG, GIF, WEBP (image blocks), PDF (document block), anything else (text block with filename header). File attachments are validated upstream by `fileSchema` (max 10 MB per file, max 5 files per request via `chatRequestSchema`). ### System Prompt Builder `buildSystemPrompt(basePrompt, context?)` appends a formatted `## Attached Context` section when the user has pinned dashboard items to the chat. Each `ContextAttachment` provides `{ type, label, content }`. Pass the validated `context` array from `ChatRequestInput` directly. ### Chat Request Schema `chatRequestSchema` validates the API body for chat endpoints: ```ts // Inferred shape { entityId?: string message: string history: { role: 'user' | 'assistant', content: string }[] context?: { type: string, label: string, content: string }[] files?: FileInput[] // max 5 } ``` Parse with `.parse()` or `.safeParse()` in the route handler before calling any AI functions. ### Typical Setup ```ts // apps/api/src/lib/ai.ts import { createAnthropicClient, createContextHandlerRegistry, createInsightCache } from '@fourfoldlabs/ai' import { redis } from './redis.js' import { logger } from './logger.js' export const ai = createAnthropicClient() export const registry = createContextHandlerRegistry() export const insightCache = createInsightCache({ client: redis, logger }) // Register domain handlers in feature modules, not here ``` ## Constraints ### Do Not - Don't read `process.env.ANTHROPIC_API_KEY` in app code — the SDK reads it automatically; configure model/tokens via `AnthropicClientConfig` - Don't instantiate `new Anthropic()` directly — always use `createAnthropicClient` so token limits are consistent - Don't await the full stream before sending — pipe `streamChatResponse` directly to SSE/chunked response - Don't skip `chatRequestSchema.parse()` — the file size and count limits exist to prevent abuse - Don't pass `undefined` as the cache `client` — use `null` explicitly to disable caching ### Builder Spec --- name: fourfoldlabs-ai-spec description: > Builder spec for @fourfoldlabs/ai — internal structure, tests, modification guidelines. For package maintenance in Crucible. --- # @fourfoldlabs/ai — Builder Spec ## Source Structure `packages/ai/src/` contains: - `anthropic-client.ts` — `createAnthropicClient` factory and `AnthropicClientConfig` type - `context-handler-registry.ts` — `createContextHandlerRegistry` and `ContextHandlerRegistry` type - `insight-cache.ts` — `createInsightCache`, `computeCacheKey` - `generate-insight.ts` — `generateInsight` (single-shot) - `stream-chat.ts` — `streamChatResponse` async generator - `system-prompt.ts` — `buildSystemPrompt` - `file-processor.ts` — `processFileAttachments` - `schemas.ts` — `chatRequestSchema`, `fileSchema` - `index.ts` — barrel export ## Modification Guidelines - All new functions must accept config/dependencies as parameters — never read `process.env` inside this package - New AI capabilities (embeddings, tool-use, etc.) go in their own source file and export from `index.ts` - `chatRequestSchema` is the shared contract between the API route and the UI — if the shape changes, coordinate with `@fourfoldlabs/chat` (the client sender) and the consuming route handler - Cache TTL and key prefix are caller-supplied; do not bake defaults deeper than the function signature default values - Avoid importing anything from other `@fourfoldlabs/` packages — this package sits at the base of the AI layer ## Builder Constraints - Don't import `process.env` anywhere in this package - Don't add domain-specific context handlers here — those are registered by the host app at startup - Don't couple this package to any CMS, IAM, or other domain packages - Don't change the `ContentBlock` return type of `processFileAttachments` without updating `@fourfoldlabs/chat` ## Tests - `file-processor.test.ts` — MIME dispatch table: image/PDF/text classification, data URL stripping, mixed file ordering - `system-prompt.test.ts` — prompt composition: base passthrough, context attachment formatting, numbering ## See Also Consumer guide: `/fourfoldlabs-ai-layer:fourfoldlabs-ai` --- ## @fourfoldlabs/api-core **Description:** Hono middleware factories, response helpers, logger, and AppEnv type **Version:** `0.22.2` ### Consumer Guide (fourfoldlabs-foundation) --- name: fourfoldlabs-api-core description: > @fourfoldlabs/api-core: Hono middleware factories — requestIdMiddleware, createRequestLoggerMiddleware, createCorsMiddleware, createRateLimiter, success/paginated/error response helpers, createHealthCheck, AppEnv. Use when building Hono API routes. --- # @fourfoldlabs/api-core ## Purpose Shared Hono infrastructure for all FourFoldLabs API apps. Provides request ID tracking, structured logging via pino, CORS configuration, in-memory rate limiting, and standardized JSON response helpers that enforce the `{ data }` / `{ data, pagination }` / `{ error }` envelope shapes. The `AppEnv` type extends Hono's base `Env` so domain variables and `requestId` are always typed together. ## Exports | Export | Signature | Purpose | |--------|-----------|---------| | `AppEnv` | `type AppEnv>` | Hono `Env` with `Variables.requestId` + domain vars | | `createLogger` | `(options?: LoggerOptions) => pino.Logger` | Pino logger with optional pretty-print | | `requestIdMiddleware` | `MiddlewareHandler` | Sets `requestId` var + `x-request-id` header | | `createRequestLoggerMiddleware` | `(logger: Logger) => MiddlewareHandler` | Logs method, path, status, duration per request | | `createCorsMiddleware` | `(options?: CorsOptions) => MiddlewareHandler` | Configures CORS via hono/cors | | `createRateLimiter` | `(options?: RateLimitOptions) => MiddlewareHandler` | In-memory IP-based rate limiter | | `success` | `(c, data, status?) => Response` | Wraps `{ data: T }` at 200 or 201 | | `paginated` | `(c, data, pagination) => Response` | Wraps `{ data, pagination }` | | `error` | `(c, message, status?) => Response` | Wraps `{ error: string }` | | `createHealthCheck` | `(options?: HealthCheckOptions) => Hono` | Returns a mounted Hono sub-app at `GET /` | ## Patterns ### Full Hono App Setup ```ts import { Hono } from 'hono' import { AppEnv, createCorsMiddleware, createHealthCheck, createLogger, createRateLimiter, createRequestLoggerMiddleware, requestIdMiddleware, } from '@fourfoldlabs/api-core' type Env = AppEnv<{ userId: string }> const logger = createLogger() const app = new Hono() app.use('*', requestIdMiddleware) app.use('*', createRequestLoggerMiddleware(logger)) app.use('*', createCorsMiddleware({ origins: [process.env.WEB_URL!] })) app.use('/auth/*', createRateLimiter({ maxAttempts: 5, windowMs: 60_000 })) app.route('/health', createHealthCheck({ checkDb: db.ping })) ``` ### AppEnv\ ```ts // apps/api/src/types.ts import type { AppEnv } from '@fourfoldlabs/api-core' type Env = AppEnv<{ orgId: string; userId: string }> const app = new Hono() // c.get('requestId'), c.get('orgId'), c.get('userId') are all typed ``` `requestId: string` is always present — it is merged in by the base `AppEnv` type. ### Response Helpers ```ts success(c, data: T, status?: 200 | 201) // → { data: T } paginated(c, data: T[], pagination: { total: number; limit: number; offset: number }) // → { data: T[], pagination: { total, limit, offset } } error(c, message: string, status?: 400 | 401 | 403 | 404 | 500) // → { error: string } ``` ### Middleware Options ```ts // CorsOptions interface CorsOptions { origins?: string[] // default: ['http://localhost:5173', 'http://localhost:3000'] methods?: string[] // default: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] headers?: string[] // default: ['Content-Type', 'Authorization'] } // RateLimitOptions interface RateLimitOptions { windowMs?: number // default: 900_000 (15 min) maxAttempts?: number // default: 10 message?: string // default: 'Too many attempts. Try again later.' } // Keyed by first IP in x-forwarded-for. Throws HTTPException(429) when exceeded. // HealthCheckOptions interface HealthCheckOptions { checkDb?: () => Promise // called on every health request commitSha?: string // surface the deployed git SHA } // GET /health → { status: 'ok', db: 'connected', commit_sha: '...' } // DB failure → { status: 'degraded', db: 'error', commit_sha: '...' } 503 ``` ### Logger Factory ```ts interface LoggerOptions { level?: string // default: 'info' pretty?: boolean // default: true when NODE_ENV !== 'production' } const logger = createLogger({ level: 'debug' }) ``` ## Constraints - Don't use `createRateLimiter` as a distributed rate limiter — it is in-memory and per-process only. Replace with a Redis-backed solution for multi-instance deployments. - Don't skip `requestIdMiddleware` before `createRequestLoggerMiddleware` — the logger silently omits `requestId` from structured logs. - Don't return raw `c.json(...)` in routes — always use `success`, `paginated`, or `error` to keep response shapes consistent. - Don't import from deep paths (`/middleware/cors`) — all exports are available from the package root. - Don't define custom `Variables` types inline on `new Hono<{ Variables: ... }>` — always extend via `AppEnv` so `requestId` is included automatically. ### Builder Spec --- name: fourfoldlabs-api-core-spec description: > Builder spec for @fourfoldlabs/api-core — internal structure, tests, modification guidelines. For package maintenance in Crucible. --- # @fourfoldlabs/api-core — Builder Spec ## Source Structure Internal file layout in `packages/api-core/src/`: - `index.ts` — barrel, re-exports all middleware, helpers, and types - `types.ts` — `AppEnv` type - `logger.ts` — `createLogger()` (pino) - `response.ts` — `success()`, `paginated()`, `error()` helpers - `health.ts` — `createHealthCheck()` factory - `middleware/` — one file per middleware factory: - `cors.ts` — `createCorsMiddleware()` - `rate-limit.ts` — `createRateLimiter()` - `request-id.ts` — `requestIdMiddleware` - `request-logger.ts` — `createRequestLoggerMiddleware()` ## Modification Guidelines - New middleware factories go in `middleware/.ts` and are re-exported from `index.ts`. - Response helpers (`success`, `paginated`, `error`) enforce the monorepo's `{ data }` / `{ data, pagination }` / `{ error }` envelope shapes — any new helper must follow the same pattern. - `createRateLimiter` is intentionally in-memory and per-process. If a Redis-backed solution is added, it should be a separate export (e.g. `createDistributedRateLimiter`) rather than a replacement, to avoid breaking existing usage. - All exports must be available from the package root — no deep imports. ## Builder Constraints - Don't define custom `Variables` types inline on `new Hono<{ Variables: ... }>` in consuming apps — always extend via `AppEnv` so `requestId` is always included. - Don't skip `requestIdMiddleware` before `createRequestLoggerMiddleware` — the logger silently omits `requestId` from structured logs. - Don't return raw `c.json(...)` in routes — always use `success`, `paginated`, or `error` to keep response shapes consistent. - Don't import from sub-paths (e.g. `/middleware/cors`) — all exports are available from the package root. - Don't use `createRateLimiter` as a distributed rate limiter — it is in-memory and per-process only. ## Tests No automated tests. Middleware behavior is verified through integration tests in `apps/api` and through TypeScript type checking on `AppEnv`. ## See Also Consumer guide: /fourfoldlabs-foundation:fourfoldlabs-api-core --- ## @fourfoldlabs/audit **Description:** Audit logging: Drizzle schema, write service, and shared types **Version:** `0.22.2` **Depends on:** `@fourfoldlabs/shared` ### Consumer Guide (fourfoldlabs-content-comms) --- name: fourfoldlabs-audit description: > Use when implementing audit logging or activity history — auditLogs Drizzle table, logAuditEvent, buildDiffMetadata, AuditLogTable component, createAuditClient DI, useAuditStore/createAuditSlice. Immutable log rows. --- # @fourfoldlabs/audit + @fourfoldlabs/audit-ui ## Purpose `@fourfoldlabs/audit` provides the Drizzle schema (`audit_logs`), `logAuditEvent` and `buildDiffMetadata` service functions, and Zod schemas/types shared between server and client. `@fourfoldlabs/audit-ui` is the browser-only companion: `createAuditClient` (DI factory), a Zustand store, and the `AuditLogTable` React component. Types defined in `@fourfoldlabs/audit` are imported from there by both the host API and the UI package — never duplicated. ## Exports ### Server Package (@fourfoldlabs/audit) | Export | Signature | Purpose | |--------|-----------|---------| | `auditLogs` | `PgTable` | Drizzle table definition for `audit_logs` | | `logAuditEvent` | `(db: AuditDb, event: AuditEvent) => Promise` | Insert a single audit log entry | | `buildDiffMetadata` | `(before, after) => { before, after, changed }` | Build structured diff metadata for update events | | `listAuditLogsSchema` | `ZodObject` | Validates list/query params: limit, offset, action, resourceType, resourceId, actorId, startDate, endDate | | `auditEventSchema` | `ZodObject` | Validates write event shape | | `ListAuditLogsInput` | `type` | Inferred input type for list queries | | `AuditEventInput` | `type` | Inferred input type for write events | | `AuditLog` | `interface` | `{ id, orgId, actorId, action, resourceType, resourceId, metadata, createdAt }` | | `AuditEvent` | `type` | Input event shape for `logAuditEvent` | ### `audit_logs` Table Schema | Column | Type | Constraints | |--------|------|-------------| | `id` | `uuid` | PK, `gen_random_uuid()` | | `orgId` | `uuid` | NOT NULL | | `actorId` | `uuid` | nullable — system events have no actor | | `action` | `varchar(100)` | NOT NULL | | `resourceType` | `varchar(100)` | NOT NULL | | `resourceId` | `varchar(255)` | nullable | | `metadata` | `jsonb` | nullable | | `createdAt` | `timestamptz` | NOT NULL, DEFAULT now() | Indexes: `(orgId, createdAt)`, `(orgId, resourceType, resourceId)`, `(orgId, actorId)`. ### Client Package (@fourfoldlabs/audit-ui) | Export | Signature | Purpose | |--------|-----------|---------| | `createAuditClient` | `(config: AuditClientConfig) => AuditClient` | DI factory bound to baseUrl + token | | `useAuditStore` | Zustand store hook | Standalone store | | `createAuditSlice` | `(set) => AuditState` | Composable slice for host-app Zustand stores | | `AuditLogTable` | `(props: AuditLogTableProps) => JSX` | Table with time, action, resource, actor columns | ## Patterns ### Schema Integration ```ts // apps/api/src/db/schema.ts export { auditLogs } from '@fourfoldlabs/audit' ``` ### Logging Events ```ts import { logAuditEvent } from '@fourfoldlabs/audit' await logAuditEvent(db, { orgId: c.get('orgId'), actorId: c.get('userId'), action: 'mcp_token.created', resourceType: 'mcp_token', resourceId: created.id, }) ``` ### Diff Metadata for Updates ```ts import { logAuditEvent, buildDiffMetadata } from '@fourfoldlabs/audit' const before = { name: 'Old Name', role: 'member' } const after = { name: 'New Name', role: 'member' } await logAuditEvent(db, { orgId, actorId: userId, action: 'user.updated', resourceType: 'user', resourceId: userId, metadata: buildDiffMetadata(before, after), // → { before: {...}, after: {...}, changed: ['name'] } }) ``` ### Client DI Setup ```ts import { createAuditClient } from '@fourfoldlabs/audit-ui' export const auditClient = createAuditClient({ baseUrl: import.meta.env.VITE_API_URL, getToken: () => authStore.getState().token, }) const { data, pagination } = await auditClient.list({ resourceType: 'user', limit: 50 }) const { data: tokenLogs } = await auditClient.getByResource('mcp_token', tokenId) const { data: userActions } = await auditClient.getByActor(userId) ``` ### Store Pattern Pure state store — no IO, no fetch calls. ```ts import { useAuditStore } from '@fourfoldlabs/audit-ui' const { entries, loading, setEntries, setLoading } = useAuditStore() ``` State fields: `entries`, `loading`, `pagination` Actions: `setEntries`, `setLoading`, `setPagination` Composable: ```ts import { create } from 'zustand' import { createAuditSlice } from '@fourfoldlabs/audit-ui' const useAppStore = create((set) => ({ ...createAuditSlice(set), })) ``` ### Typical Page Setup ```tsx import { AuditLogTable, useAuditStore } from '@fourfoldlabs/audit-ui' import { auditClient } from '@/lib/audit' import { useEffect } from 'react' export function AuditPage() { const { entries, loading, setEntries, setLoading } = useAuditStore() useEffect(() => { setLoading(true) auditClient.list({ limit: 50 }).then((r) => { setEntries(r.data) setLoading(false) }) }, []) return } ``` ## Constraints ### Do Not - Don't modify audit log rows after creation — they are immutable by design (no `updatedAt` column) - Don't import UI components, client, or store from `@fourfoldlabs/audit` — use `@fourfoldlabs/audit-ui` - Don't import server-only exports from `@fourfoldlabs/audit` (schema, service functions) in browser code - Don't call `createAuditClient` inside a component render — create it once at module scope - Don't put IO in the store — the store is pure state; route components and hooks handle fetching ### Builder Spec --- name: fourfoldlabs-audit-spec description: > Builder spec for @fourfoldlabs/audit + @fourfoldlabs/audit-ui — internal structure, tests, modification guidelines. For package maintenance in Crucible. --- # @fourfoldlabs/audit + @fourfoldlabs/audit-ui — Builder Spec ## Source Structure `packages/audit/src/` contains: - `audit-schema.ts` — `auditLogs` Drizzle table definition - `audit-service.ts` — `logAuditEvent`, `buildDiffMetadata` - `schemas.ts` — `listAuditLogsSchema`, `auditEventSchema` - `types.ts` — `AuditLog`, `AuditEvent` - `index.ts` — barrel export `packages/audit-ui/src/` contains: - `client.ts` — `createAuditClient` DI factory - `store/audit-slice.ts` — `useAuditStore`, `createAuditSlice` - `components/AuditLogTable.tsx` - `index.ts` — barrel export ## Modification Guidelines - The `audit_logs` table has no `updatedAt` column by design — immutability is a core contract; do not add one - `logAuditEvent` uses a duck-typed db parameter — never import Drizzle's concrete DB class - `buildDiffMetadata` computes shallow key diff — if deep diff is needed, add a separate helper rather than changing the existing function signature - New columns on `audit_logs` require a migration; because rows are immutable, new columns should be nullable or have defaults - `audit-ui` types and schemas must be re-exported from `@fourfoldlabs/audit`, not duplicated in `audit-ui` ## Builder Constraints - Don't add FK constraints in the package schema — host apps add them locally - Don't import from other `@fourfoldlabs/` domain packages (except `@fourfoldlabs/ui` in audit-ui) - Don't expose a delete or update service function — audit log immutability is a security property - Don't put IO in the store — the store is pure state ## Tests No test files currently. When adding: test `buildDiffMetadata` changed key detection, `logAuditEvent` insertion shape, and `listAuditLogsSchema` filter validation. ## See Also Consumer guide: `/fourfoldlabs-content-comms:fourfoldlabs-audit` --- ## @fourfoldlabs/audit-ui **Description:** Audit UI: AuditLogTable component, API client, and Zustand store **Version:** `0.22.2` **Depends on:** `@fourfoldlabs/audit`, `@fourfoldlabs/shared`, `@fourfoldlabs/ui` --- ## @fourfoldlabs/auth **Description:** Pluggable auth strategy with bcrypt + JWT, DI-based middleware factories **Version:** `0.22.2` ### Consumer Guide (fourfoldlabs-foundation) --- name: fourfoldlabs-auth description: > @fourfoldlabs/auth: PasswordAuthStrategy (JWT HS256 + bcrypt), createAuthMiddleware, createMcpAuthMiddleware, invitation/verification/reset flows, AuthEmailSender interface, email template builders. Use when adding auth or protecting endpoints. --- # @fourfoldlabs/auth ## Purpose Provides the full authentication stack for Hono APIs: a pluggable `AuthStrategy` interface, a `PasswordAuthStrategy` implementation (email + bcrypt + JWT HS256), Hono middleware factories for JWT and MCP token validation, and service functions for invitation, email verification, and password reset flows. All secrets and user lookups are injected via constructor or function arguments — no direct database or environment dependencies. ## Exports ### Strategy & Middleware | Export | Signature | Purpose | |--------|-----------|---------| | `AuthStrategy` | `interface` | Contract all auth strategies must implement | | `AuthUser` | `interface` | `{ id, email, role, orgId }` | | `AuthResult` | `interface` | `{ user: AuthUser, token: string }` | | `UserRecord` | `interface` | `{ id, email, role, orgId, passwordHash: string \| null }` — host app shape | | `UserLookup` | `type` | `(email: string) => Promise` | | `McpTokenLookup` | `type` | `(tokenHash: string) => Promise<{ orgId: string } \| null>` | | `credentialsSchema` | `ZodObject` | Validates `{ email, password }` login credentials | | `PasswordAuthStrategy` | `class` | Email + bcrypt + JWT HS256 strategy with `issueToken` | | `createAuthMiddleware` | `factory fn` | Hono middleware that validates JWT, sets `orgId`/`userId` on context | | `createMcpAuthMiddleware` | `factory fn` | Hono middleware that accepts both JWT and `cru_`-prefixed MCP API tokens | ### DB Schema (opt-in) | Export | Type | Purpose | |--------|------|---------| | `invitations` | `PgTable` | Invitation records with token hash, org, role, group IDs | | `emailVerificationTokens` | `PgTable` | Email verification tokens (user-scoped, single-use, 24h expiry) | | `passwordResetTokens` | `PgTable` | Password reset tokens (user-scoped, single-use, 1h expiry) | Re-export from the host app's `schema.ts` to include in migrations: ```ts export { invitations, emailVerificationTokens, passwordResetTokens } from '@fourfoldlabs/auth' ``` ### Token Utilities | Export | Signature | Purpose | |--------|-----------|---------| | `generateSecureToken` | `() => { rawToken, tokenHash }` | 32 random bytes, base64url raw, SHA-256 hex hash | | `hashSecureToken` | `(token: string) => string` | SHA-256 hash an existing token | ### Zod Schemas | Export | Shape | Purpose | |--------|-------|---------| | `inviteUserAuthSchema` | `{ email, role?, groupIds? }` | Validate invitation input | | `claimInvitationSchema` | `{ token, password }` | Validate invite claim | | `verifyEmailSchema` | `{ token }` | Validate verification token | | `forgotPasswordSchema` | `{ email }` | Validate forgot-password input | | `resetPasswordSchema` | `{ token, password }` | Validate password reset | | `changePasswordSchema` | `{ currentPassword, newPassword }` | Validate authenticated password change | ### Email Types & Templates | Export | Signature | Purpose | |--------|-----------|---------| | `AuthEmailSender` | `interface` | Email capabilities auth needs — host app implements | | `EmailBranding` | `interface` | `{ appName, appUrl, logoUrl?, primaryColor?, supportEmail? }` | | `CreateUserFn` | `type` | DI: create user from `{ email, passwordHash, orgId, role }` | | `UpdateUserFn` | `type` | DI: update user by id | | `UserByEmailLookup` | `type` | DI: find user by email | | `UserByIdLookup` | `type` | DI: find user by id | | `buildInvitationEmail` | `(params, branding) => { subject, html, text }` | Invitation email template | | `buildVerifyEmail` | `(params, branding) => { subject, html, text }` | Email verification template | | `buildPasswordResetEmail` | `(params, branding) => { subject, html, text }` | Password reset template | | `buildPasswordChangedEmail` | `(branding) => { subject, html, text }` | Password changed notification | ### Service Functions | Export | Signature | Purpose | |--------|-----------|---------| | `createInvitation` | `(db, params, emailSender) => Promise<{ id, email, expiresAt }>` | Create invitation + send email (7-day expiry) | | `claimInvitation` | `(db, input, createUser) => Promise<{ userId, email, orgId, role, groupIds }>` | Claim invite, create user with password | | `createEmailVerification` | `(db, user, appUrl, emailSender) => Promise` | Send email verification (24h expiry) | | `verifyEmail` | `(db, token, updateUser) => Promise<{ userId }>` | Consume verification token | | `initPasswordReset` | `(db, email, appUrl, lookupUser, emailSender) => Promise` | Initiate reset (anti-enumeration, 1h expiry) | | `resetPassword` | `(db, input, updateUser) => Promise<{ userId }>` | Consume reset token, set new password | | `changePassword` | `(userId, input, lookupUser, updateUser, emailSender) => Promise` | Authenticated password change | ## Patterns ### PasswordAuthStrategy ```ts new PasswordAuthStrategy(userLookup, jwtSecret, tokenExpiresIn?) ``` - `authenticate` — parses credentials, bcrypt compare (guards nullable `passwordHash`), signs JWT - `issueToken` — signs a JWT for a given `AuthUser` without credential check (used after invite claim) - `validateToken` — verifies JWT with hono/jwt, Zod-parses payload ### AuthStrategy Interface ```ts interface AuthStrategy { authenticate(credentials: unknown): Promise validateToken(token: string): Promise<{ sub: string; orgId: string; email: string; role: string }> issueToken?(user: AuthUser): Promise handleCallback?(request: Request): Promise // OAuth/SSO only } ``` ### AuthEmailSender ```ts interface AuthEmailSender { sendInvitation(p: { to, inviteUrl, orgName, invitedByEmail }): Promise sendVerifyEmail(p: { to, verifyUrl }): Promise sendPasswordReset(p: { to, resetUrl, expiresInMinutes }): Promise sendPasswordChanged(p: { to }): Promise } ``` Implement by combining `@fourfoldlabs/email`'s `EmailSender` with this package's template builders: ```ts import { createResendSender } from '@fourfoldlabs/email' import { buildInvitationEmail, type AuthEmailSender, type EmailBranding } from '@fourfoldlabs/auth' const sender = createResendSender({ apiKey, from }) const branding: EmailBranding = { appName: 'MyApp', appUrl: 'https://myapp.com' } const authEmail: AuthEmailSender = { sendInvitation: (p) => sender.send({ to: p.to, ...buildInvitationEmail(p, branding) }), sendVerifyEmail: (p) => sender.send({ to: p.to, ...buildVerifyEmail(p, branding) }), sendPasswordReset: (p) => sender.send({ to: p.to, ...buildPasswordResetEmail(p, branding) }), sendPasswordChanged: (p) => sender.send({ to: p.to, ...buildPasswordChangedEmail(branding) }), } ``` ### Schema Integration and Service Usage ```ts // apps/api/src/db/schema.ts export { invitations, emailVerificationTokens, passwordResetTokens } from '@fourfoldlabs/auth' // Route handlers import { createInvitation, claimInvitation, initPasswordReset } from '@fourfoldlabs/auth' await createInvitation(db, { email, orgId, role, groupIds, invitedBy, appUrl, orgName, invitedByEmail }, authEmail) const result = await claimInvitation(db, { token, password }, createUserFn) await initPasswordReset(db, email, appUrl, userByEmailLookup, authEmail) ``` ## Constraints - Don't import `bcryptjs` directly in route handlers — let `PasswordAuthStrategy` handle it. - Don't hardcode JWT secrets — always inject via constructor. - Don't call `strategy.authenticate` in middleware — that's for login routes only. - Don't rely on `userId` being set in MCP-token requests — only `orgId` is guaranteed on that path. - Don't skip Zod schema validation — schemas enforce password length, email format, token presence. ### Builder Spec --- name: fourfoldlabs-auth-spec description: > Builder spec for @fourfoldlabs/auth — internal structure, tests, modification guidelines. For package maintenance in Crucible. --- # @fourfoldlabs/auth — Builder Spec ## Source Structure Internal file layout in `packages/auth/src/`: - `index.ts` — barrel, re-exports everything - `strategy.ts` — `AuthStrategy` interface, `AuthUser`, `AuthResult`, `UserRecord`, `UserLookup`, `McpTokenLookup` - `password.ts` — `PasswordAuthStrategy` class (bcrypt + JWT HS256) - `password.test.ts` — spec-tests for `PasswordAuthStrategy` (authenticate, issueToken, validateToken) - `middleware.ts` — `createAuthMiddleware()`, `createMcpAuthMiddleware()` - `tokens.ts` — `generateSecureToken()`, `hashSecureToken()` - `tokens.test.ts` — crypto correctness tests - `schemas.ts` — all Zod schemas (`credentialsSchema`, `inviteUserAuthSchema`, `claimInvitationSchema`, `verifyEmailSchema`, `forgotPasswordSchema`, `resetPasswordSchema`, `changePasswordSchema`) - `schemas.test.ts` — Zod validation boundary tests - `service.ts` — `createInvitation`, `claimInvitation`, `createEmailVerification`, `verifyEmail`, `initPasswordReset`, `resetPassword`, `changePassword` - `service.test.ts` — service behavioral spec-tests - `auth-schema.ts` — Drizzle tables: `invitations`, `emailVerificationTokens`, `passwordResetTokens` - `email-types.ts` — `AuthEmailSender` interface, `EmailBranding`, DI function types - `templates.ts` — `buildInvitationEmail`, `buildVerifyEmail`, `buildPasswordResetEmail`, `buildPasswordChangedEmail` ## Modification Guidelines - New auth strategies (OAuth, SSO) go in `apps/api/src/auth/`, not in this package. This package provides only `PasswordAuthStrategy`. - New service functions that encode a flow (e.g. magic link) follow the same DI pattern: accept `db`, input, and callback functions — no direct env reads, no hardcoded secrets. - Email templates in `templates.ts` use plain HTML strings. Keep them self-contained and return `{ subject, html, text }`. - The `AuthEmailSender` interface defines capabilities auth needs. Adding a new flow requires adding the corresponding method to the interface and updating host app wiring. - When adding a Drizzle table to `auth-schema.ts`, do NOT add FK constraints — host apps add them in their own migrations. ## Builder Constraints - Don't add FK constraints in the schema tables (`auth-schema.ts`) — host apps add them in their own migrations. - Don't import `@fourfoldlabs/email` in this package — auth defines the `AuthEmailSender` interface; the host app wires in the email implementation. - Don't hardcode JWT secrets — always inject via constructor or function argument. - Don't add new strategy implementations to this package — add them in `apps/api/src/auth/`. - Don't call `strategy.authenticate` in middleware — that is for login routes only. - Don't rely on `userId` being set in MCP-token requests — only `orgId` is guaranteed on that path. - Don't import Drizzle's concrete `NodePgDatabase` class — use duck-typed `db` parameters. ## Tests - `tokens.test.ts` — crypto correctness: token structure (length, format), hash determinism, raw/hash pair consistency. - `schemas.test.ts` — Zod validation boundaries: valid/invalid credentials, invitation role enum, password length minimums, token presence. - `service.test.ts` — invitation claim (expiry guard, double-claim guard), email verification (token consumption), password reset (anti-enumeration, 1h expiry, bcrypt update), password change (current password verification). - `password.test.ts` — `PasswordAuthStrategy`: authenticate success/failure paths, `issueToken` output shape, `validateToken` verification. ## See Also Consumer guide: /fourfoldlabs-foundation:fourfoldlabs-auth --- ## @fourfoldlabs/blocks **Description:** 34 reusable React building blocks — charts, KPIs, tables, shells, forms, indicators **Version:** `0.22.2` **Depends on:** `@fourfoldlabs/shared`, `@fourfoldlabs/ui` ### Consumer Guide (fourfoldlabs-foundation) --- name: fourfoldlabs-blocks description: > @fourfoldlabs/blocks: Dashboard blocks — KpiCard, BarChartBlock, AreaChartBlock, DataTableFull, ActivityFeed, Timeline, FilterBar, SidebarShell, PageHeader, EmptyState. CMS content primitives — Card, CardArray, Image, Quote, Stripe (token-keyed pill accent). chatContext support, variant metadata. Use when building dashboards, data-heavy UIs, or rendering CMS page content. --- # @fourfoldlabs/blocks ## Purpose Composable, pre-built UI blocks for dashboard and data-heavy UIs. Each block is categorized by tier (display, interactive, controlled) and ships with typed `VariantMeta` records for variant selection guidance. Most blocks auto-generate `chatContext` for AI features. All blocks are presentational — no data fetching or store mutations belong inside them. ## Exports ### Block Catalog | Block | Tier | Category | Variants | chatContext | |-------|------|----------|----------|-------------| | KpiCard | display | kpis | default, simple, icon, comparison, progress, with-link | yes (auto) | | BarChartBlock | interactive | charts | default, stacked, horizontal, grouped, comparison | yes (auto) | | AreaChartBlock | interactive | charts | default, stacked, step, comparison | yes (auto) | | LineChartBlock | interactive | charts | default, dotted, step, comparison | yes (auto) | | DonutChartBlock | interactive | charts | default, legend-list, side-by-side, compact | yes (auto) | | SparklineBlock | display | charts | — (no variant prop) | yes (passthrough) | | HealthMeter | display | charts | default, tracker, status-list, compact | yes (auto) | | DataTable | display | lists | — (no variant prop) | yes (per-row via rowChatContext) | | DataTableFull | interactive | lists | — (sortStyle: arrows, single, filled) | yes (per-row via rowChatContext) | | ActivityFeed | display | lists | — (no variant prop) | yes (auto per-item) | | CommentBlock | controlled | lists | thread, activity, compact | yes (auto) | | Timeline | display | lists | — (no variant prop) | yes (auto) | | GridList | display | lists | — (no variant prop) | yes (auto) | | CalendarView | interactive | lists | — (no variant prop) | yes (auto) | | KanbanBoard | interactive | lists | — (no variant prop) | yes (auto) | | ProjectTimeline | controlled | lists | default, compact, extended | yes (auto) | | SidebarShell | interactive | page-shells | — (no variant prop) | yes (passthrough) | | TopNavShell | interactive | page-shells | — (no variant prop) | yes (passthrough) | | SiteHeader | interactive | page-shells | — (no variant prop; sticky toggle) | yes (auto) | | PageHeader | display | page-shells | — (no variant prop) | yes (auto) | | SettingsSection | display | page-shells | — (no variant prop) | yes (auto) | | CampaignHero | display | page-shells | centered, split, overlay, minimal | yes (auto) | | BlockPreview | interactive | page-shells | — (no variant prop) | no | | AnnouncementBar | display | feedback | — (no variant prop) | yes (auto) | | ConfirmDialog | controlled | feedback | default, destructive | no | | CommandPalette | controlled | feedback | — (no variant prop) | no | | EmptyState | display | feedback | — (no variant prop) | yes (auto) | | InsightCard | display | feedback | — (no variant prop) | yes (auto) | | OnboardingChecklist | controlled | feedback | — (no variant prop) | yes (auto) | | StatusBadge | display | indicators | dot, outline | no | | DeltaBadge | display | indicators | filled, outline, ghost | no | | TagBadge | controlled | indicators | outline, filled | no | | MiniBar | display | indicators | — (no variant prop) | no | | ProgressSteps | display | indicators | horizontal, vertical | yes (auto) | | FileUploadZone | controlled | inputs | — (no variant prop) | yes (passthrough) | | FilterBar | controlled | inputs | default, condensed, with-actions, stacked, period-selector | yes (passthrough) | | QuickActions | controlled | inputs | — (no variant prop) | no | | ContentCard | display | content | variant: bordered, bare | yes (auto) | | ContentCardArray | display | content | layout: grid, scroll-band, chip-row | yes (auto) | | ProfileCard | display | content | imageAspect: 4:3, 16:9, 1:1; variant: bordered, bare | yes (auto) | | ProfileCardArray | display | content | columns: 2, 3, 4 | yes (auto) | | ContentImage | display | content | — | yes (auto) | | ContentQuote | display | content | variant: pull, testimonial, scripture | yes (auto) | | Stripe | display | lib | — | no (decorator) | ### Tiers - **display** — Renders data only. No internal state, no user interaction beyond CSS hover effects. Examples: KpiCard, HealthMeter, ActivityFeed, Timeline, StatusBadge. - **interactive** — Owns internal state and responds to user actions (sorting, selection, filtering, toggling). Examples: BarChartBlock, DataTableFull, CalendarView, KanbanBoard, SidebarShell. - **controlled** — All interactive behavior is wired through external state and callbacks. The parent owns the state. Examples: CommentBlock, ProjectTimeline, ConfirmDialog, FilterBar, OnboardingChecklist. ### Style Tokens `ProjectTimeline` accepts `cornerStyle?: CornerStyle` (exported from the package root): | Value | Effect | |-------|--------| | `'rounded'` (default) | Timeline bars use `rounded-md` / `rounded-sm` corners | | `'squared'` | Timeline bars use `rounded-none` — flush square corners for dense data-grid UIs | ### Types | Export | Purpose | |--------|---------| | `ChatContext` | `{ type: string, label: string, content: string }` — AI context interface | | `VariantMeta` | `{ when, requires?, ignores?, example }` — variant selection metadata | | `BlockMeta` | Block catalog metadata | | `CornerStyle` | `'rounded' \| 'squared'` — style token for ProjectTimeline | ### Registry Utilities | Export | Purpose | |--------|---------| | `registry` | All blocks catalog array | | `getBlockBySlug` | Lookup a block by slug | | `getBlocksByCategory` | Filter blocks by category | ## Patterns ### Install ``` bun add @fourfoldlabs/blocks ``` ```ts import { KpiCard, BarChartBlock, DataTable, FilterBar } from '@fourfoldlabs/blocks' import type { ChatContext, VariantMeta, BlockMeta } from '@fourfoldlabs/blocks' import { registry, getBlockBySlug, getBlocksByCategory } from '@fourfoldlabs/blocks' ``` ### KpiCard ```tsx ``` ### Chart Blocks Chart blocks follow a consistent shape — `data`, `config` (Recharts `ChartConfig`), `xAxisKey`, `dataKeys`: ```tsx ``` ### Page Shells ```tsx } groups={navGroups} footer={}> {children} ``` **`SiteHeader`** — marketing / public website header. Two rows (utility strip + main), flat nav, CTA row, sticky with scroll-collapse, full-screen mobile drawer. Pair with `AnnouncementBar` via the `banner` slot. ```tsx } name="Covenant Academy" tagline="A classical Christian school since 1987" nav={[ { label: 'About', href: '/about' }, { label: 'Academics', href: '/academics', active: true }, { label: 'Admissions', href: '/admissions' }, ]} utilityLinks={[ { label: 'Parent Portal', href: '/portal' }, { label: 'Give', href: '/give' }, ]} socials={[{ label: 'Facebook', icon: , href: '...' }]} contact={{ phone: '(404) 555-0123', email: 'info@covenant.edu' }} ctas={[{ label: 'Apply', href: '/apply' }]} searchHref="/search" banner={} > ``` `SiteHeader` is flat-nav only by design — if your IA needs dropdowns, flatten it (e.g., surface "Academics" as an index page and put sub-pages there). For app shells (dashboards, internal tools), use `TopNavShell` or `SidebarShell` instead. Scroll-collapse engages whether the page scrolls on `window` or inside any `overflow-auto` ancestor — the component uses an `IntersectionObserver` sentinel. Set `sticky={false}` to disable entirely. ### Content Primitives `ContentCard`, `ContentCardArray`, `ContentImage`, `ContentQuote`, `ProfileCard`, and `ProfileCardArray` are the foundation content blocks for CMS pages. The `Content` prefix avoids a name collision with `Card` from `@fourfoldlabs/ui` (shadcn). They're pure presentational — wrappers in `@fourfoldlabs/cms-ui` add edit affordances on top of them. ```tsx import { ContentCard, ContentCardArray, ContentImage, ContentQuote, Stripe, } from '@fourfoldlabs/blocks' ({ title: p.label, body: p.description, href: p.href, stripe: { axis: 'horizontal', position: 'leading', color: 'accent' }, }))} /> ({ image: { src: l.src, alt: l.name } }))} /> ({ name: p.name, bio: p.bio, photo: p.photoUrl ? { src: p.photoUrl, alt: p.name } : undefined, stats: [ { icon: , value: p.connections, label: 'connections' }, { icon: , value: p.projects, label: 'projects' }, ], action: { label: 'Connect', icon: , href: `/people/${p.slug}` }, }))} /> ``` `ProfileCard` lays out a full-width photo at the top (default `4:3` crop), a name + optional role eyebrow + bio below, and an optional stats row with a pill-shaped primary action aligned right. When `photo` is absent the image slot renders a gradient block with the person's initials (derived from `name` unless `initials` is provided). Stats and action are both optional — omit them for a read-only card. `ContentCard` renders as `` when `href` is set, otherwise `
`. `stripe` composes the `Stripe` primitive as a decorator — position and color are engineering-controlled at the layout level (never staff-editable). **`variant`** — `'bordered'` (default) gives a rounded card with border, bg, and padding; `'bare'` removes all container chrome, leaving just the content. Rule of thumb: top/bottom stripes read best with `bare` (no competing border), left/right stripes read best with `bordered`. The consumer decides per card, or sets an array-level default via `ContentCardArray`'s `variant` prop. **`icon`** — accepts any `ReactNode`. Lucide icons get auto-sized (5×5) via a descendant selector; `` children fill the 10×10 rounded container. Common for linked navigation tiles. In `chip-row` layout the icon renders inline-left of the label at text size (3.5×3.5). Icon is engineering-controlled — not in the staff-editable draft. ```tsx } title="Advancement" body="Alumni, parents, and friends partnering in the mission." href="/advancement" stripe={{ axis: 'horizontal', position: 'leading', color: 'primary' }} className="pt-4" /> ``` ### Stripe Token-keyed pill accent for content blocks: ```tsx ``` `color` accepts only `'primary' | 'accent' | 'muted'` + semantic tokens — no arbitrary hex. Border-style stripes are gone; the pill stripe is the only stripe pattern. ### chatContext Most blocks auto-generate `chatContext` from their props for use with `@fourfoldlabs/chat`. Override by passing an explicit prop: ```ts interface ChatContext { type: string // e.g. 'kpi-card', 'bar-chart', 'activity' label: string // human-readable label content: string // serialized data summary } ``` `DataTable` and `DataTableFull` accept `rowChatContext: (row: T) => ChatContext` for per-row AI context. ### Variant Selection Each block with a `variant` prop exports `Variants` (e.g. `kpiCardVariants`) with `VariantMeta` for each variant. Check `when` to pick the right variant, `requires` for mandatory props, and `ignores` for props with no effect on that variant. ## Constraints - Don't import from sub-paths (e.g. `@fourfoldlabs/blocks/charts/bar-chart`) — always import from the package root. - Don't skip `requires` props for a chosen variant — the block will render incorrectly or not at all. - Don't pass props listed in a variant's `ignores` array — they have no effect. - Don't mix `DataTable` (display-only) with `DataTableFull` (interactive, sortable, paginated) — use `DataTable` inside composed layouts, `DataTableFull` for standalone list pages. - Don't use `BlockPreview` outside the blocks showcase — it is a development tool, not an application component. - Don't add business logic inside block components — blocks are presentational. Keep data fetching, mutations, and store updates in the parent route or hook. - Don't use `ProjectTimeline` for display-only views without passing `editable={false}` explicitly — the default allows end-date resizing via drag. ### Builder Spec --- name: fourfoldlabs-blocks-spec description: > Builder spec for @fourfoldlabs/blocks — internal structure, tests, modification guidelines. For package maintenance in Crucible. --- # @fourfoldlabs/blocks — Builder Spec ## Source Structure Internal file layout in `packages/blocks/src/`: - `index.ts` — barrel, re-exports all blocks, types, registry utilities, and `CornerStyle` - `registry.ts` — `registry` array, `getBlockBySlug`, `getBlocksByCategory` - `categories.ts` — category + tier definitions - `lib/` — shared block internals: - `block-style.ts` — `CornerStyle` type definition - `chat-context.ts` — `chatContextAttr()` helper, `ChatContext` interface - `variant-meta.ts` — `VariantMeta` interface, `BlockMeta` interface - `variant-guide.tsx` — `VariantGuide` component (showcase only) - `chart-card.tsx` — shared chart wrapper - `sparkline-chart.tsx` — shared sparkline primitive - `stripe.tsx` — `Stripe` pill-shaped accent primitive (v0.21). Props: `axis` (`horizontal` | `vertical`), `position` (`leading` | `trailing`), `color` (token-keyed: `primary`, `accent`, `muted`, plus semantic), `thickness` (`thin` | `default` | `thick`). Accepts no arbitrary hex — color is a closed enum mapped to Tailwind `bg-*` tokens - `kpis/` — `KpiCard` + variants - `charts/` — `BarChartBlock`, `AreaChartBlock`, `LineChartBlock`, `DonutChartBlock`, `SparklineBlock`, `HealthMeter` - `lists/` — `DataTable`, `DataTableFull`, `ActivityFeed`, `CommentBlock`, `Timeline`, `GridList`, `CalendarView`, `KanbanBoard`, `ProjectTimeline` - `page-shells/` — `SidebarShell`, `TopNavShell`, `SiteHeader`, `PageHeader`, `SettingsSection`, `CampaignHero`, `BlockPreview` - `feedback/` — `AnnouncementBar`, `ConfirmDialog`, `CommandPalette`, `EmptyState`, `InsightCard`, `OnboardingChecklist` - `indicators/` — `StatusBadge`, `DeltaBadge`, `TagBadge`, `MiniBar`, `ProgressSteps` - `inputs/` — `FileUploadZone`, `FilterBar`, `QuickActions` - `content/` (v0.21) — CMS content primitives: `ContentCard`, `ContentCardArray`, `ContentImage`, `ContentQuote`, `ProfileCard`, `ProfileCardArray`. Pure presentational — no edit-mode awareness. Consumed by `@fourfoldlabs/cms-ui` editable wrappers and by any layout that renders CMS content directly. The `Content` prefix avoids a name collision with `Card` from `@fourfoldlabs/ui` (shadcn). ## Content Primitives (v0.21) The `content/` category hosts the four foundation blocks that CMS pages compose: - `ContentCard` — flat-prop content card. Props: `title`, `body?`, `image?`, `icon?: ReactNode`, `href?`, `stripe?: StripeProps`, `variant?: 'bordered' | 'bare'`. Renders as an `` when `href` is set, otherwise a `
`. The `stripe` prop composes the `Stripe` primitive as a decorator (top/bottom/left/right placement via `axis` + `position`). The `icon` slot accepts any ReactNode — Lucide icons get auto-sized via a descendant selector, `` children fill the container. **Variant guidance**: top/bottom stripes typically pair with `bare` (no competing border); left/right stripes typically pair with `bordered`. Consumers decide. - `ContentCardArray` — ordered collection. Props: `items: ContentCardProps[]`, `layout: 'grid' | 'scroll-band' | 'chip-row'`, `columns?: 2 | 3 | 4` (grid only), `variant?: ContentCardVariant` (default variant applied to each item, per-item `variant` wins), `icon?: ReactNode` (default icon applied to each item). Three layouts encode the recurring CCA patterns; add a fourth only if a second consumer validates the need. `chip-row` renders via an internal `Chip` component — `icon` renders inline-left of the label at text size; `variant` doesn't apply to chips. - `ContentImage` — single image wrapper. Props: `src`, `alt`, `caption?`, `aspect?: '16:9' | '4:3' | '1:1' | 'auto'`. No media library coupling — consumer passes the resolved URL. - `ContentQuote` — callout quote. Props: `text`, `attribution?`, `variant: 'pull' | 'testimonial' | 'scripture'`. Scripture variant has the serif italic + left accent bar used for CCA's Bible verses. - `ProfileCard` — person-shaped card with a full-width photo at the top and optional stats + primary action at the bottom. Props: `name`, `eyebrow?` (small-caps credential line, e.g. "Head of School" — named `eyebrow` rather than `role` to avoid the HTML ARIA `role` attribute collision), `bio?`, `photo?`, `initials?`, `imageAspect?: '4:3' | '16:9' | '1:1'`, `stats?: ProfileCardStat[]` (each with `icon: ReactNode`, `value: string | number`, `label?`), `action?: ProfileCardAction` (`label`, `icon?`, `onClick?`, `href?` — `href` is scoped to protocol links (`mailto:`, `tel:`) and absolute external URLs; for internal routes, omit `href` and wrap the card in the consumer's router `` since the blocks package is framework-agnostic), `variant?: 'bordered' | 'bare'`, `stripe?: StripeProps`. When `photo` is absent, the image slot renders a gradient block with derived or provided initials. The action renders as a pill-shaped primary `