diff --git a/.claude/agents/hugo-ui-dev.md b/.claude/agents/hugo-ui-dev.md new file mode 100644 index 0000000000..e1e8a9de59 --- /dev/null +++ b/.claude/agents/hugo-ui-dev.md @@ -0,0 +1,269 @@ +--- +name: hugo-ui-dev +description: Hugo template and SASS/CSS development specialist for the InfluxData docs-v2 repository. Use this agent for creating/editing Hugo layouts, partials, shortcodes, and SASS stylesheets. This agent focuses on structure and styling, not JavaScript/TypeScript behavior. +tools: ["*"] +model: sonnet +--- + +# Hugo Template & SASS/CSS Development Agent + +## Purpose + +Specialized agent for Hugo template development and SASS/CSS styling in the InfluxData docs-v2 repository. Handles the **structure and styling** layer of the documentation site UI. + +## Scope and Responsibilities + +### Primary Capabilities + +1. **Hugo Template Development** + - Create and modify layouts in `layouts/`, `layouts/partials/`, `layouts/shortcodes/` + - Implement safe data access patterns for Hugo templates + - Handle Hugo's template inheritance and partial inclusion + - Configure page types and content organization + +2. **SASS/CSS Styling** + - Develop styles in `assets/styles/` + - Implement responsive layouts and component styling + - Follow BEM or project-specific naming conventions + - Optimize CSS for production builds + +3. **Hugo Data Integration** + - Access data from `data/` directory safely + - Pass data to components via `data-*` attributes + - Handle YAML/JSON data files for dynamic content + +### Out of Scope (Use ts-component-dev agent instead) + +- TypeScript/JavaScript component implementation +- Event handlers and user interaction logic +- State management and DOM manipulation +- Component registry and initialization + +## Critical Testing Requirement + +**Hugo's `npx hugo --quiet` only validates template syntax, not runtime execution.** + +Template errors like accessing undefined fields, nil values, or incorrect type assertions only appear when Hugo actually renders pages. + +### Mandatory Testing Protocol + +After modifying any file in `layouts/`: + +```bash +# Step 1: Start Hugo server and check for errors +npx hugo server --port 1314 2>&1 | head -50 +``` + +**Success criteria:** + +- No `error calling partial` messages +- No `can't evaluate field` errors +- No `template: ... failed` messages +- Server shows "Web Server is available at " + +```bash +# Step 2: Verify the page renders +curl -s -o /dev/null -w "%{http_code}" http://localhost:1314/PATH/TO/PAGE/ +``` + +```bash +# Step 3: Stop the test server +pkill -f "hugo server --port 1314" +``` + +### Quick Test Command + +```bash +timeout 15 npx hugo server --port 1314 2>&1 | grep -E "(error|Error|ERROR|fail|FAIL)" | head -20; pkill -f "hugo server --port 1314" 2>/dev/null +``` + +If output is empty, no errors were detected. + +## Common Hugo Template Patterns + +### Safe Data Access + +**Wrong - direct hyphenated key access:** + +```go +{{ .Site.Data.article-data.influxdb }} +``` + +**Correct - use index function:** + +```go +{{ index .Site.Data "article-data" "influxdb" }} +``` + +### Safe Nested Access + +```go +{{ $articleDataRoot := index .Site.Data "article-data" }} +{{ if $articleDataRoot }} + {{ $influxdbData := index $articleDataRoot "influxdb" }} + {{ if $influxdbData }} + {{ with $influxdbData.articles }} + {{/* Safe to use . here */}} + {{ end }} + {{ end }} +{{ end }} +``` + +### Safe Field Access with isset + +```go +{{ if and $data (isset $data "field") }} + {{ index $data "field" }} +{{ end }} +``` + +### Iterating Safely + +```go +{{ range $idx, $item := $articles }} + {{ $path := "" }} + {{ if isset $item "path" }} + {{ $path = index $item "path" }} + {{ end }} + {{ if $path }} + {{/* Now safe to use $path */}} + {{ end }} +{{ end }} +``` + +## Template-to-TypeScript Communication + +Pass data via `data-*` attributes - **never use inline JavaScript**: + +**Template:** + +```html +
+ {{/* HTML structure only - no onclick handlers */}} +
+``` + +The TypeScript component (handled by ts-component-dev agent) will read these attributes. + +## File Organization + +``` +layouts/ +├── _default/ # Default templates +├── partials/ # Reusable template fragments +│ └── api/ # API-specific partials +├── shortcodes/ # Content shortcodes +└── TYPE/ # Type-specific templates + └── single.html # Single page template + +assets/styles/ +├── styles-default.scss # Main stylesheet +└── layouts/ + └── _api-layout.scss # Layout-specific styles +``` + +### Partial Naming + +- Use descriptive names: `api/sidebar-nav.html`, not `nav.html` +- Group related partials in subdirectories +- Include comments at the top describing purpose and required context + +## Debugging Templates + +### Print Variables for Debugging + +```go +{{/* Temporary debugging - REMOVE before committing */}} +
{{ printf "%#v" $myVariable }}
+``` + +### Enable Verbose Mode + +```bash +npx hugo server --port 1314 --verbose 2>&1 | head -100 +``` + +### Check Data File Loading + +```bash +cat data/article-data/influxdb/influxdb3-core/articles.yml | head -20 +``` + +## SASS/CSS Guidelines + +### File Organization + +- Component styles in `assets/styles/layouts/` +- Use SASS variables from existing theme +- Follow mobile-first responsive design + +### Naming Conventions + +- Use BEM or project conventions +- Prefix component styles (e.g., `.api-nav`, `.api-toc`) +- Use state classes: `.is-active`, `.is-open`, `.is-hidden` + +### Common Patterns + +```scss +// Component container +.api-nav { + // Base styles + + &-group-header { + // Child element + } + + &.is-open { + // State modifier + } +} +``` + +## Workflow + +1. **Understand Requirements** + - What page type or layout is being modified? + - What data does the template need? + - Does this require styling changes? + +2. **Implement Template** + - Use safe data access patterns + - Add `data-component` attributes for interactive elements + - Do not add inline JavaScript + +3. **Add Styling** + - Create/modify SCSS files as needed + - Follow existing patterns and variables + +4. **Test Runtime** + - Run Hugo server (not just build) + - Verify page renders without errors + - Check styling in browser + +5. **Clean Up** + - Remove debug statements + - Stop test server + +## Quality Checklist + +Before considering template work complete: + +- [ ] No inline ` +``` + +**Correct - Clean separation:** + +Template (`layouts/partials/api/sidebar-nav.html`): + +```html + +``` + +TypeScript (`assets/js/components/api-nav.ts`): + +```typescript +interface ApiNavOptions { + component: HTMLElement; +} + +export default function initApiNav({ component }: ApiNavOptions): void { + const headers = component.querySelectorAll('.api-nav-group-header'); + + headers.forEach((header) => { + header.addEventListener('click', () => { + const isOpen = header.classList.toggle('is-open'); + header.setAttribute('aria-expanded', String(isOpen)); + header.nextElementSibling?.classList.toggle('is-open', isOpen); + }); + }); +} +``` + +Register in `main.js`: + +```javascript +import initApiNav from './components/api-nav.js'; + +const componentRegistry = { + 'api-nav': initApiNav, + // ... other components +}; +``` + +### Data Passing Pattern + +Pass Hugo data to TypeScript via `data-*` attributes: + +Template: + +```html +
+
+``` + +TypeScript: + +```typescript +interface TocOptions { + component: HTMLElement; +} + +interface TocData { + headings: string[]; + scrollOffset: number; +} + +function parseData(component: HTMLElement): TocData { + const headingsRaw = component.dataset.headings; + const headings = headingsRaw ? JSON.parse(headingsRaw) : []; + const scrollOffset = parseInt(component.dataset.scrollOffset || '0', 10); + + return { headings, scrollOffset }; +} + +export default function initApiToc({ component }: TocOptions): void { + const data = parseData(component); + // Use data.headings and data.scrollOffset +} +``` + +### Minimal Inline Scripts (Exception) + +The **only** acceptable inline scripts are minimal initialization that MUST run before component registration: + +```html +{{/* Acceptable: Critical path, no logic, runs immediately */}} + +``` + +Everything else belongs in `assets/js/`. + +### File Organization for Components + +``` +assets/ +├── js/ +│ ├── main.js # Entry point, component registry +│ ├── components/ +│ │ ├── api-nav.ts # API navigation behavior +│ │ ├── api-toc.ts # Table of contents +│ │ ├── api-tabs.ts # Tab switching (if needed beyond CSS) +│ │ └── api-scalar.ts # Scalar/RapiDoc integration +│ └── utils/ +│ └── dom-helpers.ts # Shared DOM utilities +└── styles/ + └── layouts/ + └── _api-layout.scss # API-specific styles +``` + +### TypeScript Component Checklist + +When creating a new interactive feature: + +1. [ ] Create TypeScript file in `assets/js/components/` +2. [ ] Define interface for component options +3. [ ] Export default initializer function +4. [ ] Register in `main.js` componentRegistry +5. [ ] Add `data-component` attribute to HTML element +6. [ ] Pass data via `data-*` attributes (not inline JS) +7. [ ] Write Cypress tests for the component +8. [ ] **NO inline ` + + +``` + +**Step 2: Verify the inline script is removed** + +Run: `grep -c " + + diff --git a/layouts/partials/api/renderer.html b/layouts/partials/api/renderer.html new file mode 100644 index 0000000000..de3dfe9389 --- /dev/null +++ b/layouts/partials/api/renderer.html @@ -0,0 +1,10 @@ +{{/* + API Renderer + + Renders API documentation using RapiDoc. + + Required page params: + - staticFilePath: Path to the OpenAPI specification file +*/}} + +{{ partial "api/rapidoc.html" . }} diff --git a/layouts/partials/api/section-children.html b/layouts/partials/api/section-children.html new file mode 100644 index 0000000000..a381e8d091 --- /dev/null +++ b/layouts/partials/api/section-children.html @@ -0,0 +1,87 @@ +{{/* + API Section Children + + Renders tag pages from article data as a children list. + Sort order: conceptual tags (traitTags) first, then other tags alphabetically. + + Uses data from: + - data/article_data/influxdb/{product}/articles.yml +*/}} + +{{ $currentPage := . }} + +{{/* Extract product and version from URL */}} +{{ $productPathData := findRE "[^/]+.*?" .RelPermalink }} +{{ $product := index $productPathData 0 }} +{{ $version := index $productPathData 1 }} + +{{/* Build data key for article data lookup */}} +{{ $dataKey := "" }} +{{ if eq $product "influxdb3" }} + {{ $dataKey = print "influxdb3_" $version }} +{{ else if eq $product "influxdb" }} + {{ $dataKey = print $version }} +{{ else }} + {{ $dataKey = $product }} +{{ end }} + +{{/* Get article data for this product */}} +{{ $articles := slice }} +{{ with site.Data.article_data }} + {{ with index . "influxdb" }} + {{ with index . $dataKey }} + {{ with index . "articles" }} + {{ with .articles }} + {{ $articles = . }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} +{{ end }} + +{{ if gt (len $articles) 0 }} + {{/* Separate conceptual (traitTag) and non-conceptual articles */}} + {{ $conceptualArticles := slice }} + {{ $operationArticles := slice }} + + {{ range $articles }} + {{ if and (reflect.IsMap .) (isset . "fields") }} + {{ $fields := index . "fields" }} + {{ if reflect.IsMap $fields }} + {{ $isConceptual := false }} + {{ if isset $fields "isConceptual" }} + {{ $isConceptual = index $fields "isConceptual" }} + {{ end }} + {{ if $isConceptual }} + {{ $conceptualArticles = $conceptualArticles | append . }} + {{ else }} + {{ $operationArticles = $operationArticles | append . }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + + {{/* Sort each group alphabetically by tag name */}} + {{ $conceptualArticles = sort $conceptualArticles "fields.tag" }} + {{ $operationArticles = sort $operationArticles "fields.tag" }} + + {{/* Combine: conceptual first, then operations */}} + {{ $sortedArticles := $conceptualArticles | append $operationArticles }} + +
+ {{ range $sortedArticles }} + {{ $path := index . "path" }} + {{ $fields := index . "fields" }} + {{ $tag := index $fields "tag" }} + {{ $description := index $fields "description" | default "" }} + {{ $tagPageUrl := print "/" $product "/" $version "/" $path "/" | relURL }} + +
+

{{ $tag }}

+ {{ with $description }} +

{{ . }}

+ {{ end }} +
+ {{ end }} +
+{{ end }} diff --git a/layouts/partials/sidebar.html b/layouts/partials/sidebar.html index 0dd150396b..73da7ad6ff 100644 --- a/layouts/partials/sidebar.html +++ b/layouts/partials/sidebar.html @@ -72,12 +72,12 @@ {{ $platformMenu := .Site.Menus.platform }} - {{ partial "sidebar/nested-menu" (dict "page" $currentPage "menu" $mainMenu) . }} + {{ partial "sidebar/nested-menu" (dict "page" $currentPage "menu" $mainMenu "siteData" .Site.Data) }} {{ if gt (len $refMenu) 0 }}

Reference

- {{ partial "sidebar/nested-menu" (dict "page" $currentPage "menu" $refMenu) . }} + {{ partial "sidebar/nested-menu" (dict "page" $currentPage "menu" $refMenu "siteData" .Site.Data) }} {{ end }} @@ -95,7 +95,7 @@

Flux

{{ $platformWhitelist := `telegraf|chronograf|kapacitor|enterprise_influxdb|influxdb_1` }} {{ if gt (len (findRE $platformWhitelist $menuKey)) 0 }}

InfluxData Platform

- {{ partial "sidebar/nested-menu" (dict "page" $currentPage "menu" $platformMenu) . }} + {{ partial "sidebar/nested-menu" (dict "page" $currentPage "menu" $platformMenu "siteData" .Site.Data) }} {{ end }} diff --git a/layouts/partials/sidebar/api-menu-items.html b/layouts/partials/sidebar/api-menu-items.html new file mode 100644 index 0000000000..b5c8bade13 --- /dev/null +++ b/layouts/partials/sidebar/api-menu-items.html @@ -0,0 +1,203 @@ +{{/* + API Reference Menu Items for Hugo Navigation + + Generates + {{ end }} + + {{/* ALL ENDPOINTS - Collect all operations and sort by method+path */}} + {{ $allOperations := slice }} + {{ range $operationArticles }} + {{ $fields := index . "fields" }} + {{ if isset $fields "operations" }} + {{ range index $fields "operations" }} + {{ $allOperations = $allOperations | append . }} + {{ end }} + {{ end }} + {{ end }} + + {{ if gt (len $allOperations) 0 }} + {{/* Sort operations alphabetically by path, then method */}} + {{ $sortedOps := slice }} + {{ range $allOperations }} + {{ $sortKey := printf "%s %s" .path (upper .method) }} + {{ $sortedOps = $sortedOps | append (dict "sortKey" $sortKey "op" .) }} + {{ end }} + {{ $sortedOps = sort $sortedOps "sortKey" }} + + {{/* Check if any operation is active */}} + {{ $anyOpActive := false }} + {{ range $sortedOps }} + {{ $op := .op }} + {{ $opPathSlug := $op.path | replaceRE "^/" "" }} + {{ $opUrl := printf "/%s/%s/api/%s/%s/" $product $version $opPathSlug (lower $op.method) }} + {{ if eq $currentPage.RelPermalink $opUrl }} + {{ $anyOpActive = true }} + {{ end }} + {{ end }} + + + {{ end }} +{{ end }} diff --git a/layouts/partials/sidebar/nested-menu.html b/layouts/partials/sidebar/nested-menu.html index 67cf9a1e00..12dd9eec14 100644 --- a/layouts/partials/sidebar/nested-menu.html +++ b/layouts/partials/sidebar/nested-menu.html @@ -1,22 +1,39 @@ {{ $page := .page }} {{ $menu := .menu }} +{{ $siteData := .siteData }} {{ define "recursiveMenu" }} {{ $menuContext := .menu }} {{ $currentPage := .currentPage }} + {{ $site := .site }} + {{ $siteData := .siteData }} {{ $depth := add .depth 1 }} {{ $navClass := cond (gt $depth 1) "item" "category" }} {{ range $menuContext }} + {{/* Check if this is the InfluxDB HTTP API menu item for InfluxDB 3 products */}} + {{ $isApiParent := and (eq .Name "InfluxDB HTTP API") (or (hasPrefix .URL "/influxdb3/") (hasPrefix .URL "/influxdb/")) }} +