Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions assets/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,8 @@ code {
@apply font-monogeist;
}



/* no-click turns off border and click event on small icons */
a[href*="#no-click"], img[src*="#no-click"] {
@apply border-none cursor-default pointer-events-none no-underline;
Expand Down
170 changes: 166 additions & 4 deletions build/metadata_docs/PAGE_METADATA_FORMAT.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,38 @@ Redis documentation pages include AI-friendly metadata that helps AI agents unde
- **`arity`** (integer): Number of arguments the command accepts
- **`key_specs`** (array): Key specifications for the command

### Code Examples Fields

- **`codeExamples`** (array): Array of code examples found on the page
- **`id`** (string): Unique identifier for the code example
- **`description`** (string, optional): Description of what the code example demonstrates
- **`difficulty`** (string, optional): Difficulty level - one of `"beginner"`, `"intermediate"`, `"advanced"`
- **`codetabsId`** (string, optional): DOM element ID of the codetabs container
- **`languages`** (array): Array of language-specific code variants
- **`id`** (string): Display name of the language (e.g., `"Python"`, `"Node.js"`, `"Java-Sync"`)
- **`panelId`** (string): DOM element ID of the code panel for this language
- **`langId`** (string): Stable language identifier (e.g., `"python"`, `"javascript"`, `"java"`)
- **`clientId`** (string): Stable client library identifier (e.g., `"redis-py"`, `"node-redis"`, `"lettuce"`)
- **`clientName`** (string): Human-readable client library name (e.g., `"redis-py"`, `"Jedis"`, `"Lettuce"`)

### Metadata Location Fields

- **`location`** (string): Where this metadata is located in the HTML document
- `"head"` - Metadata is in a `<script>` tag in the document head (primary copy)
- `"body"` - Metadata is in a hidden `<div>` in the document body (fallback copy)
- **`duplicateOf`** (string, optional): If this metadata is a duplicate of another instance, this field indicates the location of the primary copy
- Format: `"location:selector"` (e.g., `"head:data-ai-metadata"`)
- Only present in duplicate instances

## Example

### Basic Page Metadata (Head)

```json
{
"title": "Redis data types",
"description": "Overview of data types supported by Redis",
"location": "head",
"categories": ["docs", "develop", "stack", "oss"],
"tableOfContents": {
"sections": [
Expand All @@ -65,18 +91,111 @@ Redis documentation pages include AI-friendly metadata that helps AI agents unde
}
```

### Page with Code Examples

```json
{
"title": "SET command",
"description": "Set the string value of a key",
"location": "head",
"categories": ["docs", "commands"],
"codeExamples": [
{
"id": "set_basic",
"description": "Basic SET command usage",
"difficulty": "beginner",
"codetabsId": "set_basic_example",
"languages": [
{
"id": "Python",
"panelId": "set_basic_python",
"langId": "python",
"clientId": "redis-py",
"clientName": "redis-py"
},
{
"id": "Node.js",
"panelId": "set_basic_nodejs",
"langId": "javascript",
"clientId": "node-redis",
"clientName": "node-redis"
},
{
"id": "Java-Sync",
"panelId": "set_basic_java_sync",
"langId": "java",
"clientId": "lettuce",
"clientName": "Lettuce"
}
]
}
]
}
```

### Duplicate Metadata (Body)

```json
{
"title": "SET command",
"description": "Set the string value of a key",
"location": "body",
"duplicateOf": "head:data-ai-metadata",
"categories": ["docs", "commands"],
"codeExamples": [...]
}
```

## Embedding

### HTML Output

Metadata is embedded in a `<script>` tag in the page header:
Metadata is embedded in **two locations** for redundancy and accessibility:

#### Primary: Head (Script Tag)

```html
<script type="application/json" data-ai-metadata>
{...metadata...}
</script>
<head>
<script type="application/json" data-ai-metadata>
{
"title": "...",
"description": "...",
"location": "head",
...
}
</script>
</head>
```

**Use this location for:**
- Static analysis and extraction
- AI agent processing
- Schema validation
- Caching (primary copy)

#### Fallback: Body (Hidden Div)

```html
<body>
<div hidden data-redis-metadata="page">
{
"title": "...",
"description": "...",
"location": "body",
"duplicateOf": "head:data-ai-metadata",
...
}
</div>
</body>
```

**Use this location for:**
- DOM-based extraction (when JavaScript is available)
- Fallback access if head metadata is unavailable
- Runtime access from page scripts

**Note:** The `duplicateOf` field indicates this is a duplicate of the head version. Prefer the head version when available.

### Markdown Output (`.html.md`)

Metadata is embedded in a JSON code block at the top of the page:
Expand All @@ -87,10 +206,42 @@ Metadata is embedded in a JSON code block at the top of the page:
```
````

## Per-Codetabs Metadata

In addition to page-level metadata, each codetabs container includes a `data-codetabs-meta` attribute with language/client mappings:

```html
<div class="codetabs" id="set_example" data-codetabs-meta='{"redis-cli": {"language": "redis-cli", "client": "redis-cli"}, "Python": {"language": "python", "client": "redis-py"}, "Node.js": {"language": "javascript", "client": "node-redis"}, ...}'>
<!-- codetabs panels -->
</div>
```

This metadata provides:
- **Single source of truth** for each codetabs block
- **Runtime access** to language/client mappings
- **Zero duplication** - not repeated on every panel
- **AI agent friendly** - direct mapping from tab → language → client

### Usage Example

```javascript
// Get the codetabs metadata
const codetabsDiv = document.getElementById('set_example');
const metaStr = codetabsDiv.getAttribute('data-codetabs-meta');
const metadata = JSON.parse(metaStr);

// Find Python's client library
const pythonMeta = metadata['Python'];
console.log(pythonMeta.language); // "python"
console.log(pythonMeta.client); // "redis-py"
```

## Auto-Generation

The `tableOfContents` is automatically generated from page headings using Hugo's built-in `.TableOfContents` method. The HTML structure is converted to JSON using regex substitutions in the `layouts/partials/toc-json-regex.html` partial.

The `codeExamples` array is automatically generated from codetabs blocks found on the page using the `layouts/partials/code-examples-json.html` partial.

## Schema

The complete JSON schema is available at: `https://redis.io/schemas/page-metadata.json`
Expand All @@ -101,8 +252,19 @@ This schema enables:
- AI agent understanding of page structure
- Consistent metadata across all pages

## Metadata Precedence

When both head and body metadata are available:

1. **Prefer head metadata** - It's the primary copy and more efficient to access
2. **Use body metadata as fallback** - If head is unavailable or inaccessible
3. **Check `duplicateOf` field** - If present, indicates this is a duplicate of another instance

## Notes

- The in-page JSON metadata does **not** include a `$schema` reference. The schema is available separately for validation and documentation purposes.
- The metadata is auto-generated during the Hugo build process and does not require manual maintenance.
- Both head and body metadata contain identical content (except for `location` and `duplicateOf` fields)
- The `location` field helps downstream tooling understand which copy they're accessing
- The `duplicateOf` field enables smart caching and fallback logic in AI agents and tools

30 changes: 15 additions & 15 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,21 @@ rdi_cli_latest = "latest"
rdi_current_version = "1.15.1"

[params.clientsConfig]
"Python"={quickstartSlug="redis-py"}
"Node.js"={quickstartSlug="nodejs"}
"ioredis"={quickstartSlug="ioredis"}
"Java-Sync"={quickstartSlug="jedis"}
"Lettuce-Sync"={quickstartSlug="lettuce"}
"Java-Async"={quickstartSlug="lettuce"}
"Java-Reactive"={quickstartSlug="lettuce"}
"Go"={quickstartSlug="go"}
"C"={quickstartSlug="hiredis"}
"C#-Sync"={quickstartSlug="dotnet"}
"C#-Async"={quickstartSlug="dotnet"}
"RedisVL"={quickstartSlug="redis-vl"}
"PHP"={quickstartSlug="php"}
"Rust-Sync"={quickstartSlug="rust"}
"Rust-Async"={quickstartSlug="rust"}
"Python"={quickstartSlug="redis-py", langId="python", clientId="redis-py", clientName="redis-py"}
"Node.js"={quickstartSlug="nodejs", langId="javascript", clientId="node-redis", clientName="node-redis"}
"ioredis"={quickstartSlug="ioredis", langId="javascript", clientId="ioredis", clientName="ioredis"}
"Java-Sync"={quickstartSlug="jedis", langId="java", clientId="jedis", clientName="Jedis"}
"Lettuce-Sync"={quickstartSlug="lettuce", langId="java", clientId="lettuce", clientName="Lettuce"}
"Java-Async"={quickstartSlug="lettuce", langId="java", clientId="lettuce", clientName="Lettuce"}
"Java-Reactive"={quickstartSlug="lettuce", langId="java", clientId="lettuce", clientName="Lettuce"}
"Go"={quickstartSlug="go", langId="go", clientId="go-redis", clientName="go-redis"}
"C"={quickstartSlug="hiredis", langId="c", clientId="hiredis", clientName="hiredis"}
"C#-Sync"={quickstartSlug="dotnet", langId="csharp", clientId="stackexchange-redis", clientName="StackExchange.Redis"}
"C#-Async"={quickstartSlug="dotnet", langId="csharp", clientId="stackexchange-redis", clientName="StackExchange.Redis"}
"RedisVL"={quickstartSlug="redis-vl", langId="python", clientId="redis-vl", clientName="RedisVL"}
"PHP"={quickstartSlug="php", langId="php", clientId="predis", clientName="Predis"}
"Rust-Sync"={quickstartSlug="rust", langId="rust", clientId="redis-rs", clientName="redis-rs"}
"Rust-Async"={quickstartSlug="rust", langId="rust", clientId="redis-rs", clientName="redis-rs"}

# Mount directories for duplicate content
[module]
Expand Down
9 changes: 4 additions & 5 deletions content/develop/data-types/strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ categories:
- oss
- rs
- rc
- oss
- kubernetes
- clients
description: 'Introduction to Redis strings
Expand All @@ -26,7 +25,7 @@ Since Redis keys are strings, when we use the string type as a value too,
we are mapping a string to another string. The string data type is useful
for a number of use cases, like caching HTML fragments or pages.

{{< clients-example set_tutorial set_get >}}
{{< clients-example set="set_tutorial" step="set_get" description="Set and get a string value" >}}
> SET bike:1 Deimos
OK
> GET bike:1
Expand All @@ -45,7 +44,7 @@ The [`SET`]({{< relref "/commands/set" >}}) command has interesting options, tha
arguments. For example, I may ask [`SET`]({{< relref "/commands/set" >}}) to fail if the key already exists,
or the opposite, that it only succeed if the key already exists:

{{< clients-example set_tutorial setnx_xx >}}
{{< clients-example set="set_tutorial" step="setnx_xx" description="Use NX and XX options with SET" difficulty="intermediate" >}}
> set bike:1 bike nx
(nil)
> set bike:1 bike xx
Expand All @@ -65,7 +64,7 @@ The ability to set or retrieve the value of multiple keys in a single
command is also useful for reduced latency. For this reason there are
the [`MSET`]({{< relref "/commands/mset" >}}) and [`MGET`]({{< relref "/commands/mget" >}}) commands:

{{< clients-example set_tutorial mset >}}
{{< clients-example set="set_tutorial" step="mset" description="Set and get multiple string values" >}}
> mset bike:1 "Deimos" bike:2 "Ares" bike:3 "Vanth"
OK
> mget bike:1 bike:2 bike:3
Expand All @@ -80,7 +79,7 @@ When [`MGET`]({{< relref "/commands/mget" >}}) is used, Redis returns an array o
Even if strings are the basic values of Redis, there are interesting operations
you can perform with them. For instance, one is atomic increment:

{{< clients-example set_tutorial incr >}}
{{< clients-example set="set_tutorial" step="incr" description="Increment a counter value" >}}
> set total_crashes 0
OK
> incr total_crashes
Expand Down
3 changes: 2 additions & 1 deletion layouts/_default/section.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"topics": {{ .Params.topics | jsonify }}{{ end }}{{ if .Params.relatedPages }},
"relatedPages": {{ .Params.relatedPages | jsonify }}{{ end }}{{ if .Params.scope }},
"scope": {{ .Params.scope | jsonify }}{{ end }},
"tableOfContents": {{ partial "toc-json-regex.html" . }}
"tableOfContents": {{ partial "toc-json-regex.html" . }},
"codeExamples": {{ partial "code-examples-json.html" . }}
}
```

Expand Down
3 changes: 2 additions & 1 deletion layouts/_default/single.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"topics": {{ .Params.topics | jsonify }}{{ end }}{{ if .Params.relatedPages }},
"relatedPages": {{ .Params.relatedPages | jsonify }}{{ end }}{{ if .Params.scope }},
"scope": {{ .Params.scope | jsonify }}{{ end }},
"tableOfContents": {{ partial "toc-json-regex.html" . }}
"tableOfContents": {{ partial "toc-json-regex.html" . }},
"codeExamples": {{ partial "code-examples-json.html" . }}
}
```

Expand Down
5 changes: 4 additions & 1 deletion layouts/partials/ai-metadata-body.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{- /* Generate AI-friendly metadata as a hidden div in the page body */ -}}
{{- $metadata := dict "title" .Title "description" .Description "categories" .Params.categories -}}
{{- $metadata := dict "title" .Title "description" .Description "categories" .Params.categories "location" "body" "duplicateOf" "head:data-ai-metadata" -}}
{{- if .Params.arguments -}}
{{- $metadata = merge $metadata (dict "arguments" .Params.arguments) -}}
{{- end -}}
Expand Down Expand Up @@ -40,5 +40,8 @@
{{- $toc := partial "toc-json-regex.html" . -}}
{{- /* Manually insert tableOfContents into JSON string */ -}}
{{- $json = $json | replaceRE `}$` (printf `,"tableOfContents":%s}` $toc) -}}
{{- $codeExamples := partial "code-examples-json.html" . -}}
{{- /* Manually insert codeExamples into JSON string */ -}}
{{- $json = $json | replaceRE `}$` (printf `,"codeExamples":%s}` $codeExamples) -}}
{{- printf `<div hidden data-redis-metadata="page">%s</div>` $json | safeHTML -}}

5 changes: 4 additions & 1 deletion layouts/partials/ai-metadata.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{- /* Generate AI-friendly metadata as JSON in a script tag for the header */ -}}
{{- $metadata := dict "title" .Title "description" .Description "categories" .Params.categories -}}
{{- $metadata := dict "title" .Title "description" .Description "categories" .Params.categories "location" "head" -}}
{{- if .Params.arguments -}}
{{- $metadata = merge $metadata (dict "arguments" .Params.arguments) -}}
{{- end -}}
Expand Down Expand Up @@ -40,5 +40,8 @@
{{- $toc := partial "toc-json-regex.html" . -}}
{{- /* Manually insert tableOfContents into JSON string */ -}}
{{- $json = $json | replaceRE `}$` (printf `,"tableOfContents":%s}` $toc) -}}
{{- $codeExamples := partial "code-examples-json.html" . -}}
{{- /* Manually insert codeExamples into JSON string */ -}}
{{- $json = $json | replaceRE `}$` (printf `,"codeExamples":%s}` $codeExamples) -}}
{{- printf `<script type="application/json" data-ai-metadata>%s</script>` $json | safeHTML -}}

Loading