# CMD+K Command Palette API Documentation
## Overview
The CV application provides a command palette (CMD+K / Ctrl+K) powered by [ninja-keys](https://github.com/nickadam/ninja-keys) web component. Dynamic entries (experiences, projects, courses) are loaded from a backend API endpoint, allowing automatic updates when CV data changes without modifying JavaScript code.
## Architecture
### Design Decision
**API-First Approach**: Rather than hardcoding entries in JavaScript or reading from DOM elements, the command palette fetches its dynamic data from a dedicated API endpoint. This provides:
1. **Automatic Updates**: New CV entries appear in CMD+K without code changes
2. **Language Support**: API returns localized data based on language parameter
3. **Cache Efficiency**: 1-hour cache headers reduce redundant requests
4. **Separation of Concerns**: Frontend only handles rendering; backend owns data
### Data Flow
```
User opens CMD+K (Ctrl+K / Cmd+K)
↓
ninja-keys-init.js initializes
↓
fetch('/api/cmd-k?lang={en|es}')
↓
Backend loads CV data from JSON files
↓
Maps experiences, projects, courses to actions
↓
Returns JSON with action arrays
↓
Frontend combines with static actions
↓
ninja-keys displays searchable command palette
```
## API Endpoint
### GET /api/cmd-k
Returns dynamic entries for the ninja-keys command palette.
**URL**: `/api/cmd-k`
**Method**: `GET`
**Authentication**: None (public endpoint)
#### Query Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `lang` | string | `en` | Language code (`en` or `es`) |
#### Response
**Content-Type**: `application/json`
**Cache-Control**: `public, max-age=3600` (1 hour)
```json
{
"experiences": [
{
"id": "exp-olympic-broadcasting",
"title": "Olympic Broadcasting Services",
"section": "Experience",
"keywords": "Olympic Broadcasting Services Senior SAP Technical Consultant"
}
],
"projects": [
{
"id": "proj-somos-una-ola",
"title": "Somos Una Ola",
"section": "Projects",
"keywords": "Somos Una Ola Volunteer project promoting beach cleaning..."
}
],
"courses": [
{
"id": "course-codecademy-certifications",
"title": "Codecademy Certifications",
"section": "Courses",
"keywords": "Codecademy Certifications Codecademy"
}
]
}
```
#### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `experiences` | array | Work experience entries |
| `projects` | array | Personal/professional project entries |
| `courses` | array | Course and certification entries |
Each entry contains:
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique identifier (e.g., `exp-{companyId}`, `proj-{projectId}`) |
| `title` | string | Display title for the command palette |
| `section` | string | Section label (`Experience`, `Projects`, `Courses`) |
| `keywords` | string | Searchable keywords for filtering |
#### Example Requests
```bash
# English (default)
curl http://localhost:1999/api/cmd-k
# Spanish
curl http://localhost:1999/api/cmd-k?lang=es
# With jq formatting
curl -s http://localhost:1999/api/cmd-k | jq '.'
# Check response headers
curl -I http://localhost:1999/api/cmd-k
```
#### Error Responses
| Status | Description |
|--------|-------------|
| 500 | Failed to load CV data |
## Frontend Integration
### ninja-keys-init.js
The frontend JavaScript fetches from the API and combines with static actions:
```javascript
// Fetch dynamic entries from API
async function fetchDynamicEntries() {
try {
const response = await fetch(`/api/cmd-k?lang=${lang}`);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return await response.json();
} catch (error) {
console.error('Failed to fetch CMD+K data:', error);
return { experiences: [], projects: [], courses: [] };
}
}
// Combine with static actions
const dynamicData = await fetchDynamicEntries();
const actions = [
...staticActions,
...mapExperienceActions(dynamicData.experiences || []),
...mapProjectActions(dynamicData.projects || []),
...mapCourseActions(dynamicData.courses || [])
];
ninjaKeys.data = actions;
```
### Action Mapping
Dynamic entries are converted to ninja-keys actions with handlers:
```javascript
function mapExperienceActions(experiences) {
return experiences.map(exp => ({
id: exp.id,
title: exp.title,
section: exp.section,
keywords: `${exp.keywords} work job career`.toLowerCase(),
icon: '