diff --git a/.claude/commands/blocks-develop.md b/.claude/commands/blocks-develop.md new file mode 100644 index 0000000..45ee336 --- /dev/null +++ b/.claude/commands/blocks-develop.md @@ -0,0 +1,102 @@ +--- +description: Develop and validate TPMJS tools using the blocks CLI +--- + +Help the user develop new tools for the TPMJS registry using the blocks CLI. This workflow covers defining tools in blocks.yml, implementing them with AI SDK v6, validating with the blocks CLI, and publishing to npm. + +## Development Workflow + +### 1. Define Tool in blocks.yml + +Add tool definition to `packages/tools/official/blocks.yml`: + +```yaml +blocks: + category.toolName: + type: utility + description: "Clear description for LLMs" + path: "tool-directory-name" + domain_rules: + - id: rule_name + description: "Implementation requirement" + inputs: + - name: paramName + type: string + description: "Parameter description" + outputs: + - name: result + type: ResultType + description: "Output description" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] +``` + +### 2. Create Package Structure + +``` +packages/tools/official/tool-name/ +├── package.json # npm package with tpmjs field +├── tsconfig.json # Extends @tpmjs/tsconfig +├── tsup.config.ts # Build config +├── block.ts # REQUIRED by validator +├── index.ts # Re-export from src +└── src/index.ts # Main implementation +``` + +### 3. Implement with AI SDK v6 + +```typescript +import { jsonSchema, tool } from 'ai'; + +export const myTool = tool({ + description: 'Description for LLMs', + parameters: jsonSchema({ + type: 'object', + properties: { /* ... */ }, + required: ['field1'], + }), + async execute(input): Promise { + // REAL implementation - no stubs + return result; + }, +}); + +export default myTool; +``` + +### 4. Run Validation + +```bash +cd packages/tools/official +pnpm blocks run tool-name # Validate single tool +pnpm blocks run tool-name --force # Force full validation +pnpm blocks run --all # Validate all tools +``` + +### 5. Build and Publish + +```bash +pnpm build +npm publish --access public + +# Trigger sync to tpmjs.com +source apps/web/.env.local +curl -X POST https://tpmjs.com/api/sync/keyword -H "Authorization: Bearer $CRON_SECRET" +``` + +## Valid Categories + +For `tpmjs.category` in package.json: `research`, `web`, `data`, `documentation`, `engineering`, `security`, `statistics`, `ops`, `agent`, `sandbox`, `utilities`, `html`, `compliance` + +## Required Files + +- **block.ts** at root: `export const block = { name: 'tool-name', tools: { myTool } };` +- **index.ts** at root: `export * from './src/index.js';` +- Both are required for the validator to find the tool + +## Common Issues + +- "invalid tpmjs field" during sync = Invalid category or missing tools array +- "Tool not found in exports" = Export name must match blocks.yml +- "Required file not found" = Need index.ts and block.ts at package root + +When helping the user, read the full skill documentation at `.claude/skills/blocks-develop.md` for comprehensive details on entities, measures, and multi-tool packages. diff --git a/.claude/skills/blocks-develop.md b/.claude/skills/blocks-develop.md new file mode 100644 index 0000000..ab9917c --- /dev/null +++ b/.claude/skills/blocks-develop.md @@ -0,0 +1,357 @@ +# TPMJS Tool Development with Blocks CLI + +Use this skill when developing new tools for the TPMJS registry. This covers the full workflow from defining a tool in blocks.yml through implementation, validation, and publishing. + +## Quick Start + +```bash +# Navigate to official tools directory +cd packages/tools/official + +# Run validation on a specific tool +pnpm blocks run + +# Run validation on all tools +pnpm blocks run --all + +# Force full validation (ignore cache) +pnpm blocks run --force +``` + +## Development Workflow + +### 1. Define the Tool Block in blocks.yml + +Add your tool definition to `packages/tools/official/blocks.yml` in the `blocks:` section: + +```yaml +blocks: + # Category.toolName format + sandbox.myTool: + type: utility + description: "Clear, LLM-friendly description of what the tool does" + path: "my-tool" # Directory name under packages/tools/official/ + domain_rules: + - id: rule_name + description: "What this implementation must do" + inputs: + - name: inputName + type: string + description: "Description for LLMs" + - name: optionalInput + type: number + optional: true + description: "Optional parameter" + outputs: + - name: result + type: MyResultType + description: "What the tool returns" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] +``` + +**Key Fields:** +- `type`: Usually `utility` for single-shot tools +- `path`: Directory name (kebab-case) +- `domain_rules`: Implementation requirements the validator checks +- `inputs/outputs`: Schema for validation +- `measures`: Quality constraints from the domain section + +### 2. Create the Tool Package + +Create the directory structure: + +``` +packages/tools/official/my-tool/ +├── package.json +├── tsconfig.json +├── tsup.config.ts +├── block.ts # Required by validator +├── index.ts # Re-export from src +└── src/ + └── index.ts # Main implementation +``` + +**package.json:** +```json +{ + "name": "@tpmjs/tools-my-tool", + "version": "0.1.0", + "description": "Short description for npm", + "type": "module", + "keywords": ["tpmjs", "category-name", "ai"], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": ["dist"], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "type-check": "tsc --noEmit", + "clean": "rm -rf dist .turbo" + }, + "devDependencies": { + "@tpmjs/tsconfig": "workspace:*", + "tsup": "^8.5.1", + "typescript": "^5.9.3" + }, + "dependencies": { + "ai": "6.0.23" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/tpmjs/tpmjs.git", + "directory": "packages/tools/official/my-tool" + }, + "homepage": "https://tpmjs.com", + "license": "MIT", + "tpmjs": { + "category": "sandbox", + "frameworks": ["vercel-ai"], + "tools": [ + { + "name": "myTool", + "description": "Clear description (20+ chars) of what this tool does." + } + ] + } +} +``` + +**Valid categories for tpmjs.category:** +- `research`, `web`, `data`, `documentation`, `engineering` +- `security`, `statistics`, `ops`, `agent`, `sandbox`, `utilities` +- `html`, `compliance` + +**tsconfig.json:** +```json +{ + "extends": "@tpmjs/tsconfig/react-library.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} +``` + +**tsup.config.ts:** +```typescript +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + dts: true, + clean: true, + sourcemap: true, + target: 'es2022', +}); +``` + +**block.ts (Required by validator):** +```typescript +import { myTool } from './src/index.js'; + +export const block = { + name: 'my-tool', + description: 'Short description', + tools: { myTool }, +}; + +export default block; +``` + +**index.ts (Root re-export):** +```typescript +export * from './src/index.js'; +export { default } from './src/index.js'; +``` + +### 3. Implement the Tool + +**src/index.ts:** +```typescript +import { jsonSchema, tool } from 'ai'; + +// Define input/output types +interface MyToolInput { + param1: string; + param2?: number; +} + +interface MyToolResult { + data: string; + metadata: { + processedAt: string; + }; +} + +// Export the tool using AI SDK v6 pattern +export const myTool = tool({ + description: 'Clear description for LLMs explaining what this tool does and when to use it.', + parameters: jsonSchema({ + type: 'object', + properties: { + param1: { + type: 'string', + description: 'Description of param1', + }, + param2: { + type: 'number', + description: 'Optional description of param2', + }, + }, + required: ['param1'], + }), + async execute(input): Promise { + // REAL implementation - no stubs, no TODOs + const result = await doSomething(input.param1); + + return { + data: result, + metadata: { + processedAt: new Date().toISOString(), + }, + }; + }, +}); + +// Default export for compatibility +export default myTool; +``` + +### 4. Run Validation + +```bash +cd packages/tools/official + +# Validate your tool +pnpm blocks run my-tool + +# The validator runs 3 stages: +# 1. schema - Validates inputs/outputs match blocks.yml +# 2. shape - Verifies exports and structure +# 3. domain - Checks domain rules are satisfied +``` + +**Common validation errors:** +- `Required file "index.ts" not found` - Need index.ts at package root +- `Required file "block.ts" not found` - Need block.ts at package root +- `Tool "myTool" not found in exports` - Export name must match blocks.yml +- `invalid tpmjs field` - Category must be valid, tools array required + +### 5. Build and Publish + +```bash +# Build the package +pnpm build + +# Publish to npm +npm publish --access public + +# Trigger sync to tpmjs.com +source apps/web/.env.local +curl -X POST https://tpmjs.com/api/sync/keyword \ + -H "Authorization: Bearer $CRON_SECRET" +``` + +## Multi-Tool Packages + +For packages with multiple tools (like unsandbox): + +**blocks.yml:** +```yaml +blocks: + sandbox.executeCodeAsync: + type: utility + path: "unsandbox" # Same path for all tools in package + # ... + + sandbox.getJob: + type: utility + path: "unsandbox" # Same path + # ... +``` + +**block.ts:** +```typescript +import { executeCodeAsync, getJob, listJobs } from './src/index.js'; + +export const block = { + name: 'unsandbox', + tools: { executeCodeAsync, getJob, listJobs }, +}; + +export default block; +``` + +**package.json tpmjs field:** +```json +{ + "tpmjs": { + "category": "sandbox", + "frameworks": ["vercel-ai"], + "tools": [ + { "name": "executeCodeAsync", "description": "..." }, + { "name": "getJob", "description": "..." }, + { "name": "listJobs", "description": "..." } + ] + } +} +``` + +## Philosophy (from blocks.yml) + +- Every tool MUST be a working, production-ready implementation - no stubs, no TODOs +- Tools use AI SDK v6 `tool()` + `jsonSchema()` pattern exclusively +- Each tool does ONE thing exceptionally well (single-shot, one call in, one result out) +- Tools return structured, typed outputs that agents can reliably parse +- Error handling is explicit - throw meaningful errors, never silently fail +- Dependencies are minimal and production-stable + +## Domain Entities + +When defining outputs, reference existing entities from blocks.yml: + +```yaml +# Example entities available: +url: [href, domain, protocol, path, query, fragment] +webpage: [url, title, html, text, metadata] +text_content: [raw, sentences, paragraphs, wordCount] +claim: [statement, confidence, needsCitation, category] +timeline: [events, dateRange, gaps, eventCount] +``` + +Or define new entities in the `domain.entities` section if needed. + +## Quality Measures + +Reference these in your tool's `measures` array: + +- `working_implementation` - No stubs, TODOs, or placeholders +- `valid_output_structure` - Returns correct typed object +- `proper_error_handling` - Throws descriptive errors +- `ai_sdk_compliance` - Uses tool() and jsonSchema() +- `npm_publishable` - Valid package.json with tpmjs field +- `readme_documentation` - Has README with examples + +## Debugging Tips + +```bash +# Force rebuild without cache +pnpm blocks run my-tool --force --no-cache + +# See JSON output for debugging +pnpm blocks run my-tool --json + +# Check if validator finds your package +ls packages/tools/official/my-tool/ +# Must have: index.ts, block.ts at root level +``` diff --git a/CLAUDE.md b/CLAUDE.md index 97d9cd5..6995eac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,7 +26,37 @@ This project uses a Turborepo monorepo architecture with the following structure ### Architecture Principles +#### 1. Design System First +**Always use `@tpmjs/ui` components instead of raw HTML elements.** This ensures visual consistency, accessibility, and maintainability across the application. + +```typescript +// Good - use design system components +import { Button } from '@tpmjs/ui/Button/Button'; +import { Input } from '@tpmjs/ui/Input/Input'; +import { Table, TableRow, TableCell } from '@tpmjs/ui/Table/Table'; + + + + +// Bad - raw HTML elements + + +``` + +**When to create/update UI components:** +- If a pattern is used in 2+ places, create a reusable component in `@tpmjs/ui` +- If existing component styling doesn't match the design, update the component (not the usage site) +- If you need virtualization (e.g., `react-virtuoso`), match the design system's styling classes + +**Common components to use:** +- `Button` - all clickable actions +- `Input`, `Select`, `Textarea` - form inputs +- `Table`, `TableRow`, `TableCell` - data tables +- `Card` - content containers +- `Badge` - status indicators +- `Icon` - all icons (not inline SVGs) +- `Spinner`, `Skeleton` - loading states #### 2. No Barrel Exports diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md new file mode 100644 index 0000000..cfd8d2f --- /dev/null +++ b/DESIGN_SYSTEM.md @@ -0,0 +1,559 @@ +# TPMJS Design System Specification + +> A technical, precise design system inspired by [turbopuffer.com](https://turbopuffer.com) - warm, monospace-driven, with generous whitespace and fieldset-style containers. + +--- + +## Brand Direction + +### Mood & Personality +- **Technical & Precise** - Engineering-focused, trustworthy, developer-first +- **Warm & Distinctive** - Not cold/corporate, the copper accent adds warmth +- **Confident & Minimal** - Let the content speak, reduce visual noise + +### Reference Sites +- [turbopuffer.com](https://turbopuffer.com) - Primary inspiration +- Linear, Vercel - Secondary references for technical clarity + +--- + +## Color Palette + +### Primary Accent +```css +--color-accent: #A6592D; /* Copper/terracotta - primary brand color */ +--color-accent-hover: #8B4A26; /* Darker copper for hover states */ +--color-accent-light: #D4A574; /* Light copper for backgrounds/highlights */ +``` + +### Gradient Header +```css +/* Warm gradient for top bar/hero sections */ +--gradient-header: linear-gradient(135deg, #D4732A 0%, #8B3D1A 50%, #2D1810 100%); +``` + +### Neutral Palette +```css +/* Backgrounds */ +--color-bg-primary: #FFFFFF; /* Main background */ +--color-bg-secondary: #FAFAFA; /* Subtle sections */ +--color-bg-elevated: #FFFFFF; /* Cards, elevated surfaces */ + +/* Text */ +--color-text-primary: #1A1A1A; /* Primary text - near black */ +--color-text-secondary: #666666; /* Secondary/muted text */ +--color-text-tertiary: #999999; /* Placeholder, hints */ + +/* Borders */ +--color-border: #E5E5E5; /* Default borders */ +--color-border-strong: #CCCCCC; /* Emphasized borders */ +--color-border-focus: #A6592D; /* Focus state - uses accent */ +``` + +### Semantic Colors +```css +--color-success: #22C55E; +--color-error: #EF4444; +--color-warning: #F59E0B; +--color-info: #3B82F6; +``` + +### Dark Mode (Future) +```css +/* Dark mode should invert while keeping the warm accent */ +--color-bg-primary-dark: #0D0D0D; +--color-bg-secondary-dark: #1A1A1A; +--color-text-primary-dark: #F5F5F5; +--color-border-dark: #333333; +``` + +--- + +## Typography + +### Font Stack + +**Headings & Code: Monospace** +```css +--font-mono: 'JetBrains Mono', 'IBM Plex Mono', 'Fira Code', monospace; +``` + +**Body Text: Sans-serif (for longer reading)** +```css +--font-sans: 'Inter', 'IBM Plex Sans', system-ui, sans-serif; +``` + +### Type Scale + +| Element | Font | Size | Weight | Line Height | Letter Spacing | +|---------|------|------|--------|-------------|----------------| +| H1 | Mono | 48px (3rem) | 600 | 1.1 | -0.02em | +| H2 | Mono | 36px (2.25rem) | 600 | 1.2 | -0.01em | +| H3 | Mono | 24px (1.5rem) | 600 | 1.3 | 0 | +| H4 | Mono | 20px (1.25rem) | 600 | 1.4 | 0 | +| Body Large | Sans | 18px (1.125rem) | 400 | 1.7 | 0 | +| Body | Sans | 16px (1rem) | 400 | 1.7 | 0 | +| Body Small | Sans | 14px (0.875rem) | 400 | 1.6 | 0 | +| Caption | Sans | 12px (0.75rem) | 400 | 1.5 | 0.01em | +| Code | Mono | 14px (0.875rem) | 400 | 1.6 | 0 | + +### Typography Rules +1. **Headings are lowercase** - "pricing", "faq", "tools" (not "Pricing", "FAQ", "Tools") +2. **Generous line-height** - Minimum 1.6 for body text, 1.7 preferred +3. **Bold sparingly** - Use weight 600 for emphasis, not 700+ +4. **Monospace for data** - Numbers, metrics, technical values always in mono + +### CSS Variables +```css +/* Font families */ +--font-heading: var(--font-mono); +--font-body: var(--font-sans); +--font-code: var(--font-mono); + +/* Font sizes */ +--text-xs: 0.75rem; /* 12px */ +--text-sm: 0.875rem; /* 14px */ +--text-base: 1rem; /* 16px */ +--text-lg: 1.125rem; /* 18px */ +--text-xl: 1.25rem; /* 20px */ +--text-2xl: 1.5rem; /* 24px */ +--text-3xl: 2.25rem; /* 36px */ +--text-4xl: 3rem; /* 48px */ + +/* Line heights */ +--leading-tight: 1.2; +--leading-normal: 1.5; +--leading-relaxed: 1.7; + +/* Font weights */ +--font-normal: 400; +--font-medium: 500; +--font-semibold: 600; +``` + +--- + +## Spacing + +### Spacing Scale +```css +--space-0: 0; +--space-1: 0.25rem; /* 4px */ +--space-2: 0.5rem; /* 8px */ +--space-3: 0.75rem; /* 12px */ +--space-4: 1rem; /* 16px */ +--space-5: 1.25rem; /* 20px */ +--space-6: 1.5rem; /* 24px */ +--space-8: 2rem; /* 32px */ +--space-10: 2.5rem; /* 40px */ +--space-12: 3rem; /* 48px */ +--space-16: 4rem; /* 64px */ +--space-20: 5rem; /* 80px */ +--space-24: 6rem; /* 96px */ +``` + +### Spacing Philosophy +- **Generous whitespace** - When in doubt, add more space +- **Vertical rhythm** - Use consistent spacing between sections (typically `--space-16` to `--space-24`) +- **Component padding** - Cards and containers use `--space-6` to `--space-8` +- **Text spacing** - Paragraphs separated by `--space-4` to `--space-6` + +--- + +## Borders & Containers + +### Border Radius +```css +--radius-none: 0; /* DEFAULT - sharp corners */ +--radius-sm: 2px; /* Use sparingly for special cases */ +--radius-md: 4px; /* Use sparingly for special cases */ +``` + +**Rule: Default to 0 border-radius. Sharp corners are the brand.** + +### Border Styles + +**Dashed (Primary)** +```css +border: 1px dashed var(--color-border); +``` + +**Solid (Emphasis)** +```css +border: 2px solid var(--color-text-primary); /* Featured items */ +``` + +### Fieldset-Style Containers + +The signature container style with a label that "cuts into" the border: + +```html +
+ section title + +
+``` + +```css +.fieldset-container { + border: 1px dashed var(--color-border); + padding: var(--space-6); + margin: 0; +} + +.fieldset-container legend { + font-family: var(--font-mono); + font-size: var(--text-sm); + color: var(--color-text-secondary); + padding: 0 var(--space-2); + text-transform: lowercase; +} +``` + +### Container Variants + +| Variant | Border | Background | Use Case | +|---------|--------|------------|----------| +| Default | 1px dashed | transparent | Most containers | +| Elevated | 1px dashed | white | Cards on gray bg | +| Featured | 2px solid | white | Highlighted item | +| Ghost | none | transparent | Minimal grouping | + +--- + +## Components + +### Buttons + +**Primary Button (Accent)** +```css +.btn-primary { + background: var(--color-accent); + color: white; + border: none; + padding: var(--space-3) var(--space-6); + font-family: var(--font-mono); + font-size: var(--text-sm); + font-weight: var(--font-medium); + cursor: pointer; + transition: background 150ms ease; +} + +.btn-primary:hover { + background: var(--color-accent-hover); +} +``` + +**Secondary Button (Outline)** +```css +.btn-secondary { + background: transparent; + color: var(--color-text-primary); + border: 1px solid var(--color-border); + padding: var(--space-3) var(--space-6); + font-family: var(--font-mono); + font-size: var(--text-sm); + font-weight: var(--font-medium); + cursor: pointer; + transition: border-color 150ms ease; +} + +.btn-secondary:hover { + border-color: var(--color-text-primary); +} +``` + +**Button Sizes** +| Size | Padding | Font Size | +|------|---------|-----------| +| sm | `--space-2` `--space-4` | `--text-xs` | +| md | `--space-3` `--space-6` | `--text-sm` | +| lg | `--space-4` `--space-8` | `--text-base` | + +### Links + +```css +a { + color: var(--color-text-primary); + text-decoration: underline; + text-underline-offset: 3px; + transition: opacity 150ms ease; +} + +a:hover { + opacity: 0.7; +} +``` + +**Rule: Links are underlined, not colored.** Use underline as the primary affordance. + +### Inputs + +```css +.input { + width: 100%; + padding: var(--space-3) var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--radius-none); + font-family: var(--font-mono); + font-size: var(--text-base); + background: white; + transition: border-color 150ms ease; +} + +.input:focus { + outline: none; + border-color: var(--color-accent); +} + +.input::placeholder { + color: var(--color-text-tertiary); +} +``` + +### Cards + +```css +.card { + border: 1px dashed var(--color-border); + padding: var(--space-6); + background: white; +} + +.card--featured { + border: 2px solid var(--color-text-primary); +} + +.card__title { + font-family: var(--font-mono); + font-size: var(--text-lg); + font-weight: var(--font-semibold); + text-transform: lowercase; + margin-bottom: var(--space-2); +} + +.card__description { + font-family: var(--font-sans); + font-size: var(--text-base); + color: var(--color-text-secondary); + line-height: var(--leading-relaxed); +} +``` + +### Badges + +```css +.badge { + display: inline-flex; + align-items: center; + padding: var(--space-1) var(--space-3); + font-family: var(--font-mono); + font-size: var(--text-xs); + border: 1px solid currentColor; + text-transform: lowercase; +} + +.badge--default { color: var(--color-text-secondary); } +.badge--success { color: var(--color-success); } +.badge--error { color: var(--color-error); } +.badge--warning { color: var(--color-warning); } +``` + +### Tables + +```css +.table-container { + border: 1px dashed var(--color-border); + overflow: hidden; +} + +.table { + width: 100%; + border-collapse: collapse; + font-family: var(--font-mono); + font-size: var(--text-sm); +} + +.table th { + text-align: left; + padding: var(--space-4); + border-bottom: 1px dashed var(--color-border); + font-weight: var(--font-semibold); + text-transform: lowercase; +} + +.table td { + padding: var(--space-4); + border-bottom: 1px dashed var(--color-border); +} + +.table tr:last-child td { + border-bottom: none; +} +``` + +--- + +## Layout + +### Container Widths +```css +--container-sm: 640px; +--container-md: 768px; +--container-lg: 1024px; +--container-xl: 1280px; +``` + +### Page Structure +``` +┌─────────────────────────────────────────────┐ +│ Gradient Header Bar (announcement) │ +├─────────────────────────────────────────────┤ +│ Navigation (sticky, white bg) │ +├─────────────────────────────────────────────┤ +│ │ +│ Hero Section │ +│ (generous padding: --space-24) │ +│ │ +├─────────────────────────────────────────────┤ +│ │ +│ Content Sections │ +│ (separated by --space-16 to --space-24) │ +│ │ +│ ┌─ fieldset container ─────────────────┐ │ +│ │ section title │ │ +│ │ │ │ +│ │ Content with generous padding │ │ +│ │ │ │ +│ └──────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────┘ +``` + +--- + +## Interactions + +### Hover States +- **Buttons**: Background color change (accent → darker) +- **Links**: Opacity reduction to 0.7 +- **Cards**: Border color change (border → border-strong) +- **No transforms** - Avoid scale/translate on hover (too playful) + +### Focus States +```css +*:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; +} +``` + +### Transitions +```css +--transition-fast: 150ms ease; +--transition-base: 200ms ease; +--transition-slow: 300ms ease; +``` + +**Rule: Keep transitions subtle and fast. No bouncy/spring animations.** + +--- + +## Special Elements + +### Gradient Header Bar +```css +.header-bar { + background: var(--gradient-header); + color: white; + padding: var(--space-2) var(--space-4); + font-family: var(--font-mono); + font-size: var(--text-sm); + text-align: center; +} +``` + +### Technical Diagrams +Use ASCII-style box diagrams with monospace font: +``` +┌─────────────┐ ┌─────────────┐ +│ client │─────▶│ API │ +└─────────────┘ └─────────────┘ +``` + +### Code Blocks +```css +.code-block { + background: var(--color-bg-secondary); + border: 1px dashed var(--color-border); + padding: var(--space-4); + font-family: var(--font-mono); + font-size: var(--text-sm); + overflow-x: auto; +} +``` + +### Sliders/Range Inputs +Custom styled with accent color, monospace tooltips showing values. + +--- + +## Do's and Don'ts + +### Do +- Use lowercase for headings +- Use dashed borders for containers +- Use generous whitespace +- Use monospace for technical content +- Use underlines for links +- Keep interactions subtle and fast +- Use the copper accent sparingly but confidently + +### Don't +- Don't use rounded corners (except for special cases) +- Don't use drop shadows +- Don't use gradients (except header bar) +- Don't use icons where text works +- Don't use colored links +- Don't use bouncy animations +- Don't use multiple accent colors + +--- + +## Implementation Priority + +### Phase 1: Foundation +1. Update CSS variables (colors, spacing, typography) +2. Install fonts (JetBrains Mono, Inter) +3. Update base styles (reset, typography) + +### Phase 2: Core Components +1. Button variants +2. Input/Form elements +3. Card/Container styles +4. Badge variants + +### Phase 3: Layout +1. Fieldset-style containers +2. Page layouts with generous spacing +3. Navigation updates +4. Gradient header bar + +### Phase 4: Polish +1. Table styles +2. Code blocks +3. Interactive elements (sliders, toggles) +4. Transitions and hover states + +--- + +## References + +- **Turbopuffer**: https://turbopuffer.com - Primary design inspiration +- **JetBrains Mono**: https://www.jetbrains.com/lp/mono/ +- **Inter**: https://rsms.me/inter/ + +--- + +*Last updated: January 2025* +*Version: 1.0* diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/src/app/(auth)/sign-in/page.tsx b/apps/web/src/app/(auth)/sign-in/page.tsx index 90ecfce..ffd8a80 100644 --- a/apps/web/src/app/(auth)/sign-in/page.tsx +++ b/apps/web/src/app/(auth)/sign-in/page.tsx @@ -3,6 +3,7 @@ import { Button } from '@tpmjs/ui/Button/Button'; import { Icon } from '@tpmjs/ui/Icon/Icon'; import { Input } from '@tpmjs/ui/Input/Input'; +import { Label } from '@tpmjs/ui/Label/Label'; import Link from 'next/link'; import { useState } from 'react'; import { signIn } from '~/lib/auth-client'; @@ -77,9 +78,7 @@ export default function SignInPage() { )}
- +
- + - +
diff --git a/apps/web/src/app/(profile)/[username]/agents/[uid]/page.tsx b/apps/web/src/app/(profile)/[username]/agents/[uid]/page.tsx index f94de37..961dd7b 100644 --- a/apps/web/src/app/(profile)/[username]/agents/[uid]/page.tsx +++ b/apps/web/src/app/(profile)/[username]/agents/[uid]/page.tsx @@ -125,7 +125,7 @@ export default function PrettyAgentDetailPage(): React.ReactElement { ) : error ? (
-

{error}

+

{error}

) : agent ? (
diff --git a/apps/web/src/app/(profile)/[username]/collections/[slug]/page.tsx b/apps/web/src/app/(profile)/[username]/collections/[slug]/page.tsx index 3cf07c5..39eeeef 100644 --- a/apps/web/src/app/(profile)/[username]/collections/[slug]/page.tsx +++ b/apps/web/src/app/(profile)/[username]/collections/[slug]/page.tsx @@ -255,7 +255,7 @@ export default function PrettyCollectionDetailPage(): React.ReactElement {
) : error ? (
-

{error}

+

{error}

) : collection ? (
diff --git a/apps/web/src/app/(profile)/[username]/page.tsx b/apps/web/src/app/(profile)/[username]/page.tsx index e97f7b5..93504b7 100644 --- a/apps/web/src/app/(profile)/[username]/page.tsx +++ b/apps/web/src/app/(profile)/[username]/page.tsx @@ -83,7 +83,7 @@ export default function UserProfilePage(): React.ReactElement {
) : error ? (
-

{error}

+

{error}

) : profile ? (
diff --git a/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx b/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx index 9edaa85..9dbeb7d 100644 --- a/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx +++ b/apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx @@ -4,6 +4,7 @@ import type { AIProvider } from '@tpmjs/types/agent'; import { Badge } from '@tpmjs/ui/Badge/Badge'; import { Button } from '@tpmjs/ui/Button/Button'; import { Icon } from '@tpmjs/ui/Icon/Icon'; +import { Textarea } from '@tpmjs/ui/Textarea/Textarea'; import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; import { useCallback, useEffect, useRef, useState } from 'react'; @@ -102,10 +103,10 @@ function ToolCallCard({ onToggle: () => void; }) { const statusColors = { - pending: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30', - running: 'bg-blue-500/20 text-blue-400 border-blue-500/30', - success: 'bg-green-500/20 text-green-400 border-green-500/30', - error: 'bg-red-500/20 text-red-400 border-red-500/30', + pending: 'bg-warning/10 text-warning border-warning/30', + running: 'bg-info/10 text-info border-info/30', + success: 'bg-success/10 text-success border-success/30', + error: 'bg-error/10 text-error border-error/30', }; const statusIcons: Record = { @@ -126,10 +127,10 @@ function ToolCallCard({ return (
{/* Header */} - + {/* Expanded Content */} {isExpanded && ( @@ -180,7 +181,7 @@ function ToolCallCard({
-
+              
                 {formatJson(toolCall.output)}
               
@@ -661,14 +662,14 @@ export default function PublicAgentChatPage(): React.ReactElement {
- +

{agent.name}

@@ -683,28 +684,20 @@ export default function PublicAgentChatPage(): React.ReactElement {

{/* View Mode Tabs */}
- - +
@@ -776,13 +769,14 @@ export default function PublicAgentChatPage(): React.ReactElement { Loading older messages...
) : ( - + )}
) : null, @@ -950,22 +944,23 @@ export default function PublicAgentChatPage(): React.ReactElement { {/* Error Message */} {error && ( -
-

{error}

+
+

{error}

)} {/* Input Area */}
-