# Form → Template → Nickel Mapping

This document shows how form fields (`nickel_path`), Tera template variables, and Nickel contracts are synchronized across the three-layer configuration system.

## Pattern Overview

```
Form Fragment (forms/fragments/*.toml)
    nickel_path = ["section", "field"]
              ↓
Tera Template (templates/config.json.tera)
    {{ section.field }}
              ↓
Nickel Contracts (core/contracts.ncl)
    SectionConfig = { field | Type }
```

## Complete Mapping

### Graph Configuration

| Fragment | nickel_path | Tera Variable | Contract | Type |
|----------|------------|---------------|----------|------|
| graph.toml | `["graph", "name"]` | `{{ graph.name }}` | GraphConfig.name | String |
| graph.toml | `["graph", "version"]` | `{{ graph.version }}` | GraphConfig.version | String |
| graph.toml | `["graph", "description"]` | `{{ graph.description }}` | GraphConfig.description | String |

### Storage Configuration

| Fragment | nickel_path | Tera Variable | Contract | Type |
|----------|------------|---------------|----------|------|
| storage.toml | `["storage", "primary"]` | `{{ storage.primary }}` | StorageConfig.primary | StorageType |
| storage.toml | `["storage", "secondary", "enabled"]` | `{{ storage.secondary.enabled }}` | SecondaryStorageConfig.enabled | Bool |
| storage.toml | `["storage", "secondary", "type"]` | `{{ storage.secondary.type }}` | SecondaryStorageConfig.type | [surrealdb, sqlite] |
| storage.toml | `["storage", "secondary", "url"]` | `{{ storage.secondary.url }}` | SecondaryStorageConfig.url | String |
| storage.toml | `["storage", "secondary", "namespace"]` | `{{ storage.secondary.namespace }}` | SecondaryStorageConfig.namespace | String |
| storage.toml | `["storage", "secondary", "database"]` | `{{ storage.secondary.database }}` | SecondaryStorageConfig.database | String |

### Embeddings Configuration

| Fragment | nickel_path | Tera Variable | Contract | Type |
|----------|------------|---------------|----------|------|
| embeddings.toml | `["embeddings", "enabled"]` | `{{ embeddings.enabled }}` | EmbeddingConfig.enabled | Bool |
| embeddings.toml | `["embeddings", "provider"]` | `{{ embeddings.provider }}` | EmbeddingConfig.provider | EmbeddingProviderType |
| embeddings.toml | `["embeddings", "model"]` | `{{ embeddings.model }}` | EmbeddingConfig.model | String |
| embeddings.toml | `["embeddings", "dimensions"]` | `{{ embeddings.dimensions }}` | EmbeddingConfig.dimensions | Number |
| embeddings.toml | `["embeddings", "api_key_env"]` | `{{ embeddings.api_key_env }}` | EmbeddingConfig.api_key_env | String |

### Query Configuration

| Fragment | nickel_path | Tera Variable | Contract | Type |
|----------|------------|---------------|----------|------|
| query.toml | `["query", "similarity_threshold"]` | `{{ query.similarity_threshold }}` | QueryConfig.similarity_threshold | Number |
| query.toml | `["query", "max_results"]` | `{{ query.max_results }}` | QueryConfig.max_results | Number |
| query.toml | `["query", "recency_weight"]` | `{{ query.recency_weight }}` | QueryConfig.recency_weight | Number |
| query.toml | `["query", "cross_graph"]` | `{{ query.cross_graph }}` | QueryConfig.cross_graph | Bool |

### MCP Configuration

| Fragment | nickel_path | Tera Variable | Contract | Type |
|----------|------------|---------------|----------|------|
| mcp.toml | `["mcp", "server", "name"]` | `{{ mcp.server.name }}` | McpServerConfig.name | String |
| mcp.toml | `["mcp", "server", "version"]` | `{{ mcp.server.version }}` | McpServerConfig.version | String |
| mcp.toml | `["mcp", "server", "transport"]` | `{{ mcp.server.transport }}` | McpServerConfig.transport | [stdio, sse] |
| mcp.toml | `["mcp", "tools", "search"]` | `{{ mcp.tools.search }}` | McpToolsConfig.search | Bool |
| mcp.toml | `["mcp", "tools", "add_note"]` | `{{ mcp.tools.add_note }}` | McpToolsConfig.add_note | Bool |
| mcp.toml | `["mcp", "tools", "add_decision"]` | `{{ mcp.tools.add_decision }}` | McpToolsConfig.add_decision | Bool |
| mcp.toml | `["mcp", "tools", "link"]` | `{{ mcp.tools.link }}` | McpToolsConfig.link | Bool |
| mcp.toml | `["mcp", "tools", "get_guidelines"]` | `{{ mcp.tools.get_guidelines }}` | McpToolsConfig.get_guidelines | Bool |
| mcp.toml | `["mcp", "tools", "export"]` | `{{ mcp.tools.export }}` | McpToolsConfig.export | Bool |
| mcp.toml | `["mcp", "resources", "expose_project"]` | `{{ mcp.resources.expose_project }}` | McpResourcesConfig.expose_project | Bool |
| mcp.toml | `["mcp", "resources", "expose_shared"]` | `{{ mcp.resources.expose_shared }}` | McpResourcesConfig.expose_shared | Bool |

### Sync Configuration

| Fragment | nickel_path | Tera Variable | Contract | Type |
|----------|------------|---------------|----------|------|
| sync.toml | `["sync", "auto_index"]` | `{{ sync.auto_index }}` | SyncConfig.auto_index | Bool |
| sync.toml | `["sync", "watch_paths"]` | `{{ sync.watch_paths }}` | SyncConfig.watch_paths | Array String |
| sync.toml | `["sync", "debounce_ms"]` | `{{ sync.debounce_ms }}` | SyncConfig.debounce_ms | Number |

## Data Flow Example

### User edits form in typedialog-web:

```toml
Field: "Primary Storage"
nickel_path = ["storage", "primary"]
User selects: "surrealdb"
```

### typedialog-web writes to Nickel:

```nickel
storage = {
  primary = 'surrealdb,
  ...
}
```

### Script exports to JSON via template:

```tera
{{ storage.primary }}  # renders as: "surrealdb"
```

### Result in config.json:

```json
{
  "storage": {
    "primary": "surrealdb",
    ...
  }
}
```

### Rust loads from JSON:

```rust
let config: KbConfig = serde_json::from_str(json)?;
// config.storage.primary == "surrealdb"
```

## Verification Steps

When adding or modifying configuration:

1. **Fragment** - Add field with `nickel_path`
2. **Template** - Add corresponding Tera variable
3. **Contract** - Add type definition in `core/contracts.ncl`
4. **Default** - Add default value in `core/defaults.ncl`
5. **Validate** - Run: `just nickel::validate-config`
6. **Export** - Run: `just nickel::export-modes`
7. **Verify** - Check `.kogral/config.*.json` contains new field

## Conditional Visibility Example

Form field with conditional rendering:

```toml
[[elements]]
name = "storage_secondary_url"
nickel_path = ["storage", "secondary", "url"]
visible_if = "storage_secondary_enabled == true"
```

Template with conditional rendering:

```tera
{% if storage.secondary.enabled %}
"url": "{{ storage.secondary.url }}"
{% endif %}
```

Both use the same condition pattern for consistency.
