Initial commit: AI Ops Templates repository
- Schema.org JSON-LD templates (product, event, local-business, faq) - Brand, UI, SEO, and decision guide rules - Working code snippets (vendor-card, schema-inject, deploy-webhook) - JSON schemas for project config validation - Client presets (slc-bride, default) - Self-update protocol with changelog tracking Made-with: Cursor
This commit is contained in:
commit
3cb8d3cb3f
24 changed files with 2202 additions and 0 deletions
128
.cursor/rules/ai-context-update.md
Normal file
128
.cursor/rules/ai-context-update.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# AI Context Update Rule
|
||||
|
||||
This rule defines when and how context files must be updated to prevent drift.
|
||||
|
||||
---
|
||||
|
||||
## MANDATORY: Update Context After These Events
|
||||
|
||||
When ANY of the following occur, you MUST update the relevant context files BEFORE completing the task:
|
||||
|
||||
### 1. New Feature Added
|
||||
**Update**: Client's SKILL.md with new patterns
|
||||
**File**: `.cursor/skills/{{CLIENT}}/SKILL.md`
|
||||
**Section**: Add to "Recent Changes" and relevant feature sections
|
||||
|
||||
### 2. Brand Colors/Fonts Changed
|
||||
**Update**: Presets file
|
||||
**File**: `ai-ops-templates/presets/{{CLIENT}}.json`
|
||||
**Section**: `brand.colors` or `brand.fonts`
|
||||
|
||||
### 3. New API Endpoint Created
|
||||
**Update**: Project config and context manifest
|
||||
**Files**:
|
||||
- `.ai-cli.json` under `apis` section
|
||||
- `.ai-context.json` under `api_endpoints`
|
||||
|
||||
### 4. New Database Table/Column
|
||||
**Update**: Context manifest and relevant skill file
|
||||
**Files**:
|
||||
- `.ai-context.json` under `database_schema.tables`
|
||||
- `.cursor/skills/{{CLIENT}}/SKILL.md` under "Database Schema"
|
||||
|
||||
### 5. New Component Pattern
|
||||
**Update**: Add working example to snippets
|
||||
**File**: `ai-ops-templates/snippets/{{COMPONENT_NAME}}.tsx`
|
||||
**Include**: Full working code with usage comments
|
||||
|
||||
### 6. Bug Fix Revealed New Pattern
|
||||
**Update**: Common fixes documentation
|
||||
**File**: `ai-ops-templates/rules/common-fixes.md`
|
||||
**Include**: Symptom, cause, solution, and code example
|
||||
|
||||
### 7. New MCP Tool Added
|
||||
**Update**: Decision guide
|
||||
**File**: `ai-ops-templates/rules/ai-decision-guide.md`
|
||||
**Section**: "Tool Selection Quick Reference"
|
||||
|
||||
---
|
||||
|
||||
## Update Command Pattern
|
||||
|
||||
After making changes, ALWAYS ask the user:
|
||||
|
||||
> "Should I update the AI context files to reflect these changes?"
|
||||
|
||||
If **yes**, update:
|
||||
1. The specific file that changed
|
||||
2. The `CHANGELOG.md` with date and summary
|
||||
3. The `_meta.last_updated` timestamp in the changed file
|
||||
4. Any dependent files that reference the changed file
|
||||
|
||||
---
|
||||
|
||||
## Change Log Format
|
||||
|
||||
Every context file must include a `_meta` section:
|
||||
|
||||
```json
|
||||
{
|
||||
"_meta": {
|
||||
"last_updated": "2026-03-06T15:52:00Z",
|
||||
"version": "1.0.0",
|
||||
"updated_by": "ai-agent",
|
||||
"change_summary": "Description of what changed"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For Markdown files, use this footer format:
|
||||
|
||||
```markdown
|
||||
---
|
||||
## Context Metadata (Auto-Updated)
|
||||
_last_updated: 2026-03-06
|
||||
_version: 1.0.0
|
||||
_synced_from: git.abundancepartners.app/abundance/ai-ops-templates
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files That Require _meta Sections
|
||||
|
||||
| File Type | _meta Required |
|
||||
|-----------|----------------|
|
||||
| JSON configs | Yes |
|
||||
| JSON schemas | Yes |
|
||||
| TypeScript snippets | No (use comments) |
|
||||
| Shell scripts | No (use comments) |
|
||||
| Markdown files | Yes (footer format) |
|
||||
|
||||
---
|
||||
|
||||
## Example Update Workflow
|
||||
|
||||
```
|
||||
User: "Add a new vendor search feature"
|
||||
|
||||
AI Actions:
|
||||
1. Implement the feature
|
||||
2. Ask: "Should I update the AI context files?"
|
||||
3. If yes:
|
||||
a. Add to snippets/vendor-search.tsx
|
||||
b. Update .ai-context.json with new API endpoint
|
||||
c. Update SKILL.md with new pattern
|
||||
d. Update CHANGELOG.md
|
||||
e. Update last-sync.json timestamps
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Never Skip Context Updates
|
||||
|
||||
Context that isn't updated becomes actively harmful:
|
||||
- Wrong patterns get repeated
|
||||
- Time is wasted re-explaining the same things
|
||||
- AI hallucinations increase
|
||||
|
||||
Always update context. It takes 30 seconds but saves hours later.
|
||||
108
README.md
Normal file
108
README.md
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# AI Ops Templates
|
||||
|
||||
A curated repository of templates, rules, snippets, and presets for AI-assisted development across all Abundance client projects.
|
||||
|
||||
## Purpose
|
||||
|
||||
This repository provides structured context that enables AI agents (like Cursor's Composer) to:
|
||||
- Build features 10x faster with accurate brand/style context
|
||||
- Follow established patterns without re-explanation
|
||||
- Maintain consistency across all client projects
|
||||
- Self-update context to prevent drift
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
ai-ops-templates/
|
||||
├── templates/ # Schema.org JSON-LD templates
|
||||
│ ├── schema-product.json
|
||||
│ ├── schema-event.json
|
||||
│ ├── schema-local-business.json
|
||||
│ └── schema-faq.json
|
||||
├── rules/ # Development rules and guidelines
|
||||
│ ├── tailwind-brand.json
|
||||
│ ├── ui-fixes.json
|
||||
│ ├── seo-rules.md
|
||||
│ ├── common-fixes.md
|
||||
│ └── ai-decision-guide.md
|
||||
├── snippets/ # Reusable code components
|
||||
│ ├── add-internal-link.tsx
|
||||
│ ├── alt-text-placeholder.go
|
||||
│ ├── breadcrumb.tsx
|
||||
│ ├── vendor-card.tsx
|
||||
│ ├── schema-inject.ts
|
||||
│ └── deploy-webhook.sh
|
||||
├── schemas/ # JSON schemas for validation
|
||||
│ ├── ai-cli.schema.json
|
||||
│ └── ai-context.schema.json
|
||||
├── presets/ # Client brand presets
|
||||
│ ├── slc-bride.json
|
||||
│ └── default.json
|
||||
├── context/ # Context tracking
|
||||
│ ├── CHANGELOG.md
|
||||
│ └── last-sync.json
|
||||
├── skill-templates/ # Templates for client SKILL.md files
|
||||
│ └── client-skill.template.md
|
||||
└── .cursor/rules/ # Cursor-specific rules
|
||||
└── ai-context-update.md
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### For AI Agents
|
||||
|
||||
1. Read `.ai-context.json` in the project root first
|
||||
2. Reference `presets/<client>.json` for brand rules
|
||||
3. Use `snippets/` for component patterns
|
||||
4. Check `rules/ai-decision-guide.md` for tool selection
|
||||
5. Update context after making changes (see `.cursor/rules/ai-context-update.md`)
|
||||
|
||||
### For Humans
|
||||
|
||||
1. Copy `presets/default.json` to create new client presets
|
||||
2. Use `skill-templates/client-skill.template.md` for new client SKILL.md files
|
||||
3. Update `CHANGELOG.md` when making changes
|
||||
4. Run `context/last-sync.json` updates via the MCP tool
|
||||
|
||||
## Per-Project Setup
|
||||
|
||||
Each client project needs:
|
||||
|
||||
1. **`.ai-cli.json`** - Project configuration (use `schemas/ai-cli.schema.json`)
|
||||
2. **`.ai-context.json`** - Context manifest (use `schemas/ai-context.schema.json`)
|
||||
3. **`.cursor/skills/{client}/SKILL.md`** - Client-specific skill file
|
||||
|
||||
## Self-Update Protocol
|
||||
|
||||
This repository uses a self-update protocol to prevent context drift. See `.cursor/rules/ai-context-update.md` for details.
|
||||
|
||||
Key triggers for updates:
|
||||
- New feature added
|
||||
- Brand changes
|
||||
- New API endpoints
|
||||
- Database schema changes
|
||||
- Bug fixes that reveal patterns
|
||||
|
||||
## Adding New Clients
|
||||
|
||||
1. Create preset: `presets/{client-slug}.json`
|
||||
2. Create skill file: `.cursor/skills/{client-slug}/SKILL.md`
|
||||
3. Add to project: `.ai-cli.json` and `.ai-context.json`
|
||||
4. Update this README if needed
|
||||
|
||||
## Repository URL
|
||||
|
||||
```
|
||||
https://git.abundancepartners.app/abundance/ai-ops-templates
|
||||
```
|
||||
|
||||
Raw file access:
|
||||
```
|
||||
https://git.abundancepartners.app/abundance/ai-ops-templates/raw/main/{path}
|
||||
```
|
||||
|
||||
## Version
|
||||
|
||||
- **Version**: 1.0.0
|
||||
- **Last Updated**: 2026-03-06
|
||||
- **Maintained By**: AI Agent (auto-updated)
|
||||
52
context/CHANGELOG.md
Normal file
52
context/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# AI Context Changelog
|
||||
|
||||
This file tracks all changes to the ai-ops-templates repository. Updates are logged automatically when context files are modified.
|
||||
|
||||
---
|
||||
|
||||
## Format
|
||||
|
||||
```
|
||||
## [YYYY-MM-DD] - Version X.X.X
|
||||
|
||||
### Added
|
||||
- New features or files
|
||||
|
||||
### Changed
|
||||
- Modifications to existing files
|
||||
|
||||
### Fixed
|
||||
- Bug fixes or corrections
|
||||
|
||||
### Context Updates
|
||||
- Changes triggered by project work
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [2026-03-06] - Version 1.0.0
|
||||
|
||||
### Added
|
||||
- Initial repository structure
|
||||
- templates/ - Schema.org JSON-LD templates (product, event, local-business, faq)
|
||||
- rules/ - Brand, UI, SEO, and decision guide rules
|
||||
- snippets/ - Reusable code components and utilities
|
||||
- schemas/ - JSON schemas for configuration validation
|
||||
- presets/ - Client brand presets (slc-bride, default)
|
||||
- context/ - Changelog and sync tracking
|
||||
- skill-templates/ - Client SKILL.md template
|
||||
|
||||
### Architecture
|
||||
- Self-update protocol implemented
|
||||
- Meta sections in all JSON files for version tracking
|
||||
- Decision guide for AI tool/pattern selection
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
To add new entries:
|
||||
1. Add new section with date and version
|
||||
2. List changes in appropriate category
|
||||
3. Update last-sync.json timestamp
|
||||
4. Commit changes
|
||||
-->
|
||||
42
context/last-sync.json
Normal file
42
context/last-sync.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"_meta": {
|
||||
"last_updated": "2026-03-06T15:52:00Z",
|
||||
"version": "1.0.0",
|
||||
"description": "Timestamp tracking for context synchronization"
|
||||
},
|
||||
"last_sync": "2026-03-06T15:52:00Z",
|
||||
"repository_url": "https://git.abundancepartners.app/abundance/ai-ops-templates",
|
||||
"branch": "main",
|
||||
"commit_hash": "initial",
|
||||
"files": {
|
||||
"templates": {
|
||||
"schema-product.json": "2026-03-06T15:52:00Z",
|
||||
"schema-event.json": "2026-03-06T15:52:00Z",
|
||||
"schema-local-business.json": "2026-03-06T15:52:00Z",
|
||||
"schema-faq.json": "2026-03-06T15:52:00Z"
|
||||
},
|
||||
"rules": {
|
||||
"tailwind-brand.json": "2026-03-06T15:52:00Z",
|
||||
"ui-fixes.json": "2026-03-06T15:52:00Z",
|
||||
"seo-rules.md": "2026-03-06T15:52:00Z",
|
||||
"common-fixes.md": "2026-03-06T15:52:00Z",
|
||||
"ai-decision-guide.md": "2026-03-06T15:52:00Z"
|
||||
},
|
||||
"snippets": {
|
||||
"add-internal-link.tsx": "2026-03-06T15:52:00Z",
|
||||
"alt-text-placeholder.go": "2026-03-06T15:52:00Z",
|
||||
"breadcrumb.tsx": "2026-03-06T15:52:00Z",
|
||||
"vendor-card.tsx": "2026-03-06T15:52:00Z",
|
||||
"schema-inject.ts": "2026-03-06T15:52:00Z",
|
||||
"deploy-webhook.sh": "2026-03-06T15:52:00Z"
|
||||
},
|
||||
"presets": {
|
||||
"slc-bride.json": "2026-03-06T15:52:00Z",
|
||||
"default.json": "2026-03-06T15:52:00Z"
|
||||
},
|
||||
"schemas": {
|
||||
"ai-cli.schema.json": "2026-03-06T15:52:00Z",
|
||||
"ai-context.schema.json": "2026-03-06T15:52:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
88
presets/default.json
Normal file
88
presets/default.json
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"_meta": {
|
||||
"last_updated": "2026-03-06T15:52:00Z",
|
||||
"version": "1.0.0",
|
||||
"description": "Default brand preset - neutral starting point for new clients"
|
||||
},
|
||||
"client": "default",
|
||||
"site_url": "{{SITE_URL}}",
|
||||
"brand": {
|
||||
"name": "{{BRAND_NAME}}",
|
||||
"tagline": "{{TAGLINE}}",
|
||||
"colors": {
|
||||
"primary": {
|
||||
"50": "#f0f9ff",
|
||||
"100": "#e0f2fe",
|
||||
"200": "#bae6fd",
|
||||
"300": "#7dd3fc",
|
||||
"400": "#38bdf8",
|
||||
"500": "#0ea5e9",
|
||||
"600": "#0284c7",
|
||||
"700": "#0369a1",
|
||||
"800": "#075985",
|
||||
"900": "#0c4a6e",
|
||||
"950": "#082f49"
|
||||
},
|
||||
"secondary": {
|
||||
"50": "#f8fafc",
|
||||
"100": "#f1f5f9",
|
||||
"200": "#e2e8f0",
|
||||
"300": "#cbd5e1",
|
||||
"400": "#94a3b8",
|
||||
"500": "#64748b",
|
||||
"600": "#475569",
|
||||
"700": "#334155",
|
||||
"800": "#1e293b",
|
||||
"900": "#0f172a",
|
||||
"950": "#020617"
|
||||
},
|
||||
"background": "#ffffff",
|
||||
"foreground": "#0f172a",
|
||||
"muted": "#f1f5f9",
|
||||
"mutedForeground": "#64748b",
|
||||
"card": "#ffffff",
|
||||
"cardForeground": "#0f172a",
|
||||
"popover": "#ffffff",
|
||||
"popoverForeground": "#0f172a",
|
||||
"border": "#e2e8f0",
|
||||
"input": "#e2e8f0",
|
||||
"ring": "#0ea5e9",
|
||||
"destructive": "#ef4444",
|
||||
"destructiveForeground": "#fafafa"
|
||||
},
|
||||
"fonts": {
|
||||
"heading": "Inter",
|
||||
"body": "Inter",
|
||||
"mono": "JetBrains Mono"
|
||||
},
|
||||
"borderRadius": {
|
||||
"sm": "0.25rem",
|
||||
"md": "0.5rem",
|
||||
"lg": "0.75rem",
|
||||
"xl": "1rem"
|
||||
},
|
||||
"spacing": {
|
||||
"section": "4rem",
|
||||
"container": "1280px"
|
||||
}
|
||||
},
|
||||
"seo": {
|
||||
"title_template": "{page} | {{BRAND_NAME}}",
|
||||
"default_title": "{{SEO_TITLE}}",
|
||||
"default_description": "{{SEO_DESCRIPTION}}",
|
||||
"schema_types": ["LocalBusiness", "Product"],
|
||||
"social": {
|
||||
"facebook": "{{FACEBOOK_URL}}",
|
||||
"instagram": "{{INSTAGRAM_URL}}",
|
||||
"twitter": "{{TWITTER_URL}}"
|
||||
}
|
||||
},
|
||||
"infrastructure": {
|
||||
"server": "{{SERVER_NAME}}",
|
||||
"server_ip": "{{SERVER_IP}}",
|
||||
"database": "{{DATABASE_NAME}}",
|
||||
"coolify_url": "https://app.abundancepartners.app"
|
||||
},
|
||||
"features": {},
|
||||
"routes": {}
|
||||
}
|
||||
100
presets/slc-bride.json
Normal file
100
presets/slc-bride.json
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"_meta": {
|
||||
"last_updated": "2026-03-06T15:52:00Z",
|
||||
"version": "1.0.0",
|
||||
"description": "SLC Bride brand preset - Salt Lake City wedding vendor directory"
|
||||
},
|
||||
"client": "slc-bride",
|
||||
"site_url": "https://saltlakebride.com",
|
||||
"brand": {
|
||||
"name": "Salt Lake Bride",
|
||||
"tagline": "Your Complete Salt Lake City Wedding Resource",
|
||||
"colors": {
|
||||
"primary": {
|
||||
"50": "#fdf2f8",
|
||||
"100": "#fce7f3",
|
||||
"200": "#fbcfe8",
|
||||
"300": "#f9a8d4",
|
||||
"400": "#f472b6",
|
||||
"500": "#ec4899",
|
||||
"600": "#db2777",
|
||||
"700": "#be185d",
|
||||
"800": "#9d174d",
|
||||
"900": "#831843",
|
||||
"950": "#500724"
|
||||
},
|
||||
"secondary": {
|
||||
"50": "#faf5ff",
|
||||
"100": "#f3e8ff",
|
||||
"200": "#e9d5ff",
|
||||
"300": "#d8b4fe",
|
||||
"400": "#c084fc",
|
||||
"500": "#a855f7",
|
||||
"600": "#9333ea",
|
||||
"700": "#7e22ce",
|
||||
"800": "#6b21a8",
|
||||
"900": "#581c87",
|
||||
"950": "#3b0764"
|
||||
},
|
||||
"background": "#ffffff",
|
||||
"foreground": "#1a1a2e",
|
||||
"muted": "#f4f4f5",
|
||||
"mutedForeground": "#71717a",
|
||||
"card": "#ffffff",
|
||||
"cardForeground": "#1a1a2e",
|
||||
"popover": "#ffffff",
|
||||
"popoverForeground": "#1a1a2e",
|
||||
"border": "#e4e4e7",
|
||||
"input": "#e4e4e7",
|
||||
"ring": "#ec4899",
|
||||
"destructive": "#ef4444",
|
||||
"destructiveForeground": "#fafafa"
|
||||
},
|
||||
"fonts": {
|
||||
"heading": "Playfair Display",
|
||||
"body": "Inter",
|
||||
"mono": "JetBrains Mono"
|
||||
},
|
||||
"borderRadius": {
|
||||
"sm": "0.25rem",
|
||||
"md": "0.5rem",
|
||||
"lg": "0.75rem",
|
||||
"xl": "1rem"
|
||||
},
|
||||
"spacing": {
|
||||
"section": "4rem",
|
||||
"container": "1280px"
|
||||
}
|
||||
},
|
||||
"seo": {
|
||||
"title_template": "{page} | Salt Lake Bride",
|
||||
"default_title": "Salt Lake Bride - Your Complete Salt Lake City Wedding Resource",
|
||||
"default_description": "Find the best wedding vendors, venues, and resources in Salt Lake City. Browse photographers, florists, caterers, and more for your perfect Utah wedding.",
|
||||
"schema_types": ["LocalBusiness", "Event", "FAQPage"],
|
||||
"social": {
|
||||
"facebook": "https://facebook.com/saltlakebride",
|
||||
"instagram": "https://instagram.com/saltlakebride",
|
||||
"pinterest": "https://pinterest.com/saltlakebride"
|
||||
}
|
||||
},
|
||||
"infrastructure": {
|
||||
"server": "slcbride",
|
||||
"server_ip": "89.117.22.126",
|
||||
"database": "slcbride",
|
||||
"coolify_url": "https://app.abundancepartners.app"
|
||||
},
|
||||
"features": {
|
||||
"vendor_directory": true,
|
||||
"venue_listings": true,
|
||||
"blog": true,
|
||||
"real_weddings": true,
|
||||
"planning_tools": true
|
||||
},
|
||||
"routes": {
|
||||
"vendors": "/vendors",
|
||||
"venues": "/venues",
|
||||
"blog": "/blog",
|
||||
"real_weddings": "/real-weddings",
|
||||
"planning": "/planning"
|
||||
}
|
||||
}
|
||||
166
rules/ai-decision-guide.md
Normal file
166
rules/ai-decision-guide.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# AI Decision Guide
|
||||
|
||||
This document maps common scenarios to the correct tools and patterns.
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Page
|
||||
|
||||
1. Check `.ai-context.json` for route patterns
|
||||
2. Read brand preset from `presets/<client>.json`
|
||||
3. Copy structure from `snippets/` similar pages
|
||||
4. Add schema from `templates/` if SEO-relevant
|
||||
5. Follow heading hierarchy from `rules/seo-rules.md`
|
||||
|
||||
---
|
||||
|
||||
## Deploying Changes
|
||||
|
||||
### ALWAYS follow this sequence:
|
||||
|
||||
```bash
|
||||
# Step 1: Deploy to staging
|
||||
deploy_push({ target: "staging", app_id: "<from .ai-cli.json>" })
|
||||
|
||||
# Step 2: Verify on staging URL
|
||||
# Check: functionality, styling, data loading
|
||||
|
||||
# Step 3: If successful, deploy to prod
|
||||
deploy_push({ target: "prod", app_id: "<from .ai-cli.json>" })
|
||||
```
|
||||
|
||||
### Never skip staging deployment for production changes.
|
||||
|
||||
---
|
||||
|
||||
## Fixing Brand/Style Issues
|
||||
|
||||
1. Read `presets/<client>.json` for brand rules
|
||||
2. Check `rules/ui-fixes.json` for patterns
|
||||
3. Use shadcn MCP for component updates
|
||||
4. Verify dark mode support if applicable
|
||||
|
||||
### Common Patterns:
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Colors not matching | Check CSS variables match preset |
|
||||
| Font not loading | Verify Google Fonts import |
|
||||
| Spacing inconsistent | Use Tailwind spacing scale |
|
||||
|
||||
---
|
||||
|
||||
## Adding Structured Data
|
||||
|
||||
1. Fetch template: `seo_add_schema({ template: "product", data: {...} })`
|
||||
2. Template pulled from Forgejo with current brand
|
||||
3. Inject into page `<head>` as JSON-LD script tag
|
||||
4. Validate with Google Rich Results Test
|
||||
|
||||
### Template Selection:
|
||||
| Content Type | Template |
|
||||
|--------------|----------|
|
||||
| Product/Service | `schema-product.json` |
|
||||
| Event | `schema-event.json` |
|
||||
| Business Listing | `schema-local-business.json` |
|
||||
| FAQ Section | `schema-faq.json` |
|
||||
|
||||
---
|
||||
|
||||
## Database Operations
|
||||
|
||||
1. Read `.ai-cli.json` for database connection info
|
||||
2. Use `supabase_query` with parameterized queries only
|
||||
3. **NEVER** use string concatenation for queries
|
||||
4. Check RLS policies if query fails
|
||||
|
||||
### Safe Query Pattern:
|
||||
```javascript
|
||||
// Correct
|
||||
supabase_query({
|
||||
query: "SELECT * FROM vendors WHERE category = $1 AND active = $2",
|
||||
params: ["photography", true]
|
||||
})
|
||||
|
||||
// WRONG - Never do this
|
||||
supabase_query({
|
||||
query: `SELECT * FROM vendors WHERE category = '${category}'`
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Creating New Components
|
||||
|
||||
1. Check `snippets/` for existing similar components
|
||||
2. Follow brand preset for styling
|
||||
3. Use shadcn/ui as base when possible
|
||||
4. Add accessibility attributes (see `rules/ui-fixes.json`)
|
||||
5. After creation, update context:
|
||||
```
|
||||
context_update({
|
||||
file: "snippets/components.md",
|
||||
section: "new-components",
|
||||
content: { name: "ComponentName", path: "..." }
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handling Errors
|
||||
|
||||
### Step 1: Identify Error Type
|
||||
| Error Type | Check |
|
||||
|------------|-------|
|
||||
| Type Error | TypeScript interfaces, props |
|
||||
| Runtime Error | Console logs, stack trace |
|
||||
| Build Error | Dependencies, env vars |
|
||||
| Database Error | RLS policies, query syntax |
|
||||
|
||||
### Step 2: Check Common Fixes
|
||||
Look in `rules/common-fixes.md` for known solutions
|
||||
|
||||
### Step 3: If New Pattern Found
|
||||
1. Document the fix
|
||||
2. Update `rules/common-fixes.md`
|
||||
3. Run `context_update` to log the change
|
||||
|
||||
---
|
||||
|
||||
## Context Update Required When
|
||||
|
||||
| Event | Action |
|
||||
|-------|--------|
|
||||
| New feature added | Update client's SKILL.md |
|
||||
| Bug fix discovered | Add to `rules/common-fixes.md` |
|
||||
| Brand changed | Update `presets/<client>.json` |
|
||||
| New component pattern | Add to `snippets/` |
|
||||
| New API endpoint | Add to `.ai-cli.json` |
|
||||
| Database schema change | Update relevant schemas |
|
||||
|
||||
### After ANY context update:
|
||||
1. Update `context/CHANGELOG.md`
|
||||
2. Update `last_updated` in `_meta` section
|
||||
3. Notify user if major change
|
||||
|
||||
---
|
||||
|
||||
## Tool Selection Quick Reference
|
||||
|
||||
| Task | Tool |
|
||||
|------|------|
|
||||
| Check MCP connection | `ping()` |
|
||||
| Deploy app | `deploy_push()` |
|
||||
| Add SEO schema | `seo_add_schema()` |
|
||||
| Query database | `supabase_query()` |
|
||||
| Update context files | `context_update()` |
|
||||
| UI components/themes | shadcn MCP |
|
||||
| File operations | filesystem MCP |
|
||||
|
||||
---
|
||||
|
||||
## Emergency Contacts
|
||||
|
||||
If unable to resolve:
|
||||
1. Check server logs: `abundance docker logs <server> <container> -f --tail 100`
|
||||
2. Check health: `abundance health check`
|
||||
3. Escalate with full error context
|
||||
208
rules/common-fixes.md
Normal file
208
rules/common-fixes.md
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
# Common Fixes
|
||||
|
||||
This document tracks recurring issues and their solutions. Update when new patterns are discovered.
|
||||
|
||||
---
|
||||
|
||||
## React/Next.js Issues
|
||||
|
||||
### Hydration Mismatch
|
||||
**Symptom:** "Hydration failed because the server rendered HTML didn't match the client"
|
||||
|
||||
**Solution:**
|
||||
```tsx
|
||||
// Use useEffect for client-only rendering
|
||||
const [mounted, setMounted] = useState(false)
|
||||
useEffect(() => setMounted(true), [])
|
||||
if (!mounted) return null
|
||||
```
|
||||
|
||||
### Event Handler Type Errors
|
||||
**Symptom:** Type errors on onClick handlers
|
||||
|
||||
**Solution:**
|
||||
```tsx
|
||||
// Always type the event parameter
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => handleClick(e)}
|
||||
// For forms
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => handleSubmit(e)}
|
||||
```
|
||||
|
||||
### State Not Updating
|
||||
**Symptom:** State updates not reflecting in UI
|
||||
|
||||
**Solution:**
|
||||
```tsx
|
||||
// For arrays/objects, always create new references
|
||||
setItems([...items, newItem])
|
||||
setUser({ ...user, name: 'New Name' })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tailwind CSS Issues
|
||||
|
||||
### Dark Mode Not Working
|
||||
**Symptom:** Dark mode classes not applying
|
||||
|
||||
**Solution:**
|
||||
```js
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
darkMode: 'class', // or 'media'
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Colors Not Applying
|
||||
**Symptom:** Custom color classes not generating
|
||||
|
||||
**Solution:**
|
||||
```js
|
||||
// tailwind.config.js - extend, don't replace
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: 'var(--primary)',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Form Issues
|
||||
|
||||
### Zod Validation Not Triggering
|
||||
**Symptom:** Form submits with invalid data
|
||||
|
||||
**Solution:**
|
||||
```tsx
|
||||
// Ensure resolver is passed correctly
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
mode: 'onChange', // or 'onBlur', 'all'
|
||||
})
|
||||
```
|
||||
|
||||
### React Hook Form Not Re-rendering
|
||||
**Symptom:** Form values update but UI doesn't
|
||||
|
||||
**Solution:**
|
||||
```tsx
|
||||
// Use watch or useWatch for reactive updates
|
||||
const watchedValue = form.watch('fieldName')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database/Supabase Issues
|
||||
|
||||
### RLS Policy Blocking Queries
|
||||
**Symptom:** "new row violates row-level security policy"
|
||||
|
||||
**Solution:**
|
||||
```sql
|
||||
-- Check policies on table
|
||||
SELECT * FROM pg_policies WHERE tablename = 'your_table';
|
||||
|
||||
-- Add policy for authenticated users
|
||||
CREATE POLICY "Users can insert own data"
|
||||
ON your_table FOR INSERT
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
```
|
||||
|
||||
### Connection Pool Exhausted
|
||||
**Symptom:** "remaining connection slots are reserved"
|
||||
|
||||
**Solution:**
|
||||
- Use Supabase connection pooling (port 6543)
|
||||
- Close connections properly
|
||||
- Use transaction mode for serverless
|
||||
|
||||
---
|
||||
|
||||
## Deployment Issues
|
||||
|
||||
### Build Fails on Server
|
||||
**Symptom:** Works locally but fails in production
|
||||
|
||||
**Common Causes:**
|
||||
1. Environment variables missing
|
||||
2. Node version mismatch
|
||||
3. Missing devDependencies in production
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check Node version
|
||||
node --version
|
||||
|
||||
# Verify env vars are set
|
||||
# Add to Coolify environment variables
|
||||
```
|
||||
|
||||
### Container Health Check Failing
|
||||
**Symptom:** Container restarts repeatedly
|
||||
|
||||
**Solution:**
|
||||
```yaml
|
||||
# docker-compose.yaml - increase health check interval
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Type Errors
|
||||
|
||||
### "Type X is not assignable to type Y"
|
||||
**Symptom:** TypeScript errors in component props
|
||||
|
||||
**Solution:**
|
||||
```tsx
|
||||
// Use proper type imports
|
||||
import type { SomeType } from './types'
|
||||
|
||||
// For component props
|
||||
interface ComponentProps {
|
||||
data: SomeType
|
||||
onClick?: () => void
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Issues
|
||||
|
||||
### Slow Page Load
|
||||
**Diagnosis Steps:**
|
||||
1. Check Network tab for large payloads
|
||||
2. Use Lighthouse for audit
|
||||
3. Check for unnecessary re-renders
|
||||
|
||||
**Quick Fixes:**
|
||||
```tsx
|
||||
// Memoize expensive computations
|
||||
const memoizedValue = useMemo(() => computeExpensive(a, b), [a, b])
|
||||
|
||||
// Memoize components
|
||||
const MemoizedComponent = memo(ExpensiveComponent)
|
||||
|
||||
// Code split
|
||||
const HeavyComponent = lazy(() => import('./HeavyComponent'))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update Log
|
||||
|
||||
| Date | Issue | Solution Added |
|
||||
|------|-------|----------------|
|
||||
| 2026-03-06 | Initial | Created document |
|
||||
|
||||
**To add new fixes:** Update this file and increment version in _meta
|
||||
115
rules/seo-rules.md
Normal file
115
rules/seo-rules.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# SEO Rules
|
||||
|
||||
## Internal Linking
|
||||
|
||||
### Link Patterns
|
||||
- Link to relevant vendor/category pages from content
|
||||
- Use descriptive anchor text (avoid "click here")
|
||||
- Link to related services/products where contextually relevant
|
||||
- Maximum 100 internal links per page
|
||||
|
||||
### URL Structure
|
||||
- Use kebab-case for URLs: `/vendors/wedding-photography`
|
||||
- Include location where relevant: `/venues/salt-lake-city/hotels`
|
||||
- Avoid query parameters for canonical content
|
||||
|
||||
### Anchor Text Guidelines
|
||||
- Include target keywords naturally
|
||||
- Match the linked page's primary topic
|
||||
- Keep under 60 characters
|
||||
- Examples:
|
||||
- Good: "wedding photography packages"
|
||||
- Bad: "click here for more info"
|
||||
|
||||
---
|
||||
|
||||
## Alt Text
|
||||
|
||||
### Format
|
||||
```
|
||||
[Subject] + [Context/Action] + [Relevance to page content]
|
||||
```
|
||||
|
||||
### Examples
|
||||
- Product image: "White wedding dress with lace details at Salt Lake Bride bridal expo"
|
||||
- Vendor photo: "Portrait of wedding photographer Sarah Smith at outdoor ceremony"
|
||||
- Venue image: "Mountain view ceremony space at The Grand America Hotel Salt Lake City"
|
||||
|
||||
### Rules
|
||||
1. Be descriptive but concise (125 characters max)
|
||||
2. Include relevant keywords naturally
|
||||
3. Don't start with "Image of" or "Picture of"
|
||||
4. Describe what's visible, not decorative elements
|
||||
5. For decorative images, use empty alt=""
|
||||
|
||||
---
|
||||
|
||||
## Meta Descriptions
|
||||
|
||||
### Format
|
||||
- 150-160 characters
|
||||
- Include primary keyword near beginning
|
||||
- Include call-to-action when appropriate
|
||||
- Match page content accurately
|
||||
|
||||
### Template
|
||||
```
|
||||
Discover {{SERVICE}} in {{LOCATION}}. {{UNIQUE_VALUE_PROPOSITION}}. Browse {{COUNT}} options on {{SITE_NAME}}.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schema Markup
|
||||
|
||||
### Required Schemas by Page Type
|
||||
| Page Type | Required Schema |
|
||||
|-----------|----------------|
|
||||
| Vendor listing | LocalBusiness |
|
||||
| Product page | Product |
|
||||
| Event page | Event |
|
||||
| FAQ page | FAQPage |
|
||||
| Article | Article |
|
||||
| Homepage | Organization + WebSite |
|
||||
|
||||
### Implementation
|
||||
- Use JSON-LD format in `<head>`
|
||||
- Include all required properties
|
||||
- Validate with Google Rich Results Test
|
||||
|
||||
---
|
||||
|
||||
## Heading Hierarchy
|
||||
|
||||
### Rules
|
||||
1. One H1 per page (main page title)
|
||||
2. H2 for major sections
|
||||
3. H3 for subsections
|
||||
4. Never skip heading levels
|
||||
5. Include keywords naturally in headings
|
||||
|
||||
### Example Structure
|
||||
```
|
||||
H1: Wedding Photographers in Salt Lake City
|
||||
H2: Top Rated Photographers
|
||||
H3: Portrait Photography
|
||||
H3: Documentary Style
|
||||
H2: How to Choose Your Photographer
|
||||
H3: Budget Considerations
|
||||
H3: Style Matching
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Image Optimization
|
||||
|
||||
### Technical Requirements
|
||||
- Format: WebP with JPEG fallback
|
||||
- Max file size: 200KB for hero, 100KB for thumbnails
|
||||
- Use responsive images with srcset
|
||||
- Lazy load images below the fold
|
||||
|
||||
### Naming Convention
|
||||
```
|
||||
[category]-[subject]-[detail]-[location].webp
|
||||
Example: venue-ballroom-crystal-chandeliers-salt-lake-city.webp
|
||||
```
|
||||
68
rules/tailwind-brand.json
Normal file
68
rules/tailwind-brand.json
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"_meta": {
|
||||
"last_updated": "2026-03-06T15:52:00Z",
|
||||
"version": "1.0.0",
|
||||
"description": "Tailwind brand configuration template - customize per client"
|
||||
},
|
||||
"colors": {
|
||||
"primary": {
|
||||
"50": "{{PRIMARY_50}}",
|
||||
"100": "{{PRIMARY_100}}",
|
||||
"200": "{{PRIMARY_200}}",
|
||||
"300": "{{PRIMARY_300}}",
|
||||
"400": "{{PRIMARY_400}}",
|
||||
"500": "{{PRIMARY_500}}",
|
||||
"600": "{{PRIMARY_600}}",
|
||||
"700": "{{PRIMARY_700}}",
|
||||
"800": "{{PRIMARY_800}}",
|
||||
"900": "{{PRIMARY_900}}",
|
||||
"950": "{{PRIMARY_950}}"
|
||||
},
|
||||
"secondary": {
|
||||
"50": "{{SECONDARY_50}}",
|
||||
"100": "{{SECONDARY_100}}",
|
||||
"200": "{{SECONDARY_200}}",
|
||||
"300": "{{SECONDARY_300}}",
|
||||
"400": "{{SECONDARY_400}}",
|
||||
"500": "{{SECONDARY_500}}",
|
||||
"600": "{{SECONDARY_600}}",
|
||||
"700": "{{SECONDARY_700}}",
|
||||
"800": "{{SECONDARY_800}}",
|
||||
"900": "{{SECONDARY_900}}",
|
||||
"950": "{{SECONDARY_950}}"
|
||||
},
|
||||
"background": "{{BACKGROUND}}",
|
||||
"foreground": "{{FOREGROUND}}",
|
||||
"muted": "{{MUTED}}",
|
||||
"mutedForeground": "{{MUTED_FOREGROUND}}",
|
||||
"card": "{{CARD}}",
|
||||
"cardForeground": "{{CARD_FOREGROUND}}",
|
||||
"popover": "{{POPOVER}}",
|
||||
"popoverForeground": "{{POPOVER_FOREGROUND}}",
|
||||
"border": "{{BORDER}}",
|
||||
"input": "{{INPUT}}",
|
||||
"ring": "{{RING}}",
|
||||
"destructive": "{{DESTRUCTIVE}}",
|
||||
"destructiveForeground": "{{DESTRUCTIVE_FOREGROUND}}"
|
||||
},
|
||||
"fonts": {
|
||||
"heading": "{{HEADING_FONT}}",
|
||||
"body": "{{BODY_FONT}}",
|
||||
"mono": "{{MONO_FONT}}"
|
||||
},
|
||||
"borderRadius": {
|
||||
"sm": "{{RADIUS_SM}}",
|
||||
"md": "{{RADIUS_MD}}",
|
||||
"lg": "{{RADIUS_LG}}",
|
||||
"xl": "{{RADIUS_XL}}"
|
||||
},
|
||||
"spacing": {
|
||||
"section": "{{SECTION_SPACING}}",
|
||||
"container": "{{CONTAINER_MAX_WIDTH}}"
|
||||
},
|
||||
"shadows": {
|
||||
"sm": "{{SHADOW_SM}}",
|
||||
"md": "{{SHADOW_MD}}",
|
||||
"lg": "{{SHADOW_LG}}"
|
||||
}
|
||||
}
|
||||
58
rules/ui-fixes.json
Normal file
58
rules/ui-fixes.json
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"_meta": {
|
||||
"last_updated": "2026-03-06T15:52:00Z",
|
||||
"version": "1.0.0",
|
||||
"description": "Common UI fixes and patterns for consistent implementation"
|
||||
},
|
||||
"eventHandling": {
|
||||
"description": "Use onClick handlers with proper event typing",
|
||||
"pattern": "onClick={(e: React.MouseEvent<HTMLButtonElement>) => handleClick(e)}",
|
||||
"avoid": "onClick={handleClick()} or onClick={() => handleClick}"
|
||||
},
|
||||
"darkMode": {
|
||||
"description": "Use CSS variables for dark mode support",
|
||||
"pattern": "className=\"bg-background text-foreground dark:bg-background-dark dark:text-foreground-dark\"",
|
||||
"cssVariables": [
|
||||
"--background",
|
||||
"--foreground",
|
||||
"--muted",
|
||||
"--muted-foreground",
|
||||
"--card",
|
||||
"--card-foreground"
|
||||
],
|
||||
"tailwindConfig": {
|
||||
"darkMode": "class"
|
||||
}
|
||||
},
|
||||
"responsiveDesign": {
|
||||
"mobileFirst": true,
|
||||
"breakpoints": {
|
||||
"sm": "640px",
|
||||
"md": "768px",
|
||||
"lg": "1024px",
|
||||
"xl": "1280px",
|
||||
"2xl": "1536px"
|
||||
},
|
||||
"pattern": "className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3\""
|
||||
},
|
||||
"accessibility": {
|
||||
"description": "Required accessibility attributes",
|
||||
"rules": [
|
||||
"Always include alt text for images",
|
||||
"Use aria-label for icon-only buttons",
|
||||
"Ensure color contrast ratio >= 4.5:1",
|
||||
"Use semantic HTML elements",
|
||||
"Include focus states for interactive elements"
|
||||
]
|
||||
},
|
||||
"formHandling": {
|
||||
"description": "React Hook Form patterns with Zod validation",
|
||||
"pattern": "const form = useForm<FormData>({ resolver: zodResolver(formSchema) })",
|
||||
"validation": "Use Zod schemas for both client and server validation"
|
||||
},
|
||||
"loadingStates": {
|
||||
"skeleton": "Use Skeleton component from shadcn/ui",
|
||||
"spinner": "Use Loader2 icon with animate-spin",
|
||||
"disabled": "Disable buttons during async operations: disabled={isSubmitting}"
|
||||
}
|
||||
}
|
||||
113
schemas/ai-cli.schema.json
Normal file
113
schemas/ai-cli.schema.json
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://git.abundancepartners.app/abundance/ai-ops-templates/schemas/ai-cli.schema.json",
|
||||
"title": "AI CLI Configuration",
|
||||
"description": "Schema for per-project AI CLI configuration",
|
||||
"type": "object",
|
||||
"required": ["project", "infrastructure"],
|
||||
"properties": {
|
||||
"_meta": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"last_updated": { "type": "string", "format": "date-time" },
|
||||
"version": { "type": "string" },
|
||||
"updated_by": { "type": "string" },
|
||||
"change_summary": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "Project identifier matching the preset name"
|
||||
},
|
||||
"preset": {
|
||||
"type": "string",
|
||||
"description": "URL to the brand preset JSON file"
|
||||
},
|
||||
"brand": {
|
||||
"type": "object",
|
||||
"description": "Brand overrides (if different from preset)",
|
||||
"properties": {
|
||||
"colors": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"primary": { "type": "string" },
|
||||
"secondary": { "type": "string" },
|
||||
"background": { "type": "string" },
|
||||
"foreground": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"fonts": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"heading": { "type": "string" },
|
||||
"body": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"infrastructure": {
|
||||
"type": "object",
|
||||
"required": ["server"],
|
||||
"properties": {
|
||||
"server": {
|
||||
"type": "string",
|
||||
"description": "Server short name (e.g., slcbride, abundance)"
|
||||
},
|
||||
"server_ip": {
|
||||
"type": "string",
|
||||
"format": "ipv4"
|
||||
},
|
||||
"database": {
|
||||
"type": "string",
|
||||
"description": "Database identifier"
|
||||
},
|
||||
"coolify_app_id": {
|
||||
"type": "string",
|
||||
"description": "Coolify application UUID"
|
||||
},
|
||||
"coolify_url": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schema_templates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["product", "event", "local-business", "faq"]
|
||||
}
|
||||
},
|
||||
"internal_link_patterns": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"title_template": { "type": "string" },
|
||||
"default_description": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"apis": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"base_url": { "type": "string", "format": "uri" },
|
||||
"auth_type": { "type": "string", "enum": ["bearer", "api_key", "basic"] },
|
||||
"env_key": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "type": "boolean" }
|
||||
},
|
||||
"routes": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
119
schemas/ai-context.schema.json
Normal file
119
schemas/ai-context.schema.json
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://git.abundancepartners.app/abundance/ai-ops-templates/schemas/ai-context.schema.json",
|
||||
"title": "AI Context Manifest",
|
||||
"description": "Schema for per-project AI context manifest - the single source of truth for AI agents",
|
||||
"type": "object",
|
||||
"required": ["client", "infrastructure"],
|
||||
"properties": {
|
||||
"_meta": {
|
||||
"type": "object",
|
||||
"required": ["last_updated", "version"],
|
||||
"properties": {
|
||||
"last_updated": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "ISO 8601 timestamp of last update"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
||||
"description": "Semantic version of context"
|
||||
},
|
||||
"synced_from": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "URL of the templates repo this was synced from"
|
||||
},
|
||||
"updated_by": {
|
||||
"type": "string",
|
||||
"enum": ["ai-agent", "human"],
|
||||
"description": "Who made the last update"
|
||||
}
|
||||
}
|
||||
},
|
||||
"client": {
|
||||
"type": "string",
|
||||
"description": "Client/project identifier"
|
||||
},
|
||||
"brand_preset": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "URL to the brand preset JSON"
|
||||
},
|
||||
"mcp_namespace": {
|
||||
"type": "string",
|
||||
"enum": ["all", "ui", "deploy", "seo", "supabase"],
|
||||
"default": "all",
|
||||
"description": "Which MCP tools are available for this project"
|
||||
},
|
||||
"infrastructure": {
|
||||
"type": "object",
|
||||
"required": ["server"],
|
||||
"properties": {
|
||||
"server": {
|
||||
"type": "string",
|
||||
"description": "Server short name"
|
||||
},
|
||||
"database": {
|
||||
"type": "string",
|
||||
"description": "Database identifier or 'from-env'"
|
||||
},
|
||||
"coolify_app_id": {
|
||||
"type": "string",
|
||||
"description": "Coolify app UUID or 'from-env'"
|
||||
},
|
||||
"coolify_staging_app_id": {
|
||||
"type": "string",
|
||||
"description": "Staging Coolify app UUID"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_patterns": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Paths to commonly used snippet files"
|
||||
},
|
||||
"routes": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "type": "string" },
|
||||
"description": "Route patterns for the application"
|
||||
},
|
||||
"database_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"description": { "type": "string" },
|
||||
"key_columns": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"api_endpoints": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"path": { "type": "string" },
|
||||
"method": { "type": "string", "enum": ["GET", "POST", "PUT", "DELETE", "PATCH"] },
|
||||
"description": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"type": "string",
|
||||
"description": "Any additional context for AI agents"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
173
skill-templates/client-skill.template.md
Normal file
173
skill-templates/client-skill.template.md
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
---
|
||||
name: {{CLIENT_NAME}}
|
||||
description: Context and operations guide for {{CLIENT_NAME}} project. Use when working on {{DESCRIPTION}}.
|
||||
---
|
||||
|
||||
# {{CLIENT_NAME}}
|
||||
|
||||
{{PROJECT_DESCRIPTION}}
|
||||
|
||||
## Project Overview
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Client** | {{CLIENT_DISPLAY_NAME}} |
|
||||
| **Site URL** | {{SITE_URL}} |
|
||||
| **Server** | {{SERVER_NAME}} ({{SERVER_IP}}) |
|
||||
| **Database** | {{DATABASE_NAME}} |
|
||||
| **Status** | {{PROJECT_STATUS}} |
|
||||
|
||||
---
|
||||
|
||||
## Infrastructure
|
||||
|
||||
### Server Access
|
||||
```bash
|
||||
# SSH into server
|
||||
ssh root@{{SERVER_IP}}
|
||||
|
||||
# Or use abundance CLI
|
||||
abundance srv ssh {{SERVER_NAME}}
|
||||
```
|
||||
|
||||
### Coolify Deployment
|
||||
- **Dashboard**: {{COOLIFY_URL}}
|
||||
- **App ID**: {{COOLIFY_APP_ID}}
|
||||
- **Deploy**: `abundance coolify deploy application {{COOLIFY_APP_ID}}`
|
||||
|
||||
### Database
|
||||
```bash
|
||||
# Query database
|
||||
abundance db query {{DATABASE_NAME}} "SELECT * FROM {{TABLE}} LIMIT 5"
|
||||
|
||||
# Create SSH tunnel
|
||||
abundance db tunnel {{DATABASE_NAME}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Brand Guidelines
|
||||
|
||||
### Colors
|
||||
| Color | Hex | Usage |
|
||||
|-------|-----|-------|
|
||||
| Primary | `{{PRIMARY_COLOR}}` | Buttons, links, highlights |
|
||||
| Secondary | `{{SECONDARY_COLOR}}` | Accents, badges |
|
||||
| Background | `{{BACKGROUND_COLOR}}` | Page background |
|
||||
| Text | `{{TEXT_COLOR}}` | Body text |
|
||||
|
||||
### Typography
|
||||
- **Headings**: {{HEADING_FONT}}
|
||||
- **Body**: {{BODY_FONT}}
|
||||
|
||||
### Tailwind Config
|
||||
Brand colors are defined in `presets/{{CLIENT_SLUG}}.json`. Reference via CSS variables:
|
||||
```css
|
||||
background-color: var(--primary);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Routes
|
||||
|
||||
| Route | Description | Component |
|
||||
|-------|-------------|-----------|
|
||||
| {{ROUTE_1}} | {{ROUTE_1_DESC}} | `{{ROUTE_1_COMPONENT}}` |
|
||||
| {{ROUTE_2}} | {{ROUTE_2_DESC}} | `{{ROUTE_2_COMPONENT}}` |
|
||||
| {{ROUTE_3}} | {{ROUTE_3_DESC}} | `{{ROUTE_3_COMPONENT}}` |
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Key Tables
|
||||
| Table | Description | Key Columns |
|
||||
|-------|-------------|-------------|
|
||||
| {{TABLE_1}} | {{TABLE_1_DESC}} | {{TABLE_1_COLS}} |
|
||||
| {{TABLE_2}} | {{TABLE_2_DESC}} | {{TABLE_2_COLS}} |
|
||||
|
||||
### Common Queries
|
||||
```sql
|
||||
-- {{QUERY_1_DESC}}
|
||||
SELECT * FROM {{TABLE}} WHERE {{CONDITION}};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## APIs
|
||||
|
||||
### {{API_1_NAME}}
|
||||
- **Base URL**: `{{API_1_URL}}`
|
||||
- **Auth**: {{API_1_AUTH}}
|
||||
- **Env Key**: `{{API_1_ENV_KEY}}`
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### {{PATTERN_1_NAME}}
|
||||
```{{PATTERN_1_LANG}}
|
||||
{{PATTERN_1_CODE}}
|
||||
```
|
||||
|
||||
### {{PATTERN_2_NAME}}
|
||||
```{{PATTERN_2_LANG}}
|
||||
{{PATTERN_2_CODE}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integrations
|
||||
|
||||
| Service | Purpose | Config Location |
|
||||
|---------|---------|-----------------|
|
||||
| {{SERVICE_1}} | {{SERVICE_1_PURPOSE}} | {{SERVICE_1_CONFIG}} |
|
||||
| {{SERVICE_2}} | {{SERVICE_2_PURPOSE}} | {{SERVICE_2_CONFIG}} |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### {{ISSUE_1}}
|
||||
**Symptom**: {{ISSUE_1_SYMPTOM}}
|
||||
**Solution**: {{ISSUE_1_SOLUTION}}
|
||||
|
||||
### {{ISSUE_2}}
|
||||
**Symptom**: {{ISSUE_2_SYMPTOM}}
|
||||
**Solution**: {{ISSUE_2_SOLUTION}}
|
||||
|
||||
---
|
||||
|
||||
## Recent Changes
|
||||
|
||||
| Date | Change | Notes |
|
||||
|------|--------|-------|
|
||||
| {{DATE_1}} | {{CHANGE_1}} | {{NOTES_1}} |
|
||||
|
||||
---
|
||||
|
||||
## Context Metadata (Auto-Updated)
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| _last_updated | {{LAST_UPDATED}} |
|
||||
| _version | {{VERSION}} |
|
||||
| _synced_from | git.abundancepartners.app/abundance/ai-ops-templates |
|
||||
|
||||
---
|
||||
|
||||
## Quick Commands
|
||||
|
||||
```bash
|
||||
# Check server status
|
||||
abundance srv status
|
||||
|
||||
# View logs
|
||||
abundance docker logs {{SERVER_NAME}} {{CONTAINER}} -f --tail 100
|
||||
|
||||
# Deploy to staging
|
||||
deploy_push({ target: "staging", app_id: "{{STAGING_APP_ID}}" })
|
||||
|
||||
# Deploy to production
|
||||
deploy_push({ target: "prod", app_id: "{{PROD_APP_ID}}" })
|
||||
```
|
||||
39
snippets/add-internal-link.tsx
Normal file
39
snippets/add-internal-link.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import Link from 'next/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface InternalLinkProps {
|
||||
href: string
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
variant?: 'default' | 'muted' | 'underline'
|
||||
}
|
||||
|
||||
export function InternalLink({
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
variant = 'default',
|
||||
}: InternalLinkProps) {
|
||||
const variants = {
|
||||
default: 'text-primary hover:text-primary/80',
|
||||
muted: 'text-muted-foreground hover:text-foreground',
|
||||
underline: 'text-foreground underline underline-offset-4 hover:text-primary',
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className={cn(
|
||||
'transition-colors duration-200',
|
||||
variants[variant],
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
// Usage example:
|
||||
// <InternalLink href="/vendors/photography">Wedding Photography</InternalLink>
|
||||
// <InternalLink href="/venues" variant="muted">View all venues</InternalLink>
|
||||
49
snippets/alt-text-placeholder.go
Normal file
49
snippets/alt-text-placeholder.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package templates
|
||||
|
||||
// AltTextPlaceholder generates SEO-friendly alt text for images
|
||||
// Replace placeholders with actual data before use
|
||||
|
||||
type ImageContext struct {
|
||||
Subject string // What is shown in the image
|
||||
Context string // What's happening / setting
|
||||
Location string // Where the image was taken
|
||||
Relevance string // Why it's relevant to the page
|
||||
}
|
||||
|
||||
func GenerateAltText(ctx ImageContext) string {
|
||||
// Pattern: [Subject] + [Context] + [Location] + [Relevance]
|
||||
// Example: "Wedding photographer capturing moments at outdoor ceremony in Salt Lake City"
|
||||
|
||||
parts := []string{}
|
||||
|
||||
if ctx.Subject != "" {
|
||||
parts = append(parts, ctx.Subject)
|
||||
}
|
||||
if ctx.Context != "" {
|
||||
parts = append(parts, ctx.Context)
|
||||
}
|
||||
if ctx.Location != "" {
|
||||
parts = append(parts, "in "+ctx.Location)
|
||||
}
|
||||
if ctx.Relevance != "" {
|
||||
parts = append(parts, ctx.Relevance)
|
||||
}
|
||||
|
||||
alt := strings.Join(parts, " ")
|
||||
|
||||
// Truncate to 125 characters max (SEO best practice)
|
||||
if len(alt) > 125 {
|
||||
alt = alt[:122] + "..."
|
||||
}
|
||||
|
||||
return alt
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
// alt := GenerateAltText(ImageContext{
|
||||
// Subject: "Bride and groom",
|
||||
// Context: "exchanging vows",
|
||||
// Location: "Salt Lake City Temple",
|
||||
// Relevance: "wedding ceremony",
|
||||
// })
|
||||
// Result: "Bride and groom exchanging vows in Salt Lake City Temple wedding ceremony"
|
||||
78
snippets/breadcrumb.tsx
Normal file
78
snippets/breadcrumb.tsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import Link from 'next/link'
|
||||
import { ChevronRight, Home } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface BreadcrumbItem {
|
||||
label: string
|
||||
href?: string
|
||||
}
|
||||
|
||||
interface BreadcrumbProps {
|
||||
items: BreadcrumbItem[]
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Breadcrumb({ items, className }: BreadcrumbProps) {
|
||||
// Generate JSON-LD structured data for SEO
|
||||
const structuredData = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: items.map((item, index) => ({
|
||||
'@type': 'ListItem',
|
||||
position: index + 1,
|
||||
name: item.label,
|
||||
item: item.href ? `${process.env.NEXT_PUBLIC_SITE_URL}${item.href}` : undefined,
|
||||
})),
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Inject structured data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
|
||||
/>
|
||||
|
||||
{/* Visible breadcrumb */}
|
||||
<nav aria-label="Breadcrumb" className={cn('flex items-center text-sm', className)}>
|
||||
<ol className="flex items-center gap-2">
|
||||
<li>
|
||||
<Link
|
||||
href="/"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||
aria-label="Home"
|
||||
>
|
||||
<Home className="h-4 w-4" />
|
||||
</Link>
|
||||
</li>
|
||||
{items.map((item, index) => (
|
||||
<li key={item.href || index} className="flex items-center gap-2">
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||
{item.href && index < items.length - 1 ? (
|
||||
<Link
|
||||
href={item.href}
|
||||
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-foreground font-medium" aria-current="page">
|
||||
{item.label}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// Usage example:
|
||||
// <Breadcrumb
|
||||
// items={[
|
||||
// { label: 'Vendors', href: '/vendors' },
|
||||
// { label: 'Photography', href: '/vendors/photography' },
|
||||
// { label: 'John Smith Photography' }, // Current page - no href
|
||||
// ]}
|
||||
// />
|
||||
134
snippets/deploy-webhook.sh
Normal file
134
snippets/deploy-webhook.sh
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Deploy Webhook Trigger Script
|
||||
# Triggers Coolify deployment via webhook or API
|
||||
|
||||
# Configuration - Set these in your environment or .env file
|
||||
# COOLIFY_WEBHOOK_URL - The webhook URL from Coolify dashboard
|
||||
# COOLIFY_API_URL - Your Coolify instance URL (e.g., https://app.abundancepartners.app)
|
||||
# COOLIFY_API_TOKEN - Your API token
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Method 1: Trigger via Webhook URL
|
||||
deploy_via_webhook() {
|
||||
local webhook_url=$1
|
||||
|
||||
if [ -z "$webhook_url" ]; then
|
||||
log_error "Webhook URL not provided"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Triggering deployment via webhook..."
|
||||
|
||||
response=$(curl -s -w "\n%{http_code}" -X POST "$webhook_url")
|
||||
http_code=$(echo "$response" | tail -n1)
|
||||
body=$(echo "$response" | sed '$d')
|
||||
|
||||
if [ "$http_code" -eq 200 ] || [ "$http_code" -eq 201 ]; then
|
||||
log_info "Deployment triggered successfully"
|
||||
echo "$body"
|
||||
else
|
||||
log_error "Deployment failed with HTTP $http_code"
|
||||
echo "$body"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Method 2: Trigger via Coolify API
|
||||
deploy_via_api() {
|
||||
local app_uuid=$1
|
||||
local api_url=${COOLIFY_API_URL:-"https://app.abundancepartners.app"}
|
||||
local api_token=$COOLIFY_API_TOKEN
|
||||
|
||||
if [ -z "$app_uuid" ] || [ -z "$api_token" ]; then
|
||||
log_error "App UUID and API token required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Triggering deployment via API for app: $app_uuid"
|
||||
|
||||
response=$(curl -s -w "\n%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: Bearer $api_token" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${api_url}/api/v1/deploy?uuid=${app_uuid}")
|
||||
|
||||
http_code=$(echo "$response" | tail -n1)
|
||||
body=$(echo "$response" | sed '$d')
|
||||
|
||||
if [ "$http_code" -eq 200 ] || [ "$http_code" -eq 201 ]; then
|
||||
log_info "Deployment triggered successfully"
|
||||
echo "$body"
|
||||
else
|
||||
log_error "Deployment failed with HTTP $http_code"
|
||||
echo "$body"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check deployment status
|
||||
check_status() {
|
||||
local app_uuid=$1
|
||||
local api_url=${COOLIFY_API_URL:-"https://app.abundancepartners.app"}
|
||||
local api_token=$COOLIFY_API_TOKEN
|
||||
|
||||
log_info "Checking deployment status..."
|
||||
|
||||
curl -s -H "Authorization: Bearer $api_token" \
|
||||
"${api_url}/api/v1/applications/${app_uuid}" | jq '.'
|
||||
}
|
||||
|
||||
# Usage
|
||||
usage() {
|
||||
echo "Usage: $0 [command] [options]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " webhook <webhook_url> Deploy via webhook URL"
|
||||
echo " api <app_uuid> Deploy via Coolify API"
|
||||
echo " status <app_uuid> Check deployment status"
|
||||
echo ""
|
||||
echo "Environment Variables:"
|
||||
echo " COOLIFY_API_URL Your Coolify instance URL"
|
||||
echo " COOLIFY_API_TOKEN Your API token"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 webhook https://app.abundancepartners.app/api/v1/webhooks/deploy/abc123"
|
||||
echo " $0 api abc123-def456-ghi789"
|
||||
echo " COOLIFY_API_TOKEN=your_token $0 api abc123-def456-ghi789"
|
||||
}
|
||||
|
||||
# Main
|
||||
case "$1" in
|
||||
webhook)
|
||||
deploy_via_webhook "$2"
|
||||
;;
|
||||
api)
|
||||
deploy_via_api "$2"
|
||||
;;
|
||||
status)
|
||||
check_status "$2"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
99
snippets/schema-inject.ts
Normal file
99
snippets/schema-inject.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* Schema Injection Utility
|
||||
* Injects JSON-LD structured data into page head
|
||||
*/
|
||||
|
||||
// Schema type definitions
|
||||
export type SchemaType = 'Product' | 'Event' | 'LocalBusiness' | 'FAQPage' | 'Article' | 'Organization'
|
||||
|
||||
interface SchemaConfig {
|
||||
type: SchemaType
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates JSON-LD script tag for structured data
|
||||
*/
|
||||
export function generateSchemaScript(config: SchemaConfig | SchemaConfig[]): string {
|
||||
const schemas = Array.isArray(config) ? config : [config]
|
||||
|
||||
const structuredData = schemas.map(({ type, data }) => ({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': type,
|
||||
...data,
|
||||
}))
|
||||
|
||||
// Return single schema or array
|
||||
const output = structuredData.length === 1 ? structuredData[0] : structuredData
|
||||
|
||||
return JSON.stringify(output)
|
||||
}
|
||||
|
||||
/**
|
||||
* React component for injecting schema in Next.js
|
||||
*/
|
||||
export function SchemaInjector({ config }: { config: SchemaConfig | SchemaConfig[] }) {
|
||||
const schemaJson = generateSchemaScript(config)
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: schemaJson }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-side schema generation for getServerSideProps / getStaticProps
|
||||
*/
|
||||
export function getSchemaForPage(
|
||||
type: SchemaType,
|
||||
data: Record<string, unknown>
|
||||
): { __html: string } {
|
||||
return {
|
||||
__html: generateSchemaScript({ type, data }),
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage in Next.js page:
|
||||
//
|
||||
// import { SchemaInjector } from '@/lib/schema-inject'
|
||||
//
|
||||
// export default function VendorPage({ vendor }) {
|
||||
// return (
|
||||
// <>
|
||||
// <Head>
|
||||
// <SchemaInjector
|
||||
// config={{
|
||||
// type: 'LocalBusiness',
|
||||
// data: {
|
||||
// name: vendor.name,
|
||||
// description: vendor.description,
|
||||
// address: {
|
||||
// '@type': 'PostalAddress',
|
||||
// streetAddress: vendor.address,
|
||||
// addressLocality: vendor.city,
|
||||
// addressRegion: vendor.state,
|
||||
// },
|
||||
// aggregateRating: {
|
||||
// '@type': 'AggregateRating',
|
||||
// ratingValue: vendor.rating,
|
||||
// reviewCount: vendor.review_count,
|
||||
// },
|
||||
// },
|
||||
// }}
|
||||
// />
|
||||
// </Head>
|
||||
// {/* Page content */}
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// Example for multiple schemas on one page:
|
||||
//
|
||||
// <SchemaInjector
|
||||
// config={[
|
||||
// { type: 'LocalBusiness', data: businessData },
|
||||
// { type: 'FAQPage', data: faqData },
|
||||
// ]}
|
||||
// />
|
||||
92
snippets/vendor-card.tsx
Normal file
92
snippets/vendor-card.tsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Star, MapPin } from 'lucide-react'
|
||||
|
||||
interface Vendor {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
category: string
|
||||
description: string
|
||||
image_url: string
|
||||
rating: number
|
||||
review_count: number
|
||||
location: string
|
||||
price_range?: string
|
||||
}
|
||||
|
||||
interface VendorCardProps {
|
||||
vendor: Vendor
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function VendorCard({ vendor, className }: VendorCardProps) {
|
||||
return (
|
||||
<Link href={`/vendors/${vendor.slug}`}>
|
||||
<Card className={cn(
|
||||
'group overflow-hidden transition-all duration-300 hover:shadow-lg',
|
||||
className
|
||||
)}>
|
||||
{/* Image Container */}
|
||||
<div className="relative aspect-[4/3] overflow-hidden">
|
||||
<Image
|
||||
src={vendor.image_url}
|
||||
alt={`${vendor.name} - ${vendor.category} in ${vendor.location}`}
|
||||
fill
|
||||
className="object-cover transition-transform duration-300 group-hover:scale-105"
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
/>
|
||||
{vendor.price_range && (
|
||||
<Badge className="absolute top-3 right-3" variant="secondary">
|
||||
{vendor.price_range}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<CardContent className="p-4">
|
||||
{/* Category Badge */}
|
||||
<Badge variant="outline" className="mb-2">
|
||||
{vendor.category}
|
||||
</Badge>
|
||||
|
||||
{/* Name */}
|
||||
<h3 className="font-semibold text-lg mb-1 group-hover:text-primary transition-colors">
|
||||
{vendor.name}
|
||||
</h3>
|
||||
|
||||
{/* Rating */}
|
||||
<div className="flex items-center gap-1 mb-2">
|
||||
<Star className="h-4 w-4 fill-yellow-400 text-yellow-400" />
|
||||
<span className="font-medium">{vendor.rating.toFixed(1)}</span>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
({vendor.review_count} reviews)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Location */}
|
||||
<div className="flex items-center gap-1 text-muted-foreground text-sm">
|
||||
<MapPin className="h-3 w-3" />
|
||||
<span>{vendor.location}</span>
|
||||
</div>
|
||||
|
||||
{/* Description - truncated */}
|
||||
<p className="mt-2 text-sm text-muted-foreground line-clamp-2">
|
||||
{vendor.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
// Usage:
|
||||
// import { VendorCard } from '@/components/vendor-card'
|
||||
//
|
||||
// <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
// {vendors.map((vendor) => (
|
||||
// <VendorCard key={vendor.id} vendor={vendor} />
|
||||
// ))}
|
||||
// </div>
|
||||
45
templates/schema-event.json
Normal file
45
templates/schema-event.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"_meta": {
|
||||
"last_updated": "2026-03-06T15:52:00Z",
|
||||
"version": "1.0.0",
|
||||
"description": "Event structured data template (Schema.org JSON-LD)"
|
||||
},
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Event",
|
||||
"name": "{{EVENT_NAME}}",
|
||||
"description": "{{EVENT_DESCRIPTION}}",
|
||||
"image": "{{EVENT_IMAGE_URL}}",
|
||||
"startDate": "{{START_DATE}}",
|
||||
"endDate": "{{END_DATE}}",
|
||||
"eventStatus": "https://schema.org/EventScheduled",
|
||||
"eventAttendanceMode": "https://schema.org/{{ATTENDANCE_MODE}}",
|
||||
"location": {
|
||||
"@type": "{{LOCATION_TYPE}}",
|
||||
"name": "{{LOCATION_NAME}}",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"streetAddress": "{{STREET_ADDRESS}}",
|
||||
"addressLocality": "{{CITY}}",
|
||||
"addressRegion": "{{STATE}}",
|
||||
"postalCode": "{{POSTAL_CODE}}",
|
||||
"addressCountry": "{{COUNTRY}}"
|
||||
}
|
||||
},
|
||||
"organizer": {
|
||||
"@type": "Organization",
|
||||
"name": "{{ORGANIZER_NAME}}",
|
||||
"url": "{{ORGANIZER_URL}}"
|
||||
},
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"url": "{{TICKET_URL}}",
|
||||
"price": "{{PRICE}}",
|
||||
"priceCurrency": "{{CURRENCY}}",
|
||||
"availability": "https://schema.org/{{TICKET_AVAILABILITY}}",
|
||||
"validFrom": "{{SALE_START_DATE}}"
|
||||
},
|
||||
"performer": {
|
||||
"@type": "{{PERFORMER_TYPE}}",
|
||||
"name": "{{PERFORMER_NAME}}"
|
||||
}
|
||||
}
|
||||
35
templates/schema-faq.json
Normal file
35
templates/schema-faq.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"_meta": {
|
||||
"last_updated": "2026-03-06T15:52:00Z",
|
||||
"version": "1.0.0",
|
||||
"description": "FAQ structured data template (Schema.org JSON-LD)"
|
||||
},
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
"mainEntity": [
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "{{QUESTION_1}}",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "{{ANSWER_1}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "{{QUESTION_2}}",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "{{ANSWER_2}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "{{QUESTION_3}}",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "{{ANSWER_3}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
59
templates/schema-local-business.json
Normal file
59
templates/schema-local-business.json
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"_meta": {
|
||||
"last_updated": "2026-03-06T15:52:00Z",
|
||||
"version": "1.0.0",
|
||||
"description": "Local Business structured data template (Schema.org JSON-LD)"
|
||||
},
|
||||
"@context": "https://schema.org",
|
||||
"@type": "{{BUSINESS_TYPE}}",
|
||||
"name": "{{BUSINESS_NAME}}",
|
||||
"description": "{{BUSINESS_DESCRIPTION}}",
|
||||
"image": "{{BUSINESS_IMAGE_URL}}",
|
||||
"url": "{{BUSINESS_URL}}",
|
||||
"telephone": "{{PHONE}}",
|
||||
"email": "{{EMAIL}}",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"streetAddress": "{{STREET_ADDRESS}}",
|
||||
"addressLocality": "{{CITY}}",
|
||||
"addressRegion": "{{STATE}}",
|
||||
"postalCode": "{{POSTAL_CODE}}",
|
||||
"addressCountry": "{{COUNTRY}}"
|
||||
},
|
||||
"geo": {
|
||||
"@type": "GeoCoordinates",
|
||||
"latitude": "{{LATITUDE}}",
|
||||
"longitude": "{{LONGITUDE}}"
|
||||
},
|
||||
"openingHoursSpecification": [
|
||||
{
|
||||
"@type": "OpeningHoursSpecification",
|
||||
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
|
||||
"opens": "{{WEEKDAY_OPEN}}",
|
||||
"closes": "{{WEEKDAY_CLOSE}}"
|
||||
},
|
||||
{
|
||||
"@type": "OpeningHoursSpecification",
|
||||
"dayOfWeek": ["Saturday"],
|
||||
"opens": "{{SAT_OPEN}}",
|
||||
"closes": "{{SAT_CLOSE}}"
|
||||
},
|
||||
{
|
||||
"@type": "OpeningHoursSpecification",
|
||||
"dayOfWeek": ["Sunday"],
|
||||
"opens": "{{SUN_OPEN}}",
|
||||
"closes": "{{SUN_CLOSE}}"
|
||||
}
|
||||
],
|
||||
"priceRange": "{{PRICE_RANGE}}",
|
||||
"aggregateRating": {
|
||||
"@type": "AggregateRating",
|
||||
"ratingValue": "{{RATING_VALUE}}",
|
||||
"reviewCount": "{{REVIEW_COUNT}}"
|
||||
},
|
||||
"sameAs": [
|
||||
"{{FACEBOOK_URL}}",
|
||||
"{{INSTAGRAM_URL}}",
|
||||
"{{TWITTER_URL}}"
|
||||
]
|
||||
}
|
||||
34
templates/schema-product.json
Normal file
34
templates/schema-product.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"_meta": {
|
||||
"last_updated": "2026-03-06T15:52:00Z",
|
||||
"version": "1.0.0",
|
||||
"description": "Product structured data template (Schema.org JSON-LD)"
|
||||
},
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Product",
|
||||
"name": "{{PRODUCT_NAME}}",
|
||||
"description": "{{PRODUCT_DESCRIPTION}}",
|
||||
"image": [
|
||||
"{{PRODUCT_IMAGE_URL}}"
|
||||
],
|
||||
"brand": {
|
||||
"@type": "Brand",
|
||||
"name": "{{BRAND_NAME}}"
|
||||
},
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"url": "{{PRODUCT_URL}}",
|
||||
"priceCurrency": "{{CURRENCY}}",
|
||||
"price": "{{PRICE}}",
|
||||
"availability": "https://schema.org/{{AVAILABILITY}}",
|
||||
"seller": {
|
||||
"@type": "Organization",
|
||||
"name": "{{SELLER_NAME}}"
|
||||
}
|
||||
},
|
||||
"aggregateRating": {
|
||||
"@type": "AggregateRating",
|
||||
"ratingValue": "{{RATING_VALUE}}",
|
||||
"reviewCount": "{{REVIEW_COUNT}}"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue